-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcors.ts
125 lines (100 loc) · 4.38 KB
/
cors.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// deno-lint-ignore-file no-explicit-any
import type { Temporal } from 'https://cdn.skypack.dev/temporal-spec@0.0.2?dts';
import type { SetRequired } from 'https://cdn.skypack.dev/type-fest@2.15.1?dts';
import type { Awaitable } from "./utils/common-types.ts";
import type { Context } from "./index.ts";
export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
export const ORIGIN = 'Origin';
export const REQUEST_METHOD = 'Access-Control-Request-Method';
export const REQUEST_HEADERS = 'Access-Control-Request-Headers';
export const ALLOW_ORIGIN = 'Access-Control-Allow-Origin';
export const ALLOW_METHODS = 'Access-Control-Allow-Methods';
export const ALLOW_HEADERS = 'Access-Control-Allow-Headers';
export const ALLOW_CREDENTIALS = 'Access-Control-Allow-Credentials';
export const VARY = 'VARY';
export interface CORSOptions {
origin?: string | { origin: string }
methods?: Method[],
headers?: string[],
credentials?: boolean;
maxAge?: number | Temporal.Duration;
}
export type StrictCORSOptions = SetRequired<CORSOptions, 'origin' | 'methods' | 'headers'>;
const SECOND = { unit: 'second', relativeTo: '1970-01-01' } as Temporal.DurationTotalOf;
const isDuration = (x?: unknown): x is Temporal.Duration => (<any>x)?.[Symbol.toStringTag] === 'Temporal.Duration'
const toMaxAge = (x: number | Temporal.Duration) => (isDuration(x) ? x.total(SECOND) : x).toString()
/**
* A CORS middleware that gives clients exactly the permissions they ask for, unless constrained by the definitions in `options`.
*
* Note that applying this middleware to your routes isn't enough for non-GET requests.
* Pre-flight/OPTIONS routes need to be added manually:
* ```
* router.options('/your/path', anyCORS(), () => noContent())
* router.post('/your/path', anyCORS(), (req, {}) => ok())
* ```
*/
export const cors = (options: CORSOptions = {}) => async <X extends Context>(ax: Awaitable<X>): Promise<X> => {
const x = await ax;
const req = x.request;
x.effects.push(res => {
const optOrigin = typeof options.origin === 'string'
? new URL(options.origin)
: options.origin;
res.headers.set(ALLOW_ORIGIN, optOrigin?.origin ?? req.headers.get(ORIGIN) ?? '*');
const requestedMethod = <Method>req.headers.get(REQUEST_METHOD);
if (requestedMethod && (options.methods?.includes(requestedMethod) ?? true)) {
res.headers.append(ALLOW_METHODS, requestedMethod);
}
const requestedHeaders = new Set(req.headers.get(REQUEST_HEADERS)?.split(',')?.map(h => h.trim()))
for (const h of options.headers?.filter(h => requestedHeaders.has(h)) ?? requestedHeaders) {
res.headers.append(ALLOW_HEADERS, h);
}
if (options.credentials)
res.headers.set(ALLOW_CREDENTIALS, 'true');
if (options.maxAge)
res.headers.set('Access-Control-Max-Age', toMaxAge(options.maxAge))
if (!options.origin) res.headers.append(VARY, ORIGIN)
if (!options.methods) res.headers.append(VARY, REQUEST_METHOD)
if (!options.headers) res.headers.append(VARY, REQUEST_HEADERS)
return res;
})
return x;
}
export { cors as anyCORS }
/**
* A CORS middleware that only grants the permissions defined via `options`.
*
* Note that applying this middleware to your routes isn't enough for non-GET requests.
* Pre-flight/OPTIONS routes need to be added manually:
* ```
* router.options('/your/path', strictCORS({ ... }), () => noContent())
* router.post('/your/path', strictCORS({ ... }), (req, {}) => ok())
* ```
*/
export const strictCORS = (options: StrictCORSOptions) => async <X extends Context>(ax: Awaitable<X>): Promise<X> => {
const x = await ax;
const req = x.request;
x.effects.push(res => {
const optOrigin = typeof options.origin === 'string'
? new URL(options.origin)
: options.origin;
res.headers.set(ALLOW_ORIGIN, optOrigin.origin);
const requestedMethod = <Method>req.headers.get(REQUEST_METHOD);
if (requestedMethod && options.methods.includes(requestedMethod)) {
for (const m of options.methods) {
res.headers.append(ALLOW_METHODS, m);
}
}
if (req.headers.get(REQUEST_HEADERS)) {
for (const h of options.headers) {
res.headers.append(ALLOW_HEADERS, h);
}
}
if (options.credentials)
res.headers.set(ALLOW_CREDENTIALS, 'true');
if (options.maxAge)
res.headers.set('Access-Control-Max-Age', toMaxAge(options.maxAge))
return res;
})
return x;
}