@tekir/cors 0.1.2 → 0.1.4
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.d.ts +4 -0
- package/dist/cors.js +30 -67
- package/package.json +1 -1
- package/src/cors.ts +29 -65
package/dist/cors.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ import type { CorsConfig } from './types';
|
|
|
4
4
|
* Supports wildcard, array, string, and function-based origin validation.
|
|
5
5
|
* When `credentials: true` with `origin: true`, reflects the request origin instead of using `*`.
|
|
6
6
|
*
|
|
7
|
+
* Headers are written to `ctx.$responseHeaders` so the framework merges them
|
|
8
|
+
* onto the outgoing response right before it goes on the wire, regardless of
|
|
9
|
+
* which middleware built the response or where in the chain CORS sits.
|
|
10
|
+
*
|
|
7
11
|
* @param userConfig - CORS configuration options.
|
|
8
12
|
* @param userConfig.origin - Allowed origins: `true` (all), `false` (none), `string`, `string[]`, or `(origin) => boolean`.
|
|
9
13
|
* @param userConfig.methods - Allowed HTTP methods. Defaults to `['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']`.
|
package/dist/cors.js
CHANGED
|
@@ -11,6 +11,10 @@ const defaults = {
|
|
|
11
11
|
* Supports wildcard, array, string, and function-based origin validation.
|
|
12
12
|
* When `credentials: true` with `origin: true`, reflects the request origin instead of using `*`.
|
|
13
13
|
*
|
|
14
|
+
* Headers are written to `ctx.$responseHeaders` so the framework merges them
|
|
15
|
+
* onto the outgoing response right before it goes on the wire, regardless of
|
|
16
|
+
* which middleware built the response or where in the chain CORS sits.
|
|
17
|
+
*
|
|
14
18
|
* @param userConfig - CORS configuration options.
|
|
15
19
|
* @param userConfig.origin - Allowed origins: `true` (all), `false` (none), `string`, `string[]`, or `(origin) => boolean`.
|
|
16
20
|
* @param userConfig.methods - Allowed HTTP methods. Defaults to `['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']`.
|
|
@@ -44,7 +48,7 @@ export function cors(userConfig = {}) {
|
|
|
44
48
|
const origin = ctx.request.header('origin') || ctx.headers?.origin || '';
|
|
45
49
|
let allowOrigin = '';
|
|
46
50
|
if (cfg.origin === true) {
|
|
47
|
-
// When credentials are enabled, never use wildcard
|
|
51
|
+
// When credentials are enabled, never use wildcard, reflect the request origin instead.
|
|
48
52
|
if (cfg.credentials && origin) {
|
|
49
53
|
allowOrigin = origin;
|
|
50
54
|
}
|
|
@@ -67,78 +71,37 @@ export function cors(userConfig = {}) {
|
|
|
67
71
|
}
|
|
68
72
|
if (!allowOrigin)
|
|
69
73
|
return next();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
'Access-Control-Allow-Headers': cfg.headers === true
|
|
78
|
-
? ctx.request.header('access-control-request-headers') || '*'
|
|
79
|
-
: Array.isArray(cfg.headers) ? cfg.headers.join(', ') : '',
|
|
80
|
-
...(cfg.credentials ? { 'Access-Control-Allow-Credentials': 'true' } : {}),
|
|
81
|
-
...(cfg.maxAge ? { 'Access-Control-Max-Age': String(cfg.maxAge) } : {}),
|
|
82
|
-
...(cfg.exposeHeaders?.length ? { 'Access-Control-Expose-Headers': cfg.exposeHeaders.join(', ') } : {}),
|
|
83
|
-
'Vary': 'Origin',
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
// Capture any throw from `next()` so the CORS merge below still runs
|
|
88
|
-
// even when a downstream middleware errors and an outer error handler
|
|
89
|
-
// turned it into a Response on `ctx.$result`. Without this guard the
|
|
90
|
-
// browser sees a "CORS error" on every error response, regardless of
|
|
91
|
-
// middleware order.
|
|
92
|
-
let nextError;
|
|
93
|
-
try {
|
|
94
|
-
await next();
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
nextError = err;
|
|
98
|
-
}
|
|
99
|
-
// Inject CORS headers into the actual response. Routes return a mix of
|
|
100
|
-
// Response objects (SSE streams, file downloads, custom payloads) and
|
|
101
|
-
// plain values that tekir wraps automatically. Coerce both shapes into
|
|
102
|
-
// a Response here so the headers always land on the wire.
|
|
103
|
-
const result = ctx.$result;
|
|
104
|
-
let response;
|
|
105
|
-
if (result instanceof Response) {
|
|
106
|
-
response = result;
|
|
107
|
-
}
|
|
108
|
-
else if (result == null) {
|
|
109
|
-
response = new Response(null, { status: 204 });
|
|
110
|
-
}
|
|
111
|
-
else if (typeof result === 'object') {
|
|
112
|
-
response = Response.json(result);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
response = new Response(String(result));
|
|
116
|
-
}
|
|
117
|
-
const merged = new Headers(response.headers);
|
|
118
|
-
merged.set('Access-Control-Allow-Origin', allowOrigin);
|
|
74
|
+
// Stash the negotiated CORS headers on ctx so the framework merges them
|
|
75
|
+
// onto whatever response goes out. Writing here (instead of mutating
|
|
76
|
+
// `ctx.$result` after `next()`) makes CORS ordering-independent: it
|
|
77
|
+
// works whether `cors()` sits before or after error handlers and slots
|
|
78
|
+
// headers onto framework-handled 404s and 500s alike.
|
|
79
|
+
const headers = (ctx.$responseHeaders ??= new Headers());
|
|
80
|
+
headers.set('Access-Control-Allow-Origin', allowOrigin);
|
|
119
81
|
if (cfg.credentials)
|
|
120
|
-
|
|
82
|
+
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
121
83
|
if (cfg.exposeHeaders?.length)
|
|
122
|
-
|
|
84
|
+
headers.set('Access-Control-Expose-Headers', cfg.exposeHeaders.join(', '));
|
|
123
85
|
// Vary: Origin keeps caches honest when the allow-origin is request-derived.
|
|
124
|
-
const existingVary =
|
|
86
|
+
const existingVary = headers.get('Vary');
|
|
125
87
|
if (!existingVary) {
|
|
126
|
-
|
|
88
|
+
headers.set('Vary', 'Origin');
|
|
127
89
|
}
|
|
128
90
|
else if (!existingVary.split(',').map(s => s.trim().toLowerCase()).includes('origin')) {
|
|
129
|
-
|
|
91
|
+
headers.set('Vary', `${existingVary}, Origin`);
|
|
92
|
+
}
|
|
93
|
+
const method = ctx.request?.method || ctx.request?.raw?.method || '';
|
|
94
|
+
if (method === 'OPTIONS') {
|
|
95
|
+
headers.set('Access-Control-Allow-Methods', (cfg.methods || []).join(', '));
|
|
96
|
+
headers.set('Access-Control-Allow-Headers', cfg.headers === true
|
|
97
|
+
? ctx.request.header('access-control-request-headers') || '*'
|
|
98
|
+
: Array.isArray(cfg.headers) ? cfg.headers.join(', ') : '');
|
|
99
|
+
if (cfg.maxAge)
|
|
100
|
+
headers.set('Access-Control-Max-Age', String(cfg.maxAge));
|
|
101
|
+
// Short-circuit the chain. Framework merges $responseHeaders onto this
|
|
102
|
+
// bare 204, so the preflight response carries all the negotiated bits.
|
|
103
|
+
return new Response(null, { status: 204 });
|
|
130
104
|
}
|
|
131
|
-
|
|
132
|
-
status: response.status,
|
|
133
|
-
statusText: response.statusText,
|
|
134
|
-
headers: merged,
|
|
135
|
-
});
|
|
136
|
-
// Re-throw so the outer error handler can still see the original error
|
|
137
|
-
// for logging or transformation. By now `ctx.$result` carries the CORS
|
|
138
|
-
// headers, so even if the outer handler builds its own error response
|
|
139
|
-
// it can copy them off the existing one. If the outer handler swallows
|
|
140
|
-
// the throw, we already wrote a coerced response above.
|
|
141
|
-
if (nextError !== undefined)
|
|
142
|
-
throw nextError;
|
|
105
|
+
await next();
|
|
143
106
|
};
|
|
144
107
|
}
|
package/package.json
CHANGED
package/src/cors.ts
CHANGED
|
@@ -14,6 +14,10 @@ const defaults: CorsConfig = {
|
|
|
14
14
|
* Supports wildcard, array, string, and function-based origin validation.
|
|
15
15
|
* When `credentials: true` with `origin: true`, reflects the request origin instead of using `*`.
|
|
16
16
|
*
|
|
17
|
+
* Headers are written to `ctx.$responseHeaders` so the framework merges them
|
|
18
|
+
* onto the outgoing response right before it goes on the wire, regardless of
|
|
19
|
+
* which middleware built the response or where in the chain CORS sits.
|
|
20
|
+
*
|
|
17
21
|
* @param userConfig - CORS configuration options.
|
|
18
22
|
* @param userConfig.origin - Allowed origins: `true` (all), `false` (none), `string`, `string[]`, or `(origin) => boolean`.
|
|
19
23
|
* @param userConfig.methods - Allowed HTTP methods. Defaults to `['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']`.
|
|
@@ -49,7 +53,7 @@ export function cors(userConfig: CorsConfig = {}) {
|
|
|
49
53
|
|
|
50
54
|
let allowOrigin = ''
|
|
51
55
|
if (cfg.origin === true) {
|
|
52
|
-
// When credentials are enabled, never use wildcard
|
|
56
|
+
// When credentials are enabled, never use wildcard, reflect the request origin instead.
|
|
53
57
|
if (cfg.credentials && origin) {
|
|
54
58
|
allowOrigin = origin
|
|
55
59
|
} else {
|
|
@@ -68,75 +72,35 @@ export function cors(userConfig: CorsConfig = {}) {
|
|
|
68
72
|
|
|
69
73
|
if (!allowOrigin) return next()
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
: Array.isArray(cfg.headers) ? cfg.headers.join(', ') : '',
|
|
81
|
-
...(cfg.credentials ? { 'Access-Control-Allow-Credentials': 'true' } : {}),
|
|
82
|
-
...(cfg.maxAge ? { 'Access-Control-Max-Age': String(cfg.maxAge) } : {}),
|
|
83
|
-
...(cfg.exposeHeaders?.length ? { 'Access-Control-Expose-Headers': cfg.exposeHeaders.join(', ') } : {}),
|
|
84
|
-
'Vary': 'Origin',
|
|
85
|
-
},
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Capture any throw from `next()` so the CORS merge below still runs
|
|
90
|
-
// even when a downstream middleware errors and an outer error handler
|
|
91
|
-
// turned it into a Response on `ctx.$result`. Without this guard the
|
|
92
|
-
// browser sees a "CORS error" on every error response, regardless of
|
|
93
|
-
// middleware order.
|
|
94
|
-
let nextError: unknown
|
|
95
|
-
try {
|
|
96
|
-
await next()
|
|
97
|
-
} catch (err) {
|
|
98
|
-
nextError = err
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Inject CORS headers into the actual response. Routes return a mix of
|
|
102
|
-
// Response objects (SSE streams, file downloads, custom payloads) and
|
|
103
|
-
// plain values that tekir wraps automatically. Coerce both shapes into
|
|
104
|
-
// a Response here so the headers always land on the wire.
|
|
105
|
-
const result = ctx.$result
|
|
106
|
-
let response: Response
|
|
107
|
-
if (result instanceof Response) {
|
|
108
|
-
response = result
|
|
109
|
-
} else if (result == null) {
|
|
110
|
-
response = new Response(null, { status: 204 })
|
|
111
|
-
} else if (typeof result === 'object') {
|
|
112
|
-
response = Response.json(result)
|
|
113
|
-
} else {
|
|
114
|
-
response = new Response(String(result))
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const merged = new Headers(response.headers)
|
|
118
|
-
merged.set('Access-Control-Allow-Origin', allowOrigin)
|
|
119
|
-
if (cfg.credentials) merged.set('Access-Control-Allow-Credentials', 'true')
|
|
120
|
-
if (cfg.exposeHeaders?.length) merged.set('Access-Control-Expose-Headers', cfg.exposeHeaders.join(', '))
|
|
75
|
+
// Stash the negotiated CORS headers on ctx so the framework merges them
|
|
76
|
+
// onto whatever response goes out. Writing here (instead of mutating
|
|
77
|
+
// `ctx.$result` after `next()`) makes CORS ordering-independent: it
|
|
78
|
+
// works whether `cors()` sits before or after error handlers and slots
|
|
79
|
+
// headers onto framework-handled 404s and 500s alike.
|
|
80
|
+
const headers: Headers = (ctx.$responseHeaders ??= new Headers())
|
|
81
|
+
headers.set('Access-Control-Allow-Origin', allowOrigin)
|
|
82
|
+
if (cfg.credentials) headers.set('Access-Control-Allow-Credentials', 'true')
|
|
83
|
+
if (cfg.exposeHeaders?.length) headers.set('Access-Control-Expose-Headers', cfg.exposeHeaders.join(', '))
|
|
121
84
|
// Vary: Origin keeps caches honest when the allow-origin is request-derived.
|
|
122
|
-
const existingVary =
|
|
85
|
+
const existingVary = headers.get('Vary')
|
|
123
86
|
if (!existingVary) {
|
|
124
|
-
|
|
87
|
+
headers.set('Vary', 'Origin')
|
|
125
88
|
} else if (!existingVary.split(',').map(s => s.trim().toLowerCase()).includes('origin')) {
|
|
126
|
-
|
|
89
|
+
headers.set('Vary', `${existingVary}, Origin`)
|
|
127
90
|
}
|
|
128
91
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
headers
|
|
133
|
-
|
|
92
|
+
const method = ctx.request?.method || ctx.request?.raw?.method || ''
|
|
93
|
+
if (method === 'OPTIONS') {
|
|
94
|
+
headers.set('Access-Control-Allow-Methods', (cfg.methods || []).join(', '))
|
|
95
|
+
headers.set('Access-Control-Allow-Headers', cfg.headers === true
|
|
96
|
+
? ctx.request.header('access-control-request-headers') || '*'
|
|
97
|
+
: Array.isArray(cfg.headers) ? cfg.headers.join(', ') : '')
|
|
98
|
+
if (cfg.maxAge) headers.set('Access-Control-Max-Age', String(cfg.maxAge))
|
|
99
|
+
// Short-circuit the chain. Framework merges $responseHeaders onto this
|
|
100
|
+
// bare 204, so the preflight response carries all the negotiated bits.
|
|
101
|
+
return new Response(null, { status: 204 })
|
|
102
|
+
}
|
|
134
103
|
|
|
135
|
-
|
|
136
|
-
// for logging or transformation. By now `ctx.$result` carries the CORS
|
|
137
|
-
// headers, so even if the outer handler builds its own error response
|
|
138
|
-
// it can copy them off the existing one. If the outer handler swallows
|
|
139
|
-
// the throw, we already wrote a coerced response above.
|
|
140
|
-
if (nextError !== undefined) throw nextError
|
|
104
|
+
await next()
|
|
141
105
|
}
|
|
142
106
|
}
|