@fuzdev/fuz_app 0.30.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.
Files changed (207) hide show
  1. package/dist/actions/CLAUDE.md +630 -0
  2. package/dist/actions/action_rpc.d.ts +29 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +42 -6
  5. package/dist/actions/action_types.d.ts +2 -2
  6. package/dist/actions/cancel.d.ts +12 -13
  7. package/dist/actions/cancel.d.ts.map +1 -1
  8. package/dist/actions/cancel.js +10 -13
  9. package/dist/actions/heartbeat.d.ts +8 -13
  10. package/dist/actions/heartbeat.d.ts.map +1 -1
  11. package/dist/actions/heartbeat.js +5 -8
  12. package/dist/actions/register_action_ws.d.ts +3 -3
  13. package/dist/actions/register_action_ws.js +2 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +4 -4
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +3 -3
  17. package/dist/actions/socket.svelte.d.ts +16 -16
  18. package/dist/actions/socket.svelte.d.ts.map +1 -1
  19. package/dist/actions/socket.svelte.js +15 -15
  20. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  21. package/dist/auth/CLAUDE.md +923 -0
  22. package/dist/auth/account_action_specs.d.ts +216 -0
  23. package/dist/auth/account_action_specs.d.ts.map +1 -0
  24. package/dist/auth/account_action_specs.js +159 -0
  25. package/dist/auth/account_actions.d.ts +51 -0
  26. package/dist/auth/account_actions.d.ts.map +1 -0
  27. package/dist/auth/account_actions.js +119 -0
  28. package/dist/auth/account_queries.d.ts +6 -2
  29. package/dist/auth/account_queries.d.ts.map +1 -1
  30. package/dist/auth/account_queries.js +40 -4
  31. package/dist/auth/account_routes.d.ts +94 -16
  32. package/dist/auth/account_routes.d.ts.map +1 -1
  33. package/dist/auth/account_routes.js +108 -180
  34. package/dist/auth/account_schema.d.ts +85 -30
  35. package/dist/auth/account_schema.d.ts.map +1 -1
  36. package/dist/auth/account_schema.js +40 -8
  37. package/dist/auth/admin_action_specs.d.ts +674 -0
  38. package/dist/auth/admin_action_specs.d.ts.map +1 -0
  39. package/dist/auth/admin_action_specs.js +287 -0
  40. package/dist/auth/admin_actions.d.ts +69 -0
  41. package/dist/auth/admin_actions.d.ts.map +1 -0
  42. package/dist/auth/admin_actions.js +256 -0
  43. package/dist/auth/api_token.d.ts +10 -0
  44. package/dist/auth/api_token.d.ts.map +1 -1
  45. package/dist/auth/api_token.js +9 -0
  46. package/dist/auth/api_token_queries.d.ts +3 -3
  47. package/dist/auth/api_token_queries.js +3 -3
  48. package/dist/auth/app_settings_schema.d.ts +4 -3
  49. package/dist/auth/app_settings_schema.d.ts.map +1 -1
  50. package/dist/auth/app_settings_schema.js +2 -1
  51. package/dist/auth/audit_log_routes.d.ts +14 -6
  52. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  53. package/dist/auth/audit_log_routes.js +22 -79
  54. package/dist/auth/audit_log_schema.d.ts +100 -29
  55. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  56. package/dist/auth/audit_log_schema.js +83 -11
  57. package/dist/auth/bootstrap_routes.d.ts +14 -0
  58. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  59. package/dist/auth/bootstrap_routes.js +10 -3
  60. package/dist/auth/cleanup.d.ts +63 -0
  61. package/dist/auth/cleanup.d.ts.map +1 -0
  62. package/dist/auth/cleanup.js +80 -0
  63. package/dist/auth/invite_schema.d.ts +11 -10
  64. package/dist/auth/invite_schema.d.ts.map +1 -1
  65. package/dist/auth/invite_schema.js +4 -3
  66. package/dist/auth/migrations.d.ts +6 -0
  67. package/dist/auth/migrations.d.ts.map +1 -1
  68. package/dist/auth/migrations.js +28 -0
  69. package/dist/auth/permit_offer_action_specs.d.ts +364 -0
  70. package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
  71. package/dist/auth/permit_offer_action_specs.js +216 -0
  72. package/dist/auth/permit_offer_actions.d.ts +96 -0
  73. package/dist/auth/permit_offer_actions.d.ts.map +1 -0
  74. package/dist/auth/permit_offer_actions.js +428 -0
  75. package/dist/auth/permit_offer_notifications.d.ts +361 -0
  76. package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
  77. package/dist/auth/permit_offer_notifications.js +179 -0
  78. package/dist/auth/permit_offer_queries.d.ts +165 -0
  79. package/dist/auth/permit_offer_queries.d.ts.map +1 -0
  80. package/dist/auth/permit_offer_queries.js +390 -0
  81. package/dist/auth/permit_offer_schema.d.ts +103 -0
  82. package/dist/auth/permit_offer_schema.d.ts.map +1 -0
  83. package/dist/auth/permit_offer_schema.js +142 -0
  84. package/dist/auth/permit_queries.d.ts +77 -14
  85. package/dist/auth/permit_queries.d.ts.map +1 -1
  86. package/dist/auth/permit_queries.js +119 -24
  87. package/dist/auth/session_queries.d.ts +4 -2
  88. package/dist/auth/session_queries.d.ts.map +1 -1
  89. package/dist/auth/session_queries.js +4 -2
  90. package/dist/auth/signup_routes.d.ts +13 -0
  91. package/dist/auth/signup_routes.d.ts.map +1 -1
  92. package/dist/auth/signup_routes.js +14 -7
  93. package/dist/http/CLAUDE.md +584 -0
  94. package/dist/http/pending_effects.d.ts +29 -0
  95. package/dist/http/pending_effects.d.ts.map +1 -0
  96. package/dist/http/pending_effects.js +31 -0
  97. package/dist/http/route_spec.d.ts.map +1 -1
  98. package/dist/http/route_spec.js +4 -3
  99. package/dist/rate_limiter.d.ts +30 -0
  100. package/dist/rate_limiter.d.ts.map +1 -1
  101. package/dist/rate_limiter.js +25 -2
  102. package/dist/realtime/sse_auth_guard.d.ts +2 -0
  103. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  104. package/dist/realtime/sse_auth_guard.js +5 -3
  105. package/dist/testing/CLAUDE.md +668 -1
  106. package/dist/testing/admin_integration.d.ts +10 -7
  107. package/dist/testing/admin_integration.d.ts.map +1 -1
  108. package/dist/testing/admin_integration.js +382 -482
  109. package/dist/testing/app_server.d.ts +7 -6
  110. package/dist/testing/app_server.d.ts.map +1 -1
  111. package/dist/testing/attack_surface.d.ts +9 -3
  112. package/dist/testing/attack_surface.d.ts.map +1 -1
  113. package/dist/testing/attack_surface.js +4 -4
  114. package/dist/testing/audit_completeness.d.ts +6 -0
  115. package/dist/testing/audit_completeness.d.ts.map +1 -1
  116. package/dist/testing/audit_completeness.js +158 -134
  117. package/dist/testing/auth_apps.d.ts.map +1 -1
  118. package/dist/testing/auth_apps.js +4 -33
  119. package/dist/testing/db.d.ts +1 -1
  120. package/dist/testing/db.d.ts.map +1 -1
  121. package/dist/testing/db.js +2 -0
  122. package/dist/testing/entities.d.ts +35 -13
  123. package/dist/testing/entities.d.ts.map +1 -1
  124. package/dist/testing/entities.js +17 -0
  125. package/dist/testing/integration.d.ts +10 -0
  126. package/dist/testing/integration.d.ts.map +1 -1
  127. package/dist/testing/integration.js +352 -340
  128. package/dist/testing/integration_helpers.d.ts +16 -5
  129. package/dist/testing/integration_helpers.d.ts.map +1 -1
  130. package/dist/testing/integration_helpers.js +24 -4
  131. package/dist/testing/rate_limiting.d.ts +7 -0
  132. package/dist/testing/rate_limiting.d.ts.map +1 -1
  133. package/dist/testing/rate_limiting.js +41 -10
  134. package/dist/testing/rpc_helpers.d.ts +153 -1
  135. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  136. package/dist/testing/rpc_helpers.js +184 -8
  137. package/dist/testing/sse_round_trip.d.ts +8 -0
  138. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  139. package/dist/testing/sse_round_trip.js +10 -3
  140. package/dist/testing/standard.d.ts +9 -1
  141. package/dist/testing/standard.d.ts.map +1 -1
  142. package/dist/testing/standard.js +6 -2
  143. package/dist/testing/surface_invariants.d.ts +7 -3
  144. package/dist/testing/surface_invariants.d.ts.map +1 -1
  145. package/dist/testing/surface_invariants.js +5 -4
  146. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  147. package/dist/testing/ws_round_trip.js +9 -38
  148. package/dist/ui/AccountSessions.svelte +8 -4
  149. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  150. package/dist/ui/AdminAccounts.svelte +61 -33
  151. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  152. package/dist/ui/AdminAuditLog.svelte +3 -2
  153. package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
  154. package/dist/ui/AdminInvites.svelte +3 -2
  155. package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
  156. package/dist/ui/AdminOverview.svelte +14 -9
  157. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  158. package/dist/ui/AdminPermitHistory.svelte +3 -2
  159. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
  160. package/dist/ui/AdminSessions.svelte +29 -25
  161. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  162. package/dist/ui/CLAUDE.md +351 -0
  163. package/dist/ui/OpenSignupToggle.svelte +6 -3
  164. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  165. package/dist/ui/PermitOfferForm.svelte +141 -0
  166. package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
  167. package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
  168. package/dist/ui/PermitOfferHistory.svelte +109 -0
  169. package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
  170. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
  171. package/dist/ui/PermitOfferInbox.svelte +121 -0
  172. package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
  173. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
  174. package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
  175. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  176. package/dist/ui/account_sessions_state.svelte.js +39 -16
  177. package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
  178. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  179. package/dist/ui/admin_accounts_state.svelte.js +99 -23
  180. package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
  181. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  182. package/dist/ui/admin_invites_state.svelte.js +38 -26
  183. package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
  184. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  185. package/dist/ui/admin_sessions_state.svelte.js +35 -21
  186. package/dist/ui/app_settings_state.svelte.d.ts +39 -0
  187. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  188. package/dist/ui/app_settings_state.svelte.js +34 -18
  189. package/dist/ui/audit_log_state.svelte.d.ts +40 -3
  190. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  191. package/dist/ui/audit_log_state.svelte.js +36 -42
  192. package/dist/ui/auth_state.svelte.d.ts +4 -3
  193. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  194. package/dist/ui/auth_state.svelte.js +4 -1
  195. package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
  196. package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
  197. package/dist/ui/permit_offers_state.svelte.js +197 -0
  198. package/package.json +3 -3
  199. package/dist/auth/admin_routes.d.ts +0 -29
  200. package/dist/auth/admin_routes.d.ts.map +0 -1
  201. package/dist/auth/admin_routes.js +0 -226
  202. package/dist/auth/app_settings_routes.d.ts +0 -27
  203. package/dist/auth/app_settings_routes.d.ts.map +0 -1
  204. package/dist/auth/app_settings_routes.js +0 -66
  205. package/dist/auth/invite_routes.d.ts +0 -18
  206. package/dist/auth/invite_routes.d.ts.map +0 -1
  207. package/dist/auth/invite_routes.js +0 -129
@@ -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 warnings for mismatches. In production, returns the handler unchanged.
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.warn(`Output schema mismatch: ${c.req.method} ${c.req.path}`, result.error.issues);
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.warn(`Error schema mismatch (${response.status}): ${c.req.method} ${c.req.path}`, result.error.issues);
171
+ log.error(`Error schema mismatch (${response.status}): ${c.req.method} ${c.req.path}`, result.error.issues);
171
172
  }
172
173
  }
173
174
  catch {
@@ -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;AAIlC;;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;CAC5B;AAED,iFAAiF;AACjF,eAAO,MAAM,2BAA2B,EAAE,kBAIzC,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,gCAAgC,EAAE,kBAI9C,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;;;;;;;;;;GAUG;AACH,qBAAa,WAAW;;IACvB,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;gBAOzB,OAAO,EAAE,kBAAkB;IAWvC,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;IAYvC,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"}
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"}
@@ -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 = new Map();
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
- for (const [key, timestamps] of this.#attempts) {
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 /sessions/:id/revoke handler).
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: 'audit_log',
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('audit_log', { method: event.event_type, params: event });
151
+ registry.broadcast(AUDIT_LOG_CHANNEL, { method: event.event_type, params: event });
150
152
  guard(event);
151
153
  },
152
154
  registry,