@tekir/cors 0.1.5 → 0.1.7
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/index.js +69 -1
- package/package.json +2 -2
- package/dist/cors.js +0 -129
- package/dist/types.js +0 -1
package/dist/index.js
CHANGED
|
@@ -1 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
// src/cors.ts
|
|
2
|
+
var defaults = {
|
|
3
|
+
enabled: true,
|
|
4
|
+
origin: true,
|
|
5
|
+
methods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
|
|
6
|
+
headers: true,
|
|
7
|
+
credentials: false,
|
|
8
|
+
maxAge: 86400
|
|
9
|
+
};
|
|
10
|
+
function cors(userConfig = {}) {
|
|
11
|
+
const cfg = { ...defaults, ...userConfig };
|
|
12
|
+
if (cfg.credentials && cfg.origin === true) {
|
|
13
|
+
throw new Error("@tekir/cors: `credentials: true` cannot be combined with `origin: true`. " + "Reflecting every Origin with credentials enabled lets any site read authenticated responses. " + "Provide an explicit allowlist (string, string[], or a validator function).");
|
|
14
|
+
}
|
|
15
|
+
return async (ctx, next) => {
|
|
16
|
+
if (!cfg.enabled)
|
|
17
|
+
return next();
|
|
18
|
+
const origin = ctx.request.header("origin") || ctx.headers?.origin || "";
|
|
19
|
+
let allowOrigin = "";
|
|
20
|
+
if (cfg.origin === true) {
|
|
21
|
+
allowOrigin = origin || "*";
|
|
22
|
+
} else if (cfg.origin === false) {
|
|
23
|
+
allowOrigin = "";
|
|
24
|
+
} else if (typeof cfg.origin === "string") {
|
|
25
|
+
allowOrigin = cfg.origin;
|
|
26
|
+
} else if (Array.isArray(cfg.origin)) {
|
|
27
|
+
allowOrigin = cfg.origin.includes(origin) ? origin : "";
|
|
28
|
+
} else if (typeof cfg.origin === "function") {
|
|
29
|
+
allowOrigin = cfg.origin(origin) ? origin : "";
|
|
30
|
+
}
|
|
31
|
+
if (cfg.credentials && allowOrigin === "null")
|
|
32
|
+
return next();
|
|
33
|
+
if (!allowOrigin)
|
|
34
|
+
return next();
|
|
35
|
+
const headers = ctx.$responseHeaders ??= new Headers;
|
|
36
|
+
headers.set("Access-Control-Allow-Origin", allowOrigin);
|
|
37
|
+
if (cfg.credentials)
|
|
38
|
+
headers.set("Access-Control-Allow-Credentials", "true");
|
|
39
|
+
if (cfg.exposeHeaders?.length)
|
|
40
|
+
headers.set("Access-Control-Expose-Headers", cfg.exposeHeaders.join(", "));
|
|
41
|
+
const existingVary = headers.get("Vary");
|
|
42
|
+
if (!existingVary) {
|
|
43
|
+
headers.set("Vary", "Origin");
|
|
44
|
+
} else if (!existingVary.split(",").map((s) => s.trim().toLowerCase()).includes("origin")) {
|
|
45
|
+
headers.set("Vary", `${existingVary}, Origin`);
|
|
46
|
+
}
|
|
47
|
+
const method = ctx.request?.method || ctx.request?.raw?.method || "";
|
|
48
|
+
if (method === "OPTIONS") {
|
|
49
|
+
if (cfg.methods?.length)
|
|
50
|
+
headers.set("Access-Control-Allow-Methods", cfg.methods.join(", "));
|
|
51
|
+
let allowHeaders = "";
|
|
52
|
+
if (cfg.headers === true) {
|
|
53
|
+
const requested = ctx.request.header("access-control-request-headers") || "";
|
|
54
|
+
allowHeaders = cfg.credentials ? requested : requested || "*";
|
|
55
|
+
} else if (Array.isArray(cfg.headers)) {
|
|
56
|
+
allowHeaders = cfg.headers.join(", ");
|
|
57
|
+
}
|
|
58
|
+
if (allowHeaders)
|
|
59
|
+
headers.set("Access-Control-Allow-Headers", allowHeaders);
|
|
60
|
+
if (cfg.maxAge)
|
|
61
|
+
headers.set("Access-Control-Max-Age", String(cfg.maxAge));
|
|
62
|
+
return new Response(null, { status: 204 });
|
|
63
|
+
}
|
|
64
|
+
await next();
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
cors
|
|
69
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekir/cors",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "CORS middleware for cross-origin requests",
|
|
5
5
|
"author": "dev@tekir.io",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "
|
|
42
|
+
"build": "bun ../../scripts/build-package.ts",
|
|
43
43
|
"prepublishOnly": "bun run build"
|
|
44
44
|
},
|
|
45
45
|
"exports": {
|
package/dist/cors.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
const defaults = {
|
|
2
|
-
enabled: true,
|
|
3
|
-
origin: true,
|
|
4
|
-
methods: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
5
|
-
headers: true,
|
|
6
|
-
credentials: false,
|
|
7
|
-
maxAge: 86400,
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* CORS middleware that handles preflight OPTIONS requests and sets Access-Control headers.
|
|
11
|
-
* Supports wildcard, array, string, and function-based origin validation.
|
|
12
|
-
* When `credentials: true` with `origin: true`, reflects the request origin instead of using `*`.
|
|
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
|
-
*
|
|
18
|
-
* @param userConfig - CORS configuration options.
|
|
19
|
-
* @param userConfig.origin - Allowed origins: `true` (all), `false` (none), `string`, `string[]`, or `(origin) => boolean`.
|
|
20
|
-
* @param userConfig.methods - Allowed HTTP methods. Defaults to `['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']`.
|
|
21
|
-
* @param userConfig.credentials - Allow credentials (cookies, auth headers). Defaults to `false`.
|
|
22
|
-
* @param userConfig.maxAge - Preflight cache duration in seconds. Defaults to `86400` (24h).
|
|
23
|
-
* @param userConfig.headers - Allowed request headers: `true` (reflect), or `string[]`.
|
|
24
|
-
* @param userConfig.exposeHeaders - Headers exposed to the browser.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```ts
|
|
28
|
-
* // Allow all origins
|
|
29
|
-
* app.use(cors())
|
|
30
|
-
*
|
|
31
|
-
* // Allow specific origins with credentials
|
|
32
|
-
* app.use(cors({
|
|
33
|
-
* origin: ['https://app.com', 'https://admin.app.com'],
|
|
34
|
-
* credentials: true,
|
|
35
|
-
* }))
|
|
36
|
-
*
|
|
37
|
-
* // Dynamic origin validation
|
|
38
|
-
* app.use(cors({
|
|
39
|
-
* origin: (origin) => origin.endsWith('.myapp.com'),
|
|
40
|
-
* }))
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export function cors(userConfig = {}) {
|
|
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
|
-
}
|
|
54
|
-
return async (ctx, next) => {
|
|
55
|
-
if (!cfg.enabled)
|
|
56
|
-
return next();
|
|
57
|
-
const origin = ctx.request.header('origin') || ctx.headers?.origin || '';
|
|
58
|
-
let allowOrigin = '';
|
|
59
|
-
if (cfg.origin === true) {
|
|
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 || '*';
|
|
63
|
-
}
|
|
64
|
-
else if (cfg.origin === false) {
|
|
65
|
-
allowOrigin = '';
|
|
66
|
-
}
|
|
67
|
-
else if (typeof cfg.origin === 'string') {
|
|
68
|
-
allowOrigin = cfg.origin;
|
|
69
|
-
}
|
|
70
|
-
else if (Array.isArray(cfg.origin)) {
|
|
71
|
-
// RFC 6454 origins are compared exactly (case-sensitive scheme/host).
|
|
72
|
-
allowOrigin = cfg.origin.includes(origin) ? origin : '';
|
|
73
|
-
}
|
|
74
|
-
else if (typeof cfg.origin === 'function') {
|
|
75
|
-
allowOrigin = cfg.origin(origin) ? origin : '';
|
|
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();
|
|
81
|
-
if (!allowOrigin)
|
|
82
|
-
return next();
|
|
83
|
-
// Stash the negotiated CORS headers on ctx so the framework merges them
|
|
84
|
-
// onto whatever response goes out. Writing here (instead of mutating
|
|
85
|
-
// `ctx.$result` after `next()`) makes CORS ordering-independent: it
|
|
86
|
-
// works whether `cors()` sits before or after error handlers and slots
|
|
87
|
-
// headers onto framework-handled 404s and 500s alike.
|
|
88
|
-
const headers = (ctx.$responseHeaders ??= new Headers());
|
|
89
|
-
headers.set('Access-Control-Allow-Origin', allowOrigin);
|
|
90
|
-
if (cfg.credentials)
|
|
91
|
-
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
92
|
-
if (cfg.exposeHeaders?.length)
|
|
93
|
-
headers.set('Access-Control-Expose-Headers', cfg.exposeHeaders.join(', '));
|
|
94
|
-
// Vary: Origin keeps caches honest when the allow-origin is request-derived.
|
|
95
|
-
const existingVary = headers.get('Vary');
|
|
96
|
-
if (!existingVary) {
|
|
97
|
-
headers.set('Vary', 'Origin');
|
|
98
|
-
}
|
|
99
|
-
else if (!existingVary.split(',').map(s => s.trim().toLowerCase()).includes('origin')) {
|
|
100
|
-
headers.set('Vary', `${existingVary}, Origin`);
|
|
101
|
-
}
|
|
102
|
-
const method = ctx.request?.method || ctx.request?.raw?.method || '';
|
|
103
|
-
if (method === 'OPTIONS') {
|
|
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);
|
|
121
|
-
if (cfg.maxAge)
|
|
122
|
-
headers.set('Access-Control-Max-Age', String(cfg.maxAge));
|
|
123
|
-
// Short-circuit the chain. Framework merges $responseHeaders onto this
|
|
124
|
-
// bare 204, so the preflight response carries all the negotiated bits.
|
|
125
|
-
return new Response(null, { status: 204 });
|
|
126
|
-
}
|
|
127
|
-
await next();
|
|
128
|
-
};
|
|
129
|
-
}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|