feat: Make CorsHandler customizable.

This commit is contained in:
Ruben Verborgh 2020-11-29 23:58:42 +01:00
parent d6c0f89cf5
commit 8dec921c10
3 changed files with 49 additions and 12 deletions

View File

@ -6,7 +6,16 @@
"@type": "AllVoidCompositeHandler",
"AllVoidCompositeHandler:_handlers": [
{
"@type": "CorsHandler"
"@type": "CorsHandler",
"CorsHandler:_options_methods": [
"GET",
"HEAD",
"OPTIONS",
"POST",
"PUT",
"PATCH",
"DELETE"
]
},
{
"@type": "HeaderHandler",

View File

@ -1,21 +1,35 @@
import cors from 'cors';
import type { CorsOptions } from 'cors';
import type { RequestHandler } from 'express';
import { HttpHandler } from '../HttpHandler';
import type { HttpRequest } from '../HttpRequest';
import type { HttpResponse } from '../HttpResponse';
const defaultOptions: CorsOptions = {
origin: (origin: any, callback: any): void => callback(null, origin ?? '*'),
};
// Components.js does not support the full CorsOptions yet
interface SimpleCorsOptions {
origin?: string;
methods?: string[];
allowedHeaders?: string[];
exposedHeaders?: string[];
credentials?: boolean;
maxAge?: number;
preflightContinue?: boolean;
optionsSuccessStatus?: number;
}
/**
* Handler that sets CORS options on the response.
*/
export class CorsHandler extends HttpHandler {
private readonly corsHandler: RequestHandler;
public constructor() {
public constructor(options: SimpleCorsOptions = {}) {
super();
this.corsHandler = cors({
origin: (origin, callback): void => callback(null, (origin ?? '*') as any),
methods: [ 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE' ],
});
this.corsHandler = cors({ ...defaultOptions, ...options });
}
public async handle(input: { request: HttpRequest; response: HttpResponse }): Promise<void> {

View File

@ -3,22 +3,21 @@ import { CorsHandler } from '../../../../src/server/middleware/CorsHandler';
import { guardStream } from '../../../../src/util/GuardedStream';
describe('a CorsHandler', (): void => {
let handler: CorsHandler;
it('sets regular CORS headers.', async(): Promise<void> => {
const handler = new CorsHandler();
beforeAll(async(): Promise<void> => {
handler = new CorsHandler();
});
it('returns CORS headers.', async(): Promise<void> => {
const request = guardStream(createRequest());
const response = createResponse();
await handler.handleSafe({ request, response });
expect(response.getHeaders()).toEqual(expect.objectContaining({
'access-control-allow-origin': '*',
}));
});
it('echoes the origin when specified.', async(): Promise<void> => {
const handler = new CorsHandler();
const request = guardStream(createRequest({
headers: {
origin: 'example.org',
@ -26,8 +25,23 @@ describe('a CorsHandler', (): void => {
}));
const response = createResponse();
await handler.handleSafe({ request, response });
expect(response.getHeaders()).toEqual(expect.objectContaining({
'access-control-allow-origin': 'example.org',
}));
});
it('supports customizations.', async(): Promise<void> => {
const handler = new CorsHandler({
exposedHeaders: [ 'Custom-Header' ],
});
const request = guardStream(createRequest());
const response = createResponse();
await handler.handleSafe({ request, response });
expect(response.getHeaders()).toEqual(expect.objectContaining({
'access-control-expose-headers': 'Custom-Header',
}));
});
});