Skip to content

Commit

Permalink
fix: allow credentials for trusted CORS requests
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinKolarik committed Aug 21, 2024
1 parent 2f33c29 commit d88b742
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 8 deletions.
6 changes: 6 additions & 0 deletions config/default.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ module.exports = {
docsHost: 'https://www.jsdelivr.com',
port: 3000,
processes: 2,
cors: {
trustedOrigins: [
'https://globalping.io',
'https://staging.globalping.io',
],
},
session: {
cookieName: 'dash_session_token',
cookieSecret: '',
Expand Down
7 changes: 7 additions & 0 deletions config/development.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
module.exports = {
server: {
cors: {
trustedOrigins: [
'http://localhost:13000',
],
},
},
redis: {
url: 'redis://localhost:16379',
socket: {
Expand Down
37 changes: 34 additions & 3 deletions src/lib/http/middleware/cors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,39 @@ export const corsHandler = () => async (ctx: Context, next: Next) => {
return next();
};

export const corsAuthHandler = () => async (ctx: Context, next: Next) => {
ctx.set('Access-Control-Allow-Headers', '*, Authorization');
export const corsAuthHandler = ({ trustedOrigins = [] }: CorsOptions) => {
const exposeHeaders = [
'ETag',
'Link',
'Location',
'Retry-After',
'X-RateLimit-Limit',
'X-RateLimit-Consumed',
'X-RateLimit-Remaining',
'X-RateLimit-Reset',
'X-Credits-Consumed',
'X-Credits-Remaining',
'X-Request-Cost',
'X-Response-Time',
'Deprecation',
'Sunset',
].join(', ');

return next();
return async (ctx: Context, next: Next) => {
const origin = ctx.get('Origin');

// Allow credentials only if the request is coming from a trusted origin.
if (trustedOrigins.includes(origin)) {
ctx.set('Access-Control-Allow-Origin', ctx.get('Origin'));
ctx.set('Access-Control-Allow-Credentials', 'true');
ctx.append('Vary', 'Origin');
}

ctx.set('Access-Control-Allow-Headers', 'Authorization, Content-Type');
ctx.set('Access-Control-Expose-Headers', exposeHeaders);

return next();
};
};

export type CorsOptions = { trustedOrigins?: string[] };
7 changes: 4 additions & 3 deletions src/measurement/route/create-measurement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import config from 'config';
import type Router from '@koa/router';
import { getMeasurementRunner } from '../runner.js';
import { bodyParser } from '../../lib/http/middleware/body-parser.js';
import { corsAuthHandler } from '../../lib/http/middleware/cors.js';
import { corsAuthHandler, CorsOptions } from '../../lib/http/middleware/cors.js';
import { validate } from '../../lib/http/middleware/validate.js';
import { authenticate, AuthenticateOptions } from '../../lib/http/middleware/authenticate.js';
import { schema } from '../schema/global-schema.js';
import type { ExtendedContext } from '../../types.js';

const corsConfig = config.get<CorsOptions>('server.cors');
const sessionConfig = config.get<AuthenticateOptions['session']>('server.session');
const hostConfig = config.get<string>('server.host');
const runner = getMeasurementRunner();
Expand All @@ -26,6 +27,6 @@ const handle = async (ctx: ExtendedContext): Promise<void> => {

export const registerCreateMeasurementRoute = (router: Router): void => {
router
.options('/measurements', '/measurements', corsAuthHandler())
.post('/measurements', '/measurements', authenticate({ session: sessionConfig }), bodyParser(), validate(schema), handle);
.options('/measurements', '/measurements', corsAuthHandler(corsConfig))
.post('/measurements', '/measurements', corsAuthHandler(corsConfig), authenticate({ session: sessionConfig }), bodyParser(), validate(schema), handle);
};
19 changes: 17 additions & 2 deletions test/tests/integration/middleware/cors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ describe('cors', () => {

expect(response.headers['access-control-allow-origin']).to.equal('*');
});

describe('POST /v1/measurements', () => {
it('should include the explicit origin if it is trusted', async () => {
const response = await requestAgent.options('/v1/measurements').set('Origin', 'https://globalping.io').send() as Response;

expect(response.headers['access-control-allow-origin']).to.equal('https://globalping.io');
expect(response.headers['vary']).to.include('Origin');
});

it('should include the wildcard if the origin is not trusted', async () => {
const response = await requestAgent.options('/v1/measurements').send() as Response;

expect(response.headers['access-control-allow-origin']).to.equal('*');
});
});
});

describe('Access-Control-Allow-Headers header', () => {
Expand All @@ -34,10 +49,10 @@ describe('cors', () => {
expect(response.headers['access-control-allow-headers']).to.equal('*');
});

it('should include the header with value of *, Authorization', async () => {
it('should include the header with value of Authorization, Content-Type', async () => {
const response = await requestAgent.options('/v1/measurements').send() as Response;

expect(response.headers['access-control-allow-headers']).to.equal('*, Authorization');
expect(response.headers['access-control-allow-headers']).to.equal('Authorization, Content-Type');
});
});
});

0 comments on commit d88b742

Please sign in to comment.