@tekir/cors 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cors.js +35 -13
- package/package.json +1 -1
- package/src/cors.ts +37 -12
package/dist/cors.js
CHANGED
|
@@ -42,19 +42,24 @@ const defaults = {
|
|
|
42
42
|
*/
|
|
43
43
|
export function cors(userConfig = {}) {
|
|
44
44
|
const cfg = { ...defaults, ...userConfig };
|
|
45
|
+
// Reflecting an arbitrary Origin with `Access-Control-Allow-Credentials: true`
|
|
46
|
+
// hands any site credentialed access to this origin's responses. Refuse the
|
|
47
|
+
// dangerous `origin: true` + `credentials: true` combination at construction
|
|
48
|
+
// time and require an explicit allowlist (string/array/function) instead.
|
|
49
|
+
if (cfg.credentials && cfg.origin === true) {
|
|
50
|
+
throw new Error('@tekir/cors: `credentials: true` cannot be combined with `origin: true`. ' +
|
|
51
|
+
'Reflecting every Origin with credentials enabled lets any site read authenticated responses. ' +
|
|
52
|
+
'Provide an explicit allowlist (string, string[], or a validator function).');
|
|
53
|
+
}
|
|
45
54
|
return async (ctx, next) => {
|
|
46
55
|
if (!cfg.enabled)
|
|
47
56
|
return next();
|
|
48
57
|
const origin = ctx.request.header('origin') || ctx.headers?.origin || '';
|
|
49
58
|
let allowOrigin = '';
|
|
50
59
|
if (cfg.origin === true) {
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
allowOrigin = origin || '*';
|
|
57
|
-
}
|
|
60
|
+
// No credentials here (the credentials+true combo is rejected above), so
|
|
61
|
+
// the wildcard is safe. Reflect the request origin when present, else `*`.
|
|
62
|
+
allowOrigin = origin || '*';
|
|
58
63
|
}
|
|
59
64
|
else if (cfg.origin === false) {
|
|
60
65
|
allowOrigin = '';
|
|
@@ -63,12 +68,16 @@ export function cors(userConfig = {}) {
|
|
|
63
68
|
allowOrigin = cfg.origin;
|
|
64
69
|
}
|
|
65
70
|
else if (Array.isArray(cfg.origin)) {
|
|
66
|
-
|
|
67
|
-
allowOrigin = cfg.origin.
|
|
71
|
+
// RFC 6454 origins are compared exactly (case-sensitive scheme/host).
|
|
72
|
+
allowOrigin = cfg.origin.includes(origin) ? origin : '';
|
|
68
73
|
}
|
|
69
74
|
else if (typeof cfg.origin === 'function') {
|
|
70
75
|
allowOrigin = cfg.origin(origin) ? origin : '';
|
|
71
76
|
}
|
|
77
|
+
// A `null` origin (sandboxed iframes, data:/file: schemes) must never be
|
|
78
|
+
// trusted with credentials — it is not bound to any real site.
|
|
79
|
+
if (cfg.credentials && allowOrigin === 'null')
|
|
80
|
+
return next();
|
|
72
81
|
if (!allowOrigin)
|
|
73
82
|
return next();
|
|
74
83
|
// Stash the negotiated CORS headers on ctx so the framework merges them
|
|
@@ -92,10 +101,23 @@ export function cors(userConfig = {}) {
|
|
|
92
101
|
}
|
|
93
102
|
const method = ctx.request?.method || ctx.request?.raw?.method || '';
|
|
94
103
|
if (method === 'OPTIONS') {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
// Skip empty header values: an empty Allow-Methods/Allow-Headers silently
|
|
105
|
+
// breaks the preflight instead of leaving the browser's defaults in place.
|
|
106
|
+
if (cfg.methods?.length)
|
|
107
|
+
headers.set('Access-Control-Allow-Methods', cfg.methods.join(', '));
|
|
108
|
+
let allowHeaders = '';
|
|
109
|
+
if (cfg.headers === true) {
|
|
110
|
+
// `headers: true` reflects the requested headers. With credentials we
|
|
111
|
+
// must echo the explicit list (a `*` is invalid for credentialed
|
|
112
|
+
// requests and would fail the preflight), never a wildcard.
|
|
113
|
+
const requested = ctx.request.header('access-control-request-headers') || '';
|
|
114
|
+
allowHeaders = cfg.credentials ? requested : (requested || '*');
|
|
115
|
+
}
|
|
116
|
+
else if (Array.isArray(cfg.headers)) {
|
|
117
|
+
allowHeaders = cfg.headers.join(', ');
|
|
118
|
+
}
|
|
119
|
+
if (allowHeaders)
|
|
120
|
+
headers.set('Access-Control-Allow-Headers', allowHeaders);
|
|
99
121
|
if (cfg.maxAge)
|
|
100
122
|
headers.set('Access-Control-Max-Age', String(cfg.maxAge));
|
|
101
123
|
// Short-circuit the chain. Framework merges $responseHeaders onto this
|
package/package.json
CHANGED
package/src/cors.ts
CHANGED
|
@@ -46,6 +46,18 @@ const defaults: CorsConfig = {
|
|
|
46
46
|
export function cors(userConfig: CorsConfig = {}) {
|
|
47
47
|
const cfg = { ...defaults, ...userConfig }
|
|
48
48
|
|
|
49
|
+
// Reflecting an arbitrary Origin with `Access-Control-Allow-Credentials: true`
|
|
50
|
+
// hands any site credentialed access to this origin's responses. Refuse the
|
|
51
|
+
// dangerous `origin: true` + `credentials: true` combination at construction
|
|
52
|
+
// time and require an explicit allowlist (string/array/function) instead.
|
|
53
|
+
if (cfg.credentials && cfg.origin === true) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
'@tekir/cors: `credentials: true` cannot be combined with `origin: true`. ' +
|
|
56
|
+
'Reflecting every Origin with credentials enabled lets any site read authenticated responses. ' +
|
|
57
|
+
'Provide an explicit allowlist (string, string[], or a validator function).'
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
return async (ctx: any, next: () => Promise<void>) => {
|
|
50
62
|
if (!cfg.enabled) return next()
|
|
51
63
|
|
|
@@ -53,23 +65,24 @@ export function cors(userConfig: CorsConfig = {}) {
|
|
|
53
65
|
|
|
54
66
|
let allowOrigin = ''
|
|
55
67
|
if (cfg.origin === true) {
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
} else {
|
|
60
|
-
allowOrigin = origin || '*'
|
|
61
|
-
}
|
|
68
|
+
// No credentials here (the credentials+true combo is rejected above), so
|
|
69
|
+
// the wildcard is safe. Reflect the request origin when present, else `*`.
|
|
70
|
+
allowOrigin = origin || '*'
|
|
62
71
|
} else if (cfg.origin === false) {
|
|
63
72
|
allowOrigin = ''
|
|
64
73
|
} else if (typeof cfg.origin === 'string') {
|
|
65
74
|
allowOrigin = cfg.origin
|
|
66
75
|
} else if (Array.isArray(cfg.origin)) {
|
|
67
|
-
|
|
68
|
-
allowOrigin = cfg.origin.
|
|
76
|
+
// RFC 6454 origins are compared exactly (case-sensitive scheme/host).
|
|
77
|
+
allowOrigin = cfg.origin.includes(origin) ? origin : ''
|
|
69
78
|
} else if (typeof cfg.origin === 'function') {
|
|
70
79
|
allowOrigin = cfg.origin(origin) ? origin : ''
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
// A `null` origin (sandboxed iframes, data:/file: schemes) must never be
|
|
83
|
+
// trusted with credentials — it is not bound to any real site.
|
|
84
|
+
if (cfg.credentials && allowOrigin === 'null') return next()
|
|
85
|
+
|
|
73
86
|
if (!allowOrigin) return next()
|
|
74
87
|
|
|
75
88
|
// Stash the negotiated CORS headers on ctx so the framework merges them
|
|
@@ -91,10 +104,22 @@ export function cors(userConfig: CorsConfig = {}) {
|
|
|
91
104
|
|
|
92
105
|
const method = ctx.request?.method || ctx.request?.raw?.method || ''
|
|
93
106
|
if (method === 'OPTIONS') {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
// Skip empty header values: an empty Allow-Methods/Allow-Headers silently
|
|
108
|
+
// breaks the preflight instead of leaving the browser's defaults in place.
|
|
109
|
+
if (cfg.methods?.length) headers.set('Access-Control-Allow-Methods', cfg.methods.join(', '))
|
|
110
|
+
|
|
111
|
+
let allowHeaders = ''
|
|
112
|
+
if (cfg.headers === true) {
|
|
113
|
+
// `headers: true` reflects the requested headers. With credentials we
|
|
114
|
+
// must echo the explicit list (a `*` is invalid for credentialed
|
|
115
|
+
// requests and would fail the preflight), never a wildcard.
|
|
116
|
+
const requested = ctx.request.header('access-control-request-headers') || ''
|
|
117
|
+
allowHeaders = cfg.credentials ? requested : (requested || '*')
|
|
118
|
+
} else if (Array.isArray(cfg.headers)) {
|
|
119
|
+
allowHeaders = cfg.headers.join(', ')
|
|
120
|
+
}
|
|
121
|
+
if (allowHeaders) headers.set('Access-Control-Allow-Headers', allowHeaders)
|
|
122
|
+
|
|
98
123
|
if (cfg.maxAge) headers.set('Access-Control-Max-Age', String(cfg.maxAge))
|
|
99
124
|
// Short-circuit the chain. Framework merges $responseHeaders onto this
|
|
100
125
|
// bare 204, so the preflight response carries all the negotiated bits.
|