@jango-blockchained/hoox-shared 1.0.8 → 1.0.9
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/dist/analytics.d.ts +0 -1
- package/dist/analytics.d.ts.map +1 -1
- package/dist/colors.d.ts +0 -1
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +0 -1
- 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 +49 -6
- package/dist/d1/repository.d.ts +5 -0
- package/dist/d1/repository.d.ts.map +1 -1
- 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/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/health.js +48 -5
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +480 -15
- package/dist/kvUtils.js +48 -5
- package/dist/middleware/auth.d.ts +6 -3
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/index.js +193 -18
- package/dist/middleware/logger.d.ts +2 -1
- package/dist/middleware/logger.d.ts.map +1 -1
- package/dist/middleware/rate-limit.d.ts +3 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/path-utils.d.ts +147 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/path-utils.js +101 -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 +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +15 -8
- package/dist/service-bindings.d.ts +5 -28
- package/dist/service-bindings.d.ts.map +1 -1
- package/dist/service-bindings.js +3 -1
- package/dist/session.d.ts +9 -5
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +83 -3
- package/dist/sse.js +1 -1
- 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 +67 -3
- 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 +59 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/wizard/engine.d.ts.map +1 -1
- package/package.json +11 -3
package/dist/kvUtils.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) {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Provides both Bearer token auth and internal service-to-service auth
|
|
4
4
|
*/
|
|
5
5
|
import type { Env } from "../types";
|
|
6
|
-
import type {
|
|
6
|
+
import type { MiddlewareHandler } from "../types/router";
|
|
7
7
|
/**
|
|
8
8
|
* Constant-time string comparison to prevent timing attacks.
|
|
9
|
-
*
|
|
9
|
+
* Uses crypto.timingSafeEqual for edge-native constant-time comparison.
|
|
10
10
|
*/
|
|
11
11
|
export declare function timingSafeEqual(a: string, b: string): boolean;
|
|
12
12
|
/**
|
|
@@ -17,6 +17,9 @@ export declare function requireAuth(request: Request, env: Env): Promise<Respons
|
|
|
17
17
|
/**
|
|
18
18
|
* Environment with an internal auth key binding.
|
|
19
19
|
* Workers that accept internal service-to-service requests should extend this.
|
|
20
|
+
* Note: Uses [key: string]: unknown for compatibility with dynamic key access.
|
|
21
|
+
* This is intentionally less strict than wrangler-generated Env to allow
|
|
22
|
+
* InternalAuthEnv to be satisfied by actual Env bindings at runtime.
|
|
20
23
|
*/
|
|
21
24
|
export interface InternalAuthEnv {
|
|
22
25
|
[key: string]: unknown;
|
|
@@ -42,7 +45,7 @@ export declare function requireInternalAuth(request: Request, env: InternalAuthE
|
|
|
42
45
|
*
|
|
43
46
|
* @param keyName - The env binding name for the internal key (default: 'INTERNAL_KEY_BINDING')
|
|
44
47
|
*/
|
|
45
|
-
export declare function createInternalAuthMiddleware
|
|
48
|
+
export declare function createInternalAuthMiddleware(keyName?: string): MiddlewareHandler<any>;
|
|
46
49
|
/**
|
|
47
50
|
* Check internal auth and return a result object (non-throwing pattern).
|
|
48
51
|
* Useful when you need to check auth without immediately returning a response.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAa7D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,GACP,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAkB1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,MAA+B,GACvC,QAAQ,GAAG,IAAI,CAqBjB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,MAA+B,GACvC,iBAAiB,CAAC,GAAG,CAAC,CA0BxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,MAA+B,GACvC;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAYzC"}
|
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) {
|
|
@@ -114,10 +157,13 @@ function createLogger(ctx) {
|
|
|
114
157
|
console.error(line);
|
|
115
158
|
else if (level === "warn")
|
|
116
159
|
console.warn(line);
|
|
160
|
+
else if (level === "debug")
|
|
161
|
+
console.debug(line);
|
|
117
162
|
else
|
|
118
163
|
console.info(line);
|
|
119
164
|
}
|
|
120
165
|
return {
|
|
166
|
+
debug: (msg, ctx2) => emit("debug", msg, ctx2),
|
|
121
167
|
info: (msg, ctx2) => emit("info", msg, ctx2),
|
|
122
168
|
warn: (msg, ctx2) => emit("warn", msg, ctx2),
|
|
123
169
|
error: (msg, ctx2) => emit("error", msg, ctx2)
|
|
@@ -193,10 +239,19 @@ function requireInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
|
193
239
|
return null;
|
|
194
240
|
}
|
|
195
241
|
function createInternalAuthMiddleware(keyName = "INTERNAL_KEY_BINDING") {
|
|
196
|
-
return (request, env) => {
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
-
return
|
|
242
|
+
return async (request, env, _ctx) => {
|
|
243
|
+
const expectedKey = env[keyName];
|
|
244
|
+
if (!expectedKey) {
|
|
245
|
+
return new Response(JSON.stringify({
|
|
246
|
+
success: false,
|
|
247
|
+
error: `Internal auth key ${keyName} not configured`
|
|
248
|
+
}), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
249
|
+
}
|
|
250
|
+
const providedKey = request.headers.get("X-Internal-Auth-Key");
|
|
251
|
+
if (!providedKey || !timingSafeEqual(providedKey, expectedKey)) {
|
|
252
|
+
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
200
255
|
};
|
|
201
256
|
}
|
|
202
257
|
function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
@@ -211,8 +266,94 @@ function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
|
|
|
211
266
|
return { authorized: true };
|
|
212
267
|
}
|
|
213
268
|
// src/middleware/rate-limit.ts
|
|
269
|
+
function createStorage(kv) {
|
|
270
|
+
if (!kv) {
|
|
271
|
+
const memory = new Map;
|
|
272
|
+
return {
|
|
273
|
+
async get(key) {
|
|
274
|
+
const entry = memory.get(key);
|
|
275
|
+
if (!entry)
|
|
276
|
+
return null;
|
|
277
|
+
if (Date.now() > entry.expiresAt) {
|
|
278
|
+
memory.delete(key);
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
return entry.value;
|
|
282
|
+
},
|
|
283
|
+
async put(key, value, opts) {
|
|
284
|
+
const ttl = opts?.expirationTtl ?? 0;
|
|
285
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
286
|
+
memory.set(key, { value, expiresAt });
|
|
287
|
+
},
|
|
288
|
+
async incr(key, opts) {
|
|
289
|
+
let entry = memory.get(key);
|
|
290
|
+
let newValue;
|
|
291
|
+
let attempts = 0;
|
|
292
|
+
const maxAttempts = 100;
|
|
293
|
+
while (true) {
|
|
294
|
+
const count = entry ? parseInt(entry.value, 10) : 0;
|
|
295
|
+
newValue = count + 1;
|
|
296
|
+
if (!entry) {
|
|
297
|
+
const ttl = opts?.expirationTtl ?? 0;
|
|
298
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
299
|
+
if (memory.get(key) === undefined) {
|
|
300
|
+
memory.set(key, { value: String(newValue), expiresAt });
|
|
301
|
+
return newValue;
|
|
302
|
+
}
|
|
303
|
+
entry = memory.get(key);
|
|
304
|
+
} else {
|
|
305
|
+
if (Date.now() > entry.expiresAt) {
|
|
306
|
+
memory.delete(key);
|
|
307
|
+
entry = undefined;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const currentEntry = memory.get(key);
|
|
311
|
+
if (currentEntry === entry) {
|
|
312
|
+
const ttl = opts?.expirationTtl ?? 0;
|
|
313
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
314
|
+
memory.set(key, { value: String(newValue), expiresAt });
|
|
315
|
+
return newValue;
|
|
316
|
+
}
|
|
317
|
+
entry = currentEntry;
|
|
318
|
+
}
|
|
319
|
+
attempts++;
|
|
320
|
+
if (attempts >= maxAttempts) {
|
|
321
|
+
const current = await this.get(key);
|
|
322
|
+
const count2 = current ? parseInt(current, 10) : 0;
|
|
323
|
+
newValue = count2 + 1;
|
|
324
|
+
const ttl = opts?.expirationTtl ?? 0;
|
|
325
|
+
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : Infinity;
|
|
326
|
+
memory.set(key, { value: String(newValue), expiresAt });
|
|
327
|
+
return newValue;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
async get(key) {
|
|
335
|
+
return kv.get(key);
|
|
336
|
+
},
|
|
337
|
+
async put(key, value, opts) {
|
|
338
|
+
await kv.put(key, value, opts);
|
|
339
|
+
},
|
|
340
|
+
async incr(key, opts) {
|
|
341
|
+
const maxRetries = 3;
|
|
342
|
+
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
343
|
+
const current2 = await kv.get(key);
|
|
344
|
+
const count = current2 ? parseInt(current2, 10) : 0;
|
|
345
|
+
const next = count + 1;
|
|
346
|
+
await kv.put(key, String(next), opts);
|
|
347
|
+
return next;
|
|
348
|
+
}
|
|
349
|
+
const current = await kv.get(key);
|
|
350
|
+
return current ? parseInt(current, 10) + 1 : 1;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
214
354
|
function createRateLimiter(kv, config) {
|
|
215
355
|
const prefix = config.keyPrefix ?? "rate-limit";
|
|
356
|
+
const storage = createStorage(kv);
|
|
216
357
|
function getClientIp(request) {
|
|
217
358
|
return request.headers.get("CF-Connecting-IP") ?? "unknown";
|
|
218
359
|
}
|
|
@@ -220,23 +361,37 @@ function createRateLimiter(kv, config) {
|
|
|
220
361
|
const windowStart = Math.floor(Date.now() / (config.windowSeconds * 1000));
|
|
221
362
|
return `${prefix}:${ip}:${windowStart}`;
|
|
222
363
|
}
|
|
223
|
-
async function
|
|
224
|
-
const
|
|
225
|
-
const key = getWindowKey(ip);
|
|
226
|
-
const current = await kv.get(key);
|
|
364
|
+
async function checkWithKey(fullKey) {
|
|
365
|
+
const current = await storage.get(fullKey);
|
|
227
366
|
const count = current ? parseInt(current, 10) : 0;
|
|
228
367
|
if (count >= config.maxRequests) {
|
|
229
|
-
const retryAfter = config.windowSeconds - Date.now() % (config.windowSeconds * 1000) / 1000;
|
|
368
|
+
const retryAfter = Math.ceil(config.windowSeconds - Date.now() % (config.windowSeconds * 1000) / 1000);
|
|
230
369
|
return {
|
|
231
370
|
allowed: false,
|
|
232
371
|
remaining: 0,
|
|
233
|
-
retryAfter
|
|
372
|
+
retryAfter
|
|
234
373
|
};
|
|
235
374
|
}
|
|
236
|
-
await
|
|
375
|
+
const newCount = await storage.incr(fullKey, {
|
|
237
376
|
expirationTtl: config.windowSeconds
|
|
238
377
|
});
|
|
239
|
-
|
|
378
|
+
if (newCount > config.maxRequests) {
|
|
379
|
+
const retryAfter = Math.ceil(config.windowSeconds - Date.now() % (config.windowSeconds * 1000) / 1000);
|
|
380
|
+
return {
|
|
381
|
+
allowed: false,
|
|
382
|
+
remaining: 0,
|
|
383
|
+
retryAfter
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
allowed: true,
|
|
388
|
+
remaining: config.maxRequests - newCount
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
async function check(request) {
|
|
392
|
+
const ip = getClientIp(request);
|
|
393
|
+
const key = getWindowKey(ip);
|
|
394
|
+
return checkWithKey(key);
|
|
240
395
|
}
|
|
241
396
|
async function enforce(request) {
|
|
242
397
|
const result = await check(request);
|
|
@@ -254,7 +409,27 @@ function createRateLimiter(kv, config) {
|
|
|
254
409
|
}
|
|
255
410
|
return null;
|
|
256
411
|
}
|
|
257
|
-
|
|
412
|
+
async function checkKey(key) {
|
|
413
|
+
const fullKey = `${prefix}:${key}`;
|
|
414
|
+
return checkWithKey(fullKey);
|
|
415
|
+
}
|
|
416
|
+
async function enforceKey(key) {
|
|
417
|
+
const result = await checkKey(key);
|
|
418
|
+
if (!result.allowed) {
|
|
419
|
+
return new Response(JSON.stringify({
|
|
420
|
+
error: "Rate limit exceeded",
|
|
421
|
+
retryAfter: result.retryAfter
|
|
422
|
+
}), {
|
|
423
|
+
status: 429,
|
|
424
|
+
headers: {
|
|
425
|
+
"Content-Type": "application/json",
|
|
426
|
+
"Retry-After": String(result.retryAfter ?? config.windowSeconds)
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
return { check, enforce, checkKey, enforceKey };
|
|
258
433
|
}
|
|
259
434
|
// src/middleware/validate.ts
|
|
260
435
|
function validateJson(schema, data) {
|
|
@@ -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"}
|
|
@@ -18,7 +18,9 @@ interface RateLimitResult {
|
|
|
18
18
|
export interface RateLimiter {
|
|
19
19
|
check(request: Request): Promise<RateLimitResult>;
|
|
20
20
|
enforce(request: Request): Promise<Response | null>;
|
|
21
|
+
checkKey(key: string): Promise<RateLimitResult>;
|
|
22
|
+
enforceKey(key: string): Promise<Response | null>;
|
|
21
23
|
}
|
|
22
|
-
export declare function createRateLimiter(kv: Env["CONFIG_KV"], config: RateLimitConfig): RateLimiter;
|
|
24
|
+
export declare function createRateLimiter(kv: Env["CONFIG_KV"] | undefined, config: RateLimitConfig): RateLimiter;
|
|
23
25
|
export {};
|
|
24
26
|
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAClD,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAClD,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACpD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAChD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;CACnD;AA6HD,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,GAAG,CAAC,WAAW,CAAC,GAAG,SAAS,EAChC,MAAM,EAAE,eAAe,GACtB,WAAW,CAiHb"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Resolution Service for Hoox
|
|
3
|
+
*
|
|
4
|
+
* Provides cross-OS utilities for resolving the $HOME/.hoox directory location
|
|
5
|
+
* and constructing type-safe paths within it.
|
|
6
|
+
*
|
|
7
|
+
* Supports macOS, Linux, and Windows with proper fallback handling.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Branded type for Hoox paths to prevent accidental string usage.
|
|
11
|
+
* This ensures type safety when working with paths.
|
|
12
|
+
*/
|
|
13
|
+
export type HooxPath = string & {
|
|
14
|
+
readonly __brand: "HooxPath";
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Gets the Hoox home directory location: $HOME/.hoox
|
|
18
|
+
*
|
|
19
|
+
* Behavior:
|
|
20
|
+
* - Returns $HOME/.hoox on macOS, Linux, Windows
|
|
21
|
+
* - Falls back to current working directory if HOME is not available
|
|
22
|
+
* - Resolves to absolute path
|
|
23
|
+
*
|
|
24
|
+
* @returns Absolute path to $HOME/.hoox as a branded HooxPath
|
|
25
|
+
* @throws Never — always returns a valid path
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const hooxHome = getHooxHome();
|
|
30
|
+
* // Returns: "/Users/alice/.hoox" (macOS)
|
|
31
|
+
* // Returns: "/home/alice/.hoox" (Linux)
|
|
32
|
+
* // Returns: "C:\\Users\\alice\\.hoox" (Windows)
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function getHooxHome(): HooxPath;
|
|
36
|
+
/**
|
|
37
|
+
* Resolves a relative path within the Hoox home directory.
|
|
38
|
+
*
|
|
39
|
+
* Behavior:
|
|
40
|
+
* - Joins the relative path with $HOME/.hoox
|
|
41
|
+
* - Resolves to absolute path
|
|
42
|
+
* - Prevents path traversal attacks (../ sequences)
|
|
43
|
+
*
|
|
44
|
+
* @param relativePath - Path relative to $HOME/.hoox (e.g., "repo", "config/wrangler.jsonc")
|
|
45
|
+
* @returns Absolute path as a branded HooxPath
|
|
46
|
+
* @throws Error if path contains suspicious traversal patterns
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const repoPath = resolveHooxPath("repo");
|
|
51
|
+
* // Returns: "/Users/alice/.hoox/repo"
|
|
52
|
+
*
|
|
53
|
+
* const configPath = resolveHooxPath("config/wrangler.jsonc");
|
|
54
|
+
* // Returns: "/Users/alice/.hoox/config/wrangler.jsonc"
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveHooxPath(relativePath: string): HooxPath;
|
|
58
|
+
/**
|
|
59
|
+
* Checks if a given path is within the Hoox home directory.
|
|
60
|
+
*
|
|
61
|
+
* @param path - Path to check
|
|
62
|
+
* @returns true if path is within $HOME/.hoox, false otherwise
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* isWithinHooxHome("/Users/alice/.hoox/repo"); // true
|
|
67
|
+
* isWithinHooxHome("/Users/alice/other"); // false
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function isWithinHooxHome(path: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Gets the relative path from Hoox home directory.
|
|
73
|
+
*
|
|
74
|
+
* @param absolutePath - Absolute path to resolve
|
|
75
|
+
* @returns Relative path from $HOME/.hoox, or null if path is outside Hoox home
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* getRelativeHooxPath("/Users/alice/.hoox/repo/src");
|
|
80
|
+
* // Returns: "repo/src"
|
|
81
|
+
*
|
|
82
|
+
* getRelativeHooxPath("/Users/alice/other");
|
|
83
|
+
* // Returns: null
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare function getRelativeHooxPath(absolutePath: string): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Constructs a path to the Hoox repository location.
|
|
89
|
+
*
|
|
90
|
+
* @returns Path to $HOME/.hoox/repo as a branded HooxPath
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const repoPath = getHooxRepoPath();
|
|
95
|
+
* // Returns: "/Users/alice/.hoox/repo"
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function getHooxRepoPath(): HooxPath;
|
|
99
|
+
/**
|
|
100
|
+
* Constructs a path to the Hoox configuration directory.
|
|
101
|
+
*
|
|
102
|
+
* @returns Path to $HOME/.hoox/config as a branded HooxPath
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const configDir = getHooxConfigDir();
|
|
107
|
+
* // Returns: "/Users/alice/.hoox/config"
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function getHooxConfigDir(): HooxPath;
|
|
111
|
+
/**
|
|
112
|
+
* Constructs a path to the Hoox data directory.
|
|
113
|
+
*
|
|
114
|
+
* @returns Path to $HOME/.hoox/data as a branded HooxPath
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const dataDir = getHooxDataDir();
|
|
119
|
+
* // Returns: "/Users/alice/.hoox/data"
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function getHooxDataDir(): HooxPath;
|
|
123
|
+
/**
|
|
124
|
+
* Constructs a path to the Hoox wrangler configuration file.
|
|
125
|
+
*
|
|
126
|
+
* @returns Path to $HOME/.hoox/config/wrangler.jsonc as a branded HooxPath
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const wranglerPath = getHooxWranglerPath();
|
|
131
|
+
* // Returns: "/Users/alice/.hoox/config/wrangler.jsonc"
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare function getHooxWranglerPath(): HooxPath;
|
|
135
|
+
/**
|
|
136
|
+
* Constructs a path to the Hoox state file.
|
|
137
|
+
*
|
|
138
|
+
* @returns Path to $HOME/.hoox/data/state.json as a branded HooxPath
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const statePath = getHooxStatePath();
|
|
143
|
+
* // Returns: "/Users/alice/.hoox/data/state.json"
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export declare function getHooxStatePath(): HooxPath;
|
|
147
|
+
//# sourceMappingURL=path-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC;AAUjE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAYtC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,QAAQ,CAiC9D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAStD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkBvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,IAAI,QAAQ,CAE1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,IAAI,QAAQ,CAE3C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,IAAI,QAAQ,CAEzC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,QAAQ,CAE9C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,IAAI,QAAQ,CAE3C"}
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
// src/path-utils.ts
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
import { join, resolve } from "path";
|
|
21
|
+
function createHooxPath(path) {
|
|
22
|
+
return path;
|
|
23
|
+
}
|
|
24
|
+
function getHooxHome() {
|
|
25
|
+
try {
|
|
26
|
+
const home = homedir();
|
|
27
|
+
if (!home || home.length === 0) {
|
|
28
|
+
return createHooxPath(resolve(process.cwd(), ".hoox"));
|
|
29
|
+
}
|
|
30
|
+
return createHooxPath(join(home, ".hoox"));
|
|
31
|
+
} catch {
|
|
32
|
+
return createHooxPath(resolve(process.cwd(), ".hoox"));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function resolveHooxPath(relativePath) {
|
|
36
|
+
if (!relativePath || typeof relativePath !== "string") {
|
|
37
|
+
throw new Error("relativePath must be a non-empty string");
|
|
38
|
+
}
|
|
39
|
+
if (relativePath.includes("..")) {
|
|
40
|
+
throw new Error(`Path traversal detected in relativePath: "${relativePath}"`);
|
|
41
|
+
}
|
|
42
|
+
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
43
|
+
const hooxHome = getHooxHome();
|
|
44
|
+
const fullPath = join(hooxHome, normalizedPath);
|
|
45
|
+
const resolvedPath = resolve(fullPath);
|
|
46
|
+
const resolvedHome = resolve(hooxHome);
|
|
47
|
+
if (!resolvedPath.startsWith(resolvedHome)) {
|
|
48
|
+
throw new Error(`Resolved path "${resolvedPath}" is outside Hoox home directory`);
|
|
49
|
+
}
|
|
50
|
+
return createHooxPath(resolvedPath);
|
|
51
|
+
}
|
|
52
|
+
function isWithinHooxHome(path) {
|
|
53
|
+
try {
|
|
54
|
+
const hooxHome = getHooxHome();
|
|
55
|
+
const resolvedPath = resolve(path);
|
|
56
|
+
const resolvedHome = resolve(hooxHome);
|
|
57
|
+
return resolvedPath.startsWith(resolvedHome);
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getRelativeHooxPath(absolutePath) {
|
|
63
|
+
try {
|
|
64
|
+
if (!isWithinHooxHome(absolutePath)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const hooxHome = getHooxHome();
|
|
68
|
+
const resolvedPath = resolve(absolutePath);
|
|
69
|
+
const resolvedHome = resolve(hooxHome);
|
|
70
|
+
const relativePath = resolvedPath.slice(resolvedHome.length);
|
|
71
|
+
return relativePath.startsWith("/") || relativePath.startsWith("\\") ? relativePath.slice(1) : relativePath;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function getHooxRepoPath() {
|
|
77
|
+
return resolveHooxPath("repo");
|
|
78
|
+
}
|
|
79
|
+
function getHooxConfigDir() {
|
|
80
|
+
return resolveHooxPath("config");
|
|
81
|
+
}
|
|
82
|
+
function getHooxDataDir() {
|
|
83
|
+
return resolveHooxPath("data");
|
|
84
|
+
}
|
|
85
|
+
function getHooxWranglerPath() {
|
|
86
|
+
return resolveHooxPath("config/wrangler.jsonc");
|
|
87
|
+
}
|
|
88
|
+
function getHooxStatePath() {
|
|
89
|
+
return resolveHooxPath("data/state.json");
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
resolveHooxPath,
|
|
93
|
+
isWithinHooxHome,
|
|
94
|
+
getRelativeHooxPath,
|
|
95
|
+
getHooxWranglerPath,
|
|
96
|
+
getHooxStatePath,
|
|
97
|
+
getHooxRepoPath,
|
|
98
|
+
getHooxHome,
|
|
99
|
+
getHooxDataDir,
|
|
100
|
+
getHooxConfigDir
|
|
101
|
+
};
|