Skip to content

Commit

Permalink
fix: cors
Browse files Browse the repository at this point in the history
Signed-off-by: Wouter Termont <[email protected]>
  • Loading branch information
termontwouter committed Mar 26, 2024
1 parent e524c16 commit 2acab7b
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 20 deletions.
44 changes: 24 additions & 20 deletions packages/uma/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,30 @@
"@id": "urn:uma:default:NodeHttpRequestResponseHandler",
"@type": "NodeHttpRequestResponseHandler",
"httpHandler": {
"@id": "urn:uma:default:RoutedHttpRequestHandler",
"@type": "RoutedHttpRequestHandler",
"handlerControllerList": [
{
"@id": "urn:uma:default:HttpHandlerController",
"@type": "HttpHandlerController",
"label": "ControllerList",
"routes": [
{ "@id": "urn:uma:default:UmaConfigRoute" },
{ "@id": "urn:uma:default:JwksRoute" },
{ "@id": "urn:uma:default:TokenRoute" },
{ "@id": "urn:uma:default:PermissionRegistrationRoute" },
{ "@id": "urn:uma:default:ResourceRegistrationRoute" },
{ "@id": "urn:uma:default:ResourceRegistrationOpsRoute" },
{ "@id": "urn:uma:default:IntrospectionRoute" }
]
"@id": "urn:uma:default:CorsRequestHandler",
"@type": "CorsRequestHandler",
"handler": {
"@id": "urn:uma:default:RoutedHttpRequestHandler",
"@type": "RoutedHttpRequestHandler",
"handlerControllerList": [
{
"@id": "urn:uma:default:HttpHandlerController",
"@type": "HttpHandlerController",
"label": "ControllerList",
"routes": [
{ "@id": "urn:uma:default:UmaConfigRoute" },
{ "@id": "urn:uma:default:JwksRoute" },
{ "@id": "urn:uma:default:TokenRoute" },
{ "@id": "urn:uma:default:PermissionRegistrationRoute" },
{ "@id": "urn:uma:default:ResourceRegistrationRoute" },
{ "@id": "urn:uma:default:ResourceRegistrationOpsRoute" },
{ "@id": "urn:uma:default:IntrospectionRoute" }
]
}
],
"defaultHandler": {
"@type": "DefaultRequestHandler"
}
],
"defaultHandler": {
"@type": "DefaultRequestHandler"
}
}
}
Expand All @@ -61,4 +65,4 @@
"comment": "Configuration for the UMA AS."
}
]
}
}
1 change: 1 addition & 0 deletions packages/uma/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export * from './util/http/models/HttpHandlerResponse';
export * from './util/http/models/HttpHandlerRoute';
export * from './util/http/models/HttpMethod';
export * from './util/http/server/ErrorHandler';
export * from './util/http/server/CorsRequestHandler';
export * from './util/http/server/NodeHttpRequestResponseHandler';
export * from './util/http/server/NodeHttpServer';
export * from './util/http/server/NodeHttpStreamsHandler';
Expand Down
137 changes: 137 additions & 0 deletions packages/uma/src/util/http/server/CorsRequestHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { getLogger } from '../../logging/LoggerUtils';
import { HttpHandler } from '../models/HttpHandler';
import { HttpHandlerContext } from '../models/HttpHandlerContext';
import { HttpHandlerResponse } from '../models/HttpHandlerResponse';

export const cleanHeaders = (headers: Record<string, string>): Record<string, string> => Object.entries(headers).reduce(
(acc: Record<string, string>, [ key, value ]) => {

const lKey = key.toLowerCase();

return { ... acc, [lKey]: acc[lKey] ? `${acc[lKey]},${value}` : value };

}, {},
);

export interface HttpCorsOptions {
origins?: string[];
allowMethods?: string[];
allowHeaders?: string[];
exposeHeaders?: string[];
credentials?: boolean;
maxAge?: number;
}
export class CorsRequestHandler implements HttpHandler {

public logger = getLogger();

constructor(
private handler: HttpHandler,
private options?: HttpCorsOptions,
private passThroughOptions: boolean = false,
) { }

async handle(context: HttpHandlerContext): Promise<HttpHandlerResponse> {

const { origins, allowMethods, allowHeaders, exposeHeaders, credentials, maxAge } = this.options || ({});

const requestHeaders = context.request.headers;

const cleanRequestHeaders = cleanHeaders(requestHeaders);

const {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars -- destructuring for removal */
['access-control-request-method']: requestedMethod,
['access-control-request-headers']: requestedHeaders,
... noCorsHeaders
} = cleanRequestHeaders;

const noCorsRequestContext = {
... context,
request: {
... context.request,
headers: {
... noCorsHeaders,
},
},
};

const requestedOrigin = cleanRequestHeaders.origin ?? '';

const allowOrigin = origins
? origins.includes(requestedOrigin)
? requestedOrigin
: undefined
: credentials
? requestedOrigin
: '*';

const allowHeadersOrRequested = allowHeaders?.join(',') ?? requestedHeaders;

if (context.request.method === 'OPTIONS') {

/* Preflight Request */

this.logger.debug('Processing preflight request');

const routeMethods = context.route?.operations.map((op) => op.method);
const allMethods = [ 'GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH' ];

const initialOptions = this.passThroughOptions
? this.handler.handle(noCorsRequestContext)
: Promise.resolve({ status: 204, headers: {} });

return initialOptions
.then((response) => ({
... response,
headers: response.headers ? cleanHeaders(response.headers) : {},
}))
.then((response) => ({
... response,
headers: {

... response.headers,
... allowOrigin && ({
... (allowOrigin !== '*') && {
'vary': [ ... new Set([
... response.headers.vary?.split(',').map((v) => v.trim().toLowerCase()) ?? [], `origin`
]) ].join(', ')
},
'access-control-allow-origin': allowOrigin,
'access-control-allow-methods': (allowMethods ?? routeMethods ?? allMethods).join(', '),
... (allowHeadersOrRequested) && { 'access-control-allow-headers': allowHeadersOrRequested },
... (credentials) && { 'access-control-allow-credentials': 'true' },
'access-control-max-age': (maxAge ?? -1).toString(),
}),
},
}));

} else {

/* CORS Request */

this.logger.debug('Processing CORS request');

return this.handler.handle(noCorsRequestContext)
.then((response) => ({
... response,
headers: {
... response.headers,
... allowOrigin && ({
'access-control-allow-origin': allowOrigin,
... (allowOrigin !== '*') && {
'vary': [ ... new Set([
... response.headers?.vary?.split(',').map((v) => v.trim().toLowerCase()) ?? [], `origin`
]) ].join(', ')
},
... (credentials) && { 'access-control-allow-credentials': 'true' },
... (exposeHeaders) && { 'access-control-expose-headers': exposeHeaders.join(',') },
}),
},
}));

}

}

}

0 comments on commit 2acab7b

Please sign in to comment.