Skip to content

Commit ea3b54c

Browse files
committed
Merge branch 'demo/setup' of github.com:SolidLabResearch/user-managed-access into demo/setup
2 parents 1592ae0 + 2acab7b commit ea3b54c

File tree

3 files changed

+162
-20
lines changed

3 files changed

+162
-20
lines changed

packages/uma/config/default.json

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,30 @@
3333
"@id": "urn:uma:default:NodeHttpRequestResponseHandler",
3434
"@type": "NodeHttpRequestResponseHandler",
3535
"httpHandler": {
36-
"@id": "urn:uma:default:RoutedHttpRequestHandler",
37-
"@type": "RoutedHttpRequestHandler",
38-
"handlerControllerList": [
39-
{
40-
"@id": "urn:uma:default:HttpHandlerController",
41-
"@type": "HttpHandlerController",
42-
"label": "ControllerList",
43-
"routes": [
44-
{ "@id": "urn:uma:default:UmaConfigRoute" },
45-
{ "@id": "urn:uma:default:JwksRoute" },
46-
{ "@id": "urn:uma:default:TokenRoute" },
47-
{ "@id": "urn:uma:default:PermissionRegistrationRoute" },
48-
{ "@id": "urn:uma:default:ResourceRegistrationRoute" },
49-
{ "@id": "urn:uma:default:ResourceRegistrationOpsRoute" },
50-
{ "@id": "urn:uma:default:IntrospectionRoute" }
51-
]
36+
"@id": "urn:uma:default:CorsRequestHandler",
37+
"@type": "CorsRequestHandler",
38+
"handler": {
39+
"@id": "urn:uma:default:RoutedHttpRequestHandler",
40+
"@type": "RoutedHttpRequestHandler",
41+
"handlerControllerList": [
42+
{
43+
"@id": "urn:uma:default:HttpHandlerController",
44+
"@type": "HttpHandlerController",
45+
"label": "ControllerList",
46+
"routes": [
47+
{ "@id": "urn:uma:default:UmaConfigRoute" },
48+
{ "@id": "urn:uma:default:JwksRoute" },
49+
{ "@id": "urn:uma:default:TokenRoute" },
50+
{ "@id": "urn:uma:default:PermissionRegistrationRoute" },
51+
{ "@id": "urn:uma:default:ResourceRegistrationRoute" },
52+
{ "@id": "urn:uma:default:ResourceRegistrationOpsRoute" },
53+
{ "@id": "urn:uma:default:IntrospectionRoute" }
54+
]
55+
}
56+
],
57+
"defaultHandler": {
58+
"@type": "DefaultRequestHandler"
5259
}
53-
],
54-
"defaultHandler": {
55-
"@type": "DefaultRequestHandler"
5660
}
5761
}
5862
}
@@ -61,4 +65,4 @@
6165
"comment": "Configuration for the UMA AS."
6266
}
6367
]
64-
}
68+
}

packages/uma/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export * from './util/http/models/HttpHandlerResponse';
8383
export * from './util/http/models/HttpHandlerRoute';
8484
export * from './util/http/models/HttpMethod';
8585
export * from './util/http/server/ErrorHandler';
86+
export * from './util/http/server/CorsRequestHandler';
8687
export * from './util/http/server/NodeHttpRequestResponseHandler';
8788
export * from './util/http/server/NodeHttpServer';
8889
export * from './util/http/server/NodeHttpStreamsHandler';
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { getLogger } from '../../logging/LoggerUtils';
2+
import { HttpHandler } from '../models/HttpHandler';
3+
import { HttpHandlerContext } from '../models/HttpHandlerContext';
4+
import { HttpHandlerResponse } from '../models/HttpHandlerResponse';
5+
6+
export const cleanHeaders = (headers: Record<string, string>): Record<string, string> => Object.entries(headers).reduce(
7+
(acc: Record<string, string>, [ key, value ]) => {
8+
9+
const lKey = key.toLowerCase();
10+
11+
return { ... acc, [lKey]: acc[lKey] ? `${acc[lKey]},${value}` : value };
12+
13+
}, {},
14+
);
15+
16+
export interface HttpCorsOptions {
17+
origins?: string[];
18+
allowMethods?: string[];
19+
allowHeaders?: string[];
20+
exposeHeaders?: string[];
21+
credentials?: boolean;
22+
maxAge?: number;
23+
}
24+
export class CorsRequestHandler implements HttpHandler {
25+
26+
public logger = getLogger();
27+
28+
constructor(
29+
private handler: HttpHandler,
30+
private options?: HttpCorsOptions,
31+
private passThroughOptions: boolean = false,
32+
) { }
33+
34+
async handle(context: HttpHandlerContext): Promise<HttpHandlerResponse> {
35+
36+
const { origins, allowMethods, allowHeaders, exposeHeaders, credentials, maxAge } = this.options || ({});
37+
38+
const requestHeaders = context.request.headers;
39+
40+
const cleanRequestHeaders = cleanHeaders(requestHeaders);
41+
42+
const {
43+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars -- destructuring for removal */
44+
['access-control-request-method']: requestedMethod,
45+
['access-control-request-headers']: requestedHeaders,
46+
... noCorsHeaders
47+
} = cleanRequestHeaders;
48+
49+
const noCorsRequestContext = {
50+
... context,
51+
request: {
52+
... context.request,
53+
headers: {
54+
... noCorsHeaders,
55+
},
56+
},
57+
};
58+
59+
const requestedOrigin = cleanRequestHeaders.origin ?? '';
60+
61+
const allowOrigin = origins
62+
? origins.includes(requestedOrigin)
63+
? requestedOrigin
64+
: undefined
65+
: credentials
66+
? requestedOrigin
67+
: '*';
68+
69+
const allowHeadersOrRequested = allowHeaders?.join(',') ?? requestedHeaders;
70+
71+
if (context.request.method === 'OPTIONS') {
72+
73+
/* Preflight Request */
74+
75+
this.logger.debug('Processing preflight request');
76+
77+
const routeMethods = context.route?.operations.map((op) => op.method);
78+
const allMethods = [ 'GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH' ];
79+
80+
const initialOptions = this.passThroughOptions
81+
? this.handler.handle(noCorsRequestContext)
82+
: Promise.resolve({ status: 204, headers: {} });
83+
84+
return initialOptions
85+
.then((response) => ({
86+
... response,
87+
headers: response.headers ? cleanHeaders(response.headers) : {},
88+
}))
89+
.then((response) => ({
90+
... response,
91+
headers: {
92+
93+
... response.headers,
94+
... allowOrigin && ({
95+
... (allowOrigin !== '*') && {
96+
'vary': [ ... new Set([
97+
... response.headers.vary?.split(',').map((v) => v.trim().toLowerCase()) ?? [], `origin`
98+
]) ].join(', ')
99+
},
100+
'access-control-allow-origin': allowOrigin,
101+
'access-control-allow-methods': (allowMethods ?? routeMethods ?? allMethods).join(', '),
102+
... (allowHeadersOrRequested) && { 'access-control-allow-headers': allowHeadersOrRequested },
103+
... (credentials) && { 'access-control-allow-credentials': 'true' },
104+
'access-control-max-age': (maxAge ?? -1).toString(),
105+
}),
106+
},
107+
}));
108+
109+
} else {
110+
111+
/* CORS Request */
112+
113+
this.logger.debug('Processing CORS request');
114+
115+
return this.handler.handle(noCorsRequestContext)
116+
.then((response) => ({
117+
... response,
118+
headers: {
119+
... response.headers,
120+
... allowOrigin && ({
121+
'access-control-allow-origin': allowOrigin,
122+
... (allowOrigin !== '*') && {
123+
'vary': [ ... new Set([
124+
... response.headers?.vary?.split(',').map((v) => v.trim().toLowerCase()) ?? [], `origin`
125+
]) ].join(', ')
126+
},
127+
... (credentials) && { 'access-control-allow-credentials': 'true' },
128+
... (exposeHeaders) && { 'access-control-expose-headers': exposeHeaders.join(',') },
129+
}),
130+
},
131+
}));
132+
133+
}
134+
135+
}
136+
137+
}

0 commit comments

Comments
 (0)