chore: ran security check for OWASP top 10
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
This commit is contained in:
83
api/tests/cryptographic-failures.test.ts
Normal file
83
api/tests/cryptographic-failures.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user