@fuzdev/fuz_app 0.29.0 → 0.31.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/dist/actions/CLAUDE.md +630 -0
- package/dist/actions/action_rpc.d.ts +29 -0
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +42 -6
- package/dist/actions/action_types.d.ts +2 -2
- package/dist/actions/cancel.d.ts +12 -13
- package/dist/actions/cancel.d.ts.map +1 -1
- package/dist/actions/cancel.js +10 -13
- package/dist/actions/heartbeat.d.ts +8 -13
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -8
- package/dist/actions/register_action_ws.d.ts +3 -3
- package/dist/actions/register_action_ws.js +2 -2
- package/dist/actions/register_ws_endpoint.d.ts +4 -4
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -3
- package/dist/actions/socket.svelte.d.ts +16 -16
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +15 -15
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.d.ts +15 -0
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +17 -0
- package/dist/auth/CLAUDE.md +923 -0
- package/dist/auth/account_action_specs.d.ts +216 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -0
- package/dist/auth/account_action_specs.js +159 -0
- package/dist/auth/account_actions.d.ts +51 -0
- package/dist/auth/account_actions.d.ts.map +1 -0
- package/dist/auth/account_actions.js +119 -0
- package/dist/auth/account_queries.d.ts +6 -2
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +40 -4
- package/dist/auth/account_routes.d.ts +94 -16
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +108 -180
- package/dist/auth/account_schema.d.ts +85 -30
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +40 -8
- package/dist/auth/admin_action_specs.d.ts +674 -0
- package/dist/auth/admin_action_specs.d.ts.map +1 -0
- package/dist/auth/admin_action_specs.js +287 -0
- package/dist/auth/admin_actions.d.ts +69 -0
- package/dist/auth/admin_actions.d.ts.map +1 -0
- package/dist/auth/admin_actions.js +256 -0
- package/dist/auth/api_token.d.ts +10 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +9 -0
- package/dist/auth/api_token_queries.d.ts +3 -3
- package/dist/auth/api_token_queries.js +3 -3
- package/dist/auth/app_settings_schema.d.ts +4 -3
- package/dist/auth/app_settings_schema.d.ts.map +1 -1
- package/dist/auth/app_settings_schema.js +2 -1
- package/dist/auth/audit_log_routes.d.ts +14 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +22 -79
- package/dist/auth/audit_log_schema.d.ts +100 -29
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +83 -11
- package/dist/auth/bootstrap_routes.d.ts +14 -0
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +10 -3
- package/dist/auth/cleanup.d.ts +63 -0
- package/dist/auth/cleanup.d.ts.map +1 -0
- package/dist/auth/cleanup.js +80 -0
- package/dist/auth/invite_schema.d.ts +11 -10
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +4 -3
- package/dist/auth/migrations.d.ts +6 -0
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +28 -0
- package/dist/auth/permit_offer_action_specs.d.ts +364 -0
- package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/permit_offer_action_specs.js +216 -0
- package/dist/auth/permit_offer_actions.d.ts +96 -0
- package/dist/auth/permit_offer_actions.d.ts.map +1 -0
- package/dist/auth/permit_offer_actions.js +428 -0
- package/dist/auth/permit_offer_notifications.d.ts +361 -0
- package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
- package/dist/auth/permit_offer_notifications.js +179 -0
- package/dist/auth/permit_offer_queries.d.ts +165 -0
- package/dist/auth/permit_offer_queries.d.ts.map +1 -0
- package/dist/auth/permit_offer_queries.js +390 -0
- package/dist/auth/permit_offer_schema.d.ts +103 -0
- package/dist/auth/permit_offer_schema.d.ts.map +1 -0
- package/dist/auth/permit_offer_schema.js +142 -0
- package/dist/auth/permit_queries.d.ts +77 -14
- package/dist/auth/permit_queries.d.ts.map +1 -1
- package/dist/auth/permit_queries.js +119 -24
- package/dist/auth/session_queries.d.ts +4 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +4 -2
- package/dist/auth/signup_routes.d.ts +13 -0
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +14 -7
- package/dist/http/CLAUDE.md +584 -0
- package/dist/http/pending_effects.d.ts +29 -0
- package/dist/http/pending_effects.d.ts.map +1 -0
- package/dist/http/pending_effects.js +31 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +4 -3
- package/dist/rate_limiter.d.ts +30 -0
- package/dist/rate_limiter.d.ts.map +1 -1
- package/dist/rate_limiter.js +25 -2
- package/dist/realtime/sse_auth_guard.d.ts +2 -0
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +5 -3
- package/dist/testing/CLAUDE.md +668 -1
- package/dist/testing/admin_integration.d.ts +10 -7
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +382 -482
- package/dist/testing/app_server.d.ts +7 -6
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/attack_surface.d.ts +9 -3
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +4 -4
- package/dist/testing/audit_completeness.d.ts +6 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +158 -134
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +4 -33
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +2 -0
- package/dist/testing/entities.d.ts +35 -13
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +17 -0
- package/dist/testing/integration.d.ts +10 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +352 -340
- package/dist/testing/integration_helpers.d.ts +16 -5
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +24 -4
- package/dist/testing/rate_limiting.d.ts +7 -0
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +41 -10
- package/dist/testing/rpc_helpers.d.ts +153 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +184 -8
- package/dist/testing/sse_round_trip.d.ts +8 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +10 -3
- package/dist/testing/standard.d.ts +9 -1
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +6 -2
- package/dist/testing/surface_invariants.d.ts +7 -3
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +5 -4
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +9 -38
- package/dist/ui/AccountSessions.svelte +8 -4
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +61 -33
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -2
- package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
- package/dist/ui/AdminInvites.svelte +3 -2
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +14 -9
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminPermitHistory.svelte +3 -2
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +29 -25
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +351 -0
- package/dist/ui/OpenSignupToggle.svelte +6 -3
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/PermitOfferForm.svelte +141 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferHistory.svelte +109 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferInbox.svelte +121 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +39 -16
- package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +99 -23
- package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +38 -26
- package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +35 -21
- package/dist/ui/app_settings_state.svelte.d.ts +39 -0
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +34 -18
- package/dist/ui/audit_log_state.svelte.d.ts +40 -3
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +36 -42
- package/dist/ui/auth_state.svelte.d.ts +4 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +4 -1
- package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
- package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/permit_offers_state.svelte.js +197 -0
- package/package.json +3 -3
- package/dist/auth/admin_routes.d.ts +0 -29
- package/dist/auth/admin_routes.d.ts.map +0 -1
- package/dist/auth/admin_routes.js +0 -226
- package/dist/auth/app_settings_routes.d.ts +0 -27
- package/dist/auth/app_settings_routes.d.ts.map +0 -1
- package/dist/auth/app_settings_routes.js +0 -66
- package/dist/auth/invite_routes.d.ts +0 -18
- package/dist/auth/invite_routes.d.ts.map +0 -1
- package/dist/auth/invite_routes.js +0 -129
package/dist/http/route_spec.js
CHANGED
|
@@ -133,7 +133,8 @@ const create_query_validation = (query_schema) => {
|
|
|
133
133
|
*
|
|
134
134
|
* In development, validates 2xx JSON responses against the output schema
|
|
135
135
|
* and non-2xx responses against declared error schemas.
|
|
136
|
-
* Logs
|
|
136
|
+
* Logs an error for mismatches and returns the response unchanged —
|
|
137
|
+
* does not throw. In production, returns the handler unchanged.
|
|
137
138
|
*/
|
|
138
139
|
const wrap_output_validation = (handler, output_schema, error_schemas, log) => {
|
|
139
140
|
if (!DEV)
|
|
@@ -152,7 +153,7 @@ const wrap_output_validation = (handler, output_schema, error_schemas, log) => {
|
|
|
152
153
|
const body = await cloned.json();
|
|
153
154
|
const result = output_schema.safeParse(body);
|
|
154
155
|
if (!result.success) {
|
|
155
|
-
log.
|
|
156
|
+
log.error(`Output schema mismatch: ${c.req.method} ${c.req.path}`, result.error.issues);
|
|
156
157
|
}
|
|
157
158
|
}
|
|
158
159
|
catch {
|
|
@@ -167,7 +168,7 @@ const wrap_output_validation = (handler, output_schema, error_schemas, log) => {
|
|
|
167
168
|
const body = await cloned.json();
|
|
168
169
|
const result = status_schema.safeParse(body);
|
|
169
170
|
if (!result.success) {
|
|
170
|
-
log.
|
|
171
|
+
log.error(`Error schema mismatch (${response.status}): ${c.req.method} ${c.req.path}`, result.error.issues);
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
catch {
|
package/dist/rate_limiter.d.ts
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import type { Context } from 'hono';
|
|
10
|
+
/**
|
|
11
|
+
* Default tracked-key cap: bounds worst-case memory under key-enumeration
|
|
12
|
+
* attacks (an attacker rotating source IPs cannot grow the backing map
|
|
13
|
+
* indefinitely between cleanup ticks). Tuned to comfortably fit real
|
|
14
|
+
* traffic for a single-instance deployment while capping memory at a few
|
|
15
|
+
* MB in the worst case.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_RATE_LIMITER_MAX_KEYS = 100000;
|
|
10
18
|
/**
|
|
11
19
|
* Configuration for a rate limiter instance.
|
|
12
20
|
*/
|
|
@@ -17,6 +25,22 @@ export interface RateLimiterOptions {
|
|
|
17
25
|
window_ms: number;
|
|
18
26
|
/** Interval for pruning stale entries (0 disables the timer). */
|
|
19
27
|
cleanup_interval_ms: number;
|
|
28
|
+
/**
|
|
29
|
+
* Maximum tracked keys. When exceeded, the least-recently-used key is
|
|
30
|
+
* evicted — bounds memory under key-enumeration attacks. Default:
|
|
31
|
+
* `DEFAULT_RATE_LIMITER_MAX_KEYS` (100_000). Pass `null` to disable the
|
|
32
|
+
* cap (falls back to an unbounded `Map` — only recommended when the key
|
|
33
|
+
* set is known to be closed, e.g. a per-account limiter keyed to a
|
|
34
|
+
* bounded-size account table).
|
|
35
|
+
*
|
|
36
|
+
* LRU trade-off: every `check` / `record` call marks the key as
|
|
37
|
+
* most-recently-used, so keys under active attack stay fresh and won't
|
|
38
|
+
* be evicted. A slow-burn attacker spread across many low-volume keys
|
|
39
|
+
* can, however, drop out of the table and reset their budget — set
|
|
40
|
+
* `max_keys` high enough to fit the expected legitimate key set and
|
|
41
|
+
* this stays theoretical.
|
|
42
|
+
*/
|
|
43
|
+
max_keys?: number | null;
|
|
20
44
|
}
|
|
21
45
|
/** Default options for per-IP login rate limiting: 5 attempts per 15 minutes. */
|
|
22
46
|
export declare const DEFAULT_LOGIN_IP_RATE_LIMIT: RateLimiterOptions;
|
|
@@ -40,6 +64,12 @@ export interface RateLimitResult {
|
|
|
40
64
|
* outside the window are pruned. `retry_after` reports seconds until the
|
|
41
65
|
* oldest active timestamp expires.
|
|
42
66
|
*
|
|
67
|
+
* The backing store is an `LruMap` when `options.max_keys` is a positive
|
|
68
|
+
* number (default `DEFAULT_RATE_LIMITER_MAX_KEYS`) and a plain `Map` when
|
|
69
|
+
* `max_keys` is `null`. The `LruMap` path bounds memory under
|
|
70
|
+
* key-enumeration attack at the cost of a slight per-op overhead and the
|
|
71
|
+
* LRU trade-off described on `RateLimiterOptions.max_keys`.
|
|
72
|
+
*
|
|
43
73
|
* Parameters that accept `RateLimiter | null` (e.g. `ip_rate_limiter`,
|
|
44
74
|
* `login_account_rate_limiter`) silently disable rate limiting when `null`
|
|
45
75
|
* is passed — no checks are performed and all requests are allowed through.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate_limiter.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/rate_limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"rate_limiter.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/rate_limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAKlC;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,SAAU,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,iFAAiF;AACjF,eAAO,MAAM,2BAA2B,EAAE,kBAKzC,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,gCAAgC,EAAE,kBAK9C,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,WAAW;;IACvB,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;gBAOzB,OAAO,EAAE,kBAAkB;IAcvC,8BAA8B;IAC9B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,eAAe;IA2B7D;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,eAAe;IA0B9D;;OAEG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIxB;;;;OAIG;IACH,OAAO,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI;IAgBvC,2DAA2D;IAC3D,OAAO,IAAI,IAAI;CAMf;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,OAAO,CAAC,kBAAkB,CAAC,KAAG,WAE3E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GAAI,GAAG,OAAO,EAAE,aAAa,MAAM,KAAG,QAI7E,CAAC"}
|
package/dist/rate_limiter.js
CHANGED
|
@@ -6,18 +6,29 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import { LruMap } from '@fuzdev/fuz_util/lru_map.js';
|
|
9
10
|
import { ERROR_RATE_LIMIT_EXCEEDED } from './http/error_schemas.js';
|
|
11
|
+
/**
|
|
12
|
+
* Default tracked-key cap: bounds worst-case memory under key-enumeration
|
|
13
|
+
* attacks (an attacker rotating source IPs cannot grow the backing map
|
|
14
|
+
* indefinitely between cleanup ticks). Tuned to comfortably fit real
|
|
15
|
+
* traffic for a single-instance deployment while capping memory at a few
|
|
16
|
+
* MB in the worst case.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_RATE_LIMITER_MAX_KEYS = 100_000;
|
|
10
19
|
/** Default options for per-IP login rate limiting: 5 attempts per 15 minutes. */
|
|
11
20
|
export const DEFAULT_LOGIN_IP_RATE_LIMIT = {
|
|
12
21
|
max_attempts: 5,
|
|
13
22
|
window_ms: 15 * 60_000,
|
|
14
23
|
cleanup_interval_ms: 5 * 60_000,
|
|
24
|
+
max_keys: DEFAULT_RATE_LIMITER_MAX_KEYS,
|
|
15
25
|
};
|
|
16
26
|
/** Default options for per-account login rate limiting: 10 attempts per 30 minutes. */
|
|
17
27
|
export const DEFAULT_LOGIN_ACCOUNT_RATE_LIMIT = {
|
|
18
28
|
max_attempts: 10,
|
|
19
29
|
window_ms: 30 * 60_000,
|
|
20
30
|
cleanup_interval_ms: 5 * 60_000,
|
|
31
|
+
max_keys: DEFAULT_RATE_LIMITER_MAX_KEYS,
|
|
21
32
|
};
|
|
22
33
|
/**
|
|
23
34
|
* In-memory sliding window rate limiter.
|
|
@@ -26,6 +37,12 @@ export const DEFAULT_LOGIN_ACCOUNT_RATE_LIMIT = {
|
|
|
26
37
|
* outside the window are pruned. `retry_after` reports seconds until the
|
|
27
38
|
* oldest active timestamp expires.
|
|
28
39
|
*
|
|
40
|
+
* The backing store is an `LruMap` when `options.max_keys` is a positive
|
|
41
|
+
* number (default `DEFAULT_RATE_LIMITER_MAX_KEYS`) and a plain `Map` when
|
|
42
|
+
* `max_keys` is `null`. The `LruMap` path bounds memory under
|
|
43
|
+
* key-enumeration attack at the cost of a slight per-op overhead and the
|
|
44
|
+
* LRU trade-off described on `RateLimiterOptions.max_keys`.
|
|
45
|
+
*
|
|
29
46
|
* Parameters that accept `RateLimiter | null` (e.g. `ip_rate_limiter`,
|
|
30
47
|
* `login_account_rate_limiter`) silently disable rate limiting when `null`
|
|
31
48
|
* is passed — no checks are performed and all requests are allowed through.
|
|
@@ -33,10 +50,12 @@ export const DEFAULT_LOGIN_ACCOUNT_RATE_LIMIT = {
|
|
|
33
50
|
export class RateLimiter {
|
|
34
51
|
options;
|
|
35
52
|
/** Key → array of attempt timestamps. */
|
|
36
|
-
#attempts
|
|
53
|
+
#attempts;
|
|
37
54
|
#cleanup_timer = null;
|
|
38
55
|
constructor(options) {
|
|
39
56
|
this.options = options;
|
|
57
|
+
const max_keys = options.max_keys === undefined ? DEFAULT_RATE_LIMITER_MAX_KEYS : options.max_keys;
|
|
58
|
+
this.#attempts = max_keys === null ? new Map() : new LruMap(max_keys);
|
|
40
59
|
if (options.cleanup_interval_ms > 0) {
|
|
41
60
|
this.#cleanup_timer = setInterval(() => this.cleanup(), options.cleanup_interval_ms);
|
|
42
61
|
// Allow the process to exit even if the timer is still active.
|
|
@@ -120,7 +139,11 @@ export class RateLimiter {
|
|
|
120
139
|
*/
|
|
121
140
|
cleanup(now = Date.now()) {
|
|
122
141
|
const cutoff = now - this.options.window_ms;
|
|
123
|
-
|
|
142
|
+
// Snapshot before mutating: `LruMap.set()` on an existing key moves it
|
|
143
|
+
// to the MRU end during iteration and causes re-visit in the same pass.
|
|
144
|
+
// The `Map` path is unaffected but the snapshot is cheap on both.
|
|
145
|
+
const entries = [...this.#attempts];
|
|
146
|
+
for (const [key, timestamps] of entries) {
|
|
124
147
|
const active = timestamps.filter((t) => t > cutoff);
|
|
125
148
|
if (active.length === 0) {
|
|
126
149
|
this.#attempts.delete(key);
|
|
@@ -14,6 +14,8 @@ import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
|
14
14
|
import { type AuditLogEvent } from '../auth/audit_log_schema.js';
|
|
15
15
|
import { SubscriberRegistry, type SubscribeOptions } from './subscriber_registry.js';
|
|
16
16
|
import type { SseStream, SseNotification, EventSpec } from './sse.js';
|
|
17
|
+
/** SSE channel the audit-log stream route publishes on. */
|
|
18
|
+
export declare const AUDIT_LOG_CHANNEL = "audit_log";
|
|
17
19
|
/**
|
|
18
20
|
* Audit event types that trigger SSE stream disconnection.
|
|
19
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/realtime/sse_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAC,kBAAkB,EAAE,KAAK,gBAAgB,EAAC,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAC,SAAS,EAAE,eAAe,EAAE,SAAS,EAAC,MAAM,UAAU,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAKrD,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,EACtC,UAAU,kBAAkB,CAAC,CAAC,CAAC,EAC/B,eAAe,MAAM,GAAG,IAAI,EAC5B,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA6CjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,8FAA8F;IAC9F,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC1F,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,kGAAkG;IAClG,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,yEAAyE;IACzE,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,SAAS,CAOlD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,eAAO,MAAM,oBAAoB,GAAI,SAAS;IAC7C,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,KAAG,WAgBH,CAAC"}
|
|
1
|
+
{"version":3,"file":"sse_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/realtime/sse_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAC,kBAAkB,EAAE,KAAK,gBAAgB,EAAC,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAC,SAAS,EAAE,eAAe,EAAE,SAAS,EAAC,MAAM,UAAU,CAAC;AAEpE,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAKrD,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,EACtC,UAAU,kBAAkB,CAAC,CAAC,CAAC,EAC/B,eAAe,MAAM,GAAG,IAAI,EAC5B,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA6CjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,8FAA8F;IAC9F,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC1F,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,kGAAkG;IAClG,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,yEAAyE;IACzE,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,SAAS,CAOlD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,eAAO,MAAM,oBAAoB,GAAI,SAAS;IAC7C,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,KAAG,WAgBH,CAAC"}
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { AUDIT_EVENT_TYPES, AuditLogEventJson, } from '../auth/audit_log_schema.js';
|
|
14
14
|
import { SubscriberRegistry } from './subscriber_registry.js';
|
|
15
|
+
/** SSE channel the audit-log stream route publishes on. */
|
|
16
|
+
export const AUDIT_LOG_CHANNEL = 'audit_log';
|
|
15
17
|
/**
|
|
16
18
|
* Audit event types that trigger SSE stream disconnection.
|
|
17
19
|
*
|
|
@@ -60,7 +62,7 @@ export const create_sse_auth_guard = (registry, required_role, log) => {
|
|
|
60
62
|
return;
|
|
61
63
|
// session_revoke is session-scoped, not account-scoped — close only the
|
|
62
64
|
// stream subscribed under the revoked session's hash. The hash is already
|
|
63
|
-
// in the event metadata (set by the
|
|
65
|
+
// in the event metadata (set by the `account_session_revoke` RPC handler).
|
|
64
66
|
if (event.event_type === 'session_revoke') {
|
|
65
67
|
const session_id = event.metadata?.session_id;
|
|
66
68
|
if (typeof session_id !== 'string' || session_id.length === 0)
|
|
@@ -124,7 +126,7 @@ export const AUDIT_LOG_EVENT_SPECS = AUDIT_EVENT_TYPES.map((event_type) => ({
|
|
|
124
126
|
method: event_type,
|
|
125
127
|
params: AuditLogEventJson,
|
|
126
128
|
description: `Audit log: ${event_type.replaceAll('_', ' ')}`,
|
|
127
|
-
channel:
|
|
129
|
+
channel: AUDIT_LOG_CHANNEL,
|
|
128
130
|
}));
|
|
129
131
|
/**
|
|
130
132
|
* Default max concurrent SSE subscribers per session scope for the audit log.
|
|
@@ -146,7 +148,7 @@ export const create_audit_log_sse = (options) => {
|
|
|
146
148
|
subscribe: registry.subscribe.bind(registry),
|
|
147
149
|
log: options.log,
|
|
148
150
|
on_audit_event: (event) => {
|
|
149
|
-
registry.broadcast(
|
|
151
|
+
registry.broadcast(AUDIT_LOG_CHANNEL, { method: event.event_type, params: event });
|
|
150
152
|
guard(event);
|
|
151
153
|
},
|
|
152
154
|
registry,
|