Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/src/api/routes/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ export function createTasksRouter(dispatch: DispatchFn, releasePayment: PaymentR
* application/json:
* schema:
* $ref: '#/components/schemas/Task'
* 403:
* description: Access denied — walletpublickey header is missing or does not match the task owner
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 404:
* description: Task not found
* content:
Expand Down
69 changes: 69 additions & 0 deletions backend/tests/cors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,73 @@ describe('CORS Middleware', () => {
expect(res.status).toBe(500); // cors module calls next(new Error('Not allowed by CORS')) which causes 500 in express default error handler
expect(res.text).toContain('Not allowed by CORS');
});

it('allows requests with no Origin header (e.g. server-to-server)', async () => {
const res = await request(app).get('/test');
expect(res.status).toBe(200);
});

it('sets credentials header when origin is allowed', async () => {
const res = await request(app)
.get('/test')
.set('Origin', 'https://app.trusted.com');

expect(res.headers['access-control-allow-credentials']).toBe('true');
});

it('responds to preflight OPTIONS with allowed methods', async () => {
const res = await request(app)
.options('/test')
.set('Origin', 'http://trusted.com')
.set('Access-Control-Request-Method', 'POST');

expect(res.status).toBeLessThan(300);
const methods = res.headers['access-control-allow-methods'];
expect(methods).toContain('POST');
expect(methods).toContain('GET');
});

it('falls back to http://localhost:3000 when ALLOWED_ORIGINS is unset', async () => {
jest.resetModules();
delete process.env.ALLOWED_ORIGINS;
const corsMiddleware = require('../src/api/middleware/cors').createCorsMiddleware();
const localApp = express();
localApp.use(corsMiddleware);
localApp.get('/test', (_req, res) => res.json({ ok: true }));

const res = await request(localApp)
.get('/test')
.set('Origin', 'http://localhost:3000');

expect(res.status).toBe(200);
expect(res.headers['access-control-allow-origin']).toBe('http://localhost:3000');
});
});

describe('HSTS Middleware', () => {
function buildApp(env: string) {
const app = express();
app.use((_req, res, next) => {
if (env === 'production') {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
}
next();
});
app.get('/test', (_req, res) => res.json({ ok: true }));
return app;
}

it('sets HSTS header in production', async () => {
const app = buildApp('production');
const res = await request(app).get('/test');
expect(res.headers['strict-transport-security']).toBe(
'max-age=31536000; includeSubDomains; preload',
);
});

it('does not set HSTS header in development', async () => {
const app = buildApp('development');
const res = await request(app).get('/test');
expect(res.headers['strict-transport-security']).toBeUndefined();
});
});
Loading