diff --git a/backend/src/config.js b/backend/src/config.js index 9ddbc6d..efc87bb 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -16,6 +16,8 @@ const schema = z.object({ SEP10_SERVER_KEY: z.string().min(1), JWT_SECRET: z.string().min(1), PORT: z.coerce.number().int().positive().default(4000), + // Comma-separated list of allowed origins, e.g. https://app.example.com,https://admin.example.com + ALLOWED_ORIGINS: z.string().default('http://localhost:3000'), // Transaction fees (stroops; 1 XLM = 10_000_000 stroops) SOROBAN_FEE: z.coerce.number().int().positive().default(100), diff --git a/backend/tests/cors.test.js b/backend/tests/cors.test.js new file mode 100644 index 0000000..d82a810 --- /dev/null +++ b/backend/tests/cors.test.js @@ -0,0 +1,47 @@ +const request = require('supertest'); + +describe('CORS', () => { + const ALLOWED = 'http://localhost:3000'; + const BLOCKED = 'https://evil.example.com'; + + let app; + + beforeEach(() => { + jest.resetModules(); + process.env.ALLOWED_ORIGINS = ALLOWED; + app = require('../src/app'); + }); + + it('allows requests from an allowed origin', async () => { + const res = await request(app).get('/health').set('Origin', ALLOWED); + expect(res.headers['access-control-allow-origin']).toBe(ALLOWED); + }); + + it('blocks requests from a disallowed origin', async () => { + const res = await request(app).get('/health').set('Origin', BLOCKED); + expect(res.headers['access-control-allow-origin']).toBeUndefined(); + }); + + it('handles preflight for allowed origin', async () => { + const res = await request(app) + .options('/health') + .set('Origin', ALLOWED) + .set('Access-Control-Request-Method', 'GET'); + expect(res.status).toBe(204); + expect(res.headers['access-control-allow-origin']).toBe(ALLOWED); + }); + + it('sets credentials header for allowed origin', async () => { + const res = await request(app).get('/health').set('Origin', ALLOWED); + expect(res.headers['access-control-allow-credentials']).toBe('true'); + }); + + it('supports multiple allowed origins', async () => { + jest.resetModules(); + process.env.ALLOWED_ORIGINS = `${ALLOWED},https://admin.example.com`; + const multiApp = require('../src/app'); + + const res = await request(multiApp).get('/health').set('Origin', 'https://admin.example.com'); + expect(res.headers['access-control-allow-origin']).toBe('https://admin.example.com'); + }); +});