convex-helpers 0.1.94 → 0.1.95
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.md +5 -1
- package/package.json +1 -1
- package/server/cors.d.ts +6 -1
- package/server/cors.d.ts.map +1 -1
- package/server/cors.js +44 -18
- package/server/cors.ts +54 -18
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Table of contents:
|
|
|
33
33
|
- [What can you do with triggers?](#what-can-you-do-with-triggers)
|
|
34
34
|
- [Trigger semantics](#trigger-semantics)
|
|
35
35
|
- [CORS support for HttpRouter](#cors-support-for-httprouter)
|
|
36
|
-
- [Standard Schema
|
|
36
|
+
- [Standard Schema](#standard-schema)
|
|
37
37
|
|
|
38
38
|
## Custom Functions
|
|
39
39
|
|
|
@@ -1180,12 +1180,16 @@ const cors = corsRouter(
|
|
|
1180
1180
|
http,
|
|
1181
1181
|
// Optional configuration, can be omitted entirely
|
|
1182
1182
|
{
|
|
1183
|
+
// allowedOrigins can also be a function
|
|
1184
|
+
// allowedOrigins: (req: Request) => Promise<string[]>
|
|
1183
1185
|
allowedOrigins: ["http://localhost:8080"], // Default: ["*"]
|
|
1184
1186
|
allowedMethods: ["GET", "POST"], // Defaults to route spec method
|
|
1185
1187
|
allowedHeaders: ["Content-Type"], // Default: ["Content-Type"]
|
|
1186
1188
|
exposedHeaders: ["Custom-Header"], // Default: ["Content-Range", "Accept-Ranges"]
|
|
1187
1189
|
allowCredentials: true, // Default: false
|
|
1188
1190
|
browserCacheMaxAge: 60, // Default: 86400 (1 day)
|
|
1191
|
+
// returns a 403 if the origin is not allowed
|
|
1192
|
+
enforceAllowOrigins: true, // Default: false
|
|
1189
1193
|
debug: true, // Default: false
|
|
1190
1194
|
},
|
|
1191
1195
|
);
|
package/package.json
CHANGED
package/server/cors.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type CorsConfig = {
|
|
|
31
31
|
* - https://example.com
|
|
32
32
|
* @default ["*"]
|
|
33
33
|
*/
|
|
34
|
-
allowedOrigins?: string[];
|
|
34
|
+
allowedOrigins?: string[] | ((req: Request) => Promise<string[]>);
|
|
35
35
|
/**
|
|
36
36
|
* An array of allowed headers: what headers are allowed to be sent in
|
|
37
37
|
* the request.
|
|
@@ -52,6 +52,11 @@ export type CorsConfig = {
|
|
|
52
52
|
* @default 86400 (1 day)
|
|
53
53
|
*/
|
|
54
54
|
browserCacheMaxAge?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Whether to block requests from origins that are not in the allowedOrigins list.
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
59
|
+
enforceAllowOrigins?: boolean;
|
|
55
60
|
/**
|
|
56
61
|
* Whether to log debugging information about CORS requests.
|
|
57
62
|
* @default false
|
package/server/cors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAIL,UAAU,EAIV,KAAK,SAAS,EAGf,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,uBAAuB,UAInC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAIL,UAAU,EAIV,KAAK,SAAS,EAGf,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,uBAAuB,UAInC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAClE;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,KAAK,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,UAAU,EAAE,aAAa,UAAU;;uBAK7C,iBAAiB,KAAG,IAAI;CAsD9C,CAAC;AA+DF,eAAe,UAAU,CAAC"}
|
package/server/cors.js
CHANGED
|
@@ -146,7 +146,7 @@ const SECONDS_IN_A_DAY = 60 * 60 * 24;
|
|
|
146
146
|
* - "https://example1.com, https://example2.com" (allow multiple specific domains)
|
|
147
147
|
* - "null" (allow requests from data URLs or local files)
|
|
148
148
|
*/
|
|
149
|
-
const handleCors = ({ originalHandler, allowedMethods = ["OPTIONS"], allowedOrigins = ["*"], allowedHeaders = ["Content-Type"], exposedHeaders = DEFAULT_EXPOSED_HEADERS, allowCredentials = false, browserCacheMaxAge = SECONDS_IN_A_DAY, debug = false, }) => {
|
|
149
|
+
const handleCors = ({ originalHandler, allowedMethods = ["OPTIONS"], allowedOrigins = ["*"], allowedHeaders = ["Content-Type"], exposedHeaders = DEFAULT_EXPOSED_HEADERS, allowCredentials = false, browserCacheMaxAge = SECONDS_IN_A_DAY, enforceAllowOrigins = false, debug = false, }) => {
|
|
150
150
|
const uniqueMethods = Array.from(new Set(allowedMethods.map((method) => method.toUpperCase())));
|
|
151
151
|
const filteredMethods = uniqueMethods.filter((method) => ROUTABLE_HTTP_METHODS.includes(method));
|
|
152
152
|
if (filteredMethods.length === 0) {
|
|
@@ -171,9 +171,17 @@ const handleCors = ({ originalHandler, allowedMethods = ["OPTIONS"], allowedOrig
|
|
|
171
171
|
if (exposedHeaders.length > 0) {
|
|
172
172
|
commonHeaders["Access-Control-Expose-Headers"] = exposedHeaders.join(", ");
|
|
173
173
|
}
|
|
174
|
+
async function parseAllowedOrigins(request) {
|
|
175
|
+
return Array.isArray(allowedOrigins)
|
|
176
|
+
? allowedOrigins
|
|
177
|
+
: await allowedOrigins(request);
|
|
178
|
+
}
|
|
174
179
|
// Helper function to check if origin is allowed (including wildcard subdomain matching)
|
|
175
|
-
function isAllowedOrigin(
|
|
176
|
-
|
|
180
|
+
async function isAllowedOrigin(request) {
|
|
181
|
+
const requestOrigin = request.headers.get("origin");
|
|
182
|
+
if (!requestOrigin)
|
|
183
|
+
return false;
|
|
184
|
+
return (await parseAllowedOrigins(request)).some((allowed) => {
|
|
177
185
|
if (allowed === "*")
|
|
178
186
|
return true;
|
|
179
187
|
if (allowed === requestOrigin)
|
|
@@ -208,36 +216,48 @@ const handleCors = ({ originalHandler, allowedMethods = ["OPTIONS"], allowedOrig
|
|
|
208
216
|
});
|
|
209
217
|
}
|
|
210
218
|
const requestOrigin = request.headers.get("origin");
|
|
219
|
+
const parsedAllowedOrigins = await parseAllowedOrigins(request);
|
|
220
|
+
if (debug) {
|
|
221
|
+
console.log("allowed origins", parsedAllowedOrigins);
|
|
222
|
+
}
|
|
211
223
|
// Handle origin matching
|
|
212
224
|
let allowOrigins = null;
|
|
213
|
-
if (
|
|
214
|
-
|
|
225
|
+
if (parsedAllowedOrigins.includes("*") &&
|
|
226
|
+
requestOrigin &&
|
|
227
|
+
!allowCredentials) {
|
|
228
|
+
allowOrigins = requestOrigin;
|
|
215
229
|
}
|
|
216
230
|
else if (requestOrigin) {
|
|
217
231
|
// Check if the request origin matches any of the allowed origins
|
|
218
232
|
// (including wildcard subdomain matching if configured)
|
|
219
|
-
if (isAllowedOrigin(
|
|
233
|
+
if (await isAllowedOrigin(request)) {
|
|
220
234
|
allowOrigins = requestOrigin;
|
|
221
235
|
}
|
|
222
236
|
}
|
|
223
|
-
if (!allowOrigins) {
|
|
237
|
+
if (enforceAllowOrigins && !allowOrigins) {
|
|
224
238
|
// Origin not allowed
|
|
225
|
-
console.error(`Request from origin ${requestOrigin} blocked, missing from allowed origins: ${
|
|
239
|
+
console.error(`Request from origin ${requestOrigin} blocked, missing from allowed origins: ${parsedAllowedOrigins.join()}`);
|
|
226
240
|
return new Response(null, { status: 403 });
|
|
227
241
|
}
|
|
228
242
|
/**
|
|
229
243
|
* OPTIONS has no handler and just returns headers
|
|
230
244
|
*/
|
|
231
245
|
if (request.method === "OPTIONS") {
|
|
246
|
+
const responseHeaders = new Headers({
|
|
247
|
+
...commonHeaders,
|
|
248
|
+
...(allowOrigins
|
|
249
|
+
? { "Access-Control-Allow-Origin": allowOrigins }
|
|
250
|
+
: {}),
|
|
251
|
+
"Access-Control-Allow-Methods": allowMethods,
|
|
252
|
+
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
253
|
+
"Access-Control-Max-Age": browserCacheMaxAge.toString(),
|
|
254
|
+
});
|
|
255
|
+
if (debug) {
|
|
256
|
+
console.log("CORS OPTIONS response headers", responseHeaders);
|
|
257
|
+
}
|
|
232
258
|
return new Response(null, {
|
|
233
259
|
status: 204,
|
|
234
|
-
headers:
|
|
235
|
-
...commonHeaders,
|
|
236
|
-
"Access-Control-Allow-Origin": allowOrigins,
|
|
237
|
-
"Access-Control-Allow-Methods": allowMethods,
|
|
238
|
-
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
239
|
-
"Access-Control-Max-Age": browserCacheMaxAge.toString(),
|
|
240
|
-
}),
|
|
260
|
+
headers: responseHeaders,
|
|
241
261
|
});
|
|
242
262
|
}
|
|
243
263
|
/**
|
|
@@ -254,16 +274,22 @@ const handleCors = ({ originalHandler, allowedMethods = ["OPTIONS"], allowedOrig
|
|
|
254
274
|
: originalHandler);
|
|
255
275
|
const originalResponse = await innerHandler(ctx, request);
|
|
256
276
|
/**
|
|
257
|
-
* Second, get a copy of the original response's headers
|
|
277
|
+
* Second, get a copy of the original response's headers and add the
|
|
278
|
+
* allow origin header if it's allowed
|
|
258
279
|
*/
|
|
259
280
|
const newHeaders = new Headers(originalResponse.headers);
|
|
260
|
-
|
|
281
|
+
if (allowOrigins) {
|
|
282
|
+
newHeaders.set("Access-Control-Allow-Origin", allowOrigins);
|
|
283
|
+
}
|
|
261
284
|
/**
|
|
262
|
-
* Third, add or update our CORS headers
|
|
285
|
+
* Third, add or update our other CORS headers
|
|
263
286
|
*/
|
|
264
287
|
Object.entries(commonHeaders).forEach(([key, value]) => {
|
|
265
288
|
newHeaders.set(key, value);
|
|
266
289
|
});
|
|
290
|
+
if (debug) {
|
|
291
|
+
console.log("CORS response headers", newHeaders);
|
|
292
|
+
}
|
|
267
293
|
/**
|
|
268
294
|
* Fourth, return the modified Response.
|
|
269
295
|
* A Response object is immutable, so we create a new one to return here.
|
package/server/cors.ts
CHANGED
|
@@ -48,7 +48,7 @@ export type CorsConfig = {
|
|
|
48
48
|
* - https://example.com
|
|
49
49
|
* @default ["*"]
|
|
50
50
|
*/
|
|
51
|
-
allowedOrigins?: string[];
|
|
51
|
+
allowedOrigins?: string[] | ((req: Request) => Promise<string[]>);
|
|
52
52
|
/**
|
|
53
53
|
* An array of allowed headers: what headers are allowed to be sent in
|
|
54
54
|
* the request.
|
|
@@ -69,6 +69,11 @@ export type CorsConfig = {
|
|
|
69
69
|
* @default 86400 (1 day)
|
|
70
70
|
*/
|
|
71
71
|
browserCacheMaxAge?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Whether to block requests from origins that are not in the allowedOrigins list.
|
|
74
|
+
* @default false
|
|
75
|
+
*/
|
|
76
|
+
enforceAllowOrigins?: boolean;
|
|
72
77
|
/**
|
|
73
78
|
* Whether to log debugging information about CORS requests.
|
|
74
79
|
* @default false
|
|
@@ -240,6 +245,7 @@ const handleCors = ({
|
|
|
240
245
|
exposedHeaders = DEFAULT_EXPOSED_HEADERS,
|
|
241
246
|
allowCredentials = false,
|
|
242
247
|
browserCacheMaxAge = SECONDS_IN_A_DAY,
|
|
248
|
+
enforceAllowOrigins = false,
|
|
243
249
|
debug = false,
|
|
244
250
|
}: {
|
|
245
251
|
originalHandler?: PublicHttpAction;
|
|
@@ -279,9 +285,17 @@ const handleCors = ({
|
|
|
279
285
|
commonHeaders["Access-Control-Expose-Headers"] = exposedHeaders.join(", ");
|
|
280
286
|
}
|
|
281
287
|
|
|
288
|
+
async function parseAllowedOrigins(request: Request): Promise<string[]> {
|
|
289
|
+
return Array.isArray(allowedOrigins)
|
|
290
|
+
? allowedOrigins
|
|
291
|
+
: await allowedOrigins(request);
|
|
292
|
+
}
|
|
293
|
+
|
|
282
294
|
// Helper function to check if origin is allowed (including wildcard subdomain matching)
|
|
283
|
-
function isAllowedOrigin(
|
|
284
|
-
|
|
295
|
+
async function isAllowedOrigin(request: Request): Promise<boolean> {
|
|
296
|
+
const requestOrigin = request.headers.get("origin");
|
|
297
|
+
if (!requestOrigin) return false;
|
|
298
|
+
return (await parseAllowedOrigins(request)).some((allowed) => {
|
|
285
299
|
if (allowed === "*") return true;
|
|
286
300
|
if (allowed === requestOrigin) return true;
|
|
287
301
|
if (allowed.startsWith("*.")) {
|
|
@@ -317,23 +331,32 @@ const handleCors = ({
|
|
|
317
331
|
});
|
|
318
332
|
}
|
|
319
333
|
const requestOrigin = request.headers.get("origin");
|
|
334
|
+
const parsedAllowedOrigins = await parseAllowedOrigins(request);
|
|
335
|
+
|
|
336
|
+
if (debug) {
|
|
337
|
+
console.log("allowed origins", parsedAllowedOrigins);
|
|
338
|
+
}
|
|
320
339
|
|
|
321
340
|
// Handle origin matching
|
|
322
341
|
let allowOrigins: string | null = null;
|
|
323
|
-
if (
|
|
324
|
-
|
|
342
|
+
if (
|
|
343
|
+
parsedAllowedOrigins.includes("*") &&
|
|
344
|
+
requestOrigin &&
|
|
345
|
+
!allowCredentials
|
|
346
|
+
) {
|
|
347
|
+
allowOrigins = requestOrigin;
|
|
325
348
|
} else if (requestOrigin) {
|
|
326
349
|
// Check if the request origin matches any of the allowed origins
|
|
327
350
|
// (including wildcard subdomain matching if configured)
|
|
328
|
-
if (isAllowedOrigin(
|
|
351
|
+
if (await isAllowedOrigin(request)) {
|
|
329
352
|
allowOrigins = requestOrigin;
|
|
330
353
|
}
|
|
331
354
|
}
|
|
332
355
|
|
|
333
|
-
if (!allowOrigins) {
|
|
356
|
+
if (enforceAllowOrigins && !allowOrigins) {
|
|
334
357
|
// Origin not allowed
|
|
335
358
|
console.error(
|
|
336
|
-
`Request from origin ${requestOrigin} blocked, missing from allowed origins: ${
|
|
359
|
+
`Request from origin ${requestOrigin} blocked, missing from allowed origins: ${parsedAllowedOrigins.join()}`,
|
|
337
360
|
);
|
|
338
361
|
return new Response(null, { status: 403 });
|
|
339
362
|
}
|
|
@@ -341,15 +364,21 @@ const handleCors = ({
|
|
|
341
364
|
* OPTIONS has no handler and just returns headers
|
|
342
365
|
*/
|
|
343
366
|
if (request.method === "OPTIONS") {
|
|
367
|
+
const responseHeaders = new Headers({
|
|
368
|
+
...commonHeaders,
|
|
369
|
+
...(allowOrigins
|
|
370
|
+
? { "Access-Control-Allow-Origin": allowOrigins }
|
|
371
|
+
: {}),
|
|
372
|
+
"Access-Control-Allow-Methods": allowMethods,
|
|
373
|
+
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
374
|
+
"Access-Control-Max-Age": browserCacheMaxAge.toString(),
|
|
375
|
+
});
|
|
376
|
+
if (debug) {
|
|
377
|
+
console.log("CORS OPTIONS response headers", responseHeaders);
|
|
378
|
+
}
|
|
344
379
|
return new Response(null, {
|
|
345
380
|
status: 204,
|
|
346
|
-
headers:
|
|
347
|
-
...commonHeaders,
|
|
348
|
-
"Access-Control-Allow-Origin": allowOrigins,
|
|
349
|
-
"Access-Control-Allow-Methods": allowMethods,
|
|
350
|
-
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
351
|
-
"Access-Control-Max-Age": browserCacheMaxAge.toString(),
|
|
352
|
-
}),
|
|
381
|
+
headers: responseHeaders,
|
|
353
382
|
});
|
|
354
383
|
}
|
|
355
384
|
|
|
@@ -372,18 +401,25 @@ const handleCors = ({
|
|
|
372
401
|
const originalResponse = await innerHandler(ctx, request);
|
|
373
402
|
|
|
374
403
|
/**
|
|
375
|
-
* Second, get a copy of the original response's headers
|
|
404
|
+
* Second, get a copy of the original response's headers and add the
|
|
405
|
+
* allow origin header if it's allowed
|
|
376
406
|
*/
|
|
377
407
|
const newHeaders = new Headers(originalResponse.headers);
|
|
378
|
-
|
|
408
|
+
if (allowOrigins) {
|
|
409
|
+
newHeaders.set("Access-Control-Allow-Origin", allowOrigins);
|
|
410
|
+
}
|
|
379
411
|
|
|
380
412
|
/**
|
|
381
|
-
* Third, add or update our CORS headers
|
|
413
|
+
* Third, add or update our other CORS headers
|
|
382
414
|
*/
|
|
383
415
|
Object.entries(commonHeaders).forEach(([key, value]) => {
|
|
384
416
|
newHeaders.set(key, value);
|
|
385
417
|
});
|
|
386
418
|
|
|
419
|
+
if (debug) {
|
|
420
|
+
console.log("CORS response headers", newHeaders);
|
|
421
|
+
}
|
|
422
|
+
|
|
387
423
|
/**
|
|
388
424
|
* Fourth, return the modified Response.
|
|
389
425
|
* A Response object is immutable, so we create a new one to return here.
|