@zipbul/cors 0.0.1
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/README.ko.md +428 -0
- package/README.md +428 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +359 -0
- package/dist/index.js.map +13 -0
- package/dist/src/constants.d.ts +3 -0
- package/dist/src/cors.d.ts +38 -0
- package/dist/src/enums.d.ts +48 -0
- package/dist/src/interfaces.d.ts +90 -0
- package/dist/src/options.d.ts +25 -0
- package/dist/src/types.d.ts +39 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cors.ts
|
|
3
|
+
import { HttpHeader, HttpMethod as HttpMethod2 } from "@zipbul/shared";
|
|
4
|
+
import { err as err2, isErr } from "@zipbul/result";
|
|
5
|
+
|
|
6
|
+
// src/enums.ts
|
|
7
|
+
var CorsAction;
|
|
8
|
+
((CorsAction2) => {
|
|
9
|
+
CorsAction2[CorsAction2["Continue"] = 0] = "Continue";
|
|
10
|
+
CorsAction2[CorsAction2["RespondPreflight"] = 1] = "RespondPreflight";
|
|
11
|
+
CorsAction2[CorsAction2["Reject"] = 2] = "Reject";
|
|
12
|
+
})(CorsAction ||= {});
|
|
13
|
+
var CorsRejectionReason;
|
|
14
|
+
((CorsRejectionReason2) => {
|
|
15
|
+
CorsRejectionReason2[CorsRejectionReason2["NoOrigin"] = 0] = "NoOrigin";
|
|
16
|
+
CorsRejectionReason2[CorsRejectionReason2["OriginNotAllowed"] = 1] = "OriginNotAllowed";
|
|
17
|
+
CorsRejectionReason2[CorsRejectionReason2["MethodNotAllowed"] = 2] = "MethodNotAllowed";
|
|
18
|
+
CorsRejectionReason2[CorsRejectionReason2["HeaderNotAllowed"] = 3] = "HeaderNotAllowed";
|
|
19
|
+
})(CorsRejectionReason ||= {});
|
|
20
|
+
var CorsErrorReason;
|
|
21
|
+
((CorsErrorReason2) => {
|
|
22
|
+
CorsErrorReason2[CorsErrorReason2["CredentialsWithWildcardOrigin"] = 0] = "CredentialsWithWildcardOrigin";
|
|
23
|
+
CorsErrorReason2[CorsErrorReason2["InvalidMaxAge"] = 1] = "InvalidMaxAge";
|
|
24
|
+
CorsErrorReason2[CorsErrorReason2["InvalidStatusCode"] = 2] = "InvalidStatusCode";
|
|
25
|
+
CorsErrorReason2[CorsErrorReason2["OriginFunctionError"] = 3] = "OriginFunctionError";
|
|
26
|
+
CorsErrorReason2[CorsErrorReason2["InvalidOrigin"] = 4] = "InvalidOrigin";
|
|
27
|
+
CorsErrorReason2[CorsErrorReason2["InvalidMethods"] = 5] = "InvalidMethods";
|
|
28
|
+
CorsErrorReason2[CorsErrorReason2["InvalidAllowedHeaders"] = 6] = "InvalidAllowedHeaders";
|
|
29
|
+
CorsErrorReason2[CorsErrorReason2["InvalidExposedHeaders"] = 7] = "InvalidExposedHeaders";
|
|
30
|
+
CorsErrorReason2[CorsErrorReason2["UnsafeRegExp"] = 8] = "UnsafeRegExp";
|
|
31
|
+
})(CorsErrorReason ||= {});
|
|
32
|
+
|
|
33
|
+
// src/options.ts
|
|
34
|
+
import { err } from "@zipbul/result";
|
|
35
|
+
import safe from "safe-regex2";
|
|
36
|
+
|
|
37
|
+
// src/constants.ts
|
|
38
|
+
import { HttpMethod, HttpStatus } from "@zipbul/shared";
|
|
39
|
+
var CORS_DEFAULT_METHODS = [
|
|
40
|
+
HttpMethod.Get,
|
|
41
|
+
HttpMethod.Head,
|
|
42
|
+
HttpMethod.Put,
|
|
43
|
+
HttpMethod.Patch,
|
|
44
|
+
HttpMethod.Post,
|
|
45
|
+
HttpMethod.Delete
|
|
46
|
+
];
|
|
47
|
+
var CORS_DEFAULT_OPTIONS_SUCCESS_STATUS = HttpStatus.NoContent;
|
|
48
|
+
|
|
49
|
+
// src/options.ts
|
|
50
|
+
function resolveCorsOptions(options) {
|
|
51
|
+
return {
|
|
52
|
+
origin: options?.origin ?? "*",
|
|
53
|
+
methods: options?.methods?.includes("*") ? ["*"] : (options?.methods ?? CORS_DEFAULT_METHODS).map((m) => m.toUpperCase()),
|
|
54
|
+
allowedHeaders: options?.allowedHeaders ?? null,
|
|
55
|
+
exposedHeaders: options?.exposedHeaders ?? null,
|
|
56
|
+
credentials: options?.credentials ?? false,
|
|
57
|
+
maxAge: options?.maxAge ?? null,
|
|
58
|
+
preflightContinue: options?.preflightContinue ?? false,
|
|
59
|
+
optionsSuccessStatus: options?.optionsSuccessStatus ?? CORS_DEFAULT_OPTIONS_SUCCESS_STATUS
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function isBlank(value) {
|
|
63
|
+
return value.trim().length === 0;
|
|
64
|
+
}
|
|
65
|
+
function validateCorsOptions(resolved) {
|
|
66
|
+
if (typeof resolved.origin === "string" && resolved.origin !== "*" && isBlank(resolved.origin)) {
|
|
67
|
+
return err({
|
|
68
|
+
reason: 4 /* InvalidOrigin */,
|
|
69
|
+
message: "origin must not be an empty or blank string (RFC 6454)"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (resolved.origin instanceof RegExp && !safe(resolved.origin)) {
|
|
73
|
+
return err({
|
|
74
|
+
reason: 8 /* UnsafeRegExp */,
|
|
75
|
+
message: "origin RegExp is potentially unsafe (exponential backtracking / ReDoS)"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(resolved.origin)) {
|
|
79
|
+
if (resolved.origin.length === 0) {
|
|
80
|
+
return err({
|
|
81
|
+
reason: 4 /* InvalidOrigin */,
|
|
82
|
+
message: "origin array must not be empty (RFC 6454)"
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const hasBlankEntry = resolved.origin.some((entry) => typeof entry === "string" && isBlank(entry));
|
|
86
|
+
if (hasBlankEntry) {
|
|
87
|
+
return err({
|
|
88
|
+
reason: 4 /* InvalidOrigin */,
|
|
89
|
+
message: "origin array must not contain empty or blank string entries (RFC 6454)"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const hasUnsafeRegExp = resolved.origin.some((entry) => entry instanceof RegExp && !safe(entry));
|
|
93
|
+
if (hasUnsafeRegExp) {
|
|
94
|
+
return err({
|
|
95
|
+
reason: 8 /* UnsafeRegExp */,
|
|
96
|
+
message: "origin array contains an unsafe RegExp (exponential backtracking / ReDoS)"
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (resolved.methods.length === 0) {
|
|
101
|
+
return err({
|
|
102
|
+
reason: 5 /* InvalidMethods */,
|
|
103
|
+
message: "methods must not be an empty array (RFC 9110 \xA75.6.2)"
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (resolved.methods.some(isBlank)) {
|
|
107
|
+
return err({
|
|
108
|
+
reason: 5 /* InvalidMethods */,
|
|
109
|
+
message: "methods must not contain empty or blank string entries (RFC 9110 \xA75.6.2 token)"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (resolved.allowedHeaders !== null && resolved.allowedHeaders.some(isBlank)) {
|
|
113
|
+
return err({
|
|
114
|
+
reason: 6 /* InvalidAllowedHeaders */,
|
|
115
|
+
message: "allowedHeaders must not contain empty or blank string entries (RFC 9110 \xA75.6.2 token)"
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (resolved.exposedHeaders !== null && resolved.exposedHeaders.some(isBlank)) {
|
|
119
|
+
return err({
|
|
120
|
+
reason: 7 /* InvalidExposedHeaders */,
|
|
121
|
+
message: "exposedHeaders must not contain empty or blank string entries (RFC 9110 \xA75.6.2 token)"
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (resolved.credentials === true && resolved.origin === "*") {
|
|
125
|
+
return err({
|
|
126
|
+
reason: 0 /* CredentialsWithWildcardOrigin */,
|
|
127
|
+
message: "credentials:true cannot be used with wildcard origin (*) per Fetch Standard"
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (resolved.maxAge !== null && (resolved.maxAge < 0 || !Number.isInteger(resolved.maxAge))) {
|
|
131
|
+
return err({
|
|
132
|
+
reason: 1 /* InvalidMaxAge */,
|
|
133
|
+
message: "maxAge must be a non-negative integer (delta-seconds per RFC 9111)"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (!Number.isInteger(resolved.optionsSuccessStatus) || resolved.optionsSuccessStatus < 200 || resolved.optionsSuccessStatus > 299) {
|
|
137
|
+
return err({
|
|
138
|
+
reason: 2 /* InvalidStatusCode */,
|
|
139
|
+
message: "optionsSuccessStatus must be a 2xx integer status code (200\u2013299)"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/cors.ts
|
|
146
|
+
class Cors {
|
|
147
|
+
options;
|
|
148
|
+
constructor(options) {
|
|
149
|
+
this.options = options;
|
|
150
|
+
}
|
|
151
|
+
static create(options) {
|
|
152
|
+
const resolved = resolveCorsOptions(options);
|
|
153
|
+
const validationResult = validateCorsOptions(resolved);
|
|
154
|
+
if (isErr(validationResult)) {
|
|
155
|
+
return validationResult;
|
|
156
|
+
}
|
|
157
|
+
return new Cors(resolved);
|
|
158
|
+
}
|
|
159
|
+
async handle(request) {
|
|
160
|
+
const origin = request.headers.get(HttpHeader.Origin);
|
|
161
|
+
if (origin === null || origin.length === 0) {
|
|
162
|
+
return this.reject(0 /* NoOrigin */);
|
|
163
|
+
}
|
|
164
|
+
const allowedOrigin = await this.matchOrigin(origin, request);
|
|
165
|
+
if (isErr(allowedOrigin)) {
|
|
166
|
+
return allowedOrigin;
|
|
167
|
+
}
|
|
168
|
+
if (allowedOrigin === undefined) {
|
|
169
|
+
return this.reject(1 /* OriginNotAllowed */);
|
|
170
|
+
}
|
|
171
|
+
const headers = new Headers;
|
|
172
|
+
headers.set(HttpHeader.AccessControlAllowOrigin, allowedOrigin);
|
|
173
|
+
if (allowedOrigin !== "*") {
|
|
174
|
+
headers.append(HttpHeader.Vary, HttpHeader.Origin);
|
|
175
|
+
}
|
|
176
|
+
if (this.options.credentials) {
|
|
177
|
+
headers.set(HttpHeader.AccessControlAllowCredentials, "true");
|
|
178
|
+
}
|
|
179
|
+
if (request.method !== HttpMethod2.Options) {
|
|
180
|
+
if (this.options.exposedHeaders !== null && this.options.exposedHeaders.length > 0) {
|
|
181
|
+
const exposeHeadersValue = this.serializeExposeHeaders(this.options.exposedHeaders);
|
|
182
|
+
if (exposeHeadersValue !== undefined) {
|
|
183
|
+
headers.set(HttpHeader.AccessControlExposeHeaders, exposeHeadersValue);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { action: 0 /* Continue */, headers };
|
|
187
|
+
}
|
|
188
|
+
const requestMethod = request.headers.get(HttpHeader.AccessControlRequestMethod);
|
|
189
|
+
if (requestMethod === null || requestMethod.length === 0) {
|
|
190
|
+
return { action: 0 /* Continue */, headers };
|
|
191
|
+
}
|
|
192
|
+
if (!this.isMethodAllowed(requestMethod, this.options.methods)) {
|
|
193
|
+
return this.reject(2 /* MethodNotAllowed */);
|
|
194
|
+
}
|
|
195
|
+
const allowMethodsValue = this.serializeAllowedMethods(this.options.methods, requestMethod);
|
|
196
|
+
headers.set(HttpHeader.AccessControlAllowMethods, allowMethodsValue);
|
|
197
|
+
headers.append(HttpHeader.Vary, HttpHeader.AccessControlRequestMethod);
|
|
198
|
+
const requestHeadersRaw = request.headers.get(HttpHeader.AccessControlRequestHeaders);
|
|
199
|
+
const requestHeaders = this.parseCommaSeparatedValues(requestHeadersRaw);
|
|
200
|
+
if (this.options.allowedHeaders !== null) {
|
|
201
|
+
if (!this.areRequestHeadersAllowed(requestHeaders, this.options.allowedHeaders)) {
|
|
202
|
+
return this.reject(3 /* HeaderNotAllowed */);
|
|
203
|
+
}
|
|
204
|
+
const allowHeadersValue = this.serializeAllowedHeaders(this.options.allowedHeaders, requestHeadersRaw);
|
|
205
|
+
if (allowHeadersValue !== undefined) {
|
|
206
|
+
headers.set(HttpHeader.AccessControlAllowHeaders, allowHeadersValue);
|
|
207
|
+
headers.append(HttpHeader.Vary, HttpHeader.AccessControlRequestHeaders);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
if (requestHeadersRaw !== null && requestHeadersRaw.length > 0) {
|
|
211
|
+
headers.set(HttpHeader.AccessControlAllowHeaders, requestHeadersRaw);
|
|
212
|
+
headers.append(HttpHeader.Vary, HttpHeader.AccessControlRequestHeaders);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (this.options.maxAge !== null) {
|
|
216
|
+
headers.set(HttpHeader.AccessControlMaxAge, this.options.maxAge.toString());
|
|
217
|
+
}
|
|
218
|
+
if (this.options.preflightContinue) {
|
|
219
|
+
return { action: 0 /* Continue */, headers };
|
|
220
|
+
}
|
|
221
|
+
return { action: 1 /* RespondPreflight */, headers, statusCode: this.options.optionsSuccessStatus };
|
|
222
|
+
}
|
|
223
|
+
reject(reason) {
|
|
224
|
+
return { action: 2 /* Reject */, reason };
|
|
225
|
+
}
|
|
226
|
+
async matchOrigin(origin, request) {
|
|
227
|
+
const originOption = this.options.origin;
|
|
228
|
+
if (originOption === false) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (originOption === "*") {
|
|
232
|
+
return "*";
|
|
233
|
+
}
|
|
234
|
+
if (typeof originOption === "string") {
|
|
235
|
+
return originOption === origin ? originOption : undefined;
|
|
236
|
+
}
|
|
237
|
+
if (typeof originOption === "boolean") {
|
|
238
|
+
return originOption ? origin : undefined;
|
|
239
|
+
}
|
|
240
|
+
if (originOption instanceof RegExp) {
|
|
241
|
+
originOption.lastIndex = 0;
|
|
242
|
+
return originOption.test(origin) ? origin : undefined;
|
|
243
|
+
}
|
|
244
|
+
if (Array.isArray(originOption)) {
|
|
245
|
+
const matched = originOption.some((entry) => {
|
|
246
|
+
if (entry instanceof RegExp) {
|
|
247
|
+
entry.lastIndex = 0;
|
|
248
|
+
return entry.test(origin);
|
|
249
|
+
}
|
|
250
|
+
return entry === origin;
|
|
251
|
+
});
|
|
252
|
+
return matched ? origin : undefined;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const originResult = await originOption(origin, request);
|
|
256
|
+
return this.resolveOriginResult(origin, originResult);
|
|
257
|
+
} catch {
|
|
258
|
+
return err2({
|
|
259
|
+
reason: 3 /* OriginFunctionError */,
|
|
260
|
+
message: "Origin function threw an error"
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
resolveOriginResult(origin, result) {
|
|
265
|
+
if (result === true) {
|
|
266
|
+
return origin;
|
|
267
|
+
}
|
|
268
|
+
if (typeof result === "string" && result.length > 0) {
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
serializeExposeHeaders(exposedHeaders) {
|
|
274
|
+
if (this.options.credentials && this.includesWildcard(exposedHeaders)) {
|
|
275
|
+
const explicit = exposedHeaders.filter((header) => header.trim() !== "*");
|
|
276
|
+
return explicit.length > 0 ? explicit.join(",") : undefined;
|
|
277
|
+
}
|
|
278
|
+
return exposedHeaders.join(",");
|
|
279
|
+
}
|
|
280
|
+
isMethodAllowed(requestMethod, allowedMethods) {
|
|
281
|
+
if (this.includesWildcard(allowedMethods)) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
return allowedMethods.includes(requestMethod);
|
|
285
|
+
}
|
|
286
|
+
serializeAllowedMethods(allowedMethods, requestMethod) {
|
|
287
|
+
if (!this.includesWildcard(allowedMethods)) {
|
|
288
|
+
return allowedMethods.join(",");
|
|
289
|
+
}
|
|
290
|
+
if (this.options.credentials) {
|
|
291
|
+
return requestMethod;
|
|
292
|
+
}
|
|
293
|
+
return "*";
|
|
294
|
+
}
|
|
295
|
+
areRequestHeadersAllowed(requestHeaders, allowedHeaders) {
|
|
296
|
+
if (requestHeaders.length === 0) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (allowedHeaders.length === 0) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
const hasWildcard = this.includesWildcard(allowedHeaders);
|
|
303
|
+
if (hasWildcard) {
|
|
304
|
+
const explicitHeaders = allowedHeaders.filter((header) => header.trim() !== "*");
|
|
305
|
+
const hasAuthorization = requestHeaders.some((header) => header.toLowerCase() === "authorization");
|
|
306
|
+
if (hasAuthorization && !this.includesHeader(explicitHeaders, "authorization")) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
if (!this.options.credentials) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return requestHeaders.every((header) => {
|
|
313
|
+
if (this.includesHeader(explicitHeaders, header)) {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
return header.toLowerCase() !== "authorization";
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return requestHeaders.every((header) => this.includesHeader(allowedHeaders, header));
|
|
320
|
+
}
|
|
321
|
+
serializeAllowedHeaders(allowedHeaders, requestHeadersRaw) {
|
|
322
|
+
if (allowedHeaders.length === 0) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (!this.includesWildcard(allowedHeaders)) {
|
|
326
|
+
return allowedHeaders.join(",");
|
|
327
|
+
}
|
|
328
|
+
if (this.options.credentials) {
|
|
329
|
+
if (requestHeadersRaw !== null && requestHeadersRaw.length > 0) {
|
|
330
|
+
return requestHeadersRaw;
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
return "*";
|
|
335
|
+
}
|
|
336
|
+
includesWildcard(values) {
|
|
337
|
+
return values.some((value) => value === "*");
|
|
338
|
+
}
|
|
339
|
+
includesHeader(allowedHeaders, requestHeader) {
|
|
340
|
+
return allowedHeaders.some((header) => header.toLowerCase() === requestHeader.toLowerCase());
|
|
341
|
+
}
|
|
342
|
+
parseCommaSeparatedValues(value) {
|
|
343
|
+
if (value === null || value.length === 0) {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
export {
|
|
350
|
+
validateCorsOptions,
|
|
351
|
+
resolveCorsOptions,
|
|
352
|
+
CorsRejectionReason,
|
|
353
|
+
CorsErrorReason,
|
|
354
|
+
CorsAction,
|
|
355
|
+
Cors
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
//# debugId=A8102427CA89803164756E2164756E21
|
|
359
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/cors.ts", "../src/enums.ts", "../src/options.ts", "../src/constants.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { HttpHeader, HttpMethod } from '@zipbul/shared';\nimport { err, isErr } from '@zipbul/result';\nimport type { Err, Result } from '@zipbul/result';\n\nimport { CorsAction, CorsErrorReason, CorsRejectionReason } from './enums';\nimport type { CorsError, CorsOptions, CorsPreflightResult, CorsRejectResult } from './interfaces';\nimport { resolveCorsOptions, validateCorsOptions } from './options';\nimport type { CorsResult, ResolvedCorsOptions } from './types';\nimport type { OriginResult } from './types';\n\n/**\n * Framework-agnostic CORS handler.\n * Evaluates CORS policy and returns a discriminated union result\n * instead of generating responses directly.\n */\nexport class Cors {\n private constructor(private readonly options: ResolvedCorsOptions) {}\n\n /**\n * Creates a Cors instance after resolving and validating options.\n *\n * @returns Cors instance on success, Err<CorsError> on validation failure.\n */\n public static create(options?: CorsOptions): Result<Cors, CorsError> {\n const resolved = resolveCorsOptions(options);\n const validationResult = validateCorsOptions(resolved);\n\n if (isErr(validationResult)) {\n return validationResult;\n }\n\n return new Cors(resolved);\n }\n\n /**\n * Evaluates CORS policy for the given request.\n *\n * @returns `Continue` — attach headers and proceed,\n * `RespondPreflight` — return preflight response,\n * `Reject` — deny with reason,\n * `Err<CorsError>` — origin function threw.\n */\n public async handle(request: Request): Promise<Result<CorsResult, CorsError>> {\n const origin = request.headers.get(HttpHeader.Origin);\n\n if (origin === null || origin.length === 0) {\n return this.reject(CorsRejectionReason.NoOrigin);\n }\n\n const allowedOrigin = await this.matchOrigin(origin, request);\n\n if (isErr(allowedOrigin)) {\n return allowedOrigin;\n }\n\n if (allowedOrigin === undefined) {\n return this.reject(CorsRejectionReason.OriginNotAllowed);\n }\n\n const headers = new Headers();\n\n headers.set(HttpHeader.AccessControlAllowOrigin, allowedOrigin);\n\n if (allowedOrigin !== '*') {\n headers.append(HttpHeader.Vary, HttpHeader.Origin);\n }\n\n if (this.options.credentials) {\n headers.set(HttpHeader.AccessControlAllowCredentials, 'true');\n }\n\n if (request.method !== HttpMethod.Options) {\n if (this.options.exposedHeaders !== null && this.options.exposedHeaders.length > 0) {\n const exposeHeadersValue = this.serializeExposeHeaders(this.options.exposedHeaders);\n\n if (exposeHeadersValue !== undefined) {\n headers.set(HttpHeader.AccessControlExposeHeaders, exposeHeadersValue);\n }\n }\n\n return { action: CorsAction.Continue, headers };\n }\n\n const requestMethod = request.headers.get(HttpHeader.AccessControlRequestMethod);\n\n if (requestMethod === null || requestMethod.length === 0) {\n return { action: CorsAction.Continue, headers };\n }\n\n if (!this.isMethodAllowed(requestMethod, this.options.methods)) {\n return this.reject(CorsRejectionReason.MethodNotAllowed);\n }\n\n const allowMethodsValue = this.serializeAllowedMethods(this.options.methods, requestMethod);\n\n headers.set(HttpHeader.AccessControlAllowMethods, allowMethodsValue);\n\n headers.append(HttpHeader.Vary, HttpHeader.AccessControlRequestMethod);\n\n const requestHeadersRaw = request.headers.get(HttpHeader.AccessControlRequestHeaders);\n const requestHeaders = this.parseCommaSeparatedValues(requestHeadersRaw);\n\n if (this.options.allowedHeaders !== null) {\n if (!this.areRequestHeadersAllowed(requestHeaders, this.options.allowedHeaders)) {\n return this.reject(CorsRejectionReason.HeaderNotAllowed);\n }\n\n const allowHeadersValue = this.serializeAllowedHeaders(this.options.allowedHeaders, requestHeadersRaw);\n\n if (allowHeadersValue !== undefined) {\n headers.set(HttpHeader.AccessControlAllowHeaders, allowHeadersValue);\n headers.append(HttpHeader.Vary, HttpHeader.AccessControlRequestHeaders);\n }\n } else {\n if (requestHeadersRaw !== null && requestHeadersRaw.length > 0) {\n headers.set(HttpHeader.AccessControlAllowHeaders, requestHeadersRaw);\n headers.append(HttpHeader.Vary, HttpHeader.AccessControlRequestHeaders);\n }\n }\n\n if (this.options.maxAge !== null) {\n headers.set(HttpHeader.AccessControlMaxAge, this.options.maxAge.toString());\n }\n\n if (this.options.preflightContinue) {\n return { action: CorsAction.Continue, headers };\n }\n\n return { action: CorsAction.RespondPreflight, headers, statusCode: this.options.optionsSuccessStatus };\n }\n\n private reject(reason: CorsRejectionReason): CorsRejectResult {\n return { action: CorsAction.Reject, reason };\n }\n\n private async matchOrigin(origin: string, request: Request): Promise<string | undefined | Err<CorsError>> {\n const originOption = this.options.origin;\n\n if (originOption === false) {\n return undefined;\n }\n\n if (originOption === '*') {\n return '*';\n }\n\n if (typeof originOption === 'string') {\n return originOption === origin ? originOption : undefined;\n }\n\n if (typeof originOption === 'boolean') {\n return originOption ? origin : undefined;\n }\n\n if (originOption instanceof RegExp) {\n originOption.lastIndex = 0;\n return originOption.test(origin) ? origin : undefined;\n }\n\n if (Array.isArray(originOption)) {\n const matched = originOption.some(entry => {\n if (entry instanceof RegExp) {\n entry.lastIndex = 0;\n return entry.test(origin);\n }\n\n return entry === origin;\n });\n\n return matched ? origin : undefined;\n }\n\n try {\n const originResult = await originOption(origin, request);\n\n return this.resolveOriginResult(origin, originResult);\n } catch {\n return err<CorsError>({\n reason: CorsErrorReason.OriginFunctionError,\n message: 'Origin function threw an error',\n });\n }\n }\n\n private resolveOriginResult(origin: string, result: OriginResult): string | undefined {\n if (result === true) {\n return origin;\n }\n\n if (typeof result === 'string' && result.length > 0) {\n return result;\n }\n\n return undefined;\n }\n\n private serializeExposeHeaders(exposedHeaders: string[]): string | undefined {\n if (this.options.credentials && this.includesWildcard(exposedHeaders)) {\n const explicit = exposedHeaders.filter(header => header.trim() !== '*');\n\n return explicit.length > 0 ? explicit.join(',') : undefined;\n }\n\n return exposedHeaders.join(',');\n }\n\n private isMethodAllowed(requestMethod: string, allowedMethods: Array<string>): boolean {\n if (this.includesWildcard(allowedMethods)) {\n return true;\n }\n\n return allowedMethods.includes(requestMethod);\n }\n\n private serializeAllowedMethods(allowedMethods: Array<string>, requestMethod: string): string {\n if (!this.includesWildcard(allowedMethods)) {\n return allowedMethods.join(',');\n }\n\n if (this.options.credentials) {\n return requestMethod;\n }\n\n return '*';\n }\n\n private areRequestHeadersAllowed(requestHeaders: string[], allowedHeaders: string[]): boolean {\n if (requestHeaders.length === 0) {\n return true;\n }\n\n if (allowedHeaders.length === 0) {\n return false;\n }\n\n const hasWildcard = this.includesWildcard(allowedHeaders);\n\n if (hasWildcard) {\n const explicitHeaders = allowedHeaders.filter(header => header.trim() !== '*');\n\n const hasAuthorization = requestHeaders.some(header => header.toLowerCase() === 'authorization');\n\n if (hasAuthorization && !this.includesHeader(explicitHeaders, 'authorization')) {\n return false;\n }\n\n if (!this.options.credentials) {\n return true;\n }\n\n return requestHeaders.every(header => {\n if (this.includesHeader(explicitHeaders, header)) {\n return true;\n }\n\n return header.toLowerCase() !== 'authorization';\n });\n }\n\n return requestHeaders.every(header => this.includesHeader(allowedHeaders, header));\n }\n\n private serializeAllowedHeaders(allowedHeaders: string[], requestHeadersRaw: string | null): string | undefined {\n if (allowedHeaders.length === 0) {\n return undefined;\n }\n\n if (!this.includesWildcard(allowedHeaders)) {\n return allowedHeaders.join(',');\n }\n\n if (this.options.credentials) {\n if (requestHeadersRaw !== null && requestHeadersRaw.length > 0) {\n return requestHeadersRaw;\n }\n\n return undefined;\n }\n\n return '*';\n }\n\n private includesWildcard(values: string[]): boolean {\n return values.some(value => value === '*');\n }\n\n private includesHeader(allowedHeaders: string[], requestHeader: string): boolean {\n return allowedHeaders.some(header => header.toLowerCase() === requestHeader.toLowerCase());\n }\n\n private parseCommaSeparatedValues(value: string | null): string[] {\n if (value === null || value.length === 0) {\n return [];\n }\n\n return value\n .split(',')\n .map(item => item.trim())\n .filter(item => item.length > 0);\n }\n}\n",
|
|
6
|
+
"/**\n * Discriminant for {@link CorsResult}.\n * Determines how to handle the response.\n */\nexport enum CorsAction {\n /** Attach CORS headers to the response and continue processing. */\n Continue,\n /** Return a preflight-only response immediately. */\n RespondPreflight,\n /** Reject the request. See {@link CorsRejectionReason} for details. */\n Reject,\n}\n\n/**\n * Reason why a CORS request was rejected.\n */\nexport enum CorsRejectionReason {\n /** `Origin` header is missing or empty. */\n NoOrigin,\n /** Origin is not in the allowed list. */\n OriginNotAllowed,\n /** Preflight request method is not allowed. */\n MethodNotAllowed,\n /** Preflight request header is not allowed. */\n HeaderNotAllowed,\n}\n\n/**\n * Reason why CORS options validation failed.\n */\nexport enum CorsErrorReason {\n /** credentials:true is incompatible with wildcard origin per Fetch Standard. */\n CredentialsWithWildcardOrigin,\n /** maxAge must be non-negative. */\n InvalidMaxAge,\n /** optionsSuccessStatus must be 200–299 (ok status). */\n InvalidStatusCode,\n /** Origin function threw at runtime. */\n OriginFunctionError,\n /** origin is an empty/blank string, empty array, or array containing empty/blank string entries (RFC 6454). */\n InvalidOrigin,\n /** methods is an empty array or contains empty/blank string entries (RFC 9110 §5.6.2 token). */\n InvalidMethods,\n /** allowedHeaders contains empty/blank string entries (RFC 9110 §5.6.2 token). */\n InvalidAllowedHeaders,\n /** exposedHeaders contains empty/blank string entries (RFC 9110 §5.6.2 token). */\n InvalidExposedHeaders,\n /** origin RegExp is potentially unsafe (exponential backtracking / ReDoS). */\n UnsafeRegExp,\n}\n",
|
|
7
|
+
"import { err } from '@zipbul/result';\nimport type { Result } from '@zipbul/result';\nimport safe from 'safe-regex2';\n\nimport { CORS_DEFAULT_METHODS, CORS_DEFAULT_OPTIONS_SUCCESS_STATUS } from './constants';\nimport { CorsErrorReason } from './enums';\nimport type { CorsError, CorsOptions } from './interfaces';\nimport type { ResolvedCorsOptions } from './types';\n\n/**\n * Resolves partial {@link CorsOptions} into a fully populated\n * {@link ResolvedCorsOptions} by applying defaults via nullish coalescing.\n */\nexport function resolveCorsOptions(options?: CorsOptions): ResolvedCorsOptions {\n return {\n origin: options?.origin ?? '*',\n methods: options?.methods?.includes('*')\n ? ['*']\n : (options?.methods ?? CORS_DEFAULT_METHODS).map(m => m.toUpperCase()),\n allowedHeaders: options?.allowedHeaders ?? null,\n exposedHeaders: options?.exposedHeaders ?? null,\n credentials: options?.credentials ?? false,\n maxAge: options?.maxAge ?? null,\n preflightContinue: options?.preflightContinue ?? false,\n optionsSuccessStatus: options?.optionsSuccessStatus ?? CORS_DEFAULT_OPTIONS_SUCCESS_STATUS,\n };\n}\n\n/** Returns true when a string is empty or contains only whitespace (RFC 9110 §5.6.2 token). */\nfunction isBlank(value: string): boolean {\n return value.trim().length === 0;\n}\n\n/**\n * Validates resolved CORS options against rules derived from the Fetch Standard and RFC 9110.\n *\n * - V0a: `origin` must not be an empty/blank string (RFC 6454).\n * - V_regex: `origin` RegExp must be safe (no exponential backtracking / ReDoS).\n * - V0b: `origin` array must not be empty, and must not contain empty/blank string entries (RFC 6454).\n * Array RegExp entries are also checked for ReDoS safety.\n * - V0c: `methods` must not be empty, and must not contain empty/blank string entries (RFC 9110 §5.6.2).\n * - V0d: `allowedHeaders` must not contain empty/blank string entries (RFC 9110 §5.6.2).\n * - V0e: `exposedHeaders` must not contain empty/blank string entries (RFC 9110 §5.6.2).\n * - V1: `credentials:true` with wildcard origin is forbidden (Fetch Standard §3.3.5).\n * - V2: `maxAge` must be a non-negative integer when set (RFC 9111 §1.2.1 delta-seconds).\n * - V3: `optionsSuccessStatus` must be a 2xx integer (Fetch Standard).\n *\n * @returns `undefined` (void) if valid, or `Err<CorsError>` on the first violated rule.\n */\nexport function validateCorsOptions(resolved: ResolvedCorsOptions): Result<void, CorsError> {\n // V0a — origin: empty/blank string\n if (typeof resolved.origin === 'string' && resolved.origin !== '*' && isBlank(resolved.origin)) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidOrigin,\n message: 'origin must not be an empty or blank string (RFC 6454)',\n });\n }\n\n // V_regex — origin: single unsafe RegExp (ReDoS)\n if (resolved.origin instanceof RegExp && !safe(resolved.origin)) {\n return err<CorsError>({\n reason: CorsErrorReason.UnsafeRegExp,\n message: 'origin RegExp is potentially unsafe (exponential backtracking / ReDoS)',\n });\n }\n\n // V0b — origin: empty array or array containing empty/blank string entries\n if (Array.isArray(resolved.origin)) {\n if (resolved.origin.length === 0) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidOrigin,\n message: 'origin array must not be empty (RFC 6454)',\n });\n }\n\n const hasBlankEntry = resolved.origin.some(entry => typeof entry === 'string' && isBlank(entry));\n\n if (hasBlankEntry) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidOrigin,\n message: 'origin array must not contain empty or blank string entries (RFC 6454)',\n });\n }\n\n // V_regex — origin array: unsafe RegExp entries (ReDoS)\n const hasUnsafeRegExp = resolved.origin.some(entry => entry instanceof RegExp && !safe(entry));\n\n if (hasUnsafeRegExp) {\n return err<CorsError>({\n reason: CorsErrorReason.UnsafeRegExp,\n message: 'origin array contains an unsafe RegExp (exponential backtracking / ReDoS)',\n });\n }\n }\n\n // V0c — methods: empty array or blank entries\n if (resolved.methods.length === 0) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidMethods,\n message: 'methods must not be an empty array (RFC 9110 §5.6.2)',\n });\n }\n\n if (resolved.methods.some(isBlank)) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidMethods,\n message: 'methods must not contain empty or blank string entries (RFC 9110 §5.6.2 token)',\n });\n }\n\n // V0d — allowedHeaders: blank entries (empty array is allowed — explicit \"deny all\" policy)\n if (resolved.allowedHeaders !== null && resolved.allowedHeaders.some(isBlank)) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidAllowedHeaders,\n message: 'allowedHeaders must not contain empty or blank string entries (RFC 9110 §5.6.2 token)',\n });\n }\n\n // V0e — exposedHeaders: blank entries (empty array is allowed)\n if (resolved.exposedHeaders !== null && resolved.exposedHeaders.some(isBlank)) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidExposedHeaders,\n message: 'exposedHeaders must not contain empty or blank string entries (RFC 9110 §5.6.2 token)',\n });\n }\n\n // V1 — credentials:true with wildcard origin\n if (resolved.credentials === true && resolved.origin === '*') {\n return err<CorsError>({\n reason: CorsErrorReason.CredentialsWithWildcardOrigin,\n message: 'credentials:true cannot be used with wildcard origin (*) per Fetch Standard',\n });\n }\n\n // V2 — maxAge: non-negative integer\n if (resolved.maxAge !== null && (resolved.maxAge < 0 || !Number.isInteger(resolved.maxAge))) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidMaxAge,\n message: 'maxAge must be a non-negative integer (delta-seconds per RFC 9111)',\n });\n }\n\n // V3 — optionsSuccessStatus: 2xx integer\n if (!Number.isInteger(resolved.optionsSuccessStatus) || resolved.optionsSuccessStatus < 200 || resolved.optionsSuccessStatus > 299) {\n return err<CorsError>({\n reason: CorsErrorReason.InvalidStatusCode,\n message: 'optionsSuccessStatus must be a 2xx integer status code (200–299)',\n });\n }\n\n return undefined;\n}\n",
|
|
8
|
+
"import { HttpMethod, HttpStatus } from '@zipbul/shared';\n\nexport const CORS_DEFAULT_METHODS: string[] = [\n HttpMethod.Get,\n HttpMethod.Head,\n HttpMethod.Put,\n HttpMethod.Patch,\n HttpMethod.Post,\n HttpMethod.Delete,\n];\n\nexport const CORS_DEFAULT_OPTIONS_SUCCESS_STATUS = HttpStatus.NoContent;\n"
|
|
9
|
+
],
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "A8102427CA89803164756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Result } from '@zipbul/result';
|
|
2
|
+
import type { CorsError, CorsOptions } from './interfaces';
|
|
3
|
+
import type { CorsResult } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Framework-agnostic CORS handler.
|
|
6
|
+
* Evaluates CORS policy and returns a discriminated union result
|
|
7
|
+
* instead of generating responses directly.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Cors {
|
|
10
|
+
private readonly options;
|
|
11
|
+
private constructor();
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Cors instance after resolving and validating options.
|
|
14
|
+
*
|
|
15
|
+
* @returns Cors instance on success, Err<CorsError> on validation failure.
|
|
16
|
+
*/
|
|
17
|
+
static create(options?: CorsOptions): Result<Cors, CorsError>;
|
|
18
|
+
/**
|
|
19
|
+
* Evaluates CORS policy for the given request.
|
|
20
|
+
*
|
|
21
|
+
* @returns `Continue` — attach headers and proceed,
|
|
22
|
+
* `RespondPreflight` — return preflight response,
|
|
23
|
+
* `Reject` — deny with reason,
|
|
24
|
+
* `Err<CorsError>` — origin function threw.
|
|
25
|
+
*/
|
|
26
|
+
handle(request: Request): Promise<Result<CorsResult, CorsError>>;
|
|
27
|
+
private reject;
|
|
28
|
+
private matchOrigin;
|
|
29
|
+
private resolveOriginResult;
|
|
30
|
+
private serializeExposeHeaders;
|
|
31
|
+
private isMethodAllowed;
|
|
32
|
+
private serializeAllowedMethods;
|
|
33
|
+
private areRequestHeadersAllowed;
|
|
34
|
+
private serializeAllowedHeaders;
|
|
35
|
+
private includesWildcard;
|
|
36
|
+
private includesHeader;
|
|
37
|
+
private parseCommaSeparatedValues;
|
|
38
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminant for {@link CorsResult}.
|
|
3
|
+
* Determines how to handle the response.
|
|
4
|
+
*/
|
|
5
|
+
export declare enum CorsAction {
|
|
6
|
+
/** Attach CORS headers to the response and continue processing. */
|
|
7
|
+
Continue = 0,
|
|
8
|
+
/** Return a preflight-only response immediately. */
|
|
9
|
+
RespondPreflight = 1,
|
|
10
|
+
/** Reject the request. See {@link CorsRejectionReason} for details. */
|
|
11
|
+
Reject = 2
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Reason why a CORS request was rejected.
|
|
15
|
+
*/
|
|
16
|
+
export declare enum CorsRejectionReason {
|
|
17
|
+
/** `Origin` header is missing or empty. */
|
|
18
|
+
NoOrigin = 0,
|
|
19
|
+
/** Origin is not in the allowed list. */
|
|
20
|
+
OriginNotAllowed = 1,
|
|
21
|
+
/** Preflight request method is not allowed. */
|
|
22
|
+
MethodNotAllowed = 2,
|
|
23
|
+
/** Preflight request header is not allowed. */
|
|
24
|
+
HeaderNotAllowed = 3
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Reason why CORS options validation failed.
|
|
28
|
+
*/
|
|
29
|
+
export declare enum CorsErrorReason {
|
|
30
|
+
/** credentials:true is incompatible with wildcard origin per Fetch Standard. */
|
|
31
|
+
CredentialsWithWildcardOrigin = 0,
|
|
32
|
+
/** maxAge must be non-negative. */
|
|
33
|
+
InvalidMaxAge = 1,
|
|
34
|
+
/** optionsSuccessStatus must be 200–299 (ok status). */
|
|
35
|
+
InvalidStatusCode = 2,
|
|
36
|
+
/** Origin function threw at runtime. */
|
|
37
|
+
OriginFunctionError = 3,
|
|
38
|
+
/** origin is an empty/blank string, empty array, or array containing empty/blank string entries (RFC 6454). */
|
|
39
|
+
InvalidOrigin = 4,
|
|
40
|
+
/** methods is an empty array or contains empty/blank string entries (RFC 9110 §5.6.2 token). */
|
|
41
|
+
InvalidMethods = 5,
|
|
42
|
+
/** allowedHeaders contains empty/blank string entries (RFC 9110 §5.6.2 token). */
|
|
43
|
+
InvalidAllowedHeaders = 6,
|
|
44
|
+
/** exposedHeaders contains empty/blank string entries (RFC 9110 §5.6.2 token). */
|
|
45
|
+
InvalidExposedHeaders = 7,
|
|
46
|
+
/** origin RegExp is potentially unsafe (exponential backtracking / ReDoS). */
|
|
47
|
+
UnsafeRegExp = 8
|
|
48
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { CorsAction, CorsErrorReason, CorsRejectionReason } from './enums';
|
|
2
|
+
import type { CorsMethod, OriginOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Normal request or `preflightContinue` preflight.
|
|
5
|
+
* Merge `headers` into your response.
|
|
6
|
+
*/
|
|
7
|
+
export interface CorsContinueResult {
|
|
8
|
+
action: CorsAction.Continue;
|
|
9
|
+
headers: Headers;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Preflight response.
|
|
13
|
+
* Use `headers` and `statusCode` to build a response.
|
|
14
|
+
*/
|
|
15
|
+
export interface CorsPreflightResult {
|
|
16
|
+
action: CorsAction.RespondPreflight;
|
|
17
|
+
headers: Headers;
|
|
18
|
+
statusCode: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* CORS validation failed.
|
|
22
|
+
* Inspect `reason` to build an error response.
|
|
23
|
+
*/
|
|
24
|
+
export interface CorsRejectResult {
|
|
25
|
+
action: CorsAction.Reject;
|
|
26
|
+
reason: CorsRejectionReason;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Error data returned when CORS validation fails.
|
|
30
|
+
*/
|
|
31
|
+
export interface CorsError {
|
|
32
|
+
reason: CorsErrorReason;
|
|
33
|
+
message: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Configuration for the {@link Cors} handler.
|
|
37
|
+
* All fields are optional.
|
|
38
|
+
*/
|
|
39
|
+
export interface CorsOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Allowed origin(s).
|
|
42
|
+
* Accepts `'*'`, `false`, `true`, string, RegExp, array, or async function.
|
|
43
|
+
*
|
|
44
|
+
* @defaultValue `'*'`
|
|
45
|
+
*/
|
|
46
|
+
origin?: OriginOptions;
|
|
47
|
+
/**
|
|
48
|
+
* HTTP methods allowed in preflight.
|
|
49
|
+
* Standard methods are autocompleted; any RFC 9110 §5.6.2 token is accepted.
|
|
50
|
+
* Values are normalized to uppercase internally.
|
|
51
|
+
*
|
|
52
|
+
* @defaultValue `['GET','HEAD','PUT','PATCH','POST','DELETE']`
|
|
53
|
+
* @example ['GET', 'POST', 'DELETE']
|
|
54
|
+
* @example ['*'] // allow all methods
|
|
55
|
+
* @example ['GET', 'PROPFIND'] // custom token
|
|
56
|
+
*/
|
|
57
|
+
methods?: CorsMethod[];
|
|
58
|
+
/**
|
|
59
|
+
* Request headers allowed in preflight.
|
|
60
|
+
* When omitted, echoes `Access-Control-Request-Headers`.
|
|
61
|
+
*/
|
|
62
|
+
allowedHeaders?: string[];
|
|
63
|
+
/**
|
|
64
|
+
* Response headers exposed to browser JavaScript.
|
|
65
|
+
*/
|
|
66
|
+
exposedHeaders?: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Whether to send `Access-Control-Allow-Credentials: true`.
|
|
69
|
+
*
|
|
70
|
+
* @defaultValue `false`
|
|
71
|
+
*/
|
|
72
|
+
credentials?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Preflight cache duration in seconds.
|
|
75
|
+
* When omitted, the header is not sent.
|
|
76
|
+
*/
|
|
77
|
+
maxAge?: number;
|
|
78
|
+
/**
|
|
79
|
+
* When `true`, preflight returns `Continue` instead of `RespondPreflight`.
|
|
80
|
+
*
|
|
81
|
+
* @defaultValue `false`
|
|
82
|
+
*/
|
|
83
|
+
preflightContinue?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* HTTP status for the preflight response.
|
|
86
|
+
*
|
|
87
|
+
* @defaultValue `204`
|
|
88
|
+
*/
|
|
89
|
+
optionsSuccessStatus?: number;
|
|
90
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Result } from '@zipbul/result';
|
|
2
|
+
import type { CorsError, CorsOptions } from './interfaces';
|
|
3
|
+
import type { ResolvedCorsOptions } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Resolves partial {@link CorsOptions} into a fully populated
|
|
6
|
+
* {@link ResolvedCorsOptions} by applying defaults via nullish coalescing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveCorsOptions(options?: CorsOptions): ResolvedCorsOptions;
|
|
9
|
+
/**
|
|
10
|
+
* Validates resolved CORS options against rules derived from the Fetch Standard and RFC 9110.
|
|
11
|
+
*
|
|
12
|
+
* - V0a: `origin` must not be an empty/blank string (RFC 6454).
|
|
13
|
+
* - V_regex: `origin` RegExp must be safe (no exponential backtracking / ReDoS).
|
|
14
|
+
* - V0b: `origin` array must not be empty, and must not contain empty/blank string entries (RFC 6454).
|
|
15
|
+
* Array RegExp entries are also checked for ReDoS safety.
|
|
16
|
+
* - V0c: `methods` must not be empty, and must not contain empty/blank string entries (RFC 9110 §5.6.2).
|
|
17
|
+
* - V0d: `allowedHeaders` must not contain empty/blank string entries (RFC 9110 §5.6.2).
|
|
18
|
+
* - V0e: `exposedHeaders` must not contain empty/blank string entries (RFC 9110 §5.6.2).
|
|
19
|
+
* - V1: `credentials:true` with wildcard origin is forbidden (Fetch Standard §3.3.5).
|
|
20
|
+
* - V2: `maxAge` must be a non-negative integer when set (RFC 9111 §1.2.1 delta-seconds).
|
|
21
|
+
* - V3: `optionsSuccessStatus` must be a 2xx integer (Fetch Standard).
|
|
22
|
+
*
|
|
23
|
+
* @returns `undefined` (void) if valid, or `Err<CorsError>` on the first violated rule.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateCorsOptions(resolved: ResolvedCorsOptions): Result<void, CorsError>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CorsContinueResult, CorsPreflightResult, CorsRejectResult } from './interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* HTTP method token accepted by the `methods` option.
|
|
4
|
+
* Provides autocomplete for standard RFC 9110 / RFC 5789 methods
|
|
5
|
+
* while allowing any RFC 9110 §5.6.2 token (e.g. `'PROPFIND'`, `'PURGE'`).
|
|
6
|
+
*/
|
|
7
|
+
export type CorsMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | (string & {});
|
|
8
|
+
/**
|
|
9
|
+
* Return value of an origin function.
|
|
10
|
+
* `true` to reflect, a string to override, or `false` to reject.
|
|
11
|
+
*/
|
|
12
|
+
export type OriginResult = boolean | string;
|
|
13
|
+
/**
|
|
14
|
+
* Function that dynamically resolves whether an origin is allowed.
|
|
15
|
+
*/
|
|
16
|
+
export type OriginFn = (origin: string, request: Request) => OriginResult | Promise<OriginResult>;
|
|
17
|
+
/**
|
|
18
|
+
* All accepted forms for the `origin` option.
|
|
19
|
+
*/
|
|
20
|
+
export type OriginOptions = boolean | string | RegExp | Array<string | RegExp> | OriginFn;
|
|
21
|
+
/**
|
|
22
|
+
* Discriminated union returned by {@link Cors.handle}.
|
|
23
|
+
* Branch on `action` to determine next step.
|
|
24
|
+
*/
|
|
25
|
+
export type CorsResult = CorsContinueResult | CorsPreflightResult | CorsRejectResult;
|
|
26
|
+
/**
|
|
27
|
+
* Fully resolved CORS options with all defaults applied.
|
|
28
|
+
* `null` indicates "use default behavior" (e.g., echo mode for headers).
|
|
29
|
+
*/
|
|
30
|
+
export type ResolvedCorsOptions = {
|
|
31
|
+
origin: OriginOptions;
|
|
32
|
+
methods: string[];
|
|
33
|
+
allowedHeaders: string[] | null;
|
|
34
|
+
exposedHeaders: string[] | null;
|
|
35
|
+
credentials: boolean;
|
|
36
|
+
maxAge: number | null;
|
|
37
|
+
preflightContinue: boolean;
|
|
38
|
+
optionsSuccessStatus: number;
|
|
39
|
+
};
|