import { afterAll, beforeEach, describe, expect, it, vi } from "vitest"; const ORIGINAL_ENV = { ...process.env }; beforeEach(() => { vi.resetModules(); vi.resetAllMocks(); process.env = { ...ORIGINAL_ENV }; }); afterAll(() => { process.env = ORIGINAL_ENV; }); function setRequiredBaseEnv() { process.env.DATABASE_URL = "postgres://app:app@127.0.0.1:5432/skymoney"; process.env.JWT_SECRET = "test-jwt-secret-32-chars-min-abcdef"; process.env.COOKIE_SECRET = "test-cookie-secret-32-chars-abcdef"; process.env.CORS_ORIGINS = "https://allowed.example.com"; process.env.AUTH_DISABLED = "0"; process.env.SEED_DEFAULT_BUDGET = "0"; } describe("A04 Cryptographic Failures", () => { it("rejects non-https APP_ORIGIN in production", async () => { setRequiredBaseEnv(); process.env.NODE_ENV = "production"; process.env.APP_ORIGIN = "http://allowed.example.com"; await expect(import("../src/env")).rejects.toThrow( "APP_ORIGIN must use https:// in production." ); }); it("applies secure JWT defaults for issuer and audience", async () => { setRequiredBaseEnv(); process.env.NODE_ENV = "production"; process.env.APP_ORIGIN = "https://allowed.example.com"; delete process.env.JWT_ISSUER; delete process.env.JWT_AUDIENCE; const envModule = await import("../src/env"); expect(envModule.env.JWT_ISSUER).toBe("skymoney-api"); expect(envModule.env.JWT_AUDIENCE).toBe("skymoney-web"); }); it("registers fastify-jwt with explicit algorithm and claim validation", async () => { const jwtPlugin = vi.fn(async () => undefined); vi.doMock("@fastify/jwt", () => ({ default: jwtPlugin, })); const { buildApp } = await import("../src/server"); const app = await buildApp({ NODE_ENV: "test", DATABASE_URL: "postgres://app:app@127.0.0.1:5432/skymoney", JWT_SECRET: "test-jwt-secret-32-chars-min-abcdef", COOKIE_SECRET: "test-cookie-secret-32-chars-abcdef", AUTH_DISABLED: true, SEED_DEFAULT_BUDGET: false, APP_ORIGIN: "http://localhost:5173", }); try { await app.ready(); expect(jwtPlugin.mock.calls.length).toBeGreaterThan(0); const jwtCall = jwtPlugin.mock.calls.find((call) => { const opts = call[1] as Record | undefined; return !!opts?.sign && !!opts?.verify; }); expect(jwtCall).toBeTruthy(); const jwtOptions = jwtCall?.[1] as Record; expect(jwtOptions.sign.algorithm).toBe("HS256"); expect(jwtOptions.sign.iss).toBe("skymoney-api"); expect(jwtOptions.sign.aud).toBe("skymoney-web"); expect(jwtOptions.verify.algorithms).toEqual(["HS256"]); expect(jwtOptions.verify.allowedIss).toBe("skymoney-api"); expect(jwtOptions.verify.allowedAud).toBe("skymoney-web"); } finally { await app.close(); } }); });