@hoox-sh/hoox-shared 1.3.0 → 1.5.0
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 +6 -0
- package/dist/agent-config-fields.js +23 -29
- package/dist/analytics.d.ts +3 -4
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +56 -16
- package/dist/api-client.js +12 -21
- package/dist/colors.js +5 -21
- package/dist/config.js +11 -27
- package/dist/cron-handler.js +0 -16
- package/dist/d1/index.js +12 -27
- package/dist/d1/repository.js +7 -21
- package/dist/d1/schemas.js +4 -20
- package/dist/dashboard-manifest.d.ts +2 -1
- package/dist/dashboard-manifest.d.ts.map +1 -1
- package/dist/dashboard-manifest.js +37 -33
- package/dist/entitlements.d.ts +46 -0
- package/dist/entitlements.d.ts.map +1 -0
- package/dist/entitlements.js +15 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -25
- package/dist/exchange-client.js +2 -18
- package/dist/exchanges/base-exchange-client.js +0 -16
- package/dist/exchanges/index.js +0 -16
- package/dist/format-time.js +2 -18
- package/dist/formatters.js +12 -28
- package/dist/health.js +7 -21
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3895 -3653
- package/dist/kill-switch.js +14 -20
- package/dist/kvKeys.d.ts +11 -0
- package/dist/kvKeys.d.ts.map +1 -1
- package/dist/kvKeys.js +44 -45
- package/dist/kvUtils.d.ts +34 -1
- package/dist/kvUtils.d.ts.map +1 -1
- package/dist/kvUtils.js +60 -23
- package/dist/legal.js +5 -21
- package/dist/middleware/auth.d.ts +6 -3
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +27 -29
- package/dist/middleware/cors.js +4 -20
- package/dist/middleware/index.d.ts +3 -2
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +222 -164
- package/dist/middleware/logger.js +13 -27
- package/dist/middleware/rate-limit.js +2 -18
- package/dist/middleware/security-headers.js +2 -18
- package/dist/middleware/validate.d.ts +16 -0
- package/dist/middleware/validate.d.ts.map +1 -1
- package/dist/middleware/validate.js +39 -19
- package/dist/middleware/wait-until.d.ts +32 -0
- package/dist/middleware/wait-until.d.ts.map +1 -0
- package/dist/middleware/wait-until.js +30 -0
- package/dist/operator-transport.js +13 -17
- package/dist/path-utils.js +15 -31
- package/dist/queue-handler.d.ts +6 -0
- package/dist/queue-handler.d.ts.map +1 -1
- package/dist/queue-handler.js +51 -36
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +12 -18
- package/dist/schemas/index.d.ts +2 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +91 -38
- package/dist/schemas/registry.d.ts +13 -0
- package/dist/schemas/registry.d.ts.map +1 -1
- package/dist/schemas/registry.js +82 -30
- package/dist/schemas/types.d.ts +5 -0
- package/dist/schemas/types.d.ts.map +1 -1
- package/dist/schemas/validators.d.ts.map +1 -1
- package/dist/schemas/validators.js +8 -22
- package/dist/service-bindings.js +31 -34
- package/dist/session.js +2 -18
- package/dist/sse.js +10 -18
- package/dist/stores/config-store.js +8 -22
- package/dist/stores/service-store.js +17 -30
- package/dist/stores/ui-store.js +0 -16
- package/dist/tenant.d.ts +61 -0
- package/dist/tenant.d.ts.map +1 -0
- package/dist/tenant.js +56 -0
- package/dist/test-utils.js +11 -27
- package/dist/types.d.ts +26 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +16 -26
- package/dist/wizard/engine.d.ts.map +1 -1
- package/dist/wizard/engine.js +39 -56
- package/dist/wizard/index.d.ts +1 -0
- package/dist/wizard/index.d.ts.map +1 -1
- package/dist/wizard/index.js +54 -68
- package/dist/wizard/persistence.d.ts +4 -0
- package/dist/wizard/persistence.d.ts.map +1 -1
- package/dist/wizard/persistence.js +7 -19
- package/dist/wizard/presets.d.ts.map +1 -1
- package/dist/wizard/presets.js +16 -54
- package/dist/wizard/types.d.ts +6 -0
- package/dist/wizard/types.d.ts.map +1 -1
- package/dist/wizard/types.js +5 -0
- package/package.json +7 -2
package/dist/middleware/index.js
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
|
|
18
2
|
// src/errors.ts
|
|
19
3
|
function toError(err, fallback = "Unknown error") {
|
|
20
4
|
if (err instanceof Error)
|
|
21
5
|
return err.message;
|
|
22
6
|
if (typeof err === "string")
|
|
23
7
|
return err;
|
|
24
|
-
if (err && typeof err === "object" && "message" in err
|
|
25
|
-
|
|
8
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
9
|
+
const message = err.message;
|
|
10
|
+
if (typeof message === "string")
|
|
11
|
+
return message;
|
|
26
12
|
}
|
|
27
13
|
if (err === null || err === undefined)
|
|
28
14
|
return fallback;
|
|
@@ -70,14 +56,14 @@ function sanitizeOutput(data) {
|
|
|
70
56
|
const obj = data;
|
|
71
57
|
const keys = Object.keys(obj);
|
|
72
58
|
if (keys.length < 10) {
|
|
73
|
-
const
|
|
59
|
+
const sanitized = {};
|
|
74
60
|
for (const k of keys) {
|
|
75
61
|
if (k === "stack" || k === "cause")
|
|
76
62
|
continue;
|
|
77
63
|
const v = obj[k];
|
|
78
|
-
|
|
64
|
+
sanitized[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
|
|
79
65
|
}
|
|
80
|
-
return
|
|
66
|
+
return sanitized;
|
|
81
67
|
}
|
|
82
68
|
if (!hasSensitiveFields(obj) && !hasErrorValues(obj)) {
|
|
83
69
|
return obj;
|
|
@@ -141,6 +127,61 @@ var Errors = {
|
|
|
141
127
|
}
|
|
142
128
|
};
|
|
143
129
|
|
|
130
|
+
// src/middleware/logger.ts
|
|
131
|
+
function createLogger(ctx) {
|
|
132
|
+
const base = { service: ctx.service, module: ctx.module };
|
|
133
|
+
function emit(level, message, context) {
|
|
134
|
+
const entry = {
|
|
135
|
+
level,
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
...base,
|
|
138
|
+
message,
|
|
139
|
+
...context && { context }
|
|
140
|
+
};
|
|
141
|
+
const line = JSON.stringify(entry);
|
|
142
|
+
if (level === "error")
|
|
143
|
+
console.error(line);
|
|
144
|
+
else if (level === "warn")
|
|
145
|
+
console.warn(line);
|
|
146
|
+
else if (level === "debug")
|
|
147
|
+
console.debug(line);
|
|
148
|
+
else
|
|
149
|
+
console.info(line);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
debug: (msg, ctx) => emit("debug", msg, ctx),
|
|
153
|
+
info: (msg, ctx) => emit("info", msg, ctx),
|
|
154
|
+
warn: (msg, ctx) => emit("warn", msg, ctx),
|
|
155
|
+
error: (msg, ctx) => emit("error", msg, ctx)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function withRequestLog(handler, logCtx) {
|
|
159
|
+
return async (request, env, ctx) => {
|
|
160
|
+
const start = Date.now();
|
|
161
|
+
const logger = createLogger(logCtx);
|
|
162
|
+
try {
|
|
163
|
+
const response = await handler(request, env, ctx);
|
|
164
|
+
const duration = Date.now() - start;
|
|
165
|
+
const url = new URL(request.url);
|
|
166
|
+
logger.info(`${request.method} ${url.pathname}`, {
|
|
167
|
+
method: request.method,
|
|
168
|
+
path: url.pathname,
|
|
169
|
+
status: response.status,
|
|
170
|
+
durationMs: duration
|
|
171
|
+
});
|
|
172
|
+
return response;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const duration = Date.now() - start;
|
|
175
|
+
logger.error("Request failed", {
|
|
176
|
+
method: request.method,
|
|
177
|
+
path: new URL(request.url).pathname,
|
|
178
|
+
durationMs: duration,
|
|
179
|
+
error: toError(error)
|
|
180
|
+
});
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
144
185
|
// src/middleware/auth.ts
|
|
145
186
|
function timingSafeEqual(a, b) {
|
|
146
187
|
const encoder = new TextEncoder;
|
|
@@ -149,12 +190,24 @@ function timingSafeEqual(a, b) {
|
|
|
149
190
|
const len = Math.max(aBuf.length, bBuf.length);
|
|
150
191
|
let result = aBuf.length === bBuf.length ? 0 : 1;
|
|
151
192
|
for (let i = 0;i < len; i++) {
|
|
152
|
-
const av =
|
|
153
|
-
const bv =
|
|
193
|
+
const av = aBuf[i] ?? 0;
|
|
194
|
+
const bv = bBuf[i] ?? 0;
|
|
154
195
|
result |= av ^ bv;
|
|
155
196
|
}
|
|
156
197
|
return result === 0;
|
|
157
198
|
}
|
|
199
|
+
async function timingSafeEqualAsync(a, b) {
|
|
200
|
+
const subtle = globalThis.crypto?.subtle;
|
|
201
|
+
if (!subtle || typeof subtle.digest !== "function" || typeof subtle.timingSafeEqual !== "function") {
|
|
202
|
+
return timingSafeEqual(a, b);
|
|
203
|
+
}
|
|
204
|
+
const encoder = new TextEncoder;
|
|
205
|
+
const [providedHash, expectedHash] = await Promise.all([
|
|
206
|
+
subtle.digest("SHA-256", encoder.encode(a)),
|
|
207
|
+
subtle.digest("SHA-256", encoder.encode(b))
|
|
208
|
+
]);
|
|
209
|
+
return subtle.timingSafeEqual(providedHash, expectedHash);
|
|
210
|
+
}
|
|
158
211
|
function resolveOperatorApiKey(env) {
|
|
159
212
|
const preferred = env.OPERATOR_API_KEY;
|
|
160
213
|
if (typeof preferred === "string" && preferred.length > 0) {
|
|
@@ -179,7 +232,7 @@ async function requireOperatorAuth(request, env) {
|
|
|
179
232
|
}
|
|
180
233
|
const authHeader = request.headers.get("Authorization");
|
|
181
234
|
const expectedHeader = `Bearer ${apiKey}`;
|
|
182
|
-
if (!authHeader || !
|
|
235
|
+
if (!authHeader || !await timingSafeEqualAsync(authHeader, expectedHeader)) {
|
|
183
236
|
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
184
237
|
status: 401,
|
|
185
238
|
headers: { "Content-Type": "application/json" }
|
|
@@ -212,12 +265,13 @@ function collectInternalAuthKeys(env, keyNames) {
|
|
|
212
265
|
return keys;
|
|
213
266
|
}
|
|
214
267
|
function matchesAnyInternalAuthKey(providedKey, expectedKeys) {
|
|
268
|
+
let matched = false;
|
|
215
269
|
for (const expected of expectedKeys) {
|
|
216
270
|
if (timingSafeEqual(providedKey, expected)) {
|
|
217
|
-
|
|
271
|
+
matched = true;
|
|
218
272
|
}
|
|
219
273
|
}
|
|
220
|
-
return
|
|
274
|
+
return matched;
|
|
221
275
|
}
|
|
222
276
|
function internalAuthNotConfiguredResponse(keyNames) {
|
|
223
277
|
const label = normalizeKeyNames(keyNames).join(" | ");
|
|
@@ -264,117 +318,6 @@ function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
|
264
318
|
}
|
|
265
319
|
return { authorized: true };
|
|
266
320
|
}
|
|
267
|
-
|
|
268
|
-
// src/middleware/cors.ts
|
|
269
|
-
var DEFAULT_OPTIONS = {
|
|
270
|
-
allowOrigin: "",
|
|
271
|
-
allowMethods: "GET, POST, OPTIONS, PUT, DELETE",
|
|
272
|
-
allowHeaders: "Content-Type, Authorization, X-Request-ID",
|
|
273
|
-
allowCredentials: false,
|
|
274
|
-
maxAge: 86400
|
|
275
|
-
};
|
|
276
|
-
function corsHeaders(options) {
|
|
277
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
278
|
-
const headers = {
|
|
279
|
-
"Access-Control-Allow-Methods": opts.allowMethods,
|
|
280
|
-
"Access-Control-Allow-Headers": opts.allowHeaders,
|
|
281
|
-
"Access-Control-Max-Age": String(opts.maxAge)
|
|
282
|
-
};
|
|
283
|
-
if (opts.allowOrigin) {
|
|
284
|
-
headers["Access-Control-Allow-Origin"] = opts.allowOrigin;
|
|
285
|
-
}
|
|
286
|
-
if (opts.allowCredentials) {
|
|
287
|
-
headers["Access-Control-Allow-Credentials"] = "true";
|
|
288
|
-
}
|
|
289
|
-
return headers;
|
|
290
|
-
}
|
|
291
|
-
function publicCorsHeaders(origin = "*", options) {
|
|
292
|
-
return corsHeaders({ ...options, allowOrigin: origin });
|
|
293
|
-
}
|
|
294
|
-
function internalCorsHeaders() {
|
|
295
|
-
return {};
|
|
296
|
-
}
|
|
297
|
-
function resolveCorsOptions(request, env) {
|
|
298
|
-
const raw = env && typeof env === "object" && "CORS_ALLOW_ORIGIN" in env && typeof env.CORS_ALLOW_ORIGIN === "string" ? env.CORS_ALLOW_ORIGIN : undefined;
|
|
299
|
-
const configured = raw?.trim();
|
|
300
|
-
if (!configured) {
|
|
301
|
-
return {};
|
|
302
|
-
}
|
|
303
|
-
const allowed = configured.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
304
|
-
const origin = request.headers.get("Origin");
|
|
305
|
-
if (origin && allowed.includes(origin)) {
|
|
306
|
-
return { allowOrigin: origin, allowCredentials: true };
|
|
307
|
-
}
|
|
308
|
-
if (!origin && allowed.length === 1) {
|
|
309
|
-
return { allowOrigin: allowed[0] };
|
|
310
|
-
}
|
|
311
|
-
return {};
|
|
312
|
-
}
|
|
313
|
-
function handleCorsPreflightRequest(request, options) {
|
|
314
|
-
if (request.method !== "OPTIONS")
|
|
315
|
-
return null;
|
|
316
|
-
return new Response(null, {
|
|
317
|
-
status: 204,
|
|
318
|
-
headers: corsHeaders(options)
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// src/middleware/logger.ts
|
|
323
|
-
function createLogger(ctx) {
|
|
324
|
-
const base = { service: ctx.service, module: ctx.module };
|
|
325
|
-
function emit(level, message, context) {
|
|
326
|
-
const entry = {
|
|
327
|
-
level,
|
|
328
|
-
timestamp: new Date().toISOString(),
|
|
329
|
-
...base,
|
|
330
|
-
message,
|
|
331
|
-
...context && { context }
|
|
332
|
-
};
|
|
333
|
-
const line = JSON.stringify(entry);
|
|
334
|
-
if (level === "error")
|
|
335
|
-
console.error(line);
|
|
336
|
-
else if (level === "warn")
|
|
337
|
-
console.warn(line);
|
|
338
|
-
else if (level === "debug")
|
|
339
|
-
console.debug(line);
|
|
340
|
-
else
|
|
341
|
-
console.info(line);
|
|
342
|
-
}
|
|
343
|
-
return {
|
|
344
|
-
debug: (msg, ctx2) => emit("debug", msg, ctx2),
|
|
345
|
-
info: (msg, ctx2) => emit("info", msg, ctx2),
|
|
346
|
-
warn: (msg, ctx2) => emit("warn", msg, ctx2),
|
|
347
|
-
error: (msg, ctx2) => emit("error", msg, ctx2)
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
function withRequestLog(handler, logCtx) {
|
|
351
|
-
return async (request, env, ctx) => {
|
|
352
|
-
const start = Date.now();
|
|
353
|
-
const logger = createLogger(logCtx);
|
|
354
|
-
try {
|
|
355
|
-
const response = await handler(request, env, ctx);
|
|
356
|
-
const duration = Date.now() - start;
|
|
357
|
-
const url = new URL(request.url);
|
|
358
|
-
logger.info(`${request.method} ${url.pathname}`, {
|
|
359
|
-
method: request.method,
|
|
360
|
-
path: url.pathname,
|
|
361
|
-
status: response.status,
|
|
362
|
-
durationMs: duration
|
|
363
|
-
});
|
|
364
|
-
return response;
|
|
365
|
-
} catch (error) {
|
|
366
|
-
const duration = Date.now() - start;
|
|
367
|
-
logger.error("Request failed", {
|
|
368
|
-
method: request.method,
|
|
369
|
-
path: new URL(request.url).pathname,
|
|
370
|
-
durationMs: duration,
|
|
371
|
-
error: toError(error)
|
|
372
|
-
});
|
|
373
|
-
throw error;
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
321
|
// src/middleware/rate-limit.ts
|
|
379
322
|
function createStorage(kv) {
|
|
380
323
|
if (!kv) {
|
|
@@ -429,8 +372,8 @@ function createStorage(kv) {
|
|
|
429
372
|
attempts++;
|
|
430
373
|
if (attempts >= maxAttempts) {
|
|
431
374
|
const current = await this.get(key);
|
|
432
|
-
const
|
|
433
|
-
newValue =
|
|
375
|
+
const count = current ? parseInt(current, 10) : 0;
|
|
376
|
+
newValue = count + 1;
|
|
434
377
|
const ttl = opts?.expirationTtl ?? 0;
|
|
435
378
|
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
436
379
|
memory.set(key, { value: String(newValue), expiresAt });
|
|
@@ -536,8 +479,8 @@ function createRateLimiter(kv, config) {
|
|
|
536
479
|
}
|
|
537
480
|
return { check, enforce, checkKey, enforceKey };
|
|
538
481
|
}
|
|
539
|
-
|
|
540
482
|
// src/middleware/validate.ts
|
|
483
|
+
var DEFAULT_MAX_JSON_BODY_BYTES = 1048576;
|
|
541
484
|
function validateJson(schema, data) {
|
|
542
485
|
const result = schema.safeParse(data);
|
|
543
486
|
if (!result.success) {
|
|
@@ -548,6 +491,39 @@ function validateJson(schema, data) {
|
|
|
548
491
|
}
|
|
549
492
|
return { ok: true, value: result.data };
|
|
550
493
|
}
|
|
494
|
+
async function parseJsonBody(request, schema, options = {}) {
|
|
495
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_JSON_BODY_BYTES;
|
|
496
|
+
const contentLength = request.headers.get("content-length");
|
|
497
|
+
if (contentLength !== null) {
|
|
498
|
+
const declared = Number(contentLength);
|
|
499
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
500
|
+
return {
|
|
501
|
+
ok: false,
|
|
502
|
+
error: `Request body too large (max ${maxBytes} bytes)`
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
let text;
|
|
507
|
+
try {
|
|
508
|
+
text = await request.text();
|
|
509
|
+
} catch {
|
|
510
|
+
return { ok: false, error: "Failed to read request body" };
|
|
511
|
+
}
|
|
512
|
+
const byteLength = new TextEncoder().encode(text).byteLength;
|
|
513
|
+
if (byteLength > maxBytes) {
|
|
514
|
+
return {
|
|
515
|
+
ok: false,
|
|
516
|
+
error: `Request body too large (max ${maxBytes} bytes)`
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
let data;
|
|
520
|
+
try {
|
|
521
|
+
data = text.length === 0 ? null : JSON.parse(text);
|
|
522
|
+
} catch {
|
|
523
|
+
return { ok: false, error: "Invalid JSON in request body" };
|
|
524
|
+
}
|
|
525
|
+
return validateJson(schema, data);
|
|
526
|
+
}
|
|
551
527
|
async function validateJsonLegacy(request) {
|
|
552
528
|
try {
|
|
553
529
|
const body = await request.json();
|
|
@@ -570,7 +546,84 @@ function optionalField(body, field, defaultValue) {
|
|
|
570
546
|
return defaultValue;
|
|
571
547
|
return body[field];
|
|
572
548
|
}
|
|
573
|
-
|
|
549
|
+
// src/middleware/wait-until.ts
|
|
550
|
+
function safeWaitUntil(ctx, promise, onError) {
|
|
551
|
+
ctx.waitUntil(promise.catch((err) => {
|
|
552
|
+
onError?.(err);
|
|
553
|
+
console.error(JSON.stringify({
|
|
554
|
+
message: "waitUntil background task failed",
|
|
555
|
+
error: err instanceof Error ? err.message : String(err)
|
|
556
|
+
}));
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
function waitUntilAll(ctx, promises, onError) {
|
|
560
|
+
if (promises.length === 0)
|
|
561
|
+
return;
|
|
562
|
+
safeWaitUntil(ctx, Promise.allSettled(promises).then((results) => {
|
|
563
|
+
for (const result of results) {
|
|
564
|
+
if (result.status === "rejected") {
|
|
565
|
+
onError?.(result.reason);
|
|
566
|
+
console.error(JSON.stringify({
|
|
567
|
+
message: "waitUntilAll background task failed",
|
|
568
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
569
|
+
}));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}), onError);
|
|
573
|
+
}
|
|
574
|
+
// src/middleware/cors.ts
|
|
575
|
+
var DEFAULT_OPTIONS = {
|
|
576
|
+
allowOrigin: "",
|
|
577
|
+
allowMethods: "GET, POST, OPTIONS, PUT, DELETE",
|
|
578
|
+
allowHeaders: "Content-Type, Authorization, X-Request-ID",
|
|
579
|
+
allowCredentials: false,
|
|
580
|
+
maxAge: 86400
|
|
581
|
+
};
|
|
582
|
+
function corsHeaders(options) {
|
|
583
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
584
|
+
const headers = {
|
|
585
|
+
"Access-Control-Allow-Methods": opts.allowMethods,
|
|
586
|
+
"Access-Control-Allow-Headers": opts.allowHeaders,
|
|
587
|
+
"Access-Control-Max-Age": String(opts.maxAge)
|
|
588
|
+
};
|
|
589
|
+
if (opts.allowOrigin) {
|
|
590
|
+
headers["Access-Control-Allow-Origin"] = opts.allowOrigin;
|
|
591
|
+
}
|
|
592
|
+
if (opts.allowCredentials) {
|
|
593
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
594
|
+
}
|
|
595
|
+
return headers;
|
|
596
|
+
}
|
|
597
|
+
function publicCorsHeaders(origin = "*", options) {
|
|
598
|
+
return corsHeaders({ ...options, allowOrigin: origin });
|
|
599
|
+
}
|
|
600
|
+
function internalCorsHeaders() {
|
|
601
|
+
return {};
|
|
602
|
+
}
|
|
603
|
+
function resolveCorsOptions(request, env) {
|
|
604
|
+
const raw = env && typeof env === "object" && "CORS_ALLOW_ORIGIN" in env && typeof env.CORS_ALLOW_ORIGIN === "string" ? env.CORS_ALLOW_ORIGIN : undefined;
|
|
605
|
+
const configured = raw?.trim();
|
|
606
|
+
if (!configured) {
|
|
607
|
+
return {};
|
|
608
|
+
}
|
|
609
|
+
const allowed = configured.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
610
|
+
const origin = request.headers.get("Origin");
|
|
611
|
+
if (origin && allowed.includes(origin)) {
|
|
612
|
+
return { allowOrigin: origin, allowCredentials: true };
|
|
613
|
+
}
|
|
614
|
+
if (!origin && allowed.length === 1) {
|
|
615
|
+
return { allowOrigin: allowed[0] };
|
|
616
|
+
}
|
|
617
|
+
return {};
|
|
618
|
+
}
|
|
619
|
+
function handleCorsPreflightRequest(request, options) {
|
|
620
|
+
if (request.method !== "OPTIONS")
|
|
621
|
+
return null;
|
|
622
|
+
return new Response(null, {
|
|
623
|
+
status: 204,
|
|
624
|
+
headers: corsHeaders(options)
|
|
625
|
+
});
|
|
626
|
+
}
|
|
574
627
|
// src/middleware/security-headers.ts
|
|
575
628
|
var SECURITY_HEADERS_DEFAULTS = {
|
|
576
629
|
xContentTypeOptions: "nosniff",
|
|
@@ -608,28 +661,33 @@ function wrapWithSecurityHeaders(response, options) {
|
|
|
608
661
|
});
|
|
609
662
|
}
|
|
610
663
|
export {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
validateJsonLegacy,
|
|
614
|
-
validateJson,
|
|
615
|
-
timingSafeEqual,
|
|
616
|
-
secureHeaders,
|
|
617
|
-
resolveOperatorApiKey,
|
|
618
|
-
resolveCorsOptions,
|
|
619
|
-
requireOperatorAuth,
|
|
620
|
-
requireInternalAuth,
|
|
621
|
-
requireField,
|
|
622
|
-
requireAuth,
|
|
623
|
-
publicCorsHeaders,
|
|
624
|
-
optionalField,
|
|
625
|
-
internalCorsHeaders,
|
|
626
|
-
handleCorsPreflightRequest,
|
|
627
|
-
createRateLimiter,
|
|
628
|
-
createOperatorAuthMiddleware,
|
|
629
|
-
createLogger,
|
|
630
|
-
createInternalAuthMiddleware,
|
|
631
|
-
corsHeaders,
|
|
632
|
-
collectInternalAuthKeys,
|
|
664
|
+
DEFAULT_MAX_JSON_BODY_BYTES,
|
|
665
|
+
SECURITY_HEADERS_DEFAULTS,
|
|
633
666
|
checkInternalAuth,
|
|
634
|
-
|
|
667
|
+
collectInternalAuthKeys,
|
|
668
|
+
corsHeaders,
|
|
669
|
+
createInternalAuthMiddleware,
|
|
670
|
+
createLogger,
|
|
671
|
+
createOperatorAuthMiddleware,
|
|
672
|
+
createRateLimiter,
|
|
673
|
+
handleCorsPreflightRequest,
|
|
674
|
+
internalCorsHeaders,
|
|
675
|
+
optionalField,
|
|
676
|
+
parseJsonBody,
|
|
677
|
+
publicCorsHeaders,
|
|
678
|
+
requireAuth,
|
|
679
|
+
requireField,
|
|
680
|
+
requireInternalAuth,
|
|
681
|
+
requireOperatorAuth,
|
|
682
|
+
resolveCorsOptions,
|
|
683
|
+
resolveOperatorApiKey,
|
|
684
|
+
safeWaitUntil,
|
|
685
|
+
secureHeaders,
|
|
686
|
+
timingSafeEqual,
|
|
687
|
+
timingSafeEqualAsync,
|
|
688
|
+
validateJson,
|
|
689
|
+
validateJsonLegacy,
|
|
690
|
+
waitUntilAll,
|
|
691
|
+
withRequestLog,
|
|
692
|
+
wrapWithSecurityHeaders
|
|
635
693
|
};
|
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
|
|
18
2
|
// src/errors.ts
|
|
19
3
|
function toError(err, fallback = "Unknown error") {
|
|
20
4
|
if (err instanceof Error)
|
|
21
5
|
return err.message;
|
|
22
6
|
if (typeof err === "string")
|
|
23
7
|
return err;
|
|
24
|
-
if (err && typeof err === "object" && "message" in err
|
|
25
|
-
|
|
8
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
9
|
+
const message = err.message;
|
|
10
|
+
if (typeof message === "string")
|
|
11
|
+
return message;
|
|
26
12
|
}
|
|
27
13
|
if (err === null || err === undefined)
|
|
28
14
|
return fallback;
|
|
@@ -70,14 +56,14 @@ function sanitizeOutput(data) {
|
|
|
70
56
|
const obj = data;
|
|
71
57
|
const keys = Object.keys(obj);
|
|
72
58
|
if (keys.length < 10) {
|
|
73
|
-
const
|
|
59
|
+
const sanitized = {};
|
|
74
60
|
for (const k of keys) {
|
|
75
61
|
if (k === "stack" || k === "cause")
|
|
76
62
|
continue;
|
|
77
63
|
const v = obj[k];
|
|
78
|
-
|
|
64
|
+
sanitized[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
|
|
79
65
|
}
|
|
80
|
-
return
|
|
66
|
+
return sanitized;
|
|
81
67
|
}
|
|
82
68
|
if (!hasSensitiveFields(obj) && !hasErrorValues(obj)) {
|
|
83
69
|
return obj;
|
|
@@ -163,10 +149,10 @@ function createLogger(ctx) {
|
|
|
163
149
|
console.info(line);
|
|
164
150
|
}
|
|
165
151
|
return {
|
|
166
|
-
debug: (msg,
|
|
167
|
-
info: (msg,
|
|
168
|
-
warn: (msg,
|
|
169
|
-
error: (msg,
|
|
152
|
+
debug: (msg, ctx) => emit("debug", msg, ctx),
|
|
153
|
+
info: (msg, ctx) => emit("info", msg, ctx),
|
|
154
|
+
warn: (msg, ctx) => emit("warn", msg, ctx),
|
|
155
|
+
error: (msg, ctx) => emit("error", msg, ctx)
|
|
170
156
|
};
|
|
171
157
|
}
|
|
172
158
|
function withRequestLog(handler, logCtx) {
|
|
@@ -197,6 +183,6 @@ function withRequestLog(handler, logCtx) {
|
|
|
197
183
|
};
|
|
198
184
|
}
|
|
199
185
|
export {
|
|
200
|
-
|
|
201
|
-
|
|
186
|
+
createLogger,
|
|
187
|
+
withRequestLog
|
|
202
188
|
};
|
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
|
|
18
2
|
// src/middleware/rate-limit.ts
|
|
19
3
|
function createStorage(kv) {
|
|
20
4
|
if (!kv) {
|
|
@@ -69,8 +53,8 @@ function createStorage(kv) {
|
|
|
69
53
|
attempts++;
|
|
70
54
|
if (attempts >= maxAttempts) {
|
|
71
55
|
const current = await this.get(key);
|
|
72
|
-
const
|
|
73
|
-
newValue =
|
|
56
|
+
const count = current ? parseInt(current, 10) : 0;
|
|
57
|
+
newValue = count + 1;
|
|
74
58
|
const ttl = opts?.expirationTtl ?? 0;
|
|
75
59
|
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
76
60
|
memory.set(key, { value: String(newValue), expiresAt });
|
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
|
|
18
2
|
// src/middleware/security-headers.ts
|
|
19
3
|
var SECURITY_HEADERS_DEFAULTS = {
|
|
20
4
|
xContentTypeOptions: "nosniff",
|
|
@@ -52,7 +36,7 @@ function wrapWithSecurityHeaders(response, options) {
|
|
|
52
36
|
});
|
|
53
37
|
}
|
|
54
38
|
export {
|
|
55
|
-
|
|
39
|
+
SECURITY_HEADERS_DEFAULTS,
|
|
56
40
|
secureHeaders,
|
|
57
|
-
|
|
41
|
+
wrapWithSecurityHeaders
|
|
58
42
|
};
|
|
@@ -10,11 +10,27 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import type { Result } from "../types";
|
|
13
|
+
/** Default body size cap for JSON request parsing (1 MiB). */
|
|
14
|
+
export declare const DEFAULT_MAX_JSON_BODY_BYTES = 1048576;
|
|
15
|
+
export interface ParseJsonBodyOptions {
|
|
16
|
+
/** Reject bodies larger than this many bytes (Content-Length or measured). */
|
|
17
|
+
maxBytes?: number;
|
|
18
|
+
}
|
|
13
19
|
/**
|
|
14
20
|
* Validate unknown data against a Zod schema, returning a Result type.
|
|
15
21
|
* Provides structured error messages from Zod issue paths.
|
|
16
22
|
*/
|
|
17
23
|
export declare function validateJson<T extends z.ZodTypeAny>(schema: T, data: unknown): Result<z.infer<T>>;
|
|
24
|
+
/**
|
|
25
|
+
* Parse a Request body as JSON and validate against a Zod schema.
|
|
26
|
+
* Enforces an optional max body size to stay within Workers memory limits.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const parsed = await parseJsonBody(request, WebhookPayloadSchema);
|
|
30
|
+
* if (!parsed.ok) return Errors.badRequest(parsed.error);
|
|
31
|
+
* const payload = parsed.value;
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseJsonBody<T extends z.ZodTypeAny>(request: Request, schema: T, options?: ParseJsonBodyOptions): Promise<Result<z.infer<T>>>;
|
|
18
34
|
/**
|
|
19
35
|
* Legacy signature: parse request body as JSON object.
|
|
20
36
|
* Kept for backward compatibility with existing workers.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/middleware/validate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACjD,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAWpB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAU1C;AAED,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,CAAC,CAAC,CAKX;AAED,wBAAgB,aAAa,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,CAAC,GACd,CAAC,CAGH"}
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/middleware/validate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,8DAA8D;AAC9D,eAAO,MAAM,2BAA2B,UAAY,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACjD,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAWpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACxD,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,CAAC,EACT,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAsC7B;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAU1C;AAED,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,CAAC,CAAC,CAKX;AAED,wBAAgB,aAAa,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,CAAC,GACd,CAAC,CAGH"}
|