diff --git a/v2/__tests__/unit/middlewares/cors.spec.ts b/v2/__tests__/unit/middlewares/cors.spec.ts index 8cecc4c..5a559c9 100644 --- a/v2/__tests__/unit/middlewares/cors.spec.ts +++ b/v2/__tests__/unit/middlewares/cors.spec.ts @@ -305,4 +305,71 @@ describe('CORS Middleware', () => { expect(res.headers['access-control-allow-origin']).toBe('http://any-origin.com') }) }) + + describe('Allowed Methods', () => { + it('should use default methods when allowedMethods not specified', async () => { + const app = await createAppWithCors({ + arOptions: { + whitelist: ['http://localhost:3000'], + }, + }) + + // OPTIONS preflight request should succeed and return allowed methods + const res = await request(app) + .options('/test') + .set('Origin', 'http://localhost:3000') + .set('Access-Control-Request-Method', 'POST') + + expect(res.status).toBe(204) + expect(res.headers['access-control-allow-methods']).toBeDefined() + // Default methods should include common REST methods + const methods = res.headers['access-control-allow-methods'] + expect(methods).toContain('GET') + expect(methods).toContain('POST') + expect(methods).toContain('PUT') + expect(methods).toContain('DELETE') + expect(methods).toContain('PATCH') + expect(methods).toContain('OPTIONS') + }) + + it('should constrain methods when allowedMethods is explicitly provided', async () => { + const app = await createAppWithCors({ + allowedMethods: ['GET', 'POST'], + arOptions: { + whitelist: ['http://localhost:3000'], + }, + }) + + const res = await request(app) + .options('/test') + .set('Origin', 'http://localhost:3000') + .set('Access-Control-Request-Method', 'POST') + + expect(res.status).toBe(204) + const methods = res.headers['access-control-allow-methods'] + expect(methods).toBe('GET,POST') + // Should NOT contain methods not in the explicit list + expect(methods).not.toContain('DELETE') + expect(methods).not.toContain('PUT') + }) + + it('should not crash on OPTIONS request when allowedMethods is undefined', async () => { + // This test ensures the fix for the undefined.join() error + const app = await createAppWithCors({ + arOptions: { + whitelist: ['http://localhost:3000'], + }, + // Explicitly NOT setting allowedMethods + }) + + // This should NOT throw "Cannot read properties of undefined (reading 'join')" + const res = await request(app) + .options('/test') + .set('Origin', 'http://localhost:3000') + .set('Access-Control-Request-Method', 'DELETE') + + expect(res.status).toBe(204) + expect(res.headers['access-control-allow-methods']).toBeDefined() + }) + }) }) diff --git a/v2/middlewares/security/cors.ts b/v2/middlewares/security/cors.ts index cc5512a..d8af263 100644 --- a/v2/middlewares/security/cors.ts +++ b/v2/middlewares/security/cors.ts @@ -147,11 +147,15 @@ export async function initCors(app: Application, config: CorsConfig, logger?: Lo return merged } + // Default HTTP methods if not specified - allow common REST methods + // Not constraining by default as it may break existing apps; explicit config recommended + const DEFAULT_METHODS = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'] + const corsOptions: CorsOptions = { ...(config.libOptions || {}), credentials: globalCredentials, maxAge: config.maxAge, - methods: config.allowedMethods, + methods: config.allowedMethods ?? DEFAULT_METHODS, allowedHeaders: config.allowedHeaders, exposedHeaders: config.exposedHeaders, }