@jango-blockchained/hoox-shared 1.0.8 → 1.1.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 +17 -0
- package/dist/analytics.d.ts +23 -2
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +14 -3
- package/dist/api-client.d.ts +12 -2
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +74 -7
- package/dist/colors.d.ts +63 -25
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +60 -20
- package/dist/config.d.ts +34 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -6
- package/dist/cron-handler.d.ts +34 -0
- package/dist/cron-handler.d.ts.map +1 -0
- package/dist/cron-handler.js +48 -0
- package/dist/d1/index.js +50 -6
- package/dist/d1/repository.d.ts +5 -0
- package/dist/d1/repository.d.ts.map +1 -1
- package/dist/d1/repository.js +297 -0
- package/dist/d1/schemas.js +81 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +48 -5
- package/dist/exchanges/base-exchange-client.d.ts +68 -0
- package/dist/exchanges/base-exchange-client.d.ts.map +1 -0
- package/dist/exchanges/base-exchange-client.js +120 -0
- package/dist/exchanges/index.d.ts +3 -0
- package/dist/exchanges/index.d.ts.map +1 -0
- package/dist/exchanges/index.js +120 -0
- package/dist/exchanges/types.d.ts +19 -0
- package/dist/exchanges/types.d.ts.map +1 -0
- package/dist/exchanges/types.js +1 -0
- package/dist/health.js +48 -5
- package/dist/index.d.ts +30 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1058 -73
- package/dist/kvUtils.js +48 -5
- package/dist/middleware/auth.d.ts +46 -7
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +151 -0
- package/dist/middleware/cors.d.ts +28 -2
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/cors.js +77 -0
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +361 -101
- package/dist/middleware/logger.d.ts +2 -1
- package/dist/middleware/logger.d.ts.map +1 -1
- package/dist/middleware/logger.js +202 -0
- package/dist/middleware/rate-limit.d.ts +3 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +181 -0
- package/dist/middleware/security-headers.js +58 -0
- package/dist/middleware/validate.js +56 -0
- package/dist/operator-transport.d.ts +61 -0
- package/dist/operator-transport.d.ts.map +1 -0
- package/dist/operator-transport.js +87 -0
- package/dist/path-utils.d.ts +205 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/path-utils.js +188 -0
- package/dist/queue-handler.d.ts +43 -0
- package/dist/queue-handler.d.ts.map +1 -0
- package/dist/queue-handler.js +46 -0
- package/dist/router.d.ts +2 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +15 -8
- package/dist/schemas/index.js +7 -4
- package/dist/schemas/registry.js +387 -0
- package/dist/schemas/types.js +1 -0
- package/dist/schemas/validators.d.ts.map +1 -1
- package/dist/schemas/validators.js +290 -0
- package/dist/service-bindings.d.ts +68 -26
- package/dist/service-bindings.d.ts.map +1 -1
- package/dist/service-bindings.js +219 -2
- package/dist/session.d.ts +9 -5
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +179 -4
- package/dist/sse.d.ts +12 -3
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +70 -7
- package/dist/stores/config-store.d.ts.map +1 -1
- package/dist/stores/config-store.js +129 -6
- package/dist/stores/service-store.d.ts +50 -1
- package/dist/stores/service-store.d.ts.map +1 -1
- package/dist/stores/service-store.js +162 -27
- package/dist/test-utils.d.ts +132 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +128 -0
- package/dist/types/router.d.ts +18 -8
- package/dist/types/router.d.ts.map +1 -1
- package/dist/types.d.ts +124 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/wizard/engine.d.ts.map +1 -1
- package/dist/wizard/engine.js +501 -0
- package/dist/wizard/index.js +2 -1
- package/dist/wizard/persistence.js +31 -0
- package/dist/wizard/presets.js +199 -0
- package/dist/wizard/provisioner.js +1 -0
- package/dist/wizard/types.js +1 -0
- package/package.json +46 -12
package/dist/middleware/index.js
CHANGED
|
@@ -32,22 +32,65 @@ function toError(err, fallback = "Unknown error") {
|
|
|
32
32
|
return fallback;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
var SENSITIVE_FIELDS = new Set([
|
|
36
|
+
"password",
|
|
37
|
+
"token",
|
|
38
|
+
"secret",
|
|
39
|
+
"api_key",
|
|
40
|
+
"apiKey",
|
|
41
|
+
"authorization"
|
|
42
|
+
]);
|
|
43
|
+
function hasSensitiveFields(obj) {
|
|
44
|
+
for (const key of Object.keys(obj)) {
|
|
45
|
+
if (SENSITIVE_FIELDS.has(key))
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function hasErrorValues(obj) {
|
|
51
|
+
for (const v of Object.values(obj)) {
|
|
52
|
+
if (v instanceof Error)
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
35
57
|
function sanitizeOutput(data) {
|
|
58
|
+
if (data === null || data === undefined)
|
|
59
|
+
return data;
|
|
60
|
+
const t = typeof data;
|
|
61
|
+
if (t === "string" || t === "number" || t === "boolean")
|
|
62
|
+
return data;
|
|
36
63
|
if (data instanceof Error) {
|
|
37
64
|
return { name: data.name, message: data.message };
|
|
38
65
|
}
|
|
39
|
-
if (
|
|
66
|
+
if (Array.isArray(data)) {
|
|
67
|
+
return data.map(sanitizeOutput);
|
|
68
|
+
}
|
|
69
|
+
if (t === "object") {
|
|
70
|
+
const obj = data;
|
|
71
|
+
const keys = Object.keys(obj);
|
|
72
|
+
if (keys.length < 10) {
|
|
73
|
+
const sanitized2 = {};
|
|
74
|
+
for (const k of keys) {
|
|
75
|
+
if (k === "stack" || k === "cause")
|
|
76
|
+
continue;
|
|
77
|
+
const v = obj[k];
|
|
78
|
+
sanitized2[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
|
|
79
|
+
}
|
|
80
|
+
return sanitized2;
|
|
81
|
+
}
|
|
82
|
+
if (!hasSensitiveFields(obj) && !hasErrorValues(obj)) {
|
|
83
|
+
return obj;
|
|
84
|
+
}
|
|
40
85
|
const sanitized = {};
|
|
41
|
-
for (const
|
|
86
|
+
for (const k of keys) {
|
|
42
87
|
if (k === "stack" || k === "cause")
|
|
43
88
|
continue;
|
|
89
|
+
const v = obj[k];
|
|
44
90
|
sanitized[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
|
|
45
91
|
}
|
|
46
92
|
return sanitized;
|
|
47
93
|
}
|
|
48
|
-
if (Array.isArray(data)) {
|
|
49
|
-
return data.map(sanitizeOutput);
|
|
50
|
-
}
|
|
51
94
|
return data;
|
|
52
95
|
}
|
|
53
96
|
function createJsonResponse(data, status = 200) {
|
|
@@ -98,6 +141,184 @@ var Errors = {
|
|
|
98
141
|
}
|
|
99
142
|
};
|
|
100
143
|
|
|
144
|
+
// src/middleware/auth.ts
|
|
145
|
+
function timingSafeEqual(a, b) {
|
|
146
|
+
const encoder = new TextEncoder;
|
|
147
|
+
const aBuf = encoder.encode(a);
|
|
148
|
+
const bBuf = encoder.encode(b);
|
|
149
|
+
const len = Math.max(aBuf.length, bBuf.length);
|
|
150
|
+
let result = aBuf.length === bBuf.length ? 0 : 1;
|
|
151
|
+
for (let i = 0;i < len; i++) {
|
|
152
|
+
const av = i < aBuf.length ? aBuf[i] : 0;
|
|
153
|
+
const bv = i < bBuf.length ? bBuf[i] : 0;
|
|
154
|
+
result |= av ^ bv;
|
|
155
|
+
}
|
|
156
|
+
return result === 0;
|
|
157
|
+
}
|
|
158
|
+
function resolveOperatorApiKey(env) {
|
|
159
|
+
const preferred = env.OPERATOR_API_KEY;
|
|
160
|
+
if (typeof preferred === "string" && preferred.length > 0) {
|
|
161
|
+
return preferred;
|
|
162
|
+
}
|
|
163
|
+
const legacy = env.INTERNAL_API_KEY;
|
|
164
|
+
if (typeof legacy === "string" && legacy.length > 0) {
|
|
165
|
+
return legacy;
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
async function requireAuth(request, env) {
|
|
170
|
+
return requireOperatorAuth(request, env);
|
|
171
|
+
}
|
|
172
|
+
async function requireOperatorAuth(request, env) {
|
|
173
|
+
const apiKey = resolveOperatorApiKey(env);
|
|
174
|
+
if (!apiKey) {
|
|
175
|
+
return new Response(JSON.stringify({
|
|
176
|
+
error: "Operator API key not configured",
|
|
177
|
+
hint: "Set OPERATOR_API_KEY (preferred) or INTERNAL_API_KEY on the Worker"
|
|
178
|
+
}), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
179
|
+
}
|
|
180
|
+
const authHeader = request.headers.get("Authorization");
|
|
181
|
+
const expectedHeader = `Bearer ${apiKey}`;
|
|
182
|
+
if (!authHeader || !timingSafeEqual(authHeader, expectedHeader)) {
|
|
183
|
+
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
184
|
+
status: 401,
|
|
185
|
+
headers: { "Content-Type": "application/json" }
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
function createOperatorAuthMiddleware() {
|
|
191
|
+
return async (request, env, _ctx) => {
|
|
192
|
+
const denied = await requireOperatorAuth(request, env);
|
|
193
|
+
if (denied)
|
|
194
|
+
return denied;
|
|
195
|
+
return;
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function normalizeKeyNames(keyName) {
|
|
199
|
+
if (typeof keyName === "string") {
|
|
200
|
+
return [keyName];
|
|
201
|
+
}
|
|
202
|
+
return [...keyName];
|
|
203
|
+
}
|
|
204
|
+
function collectInternalAuthKeys(env, keyNames) {
|
|
205
|
+
const keys = [];
|
|
206
|
+
for (const name of normalizeKeyNames(keyNames)) {
|
|
207
|
+
const value = env[name];
|
|
208
|
+
if (typeof value === "string" && value.length > 0) {
|
|
209
|
+
keys.push(value);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return keys;
|
|
213
|
+
}
|
|
214
|
+
function matchesAnyInternalAuthKey(providedKey, expectedKeys) {
|
|
215
|
+
for (const expected of expectedKeys) {
|
|
216
|
+
if (timingSafeEqual(providedKey, expected)) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
function internalAuthNotConfiguredResponse(keyNames) {
|
|
223
|
+
const label = normalizeKeyNames(keyNames).join(" | ");
|
|
224
|
+
return new Response(JSON.stringify({
|
|
225
|
+
success: false,
|
|
226
|
+
error: `Internal auth key(s) not configured: ${label}`
|
|
227
|
+
}), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
228
|
+
}
|
|
229
|
+
function requireInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
230
|
+
const expectedKeys = collectInternalAuthKeys(env, keyName);
|
|
231
|
+
if (expectedKeys.length === 0) {
|
|
232
|
+
return internalAuthNotConfiguredResponse(keyName);
|
|
233
|
+
}
|
|
234
|
+
const providedKey = request.headers.get("X-Internal-Auth-Key");
|
|
235
|
+
if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
|
|
236
|
+
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
function createInternalAuthMiddleware(keyName = "INTERNAL_KEY_BINDING") {
|
|
241
|
+
return async (request, env, _ctx) => {
|
|
242
|
+
const expectedKeys = collectInternalAuthKeys(env, keyName);
|
|
243
|
+
if (expectedKeys.length === 0) {
|
|
244
|
+
return internalAuthNotConfiguredResponse(keyName);
|
|
245
|
+
}
|
|
246
|
+
const providedKey = request.headers.get("X-Internal-Auth-Key");
|
|
247
|
+
if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
|
|
248
|
+
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
254
|
+
const expectedKeys = collectInternalAuthKeys(env, keyName);
|
|
255
|
+
if (expectedKeys.length === 0) {
|
|
256
|
+
return {
|
|
257
|
+
authorized: false,
|
|
258
|
+
error: `${normalizeKeyNames(keyName).join(" | ")} not configured`
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const providedKey = request.headers.get("X-Internal-Auth-Key");
|
|
262
|
+
if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
|
|
263
|
+
return { authorized: false, error: "Unauthorized" };
|
|
264
|
+
}
|
|
265
|
+
return { authorized: true };
|
|
266
|
+
}
|
|
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
|
+
|
|
101
322
|
// src/middleware/logger.ts
|
|
102
323
|
function createLogger(ctx) {
|
|
103
324
|
const base = { service: ctx.service, module: ctx.module };
|
|
@@ -114,10 +335,13 @@ function createLogger(ctx) {
|
|
|
114
335
|
console.error(line);
|
|
115
336
|
else if (level === "warn")
|
|
116
337
|
console.warn(line);
|
|
338
|
+
else if (level === "debug")
|
|
339
|
+
console.debug(line);
|
|
117
340
|
else
|
|
118
341
|
console.info(line);
|
|
119
342
|
}
|
|
120
343
|
return {
|
|
344
|
+
debug: (msg, ctx2) => emit("debug", msg, ctx2),
|
|
121
345
|
info: (msg, ctx2) => emit("info", msg, ctx2),
|
|
122
346
|
warn: (msg, ctx2) => emit("warn", msg, ctx2),
|
|
123
347
|
error: (msg, ctx2) => emit("error", msg, ctx2)
|
|
@@ -150,69 +374,91 @@ function withRequestLog(handler, logCtx) {
|
|
|
150
374
|
}
|
|
151
375
|
};
|
|
152
376
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
377
|
+
|
|
378
|
+
// src/middleware/rate-limit.ts
|
|
379
|
+
function createStorage(kv) {
|
|
380
|
+
if (!kv) {
|
|
381
|
+
const memory = new Map;
|
|
382
|
+
return {
|
|
383
|
+
async get(key) {
|
|
384
|
+
const entry = memory.get(key);
|
|
385
|
+
if (!entry)
|
|
386
|
+
return null;
|
|
387
|
+
if (Date.now() > entry.expiresAt) {
|
|
388
|
+
memory.delete(key);
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
return entry.value;
|
|
392
|
+
},
|
|
393
|
+
async put(key, value, opts) {
|
|
394
|
+
const ttl = opts?.expirationTtl ?? 0;
|
|
395
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
396
|
+
memory.set(key, { value, expiresAt });
|
|
397
|
+
},
|
|
398
|
+
async incr(key, opts) {
|
|
399
|
+
let entry = memory.get(key);
|
|
400
|
+
let newValue;
|
|
401
|
+
let attempts = 0;
|
|
402
|
+
const maxAttempts = 100;
|
|
403
|
+
while (true) {
|
|
404
|
+
const count = entry ? parseInt(entry.value, 10) : 0;
|
|
405
|
+
newValue = count + 1;
|
|
406
|
+
if (!entry) {
|
|
407
|
+
const ttl = opts?.expirationTtl ?? 0;
|
|
408
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
409
|
+
if (memory.get(key) === undefined) {
|
|
410
|
+
memory.set(key, { value: String(newValue), expiresAt });
|
|
411
|
+
return newValue;
|
|
412
|
+
}
|
|
413
|
+
entry = memory.get(key);
|
|
414
|
+
} else {
|
|
415
|
+
if (Date.now() > entry.expiresAt) {
|
|
416
|
+
memory.delete(key);
|
|
417
|
+
entry = undefined;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const currentEntry = memory.get(key);
|
|
421
|
+
if (currentEntry === entry) {
|
|
422
|
+
const ttl = opts?.expirationTtl ?? 0;
|
|
423
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
424
|
+
memory.set(key, { value: String(newValue), expiresAt });
|
|
425
|
+
return newValue;
|
|
426
|
+
}
|
|
427
|
+
entry = currentEntry;
|
|
428
|
+
}
|
|
429
|
+
attempts++;
|
|
430
|
+
if (attempts >= maxAttempts) {
|
|
431
|
+
const current = await this.get(key);
|
|
432
|
+
const count2 = current ? parseInt(current, 10) : 0;
|
|
433
|
+
newValue = count2 + 1;
|
|
434
|
+
const ttl = opts?.expirationTtl ?? 0;
|
|
435
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
436
|
+
memory.set(key, { value: String(newValue), expiresAt });
|
|
437
|
+
return newValue;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
192
442
|
}
|
|
193
|
-
return
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
443
|
+
return {
|
|
444
|
+
async get(key) {
|
|
445
|
+
return kv.get(key);
|
|
446
|
+
},
|
|
447
|
+
async put(key, value, opts) {
|
|
448
|
+
await kv.put(key, value, opts);
|
|
449
|
+
},
|
|
450
|
+
async incr(key, opts) {
|
|
451
|
+
const current = await kv.get(key);
|
|
452
|
+
const count = current ? parseInt(current, 10) : 0;
|
|
453
|
+
const next = count + 1;
|
|
454
|
+
await kv.put(key, String(next), opts);
|
|
455
|
+
return next;
|
|
456
|
+
}
|
|
200
457
|
};
|
|
201
458
|
}
|
|
202
|
-
function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
203
|
-
const expectedKey = env[keyName];
|
|
204
|
-
if (!expectedKey) {
|
|
205
|
-
return { authorized: false, error: `${keyName} not configured` };
|
|
206
|
-
}
|
|
207
|
-
const providedKey = request.headers.get("X-Internal-Auth-Key");
|
|
208
|
-
if (!providedKey || !timingSafeEqual(providedKey, expectedKey)) {
|
|
209
|
-
return { authorized: false, error: "Unauthorized" };
|
|
210
|
-
}
|
|
211
|
-
return { authorized: true };
|
|
212
|
-
}
|
|
213
|
-
// src/middleware/rate-limit.ts
|
|
214
459
|
function createRateLimiter(kv, config) {
|
|
215
460
|
const prefix = config.keyPrefix ?? "rate-limit";
|
|
461
|
+
const storage = createStorage(kv);
|
|
216
462
|
function getClientIp(request) {
|
|
217
463
|
return request.headers.get("CF-Connecting-IP") ?? "unknown";
|
|
218
464
|
}
|
|
@@ -220,23 +466,37 @@ function createRateLimiter(kv, config) {
|
|
|
220
466
|
const windowStart = Math.floor(Date.now() / (config.windowSeconds * 1000));
|
|
221
467
|
return `${prefix}:${ip}:${windowStart}`;
|
|
222
468
|
}
|
|
223
|
-
async function
|
|
224
|
-
const
|
|
225
|
-
const key = getWindowKey(ip);
|
|
226
|
-
const current = await kv.get(key);
|
|
469
|
+
async function checkWithKey(fullKey) {
|
|
470
|
+
const current = await storage.get(fullKey);
|
|
227
471
|
const count = current ? parseInt(current, 10) : 0;
|
|
228
472
|
if (count >= config.maxRequests) {
|
|
229
|
-
const retryAfter = config.windowSeconds - Date.now() % (config.windowSeconds * 1000) / 1000;
|
|
473
|
+
const retryAfter = Math.ceil(config.windowSeconds - Date.now() % (config.windowSeconds * 1000) / 1000);
|
|
230
474
|
return {
|
|
231
475
|
allowed: false,
|
|
232
476
|
remaining: 0,
|
|
233
|
-
retryAfter
|
|
477
|
+
retryAfter
|
|
234
478
|
};
|
|
235
479
|
}
|
|
236
|
-
await
|
|
480
|
+
const newCount = await storage.incr(fullKey, {
|
|
237
481
|
expirationTtl: config.windowSeconds
|
|
238
482
|
});
|
|
239
|
-
|
|
483
|
+
if (newCount > config.maxRequests) {
|
|
484
|
+
const retryAfter = Math.ceil(config.windowSeconds - Date.now() % (config.windowSeconds * 1000) / 1000);
|
|
485
|
+
return {
|
|
486
|
+
allowed: false,
|
|
487
|
+
remaining: 0,
|
|
488
|
+
retryAfter
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
allowed: true,
|
|
493
|
+
remaining: config.maxRequests - newCount
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
async function check(request) {
|
|
497
|
+
const ip = getClientIp(request);
|
|
498
|
+
const key = getWindowKey(ip);
|
|
499
|
+
return checkWithKey(key);
|
|
240
500
|
}
|
|
241
501
|
async function enforce(request) {
|
|
242
502
|
const result = await check(request);
|
|
@@ -254,8 +514,29 @@ function createRateLimiter(kv, config) {
|
|
|
254
514
|
}
|
|
255
515
|
return null;
|
|
256
516
|
}
|
|
257
|
-
|
|
517
|
+
async function checkKey(key) {
|
|
518
|
+
const fullKey = `${prefix}:${key}`;
|
|
519
|
+
return checkWithKey(fullKey);
|
|
520
|
+
}
|
|
521
|
+
async function enforceKey(key) {
|
|
522
|
+
const result = await checkKey(key);
|
|
523
|
+
if (!result.allowed) {
|
|
524
|
+
return new Response(JSON.stringify({
|
|
525
|
+
error: "Rate limit exceeded",
|
|
526
|
+
retryAfter: result.retryAfter
|
|
527
|
+
}), {
|
|
528
|
+
status: 429,
|
|
529
|
+
headers: {
|
|
530
|
+
"Content-Type": "application/json",
|
|
531
|
+
"Retry-After": String(result.retryAfter ?? config.windowSeconds)
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
return { check, enforce, checkKey, enforceKey };
|
|
258
538
|
}
|
|
539
|
+
|
|
259
540
|
// src/middleware/validate.ts
|
|
260
541
|
function validateJson(schema, data) {
|
|
261
542
|
const result = schema.safeParse(data);
|
|
@@ -289,35 +570,7 @@ function optionalField(body, field, defaultValue) {
|
|
|
289
570
|
return defaultValue;
|
|
290
571
|
return body[field];
|
|
291
572
|
}
|
|
292
|
-
|
|
293
|
-
var DEFAULT_OPTIONS = {
|
|
294
|
-
allowOrigin: "*",
|
|
295
|
-
allowMethods: "GET, POST, OPTIONS, PUT, DELETE",
|
|
296
|
-
allowHeaders: "Content-Type, Authorization, X-Request-ID, X-Internal-Auth-Key",
|
|
297
|
-
allowCredentials: false,
|
|
298
|
-
maxAge: 86400
|
|
299
|
-
};
|
|
300
|
-
function corsHeaders(options) {
|
|
301
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
302
|
-
const headers = {
|
|
303
|
-
"Access-Control-Allow-Origin": opts.allowOrigin,
|
|
304
|
-
"Access-Control-Allow-Methods": opts.allowMethods,
|
|
305
|
-
"Access-Control-Allow-Headers": opts.allowHeaders,
|
|
306
|
-
"Access-Control-Max-Age": String(opts.maxAge)
|
|
307
|
-
};
|
|
308
|
-
if (opts.allowCredentials) {
|
|
309
|
-
headers["Access-Control-Allow-Credentials"] = "true";
|
|
310
|
-
}
|
|
311
|
-
return headers;
|
|
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
|
-
}
|
|
573
|
+
|
|
321
574
|
// src/middleware/security-headers.ts
|
|
322
575
|
var SECURITY_HEADERS_DEFAULTS = {
|
|
323
576
|
xContentTypeOptions: "nosniff",
|
|
@@ -361,15 +614,22 @@ export {
|
|
|
361
614
|
validateJson,
|
|
362
615
|
timingSafeEqual,
|
|
363
616
|
secureHeaders,
|
|
617
|
+
resolveOperatorApiKey,
|
|
618
|
+
resolveCorsOptions,
|
|
619
|
+
requireOperatorAuth,
|
|
364
620
|
requireInternalAuth,
|
|
365
621
|
requireField,
|
|
366
622
|
requireAuth,
|
|
623
|
+
publicCorsHeaders,
|
|
367
624
|
optionalField,
|
|
625
|
+
internalCorsHeaders,
|
|
368
626
|
handleCorsPreflightRequest,
|
|
369
627
|
createRateLimiter,
|
|
628
|
+
createOperatorAuthMiddleware,
|
|
370
629
|
createLogger,
|
|
371
630
|
createInternalAuthMiddleware,
|
|
372
631
|
corsHeaders,
|
|
632
|
+
collectInternalAuthKeys,
|
|
373
633
|
checkInternalAuth,
|
|
374
634
|
SECURITY_HEADERS_DEFAULTS
|
|
375
635
|
};
|
|
@@ -7,10 +7,11 @@ export interface LogContext {
|
|
|
7
7
|
module?: string;
|
|
8
8
|
}
|
|
9
9
|
export interface Logger {
|
|
10
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
10
11
|
info(message: string, context?: Record<string, unknown>): void;
|
|
11
12
|
warn(message: string, context?: Record<string, unknown>): void;
|
|
12
13
|
error(message: string, context?: Record<string, unknown>): void;
|
|
13
14
|
}
|
|
14
15
|
export declare function createLogger(ctx: LogContext): Logger;
|
|
15
|
-
export declare function withRequestLog<E
|
|
16
|
+
export declare function withRequestLog<E>(handler: (request: Request, env: E, ctx: ExecutionContext) => Promise<Response>, logCtx: LogContext): (request: Request, env: E, ctx: ExecutionContext) => Promise<Response>;
|
|
16
17
|
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACjE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAChE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACjE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CA4BpD;AAED,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,CACP,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,CAAC,EACN,GAAG,EAAE,gBAAgB,KAClB,OAAO,CAAC,QAAQ,CAAC,EACtB,MAAM,EAAE,UAAU,GACjB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,QAAQ,CAAC,CA2BxE"}
|