@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 CHANGED
@@ -1 +1,69 @@
1
- export { cors } from './cors';
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.5",
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": "rm -rf dist && tsc --noEmit false",
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 {};