@getstrata/core 2.0.4 → 2.0.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.6
|
|
4
|
+
|
|
5
|
+
- Dispatch registered Bun route preflights through CORS
|
|
6
|
+
|
|
7
|
+
## 2.0.5
|
|
8
|
+
|
|
9
|
+
- Allow explicitly approved application headers in CORS
|
|
10
|
+
|
|
3
11
|
## 2.0.4
|
|
4
12
|
|
|
5
13
|
- Place cache rollout guidance beside outbox docs without overlapping API additions
|
|
@@ -5,6 +5,11 @@ interface CorsConfig {
|
|
|
5
5
|
allowedHeaders: string[];
|
|
6
6
|
maxAgeSeconds: number;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/** Additional application-approved request headers; defaults and origins remain unchanged. */
|
|
9
|
+
interface CorsOptions {
|
|
10
|
+
additionalAllowedHeaders?: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
declare function resolveCorsConfig(options?: CorsOptions): CorsConfig;
|
|
13
|
+
declare function createCorsMiddleware(options?: CorsOptions): Middleware;
|
|
14
|
+
export type { CorsConfig, CorsOptions };
|
|
10
15
|
export { createCorsMiddleware, resolveCorsConfig };
|
|
@@ -86,10 +86,28 @@ function sdkClientClassName() {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// ../../src/core/http/corsMiddleware.ts
|
|
89
|
+
function additionalHeaders(options) {
|
|
90
|
+
const configured = process.env.CORS_ADDITIONAL_ALLOWED_HEADERS?.trim();
|
|
91
|
+
const supplied = options.additionalAllowedHeaders === undefined ? [] : options.additionalAllowedHeaders;
|
|
92
|
+
if (!Array.isArray(supplied)) {
|
|
93
|
+
throw new TypeError("CORS additionalAllowedHeaders must be an array of header names.");
|
|
94
|
+
}
|
|
95
|
+
const names = [
|
|
96
|
+
...configured ? configured.split(",").map((name) => name.trim()) : [],
|
|
97
|
+
...supplied
|
|
98
|
+
];
|
|
99
|
+
return names.map((name) => {
|
|
100
|
+
if (typeof name !== "string" || name === "*" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) {
|
|
101
|
+
throw new TypeError("CORS additional headers must be explicit valid HTTP header names; wildcards are not allowed.");
|
|
102
|
+
}
|
|
103
|
+
return name;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
89
106
|
function defaultAllowedOrigins() {
|
|
90
107
|
return appUrl();
|
|
91
108
|
}
|
|
92
|
-
function resolveCorsConfig() {
|
|
109
|
+
function resolveCorsConfig(options = {}) {
|
|
110
|
+
const extraHeaders = additionalHeaders(options);
|
|
93
111
|
return {
|
|
94
112
|
allowedOrigins: (process.env.CORS_ALLOWED_ORIGINS ?? defaultAllowedOrigins()).split(",").map((origin) => origin.trim()).filter(Boolean),
|
|
95
113
|
allowedMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
|
@@ -102,22 +120,27 @@ function resolveCorsConfig() {
|
|
|
102
120
|
"X-Authenticated-User-Role",
|
|
103
121
|
"If-Match",
|
|
104
122
|
"If-None-Match",
|
|
105
|
-
"X-CSRF-Token"
|
|
106
|
-
|
|
123
|
+
"X-CSRF-Token",
|
|
124
|
+
...extraHeaders
|
|
125
|
+
].filter((name, index, names) => names.findIndex((other) => other.toLowerCase() === name.toLowerCase()) === index),
|
|
107
126
|
maxAgeSeconds: 86400
|
|
108
127
|
};
|
|
109
128
|
}
|
|
110
|
-
function createCorsMiddleware() {
|
|
129
|
+
function createCorsMiddleware(options = {}) {
|
|
130
|
+
resolveCorsConfig(options);
|
|
131
|
+
const captured = {
|
|
132
|
+
additionalAllowedHeaders: [...options.additionalAllowedHeaders ?? []]
|
|
133
|
+
};
|
|
111
134
|
return async (request, next) => {
|
|
112
135
|
if (request.method === "OPTIONS") {
|
|
113
136
|
return new Response(null, {
|
|
114
137
|
status: 204,
|
|
115
|
-
headers: buildCorsHeaders(request)
|
|
138
|
+
headers: buildCorsHeaders(request, captured)
|
|
116
139
|
});
|
|
117
140
|
}
|
|
118
141
|
const response = await next();
|
|
119
142
|
const headers = new Headers(response.headers);
|
|
120
|
-
for (const [key, value] of buildCorsHeaders(request)) {
|
|
143
|
+
for (const [key, value] of buildCorsHeaders(request, captured)) {
|
|
121
144
|
headers.set(key, value);
|
|
122
145
|
}
|
|
123
146
|
return new Response(response.body, {
|
|
@@ -127,10 +150,10 @@ function createCorsMiddleware() {
|
|
|
127
150
|
});
|
|
128
151
|
};
|
|
129
152
|
}
|
|
130
|
-
function buildCorsHeaders(request) {
|
|
153
|
+
function buildCorsHeaders(request, options) {
|
|
131
154
|
const headers = new Headers;
|
|
132
155
|
const origin = request.headers.get("origin");
|
|
133
|
-
const corsConfig = resolveCorsConfig();
|
|
156
|
+
const corsConfig = resolveCorsConfig(options);
|
|
134
157
|
const allowedOrigins = corsConfig.allowedOrigins;
|
|
135
158
|
headers.set("Vary", "Origin");
|
|
136
159
|
const allowOrigin = allowedOrigins.includes("*") ? "*" : origin && allowedOrigins.includes(origin) ? origin : null;
|
|
@@ -81,7 +81,7 @@ export { conditionalJsonResponse } from "../core/http/conditionalResponse.ts";
|
|
|
81
81
|
export type { ContentSecurityPolicyDirectives, ContentSecurityPolicyOptions, } from "../core/http/contentSecurityPolicy.ts";
|
|
82
82
|
export { configureContentSecurityPolicy, generateCspNonce, HTMX_2_0_4_INDICATOR_STYLE_HASH, resetContentSecurityPolicyForTests, resolveContentSecurityPolicy, resolveHtmlContentSecurityPolicy, serverHtmxContentSecurityPolicy, spaContentSecurityPolicy, strictApiContentSecurityPolicy, } from "../core/http/contentSecurityPolicy.ts";
|
|
83
83
|
export { readBunRequestCookie, readRequestCookie } from "../core/http/cookies.ts";
|
|
84
|
-
export { createCorsMiddleware } from "../core/http/corsMiddleware.ts";
|
|
84
|
+
export { type CorsConfig, type CorsOptions, createCorsMiddleware, } from "../core/http/corsMiddleware.ts";
|
|
85
85
|
export { createCsrfMiddleware } from "../core/http/csrfMiddleware.ts";
|
|
86
86
|
export { createCsrfProtection } from "../core/http/csrfProtection.ts";
|
|
87
87
|
export { createCsrfTokenCookie, readSubmittedCsrfToken, readSubmittedCsrfTokenFromBody, resolveCsrfToken, resolveCsrfTokenForRequest, verifyCsrfToken, } from "../core/http/csrfToken.ts";
|
package/dist/index.js
CHANGED
|
@@ -9466,10 +9466,28 @@ function readBunRequestCookie(request, name) {
|
|
|
9466
9466
|
return request.cookies.get(name) ?? readRequestCookie(request, name);
|
|
9467
9467
|
}
|
|
9468
9468
|
// ../../src/core/http/corsMiddleware.ts
|
|
9469
|
+
function additionalHeaders(options) {
|
|
9470
|
+
const configured = process.env.CORS_ADDITIONAL_ALLOWED_HEADERS?.trim();
|
|
9471
|
+
const supplied = options.additionalAllowedHeaders === undefined ? [] : options.additionalAllowedHeaders;
|
|
9472
|
+
if (!Array.isArray(supplied)) {
|
|
9473
|
+
throw new TypeError("CORS additionalAllowedHeaders must be an array of header names.");
|
|
9474
|
+
}
|
|
9475
|
+
const names = [
|
|
9476
|
+
...configured ? configured.split(",").map((name) => name.trim()) : [],
|
|
9477
|
+
...supplied
|
|
9478
|
+
];
|
|
9479
|
+
return names.map((name) => {
|
|
9480
|
+
if (typeof name !== "string" || name === "*" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) {
|
|
9481
|
+
throw new TypeError("CORS additional headers must be explicit valid HTTP header names; wildcards are not allowed.");
|
|
9482
|
+
}
|
|
9483
|
+
return name;
|
|
9484
|
+
});
|
|
9485
|
+
}
|
|
9469
9486
|
function defaultAllowedOrigins() {
|
|
9470
9487
|
return appUrl();
|
|
9471
9488
|
}
|
|
9472
|
-
function resolveCorsConfig() {
|
|
9489
|
+
function resolveCorsConfig(options = {}) {
|
|
9490
|
+
const extraHeaders = additionalHeaders(options);
|
|
9473
9491
|
return {
|
|
9474
9492
|
allowedOrigins: (process.env.CORS_ALLOWED_ORIGINS ?? defaultAllowedOrigins()).split(",").map((origin) => origin.trim()).filter(Boolean),
|
|
9475
9493
|
allowedMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
|
@@ -9482,22 +9500,27 @@ function resolveCorsConfig() {
|
|
|
9482
9500
|
"X-Authenticated-User-Role",
|
|
9483
9501
|
"If-Match",
|
|
9484
9502
|
"If-None-Match",
|
|
9485
|
-
"X-CSRF-Token"
|
|
9486
|
-
|
|
9503
|
+
"X-CSRF-Token",
|
|
9504
|
+
...extraHeaders
|
|
9505
|
+
].filter((name, index, names) => names.findIndex((other) => other.toLowerCase() === name.toLowerCase()) === index),
|
|
9487
9506
|
maxAgeSeconds: 86400
|
|
9488
9507
|
};
|
|
9489
9508
|
}
|
|
9490
|
-
function createCorsMiddleware() {
|
|
9509
|
+
function createCorsMiddleware(options = {}) {
|
|
9510
|
+
resolveCorsConfig(options);
|
|
9511
|
+
const captured = {
|
|
9512
|
+
additionalAllowedHeaders: [...options.additionalAllowedHeaders ?? []]
|
|
9513
|
+
};
|
|
9491
9514
|
return async (request, next) => {
|
|
9492
9515
|
if (request.method === "OPTIONS") {
|
|
9493
9516
|
return new Response(null, {
|
|
9494
9517
|
status: 204,
|
|
9495
|
-
headers: buildCorsHeaders(request)
|
|
9518
|
+
headers: buildCorsHeaders(request, captured)
|
|
9496
9519
|
});
|
|
9497
9520
|
}
|
|
9498
9521
|
const response = await next();
|
|
9499
9522
|
const headers = new Headers(response.headers);
|
|
9500
|
-
for (const [key, value] of buildCorsHeaders(request)) {
|
|
9523
|
+
for (const [key, value] of buildCorsHeaders(request, captured)) {
|
|
9501
9524
|
headers.set(key, value);
|
|
9502
9525
|
}
|
|
9503
9526
|
return new Response(response.body, {
|
|
@@ -9507,10 +9530,10 @@ function createCorsMiddleware() {
|
|
|
9507
9530
|
});
|
|
9508
9531
|
};
|
|
9509
9532
|
}
|
|
9510
|
-
function buildCorsHeaders(request) {
|
|
9533
|
+
function buildCorsHeaders(request, options) {
|
|
9511
9534
|
const headers = new Headers;
|
|
9512
9535
|
const origin = request.headers.get("origin");
|
|
9513
|
-
const corsConfig = resolveCorsConfig();
|
|
9536
|
+
const corsConfig = resolveCorsConfig(options);
|
|
9514
9537
|
const allowedOrigins = corsConfig.allowedOrigins;
|
|
9515
9538
|
headers.set("Vary", "Origin");
|
|
9516
9539
|
const allowOrigin = allowedOrigins.includes("*") ? "*" : origin && allowedOrigins.includes(origin) ? origin : null;
|