From 87f85a0d637109f86429dac189be78b0421d28cf Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Sun, 10 Nov 2024 13:17:21 +0000 Subject: [PATCH] unit test --- .../src/middleware/contentSecurityPolicy.ts | 5 +- .../tests/contentSecurityPolicy.spec.ts | 73 +++++++++++++++++++ packages/types/src/sdk/koa.ts | 4 +- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 packages/backend-core/src/middleware/tests/contentSecurityPolicy.spec.ts diff --git a/packages/backend-core/src/middleware/contentSecurityPolicy.ts b/packages/backend-core/src/middleware/contentSecurityPolicy.ts index f06fc567a4..d1668d3dd5 100644 --- a/packages/backend-core/src/middleware/contentSecurityPolicy.ts +++ b/packages/backend-core/src/middleware/contentSecurityPolicy.ts @@ -91,7 +91,10 @@ export async function contentSecurityPolicy(ctx: any, next: any) { const nonce = crypto.randomBytes(16).toString("base64") const directives = { ...CSP_DIRECTIVES } - directives["script-src"] = [...CSP_DIRECTIVES["script-src"], `'nonce-${nonce}'`] + directives["script-src"] = [ + ...CSP_DIRECTIVES["script-src"], + `'nonce-${nonce}'`, + ] ctx.state.nonce = nonce diff --git a/packages/backend-core/src/middleware/tests/contentSecurityPolicy.spec.ts b/packages/backend-core/src/middleware/tests/contentSecurityPolicy.spec.ts new file mode 100644 index 0000000000..745c94fcef --- /dev/null +++ b/packages/backend-core/src/middleware/tests/contentSecurityPolicy.spec.ts @@ -0,0 +1,73 @@ +import crypto from "crypto" +import contentSecurityPolicy from "../contentSecurityPolicy" + +jest.mock("crypto", () => ({ + randomBytes: jest.fn(), + randomUUID: jest.fn(), +})) + +describe("contentSecurityPolicy middleware", () => { + let ctx: any + let next: any + const mockNonce = "mocked/nonce" + + beforeEach(() => { + ctx = { + state: {}, + set: jest.fn(), + } + next = jest.fn() + crypto.randomBytes.mockReturnValue(Buffer.from(mockNonce, "base64")) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + it("should generate a nonce and set it in the script-src directive", async () => { + await contentSecurityPolicy(ctx, next) + + expect(ctx.state.nonce).toBe(mockNonce) + expect(ctx.set).toHaveBeenCalledWith( + "Content-Security-Policy", + expect.stringContaining( + `script-src 'self' 'unsafe-eval' https://*.budibase.net https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io https://d2l5prqdbvm3op.cloudfront.net https://us-assets.i.posthog.com 'nonce-${mockNonce}'` + ) + ) + expect(next).toHaveBeenCalled() + }) + + it("should include all CSP directives in the header", async () => { + await contentSecurityPolicy(ctx, next) + + const cspHeader = ctx.set.mock.calls[0][1] + expect(cspHeader).toContain("default-src 'self'") + expect(cspHeader).toContain("script-src 'self' 'unsafe-eval'") + expect(cspHeader).toContain("style-src 'self' 'unsafe-inline'") + expect(cspHeader).toContain("object-src 'none'") + expect(cspHeader).toContain("base-uri 'self'") + expect(cspHeader).toContain("connect-src 'self'") + expect(cspHeader).toContain("font-src 'self'") + expect(cspHeader).toContain("frame-src 'self'") + expect(cspHeader).toContain("img-src http: https: data: blob:") + expect(cspHeader).toContain("manifest-src 'self'") + expect(cspHeader).toContain("media-src 'self'") + expect(cspHeader).toContain("worker-src blob:") + }) + + it("should handle errors and log an error message", async () => { + const consoleSpy = jest.spyOn(console, "error").mockImplementation() + const error = new Error("Test error") + crypto.randomBytes.mockImplementation(() => { + throw error + }) + + await contentSecurityPolicy(ctx, next) + + expect(consoleSpy).toHaveBeenCalledWith( + `Error occurred in Content-Security-Policy middleware: ${error}` + ) + expect(next).not.toHaveBeenCalled() + consoleSpy.mockRestore() + }) +}) diff --git a/packages/types/src/sdk/koa.ts b/packages/types/src/sdk/koa.ts index 5ec10b94c1..826b7b371c 100644 --- a/packages/types/src/sdk/koa.ts +++ b/packages/types/src/sdk/koa.ts @@ -48,7 +48,7 @@ export interface Ctx extends Context { request: BBRequest body: ResponseBody userAgent: UserAgentContext["userAgent"] - state: { nonce: string } + state: { nonce?: string } } /** @@ -57,7 +57,7 @@ export interface Ctx extends Context { export interface UserCtx extends Ctx { user: ContextUser - state: { nonce: string } + state: { nonce?: string } roleId?: string eventEmitter?: ContextEmitter loginMethod?: LoginMethod