diff --git a/src/utils/corsConfig.ts b/src/utils/corsConfig.ts index a646aeb4..ca1d3a68 100644 --- a/src/utils/corsConfig.ts +++ b/src/utils/corsConfig.ts @@ -3,7 +3,7 @@ * * Selects the appropriate CORS middleware options based on environment: * - When CORS_ORIGIN is set: allow only those origins (comma-separated) - * - When unset in production: warn and allow no origins (restrictive default) + * - When unset in production: throws an error at startup (hard failure) * - When unset outside production: default to localhost:5173 (dev convenience) */ @@ -28,15 +28,14 @@ export interface CorsConfig { * Returns a ready-to-use Hono `cors()` middleware configured for the current * environment: * - If `corsOriginEnv` is set (comma-separated), only those origins are allowed. - * - If `corsOriginEnv` is unset in production, a warning is logged and an empty - * origin list is used (blocks all cross-origin requests). + * - If `corsOriginEnv` is unset in production, throws an `Error` to crash the + * process at startup with a clear, actionable message. * - If `corsOriginEnv` is unset outside production, `http://localhost:5173` is * used as a dev-friendly default. */ export function buildCorsMiddleware({ corsOriginEnv, isProduction, - warn = console.warn, }: CorsConfigOptions): ReturnType { const origins = corsOriginEnv ?.split(',') @@ -48,12 +47,11 @@ export function buildCorsMiddleware({ } if (isProduction) { - warn( - '[Dashboard] WARNING: CORS_ORIGIN is not set in production. ' + - 'Using restrictive default (no origins allowed). ' + + throw new Error( + '[Dashboard] CORS_ORIGIN is not set. ' + + 'This is required in production. ' + 'Set CORS_ORIGIN to your frontend URL (e.g., https://dashboard.example.com).', ); - return cors({ origin: [], credentials: true }); } // Development default diff --git a/tests/unit/utils/corsConfig.test.ts b/tests/unit/utils/corsConfig.test.ts index 8147d400..54d25dec 100644 --- a/tests/unit/utils/corsConfig.test.ts +++ b/tests/unit/utils/corsConfig.test.ts @@ -77,40 +77,22 @@ describe('buildCorsMiddleware', () => { }); describe('when CORS_ORIGIN is not set AND NODE_ENV=production', () => { - it('logs a warning at startup', () => { - const warn = vi.fn(); - buildCorsMiddleware({ - corsOriginEnv: undefined, - isProduction: true, - warn, - }); - - expect(warn).toHaveBeenCalledOnce(); - expect(warn).toHaveBeenCalledWith( - expect.stringContaining('CORS_ORIGIN is not set in production'), - ); - }); - - it('blocks all cross-origin requests (empty origin list)', async () => { - const middleware = buildCorsMiddleware({ - corsOriginEnv: undefined, - isProduction: true, - warn: vi.fn(), - }); - - const res = await fetchWithOrigin(middleware, 'https://any-origin.example.com'); - expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull(); + it('throws an error at startup', () => { + expect(() => + buildCorsMiddleware({ + corsOriginEnv: undefined, + isProduction: true, + }), + ).toThrowError(/CORS_ORIGIN is not set/); }); - it('also blocks localhost when in production without CORS_ORIGIN', async () => { - const middleware = buildCorsMiddleware({ - corsOriginEnv: undefined, - isProduction: true, - warn: vi.fn(), - }); - - const res = await fetchWithOrigin(middleware, 'http://localhost:5173'); - expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull(); + it('throws an error with an actionable message', () => { + expect(() => + buildCorsMiddleware({ + corsOriginEnv: undefined, + isProduction: true, + }), + ).toThrowError(/Set CORS_ORIGIN to your frontend URL/); }); });