84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
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<string, any> | undefined;
|
|
return !!opts?.sign && !!opts?.verify;
|
|
});
|
|
expect(jwtCall).toBeTruthy();
|
|
const jwtOptions = jwtCall?.[1] as Record<string, any>;
|
|
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();
|
|
}
|
|
});
|
|
});
|