diff --git a/src/app.config.ts b/src/app.config.ts index 81c8161..dfda20a 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -3,6 +3,16 @@ import { ValidationPipe } from '@nestjs/common'; import cookieParser from 'cookie-parser'; import helmet from 'helmet'; +function parseAllowedOrigins(allowedOrigins?: string): boolean | string[] { + if (!allowedOrigins) { + return false; + } + if (allowedOrigins === 'true' || allowedOrigins === '*') { + return true; + } + return allowedOrigins.split(',').map((o) => o.trim()); +} + export function configureApp(app: INestApplication) { app.enableShutdownHooks(); @@ -15,8 +25,7 @@ export function configureApp(app: INestApplication) { app.use(cookieParser()); app.enableCors({ - origin: - process.env.ALLOWED_ORIGINS?.split(',').map((o) => o.trim()) || false, + origin: parseAllowedOrigins(process.env.ALLOWED_ORIGINS), methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', credentials: true, }); diff --git a/test/cors.e2e.spec.ts b/test/cors.e2e.spec.ts index 9e905bc..816ecff 100644 --- a/test/cors.e2e.spec.ts +++ b/test/cors.e2e.spec.ts @@ -11,45 +11,82 @@ describe('CORS (e2e)', () => { let app: INestApplication; let httpServer: App; - beforeAll(async () => { - process.env.ALLOWED_ORIGINS = 'http://example.com,http://test.com'; + async function setupApp(originsConfig: string | undefined): Promise { + if (originsConfig === undefined) { + delete process.env.ALLOWED_ORIGINS; + } else { + process.env.ALLOWED_ORIGINS = originsConfig; + } const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); - configureApp(app); - await app.init(); - httpServer = app.getHttpServer() as App; - }); + } - afterAll(async () => { + afterEach(async () => { await app.close(); }); - it('should allow origin when it matches ALLOWED_ORIGINS', async () => { - const response = await request(httpServer) - .options('/') - .set('Origin', 'http://example.com') - .set('Access-Control-Request-Method', 'GET') - .expect(204); + describe('when ALLOWED_ORIGINS is a list', () => { + beforeEach(async () => setupApp('http://example.com,http://test.com')); + + it('should allow origin when it matches', async () => { + const response = await request(httpServer) + .options('/health') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .expect(204); + + expect(response.headers['access-control-allow-origin']).toBe( + 'http://example.com', + ); + }); + + it('should NOT allow origin when it does NOT match', async () => { + const response = await request(httpServer) + .options('/health') + .set('Origin', 'http://malicious.com') + .set('Access-Control-Request-Method', 'GET') + .expect(204); + + expect(response.headers['access-control-allow-origin']).toBeUndefined(); + }); + }); + + describe('when ALLOWED_ORIGINS is "true"', () => { + beforeEach(async () => setupApp('true')); + + it('should allow any origin for health endpoint', async () => { + const response = await request(httpServer) + .options('/health') + .set('Origin', 'http://random-origin.com') + .set('Access-Control-Request-Method', 'GET') + .expect(204); - expect(response.headers['access-control-allow-origin']).toBe( - 'http://example.com', - ); + expect(response.headers['access-control-allow-origin']).toBe( + 'http://random-origin.com', + ); + }); }); - it('should NOT allow origin when it does NOT match ALLOWED_ORIGINS', async () => { - const response = await request(httpServer) - .options('/') - .set('Origin', 'http://malicious.com') - .set('Access-Control-Request-Method', 'GET') - .expect(204); + describe('when ALLOWED_ORIGINS is "*"', () => { + beforeEach(async () => setupApp('*')); + + it('should allow any origin for health endpoint', async () => { + const response = await request(httpServer) + .options('/health') + .set('Origin', 'http://another-random.com') + .set('Access-Control-Request-Method', 'GET') + .expect(204); - expect(response.headers['access-control-allow-origin']).toBeUndefined(); + expect(response.headers['access-control-allow-origin']).toBe( + 'http://another-random.com', + ); + }); }); });