@spfn/core 0.2.0-beta.57 → 0.2.0-beta.59
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/authz/index.d.ts +34 -0
- package/dist/authz/index.js +415 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/config/index.d.ts +132 -0
- package/dist/config/index.js +29 -1
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +20 -1
- package/dist/db/index.js.map +1 -1
- package/dist/middleware/index.d.ts +56 -6
- package/dist/middleware/index.js +85 -4
- package/dist/middleware/index.js.map +1 -1
- package/dist/security/index.d.ts +83 -0
- package/dist/security/index.js +173 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server/index.d.ts +64 -1
- package/dist/server/index.js +59 -2
- package/dist/server/index.js.map +1 -1
- package/package.json +11 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Context, Next, MiddlewareHandler } from 'hono';
|
|
2
|
-
import { b as NamedMiddlewareFactory } from '../define-middleware-DuXD8Hvu.js';
|
|
2
|
+
import { b as NamedMiddlewareFactory, N as NamedMiddleware } from '../define-middleware-DuXD8Hvu.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Error Handler Middleware
|
|
@@ -285,8 +285,17 @@ declare function createCacheNonceStore(cache: CacheClient, prefix?: string): Non
|
|
|
285
285
|
*/
|
|
286
286
|
declare function createInMemoryNonceStore(): NonceStore;
|
|
287
287
|
|
|
288
|
+
/**
|
|
289
|
+
* One identity dimension to limit on. A bare string uses the top-level `limit`;
|
|
290
|
+
* an object can carry its own `limit` so dimensions can differ (e.g. a loose
|
|
291
|
+
* per-IP cap alongside a tight per-account cap).
|
|
292
|
+
*/
|
|
293
|
+
type RateLimitDimension = string | {
|
|
294
|
+
key: string;
|
|
295
|
+
limit?: number;
|
|
296
|
+
};
|
|
288
297
|
interface RateLimitOptions {
|
|
289
|
-
/** Max requests allowed per window, applied to each
|
|
298
|
+
/** Max requests allowed per window, applied to each dimension without its own `limit`. */
|
|
290
299
|
limit: number;
|
|
291
300
|
/** Window length in milliseconds. */
|
|
292
301
|
windowMs: number;
|
|
@@ -297,10 +306,11 @@ interface RateLimitOptions {
|
|
|
297
306
|
scope?: string;
|
|
298
307
|
/**
|
|
299
308
|
* Identity dimensions to limit on. Each non-empty value is counted
|
|
300
|
-
* separately and the strictest wins (e.g. by IP and by account).
|
|
301
|
-
*
|
|
309
|
+
* separately and the strictest wins (e.g. by IP and by account). A dimension
|
|
310
|
+
* may override the top-level `limit` via `{ key, limit }`. Defaults to the
|
|
311
|
+
* client IP only.
|
|
302
312
|
*/
|
|
303
|
-
by?: (c: Context) => (
|
|
313
|
+
by?: (c: Context) => (RateLimitDimension | null | undefined)[] | Promise<(RateLimitDimension | null | undefined)[]>;
|
|
304
314
|
/** Reject with 429 instead of allowing through when the cache is unavailable. */
|
|
305
315
|
failClosed?: boolean;
|
|
306
316
|
/** Message for the 429 response. */
|
|
@@ -316,6 +326,10 @@ interface RateLimitOptions {
|
|
|
316
326
|
* the forwarded header is attacker-settable, so fall back to the raw chain — whose
|
|
317
327
|
* leftmost hop is itself spoofable, so still pair with an account/target dimension
|
|
318
328
|
* for anything security-sensitive.
|
|
329
|
+
*
|
|
330
|
+
* Last resort before giving up is the TCP peer address from the Node adapter
|
|
331
|
+
* socket. Without it, a deployment that sets no forwarding header would collapse
|
|
332
|
+
* every client onto a single `'unknown'` bucket.
|
|
319
333
|
*/
|
|
320
334
|
declare function getClientIp(c: Context): string;
|
|
321
335
|
/**
|
|
@@ -329,5 +343,41 @@ declare function getClientIp(c: Context): string;
|
|
|
329
343
|
* ```
|
|
330
344
|
*/
|
|
331
345
|
declare const rateLimit: NamedMiddlewareFactory<"rateLimit", [options: RateLimitOptions]>;
|
|
346
|
+
/** Set the fail-closed default for named policies. Called by the server at boot. */
|
|
347
|
+
declare function setRateLimitFailClosedDefault(failClosed: boolean): void;
|
|
348
|
+
/**
|
|
349
|
+
* Replace the named-policy registry. Called by the server at boot; passing
|
|
350
|
+
* undefined clears it (so a restart without policies doesn't keep stale ones).
|
|
351
|
+
*/
|
|
352
|
+
declare function setRateLimitPolicies(policies?: Record<string, RateLimitOptions>): void;
|
|
353
|
+
/** Look up a configured policy by name — undefined when the app didn't set it. */
|
|
354
|
+
declare function getRateLimitPolicy(name: string): RateLimitOptions | undefined;
|
|
355
|
+
/**
|
|
356
|
+
* Rate-limit a route under a named policy.
|
|
357
|
+
*
|
|
358
|
+
* A package author tags a sensitive route with a policy name plus a safe
|
|
359
|
+
* fallback; the consuming app tunes the numbers centrally via
|
|
360
|
+
* `defineServerConfig().rateLimit({ policies: { [name]: {...} } })`. When the
|
|
361
|
+
* app configures the policy, its fields override the fallback (shallow merge);
|
|
362
|
+
* otherwise the fallback applies, so the route is protected out of the box.
|
|
363
|
+
*
|
|
364
|
+
* LAYERS on top of the global default limiter rather than replacing it: the tag
|
|
365
|
+
* has a distinct name (`rateLimit:<name>`) and does not skip the global, so a
|
|
366
|
+
* route gets both the global per-IP floor (when enabled) and this policy's own
|
|
367
|
+
* bucket — whichever is stricter trips first. Opt out of the global floor on a
|
|
368
|
+
* route with `.skip(['rateLimit'])`.
|
|
369
|
+
*
|
|
370
|
+
* The counter scope defaults to the policy name, so (a) every route sharing a
|
|
371
|
+
* policy shares one bucket, and (b) the key never collides with the global
|
|
372
|
+
* default's per-route `${method} ${path}` scope.
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```typescript
|
|
376
|
+
* route.post('/_auth/login')
|
|
377
|
+
* .use([rateLimitPolicy('auth-login', { limit: 5, windowMs: 60_000 })])
|
|
378
|
+
* .handler(...);
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
declare function rateLimitPolicy(name: string, fallback: RateLimitOptions): NamedMiddleware<string>;
|
|
332
382
|
|
|
333
|
-
export { type ClientType, ErrorHandler, type ErrorHandlerOptions, type NonceStore, type OnErrorContext, type ProxyGuardConfig, type ProxyGuardMode, type RateLimitOptions, RequestLogger, type RequestLoggerConfig, type RequestLoggerOptions, createCacheNonceStore, createInMemoryNonceStore, createProxyGuard, getClientIp, maskSensitiveData, rateLimit };
|
|
383
|
+
export { type ClientType, ErrorHandler, type ErrorHandlerOptions, type NonceStore, type OnErrorContext, type ProxyGuardConfig, type ProxyGuardMode, type RateLimitDimension, type RateLimitOptions, RequestLogger, type RequestLoggerConfig, type RequestLoggerOptions, createCacheNonceStore, createInMemoryNonceStore, createProxyGuard, getClientIp, getRateLimitPolicy, maskSensitiveData, rateLimit, rateLimitPolicy, setRateLimitFailClosedDefault, setRateLimitPolicies };
|
package/dist/middleware/index.js
CHANGED
|
@@ -237,6 +237,42 @@ function verifyProxyRequest(input) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// src/route/define-middleware.ts
|
|
240
|
+
function defineMiddleware(name, handlerOrFactory, options) {
|
|
241
|
+
const skips = options?.skips;
|
|
242
|
+
if (typeof handlerOrFactory === "function") {
|
|
243
|
+
const paramCount = handlerOrFactory.length;
|
|
244
|
+
if (paramCount === 2) {
|
|
245
|
+
return {
|
|
246
|
+
name,
|
|
247
|
+
handler: handlerOrFactory,
|
|
248
|
+
_name: name,
|
|
249
|
+
...skips
|
|
250
|
+
};
|
|
251
|
+
} else {
|
|
252
|
+
const factory = handlerOrFactory;
|
|
253
|
+
const wrapper = (...args) => factory(...args);
|
|
254
|
+
Object.defineProperty(wrapper, "name", {
|
|
255
|
+
value: name,
|
|
256
|
+
writable: false,
|
|
257
|
+
enumerable: false,
|
|
258
|
+
configurable: true
|
|
259
|
+
});
|
|
260
|
+
Object.defineProperty(wrapper, "_name", {
|
|
261
|
+
value: name,
|
|
262
|
+
writable: false,
|
|
263
|
+
enumerable: false,
|
|
264
|
+
configurable: true
|
|
265
|
+
});
|
|
266
|
+
return wrapper;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
name,
|
|
271
|
+
handler: handlerOrFactory,
|
|
272
|
+
_name: name,
|
|
273
|
+
...skips
|
|
274
|
+
};
|
|
275
|
+
}
|
|
240
276
|
function defineMiddlewareFactory(name, factory) {
|
|
241
277
|
const wrapper = (...args) => factory(...args);
|
|
242
278
|
Object.defineProperty(wrapper, "name", {
|
|
@@ -1066,7 +1102,12 @@ function getClientIp(c) {
|
|
|
1066
1102
|
}
|
|
1067
1103
|
}
|
|
1068
1104
|
const forwardedFor = c.req.header("x-forwarded-for");
|
|
1069
|
-
return forwardedFor?.split(",")[0]?.trim() || c.req.header("x-real-ip") || "unknown";
|
|
1105
|
+
return forwardedFor?.split(",")[0]?.trim() || c.req.header("x-real-ip") || socketRemoteAddress(c) || "unknown";
|
|
1106
|
+
}
|
|
1107
|
+
function socketRemoteAddress(c) {
|
|
1108
|
+
const env4 = c.env;
|
|
1109
|
+
const bindings = env4?.server ?? env4;
|
|
1110
|
+
return bindings?.incoming?.socket?.remoteAddress;
|
|
1070
1111
|
}
|
|
1071
1112
|
var rateLimit = defineMiddlewareFactory(
|
|
1072
1113
|
"rateLimit",
|
|
@@ -1086,13 +1127,18 @@ var rateLimit = defineMiddlewareFactory(
|
|
|
1086
1127
|
const dimensions = (by ? await by(c) : [getClientIp(c)]).filter((d) => Boolean(d));
|
|
1087
1128
|
const ns = scope || `${c.req.method} ${c.req.routePath || c.req.path}`;
|
|
1088
1129
|
for (const dimension of dimensions) {
|
|
1130
|
+
const key = typeof dimension === "string" ? dimension : dimension.key;
|
|
1131
|
+
if (!key) {
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
const dimLimit = typeof dimension === "string" ? limit : dimension.limit ?? limit;
|
|
1089
1135
|
const [count, pttl] = await cache.eval(
|
|
1090
1136
|
FIXED_WINDOW_LUA,
|
|
1091
1137
|
1,
|
|
1092
|
-
`ratelimit:${ns}:${
|
|
1138
|
+
`ratelimit:${ns}:${key}`,
|
|
1093
1139
|
String(windowMs)
|
|
1094
1140
|
);
|
|
1095
|
-
if (count >
|
|
1141
|
+
if (count > dimLimit) {
|
|
1096
1142
|
const retryAfter = Math.max(1, Math.ceil((pttl > 0 ? pttl : windowMs) / 1e3));
|
|
1097
1143
|
c.header("Retry-After", String(retryAfter));
|
|
1098
1144
|
throw new TooManyRequestsError({
|
|
@@ -1105,6 +1151,41 @@ var rateLimit = defineMiddlewareFactory(
|
|
|
1105
1151
|
};
|
|
1106
1152
|
}
|
|
1107
1153
|
);
|
|
1154
|
+
var policyRegistry = /* @__PURE__ */ new Map();
|
|
1155
|
+
var policyFailClosedDefault = false;
|
|
1156
|
+
function setRateLimitFailClosedDefault(failClosed) {
|
|
1157
|
+
policyFailClosedDefault = failClosed;
|
|
1158
|
+
}
|
|
1159
|
+
function setRateLimitPolicies(policies) {
|
|
1160
|
+
policyRegistry.clear();
|
|
1161
|
+
if (!policies) {
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
for (const [name, options] of Object.entries(policies)) {
|
|
1165
|
+
policyRegistry.set(name, options);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function getRateLimitPolicy(name) {
|
|
1169
|
+
return policyRegistry.get(name);
|
|
1170
|
+
}
|
|
1171
|
+
function rateLimitPolicy(name, fallback) {
|
|
1172
|
+
let resolved;
|
|
1173
|
+
const handler = (c, next) => {
|
|
1174
|
+
if (!resolved) {
|
|
1175
|
+
const configured = getRateLimitPolicy(name);
|
|
1176
|
+
const merged = configured ? { ...fallback, ...configured } : { ...fallback };
|
|
1177
|
+
if (merged.scope === void 0) {
|
|
1178
|
+
merged.scope = name;
|
|
1179
|
+
}
|
|
1180
|
+
if (merged.failClosed === void 0) {
|
|
1181
|
+
merged.failClosed = policyFailClosedDefault;
|
|
1182
|
+
}
|
|
1183
|
+
resolved = rateLimit(merged);
|
|
1184
|
+
}
|
|
1185
|
+
return resolved(c, next);
|
|
1186
|
+
};
|
|
1187
|
+
return defineMiddleware(`rateLimit:${name}`, handler);
|
|
1188
|
+
}
|
|
1108
1189
|
|
|
1109
1190
|
// src/middleware/request-logger.ts
|
|
1110
1191
|
var DEFAULT_CONFIG = {
|
|
@@ -1383,6 +1464,6 @@ function reject(c, mode, next, reason) {
|
|
|
1383
1464
|
return next();
|
|
1384
1465
|
}
|
|
1385
1466
|
|
|
1386
|
-
export { ErrorHandler, RequestLogger, createCacheNonceStore, createInMemoryNonceStore, createProxyGuard, getClientIp, maskSensitiveData2 as maskSensitiveData, rateLimit };
|
|
1467
|
+
export { ErrorHandler, RequestLogger, createCacheNonceStore, createInMemoryNonceStore, createProxyGuard, getClientIp, getRateLimitPolicy, maskSensitiveData2 as maskSensitiveData, rateLimit, rateLimitPolicy, setRateLimitFailClosedDefault, setRateLimitPolicies };
|
|
1387
1468
|
//# sourceMappingURL=index.js.map
|
|
1388
1469
|
//# sourceMappingURL=index.js.map
|