@fuzdev/fuz_app 0.106.1 → 0.108.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 (115) hide show
  1. package/dist/actions/CLAUDE.md +42 -23
  2. package/dist/actions/action_bridge.d.ts +34 -3
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +32 -1
  5. package/dist/actions/action_rpc.d.ts +5 -4
  6. package/dist/actions/action_rpc.d.ts.map +1 -1
  7. package/dist/actions/action_rpc.js +4 -3
  8. package/dist/actions/action_spec.d.ts +8 -2
  9. package/dist/actions/action_spec.d.ts.map +1 -1
  10. package/dist/actions/action_spec.js +5 -2
  11. package/dist/actions/compile_action_registry.d.ts +26 -14
  12. package/dist/actions/compile_action_registry.d.ts.map +1 -1
  13. package/dist/actions/compile_action_registry.js +29 -14
  14. package/dist/actions/perform_action.d.ts +26 -16
  15. package/dist/actions/perform_action.d.ts.map +1 -1
  16. package/dist/actions/perform_action.js +69 -44
  17. package/dist/actions/register_action_ws.d.ts +4 -4
  18. package/dist/actions/register_action_ws.js +5 -5
  19. package/dist/actions/register_ws_endpoint.d.ts +13 -5
  20. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  21. package/dist/actions/register_ws_endpoint.js +15 -6
  22. package/dist/auth/CLAUDE.md +16 -9
  23. package/dist/auth/account_route_schema.d.ts +10 -1
  24. package/dist/auth/account_route_schema.d.ts.map +1 -1
  25. package/dist/auth/account_route_schema.js +10 -1
  26. package/dist/auth/account_routes.d.ts +18 -4
  27. package/dist/auth/account_routes.d.ts.map +1 -1
  28. package/dist/auth/account_routes.js +32 -29
  29. package/dist/auth/api_token_queries.d.ts +4 -0
  30. package/dist/auth/api_token_queries.d.ts.map +1 -1
  31. package/dist/auth/api_token_queries.js +5 -1
  32. package/dist/auth/audit_log_route_schema.d.ts.map +1 -1
  33. package/dist/auth/audit_log_route_schema.js +11 -1
  34. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  35. package/dist/auth/audit_log_routes.js +4 -9
  36. package/dist/auth/auth_guard_resolver.d.ts +48 -21
  37. package/dist/auth/auth_guard_resolver.d.ts.map +1 -1
  38. package/dist/auth/auth_guard_resolver.js +60 -26
  39. package/dist/auth/bearer_auth.d.ts +10 -7
  40. package/dist/auth/bearer_auth.d.ts.map +1 -1
  41. package/dist/auth/bearer_auth.js +13 -22
  42. package/dist/auth/bootstrap_routes.d.ts +12 -2
  43. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  44. package/dist/auth/bootstrap_routes.js +15 -9
  45. package/dist/auth/middleware.d.ts +0 -3
  46. package/dist/auth/middleware.d.ts.map +1 -1
  47. package/dist/auth/middleware.js +11 -10
  48. package/dist/auth/migrations.d.ts.map +1 -1
  49. package/dist/auth/migrations.js +24 -0
  50. package/dist/auth/request_context.d.ts +54 -24
  51. package/dist/auth/request_context.d.ts.map +1 -1
  52. package/dist/auth/request_context.js +117 -39
  53. package/dist/auth/session_queries.d.ts +13 -2
  54. package/dist/auth/session_queries.d.ts.map +1 -1
  55. package/dist/auth/session_queries.js +14 -3
  56. package/dist/auth/signup_routes.d.ts +10 -0
  57. package/dist/auth/signup_routes.d.ts.map +1 -1
  58. package/dist/auth/signup_routes.js +14 -11
  59. package/dist/auth/token_scope.d.ts +109 -21
  60. package/dist/auth/token_scope.d.ts.map +1 -1
  61. package/dist/auth/token_scope.js +106 -21
  62. package/dist/http/CLAUDE.md +23 -11
  63. package/dist/http/auth_shape.d.ts +58 -3
  64. package/dist/http/auth_shape.d.ts.map +1 -1
  65. package/dist/http/auth_shape.js +63 -5
  66. package/dist/http/error_schemas.d.ts +25 -2
  67. package/dist/http/error_schemas.d.ts.map +1 -1
  68. package/dist/http/error_schemas.js +37 -15
  69. package/dist/http/route_spec.d.ts +29 -26
  70. package/dist/http/route_spec.d.ts.map +1 -1
  71. package/dist/http/route_spec.js +12 -16
  72. package/dist/http/surface_query.d.ts +2 -2
  73. package/dist/http/surface_query.js +2 -2
  74. package/dist/rate_limiter.d.ts +36 -3
  75. package/dist/rate_limiter.d.ts.map +1 -1
  76. package/dist/rate_limiter.js +36 -3
  77. package/dist/server/app_server.d.ts +27 -10
  78. package/dist/server/app_server.d.ts.map +1 -1
  79. package/dist/server/app_server.js +30 -20
  80. package/dist/server/app_server_context.d.ts +10 -2
  81. package/dist/server/app_server_context.d.ts.map +1 -1
  82. package/dist/server/serve_fact_route.d.ts.map +1 -1
  83. package/dist/server/serve_fact_route.js +12 -10
  84. package/dist/testing/CLAUDE.md +60 -11
  85. package/dist/testing/app_server.d.ts.map +1 -1
  86. package/dist/testing/app_server.js +6 -4
  87. package/dist/testing/cross_backend/conformance_case.js +1 -1
  88. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  89. package/dist/testing/cross_backend/default_spine_surface.js +9 -8
  90. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  91. package/dist/testing/cross_backend/login_security.js +59 -0
  92. package/dist/testing/cross_backend/session_cap.d.ts +19 -0
  93. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -0
  94. package/dist/testing/cross_backend/session_cap.js +93 -0
  95. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  96. package/dist/testing/cross_backend/testing_backdoor.js +6 -5
  97. package/dist/testing/cross_backend/token_scope_surface.d.ts +9 -0
  98. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -0
  99. package/dist/testing/cross_backend/token_scope_surface.js +170 -0
  100. package/dist/testing/integration.d.ts.map +1 -1
  101. package/dist/testing/integration.js +3 -2
  102. package/dist/testing/middleware.d.ts +2 -5
  103. package/dist/testing/middleware.d.ts.map +1 -1
  104. package/dist/testing/middleware.js +5 -5
  105. package/dist/testing/rate_limiting.d.ts +10 -16
  106. package/dist/testing/rate_limiting.d.ts.map +1 -1
  107. package/dist/testing/rate_limiting.js +11 -95
  108. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  109. package/dist/testing/rpc_attack_surface.js +4 -2
  110. package/dist/testing/stubs.d.ts.map +1 -1
  111. package/dist/testing/stubs.js +3 -1
  112. package/dist/testing/testing_rate_limiter.d.ts +7 -1
  113. package/dist/testing/testing_rate_limiter.d.ts.map +1 -1
  114. package/dist/testing/testing_rate_limiter.js +7 -1
  115. package/package.json +1 -1
@@ -19,7 +19,7 @@ import '../assert_dev_env.js';
19
19
  *
20
20
  * For every backdoor method, three principals:
21
21
  *
22
- * - **anonymous** (no credential) → `401` (pre-validation auth refuses an
22
+ * - **anonymous** (no credential) → `401` (pre-authorization auth refuses an
23
23
  * account-less caller before anything else).
24
24
  * - **session** (the keeper's browser-context cookie) → `403`
25
25
  * `credential_type_required` — a session cookie, even one carrying the
@@ -28,10 +28,11 @@ import '../assert_dev_env.js';
28
28
  * `credential_type_required` — same ceiling; an api token cannot reach
29
29
  * keeper operations.
30
30
  *
31
- * Each method is sent with **valid** params so the session/bearer cases
32
- * clear the dispatcher's input-validation (400) phase and actually reach the
33
- * post-authorization credential gate (the order is 401 → 400 → 403); the
34
- * handler never runs (the gate refuses first), so the writes never execute.
31
+ * Each method is sent with **valid** params so each case varies only the
32
+ * credential. (Validation runs after the gates — the order is 401 → authz →
33
+ * 403 → 400 — so the shapes are belt-and-suspenders here rather than load-
34
+ * bearing.) The handler never runs (the gate refuses first), so the writes
35
+ * never execute.
35
36
  *
36
37
  * Complements the spec-level gate check (which pins that each spec *declares*
37
38
  * `credential_types: ['daemon_token']`) and the surface-absence invariant
@@ -0,0 +1,9 @@
1
+ import '../assert_dev_env.js';
2
+ import type { RpcPathCrossSuiteOptions } from './setup.js';
3
+ /** Options for the token-scope surface parity suite. */
4
+ export interface TokenScopeSurfaceCrossTestOptions extends RpcPathCrossSuiteOptions {
5
+ /** Audit-log SSE path. Default `/api/admin/audit/stream`. */
6
+ readonly sse_path?: string;
7
+ }
8
+ export declare const describe_token_scope_surface_cross_tests: (options: TokenScopeSurfaceCrossTestOptions) => void;
9
+ //# sourceMappingURL=token_scope_surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token_scope_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/token_scope_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiE9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAkB3D,wDAAwD;AACxD,MAAM,WAAW,iCAAkC,SAAQ,wBAAwB;IAClF,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AA2DD,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IAwHF,CAAC"}
@@ -0,0 +1,170 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend parity suite for **token scoping's non-RPC surface rule** —
4
+ * *a narrowed api token is RPC-only*.
5
+ *
6
+ * ## Why this suite exists
7
+ *
8
+ * Every existing cross-impl token-scope assertion is the `rpc:<method>` arm
9
+ * (the `token_scope_cases` batch in the conformance security slate). The
10
+ * `surface:<name>` arm had **none** — and it is the load-bearing half of the
11
+ * design. Both spines implement it, at the same named surfaces, returning a
12
+ * denial body that is an explicit cross-impl contract
13
+ * (`token_scope_denied_body` ↔ `fuz_auth::token_scope_surface_denied_response`),
14
+ * and nothing compared the two.
15
+ *
16
+ * That gap has the shape this control family keeps finding: the per-method arm
17
+ * was the one everybody looked at, so it was the one that got a gate. The
18
+ * surface arm is where a divergence could sit indefinitely — the schema gate
19
+ * can't see it (no column), the action-manifest gate can't see it (no method),
20
+ * and the spec-derived suites can't see it (these are REST routes, not the
21
+ * declared RPC surface).
22
+ *
23
+ * ## What it pins
24
+ *
25
+ * - **The denial body, byte for byte** — status, `error`, and the exact
26
+ * `required_scope` capability string, per surface. A spine that renamed a
27
+ * surface, dropped the `surface:` prefix, or emitted the JSON-RPC envelope
28
+ * from a REST route fails here.
29
+ * - **Scope outranks role** — a narrowed token whose account *also* lacks the
30
+ * gating role must hear `token_scope_required`, not
31
+ * `insufficient_permissions`. This is the observable the TS↔Rust ordering
32
+ * divergence produced before both spines converged on running the surface
33
+ * gate ahead of the role gate; it was explicitly *invisible* to the
34
+ * cross-backend suites, because every scoped-token probe used a token whose
35
+ * account happened to hold the role.
36
+ *
37
+ * Two non-vacuity controls hold the negatives honest: the narrowed token must
38
+ * still reach the RPC method it *does* name (otherwise a spine that rejected
39
+ * the credential outright would pass every denial), and a **full** bearer must
40
+ * reach the same surface (otherwise a route broken for all bearers would pass
41
+ * too).
42
+ *
43
+ * ## Why the WS upgrade isn't here
44
+ *
45
+ * Rule 3's third probeable surface is the WebSocket upgrade, and it is
46
+ * deliberately omitted: `create_ws_transport` threads cookies, not bearer
47
+ * headers, and a refused upgrade surfaces as a thrown connection error whose
48
+ * body — the thing this suite exists to compare — isn't readable. The two HTTP
49
+ * surfaces are where the denial body is observable, which is what the parity
50
+ * gate needs. The WS gate keeps its per-spine coverage (fuz_app's surface
51
+ * census pins the call site; the Rust census pins its own).
52
+ *
53
+ * `$lib`-free by contract (relative specifiers only), like the sibling
54
+ * cross-backend suites.
55
+ *
56
+ * @module
57
+ */
58
+ import { describe, assert } from 'vitest';
59
+ import { account_token_create_action_spec } from '../../auth/account_action_specs.js';
60
+ import { ERROR_TOKEN_SCOPE_REQUIRED } from '../../http/error_schemas.js';
61
+ import { test_if } from './capabilities.js';
62
+ import { cross_rpc_call } from './cell_cross_helpers.js';
63
+ import { SPINE_RPC_PATH } from './spine_surface_constants.js';
64
+ /** Default audit-log SSE path — the `audit_stream` surface. */
65
+ const DEFAULT_SSE_PATH = '/api/admin/audit/stream';
66
+ /**
67
+ * A well-formed hash that no fact has. The scope gate is a pre-authorization
68
+ * guard, so it fires before the handler ever looks the hash up — but *params*
69
+ * validation runs ahead of every auth gate (it is part of addressing the
70
+ * route), so the value still has to parse.
71
+ */
72
+ const ABSENT_FACT_HASH = `blake3:${'0'.repeat(64)}`;
73
+ /** The single RPC method the narrowed tokens in this suite admit. */
74
+ const ADMITTED_METHOD = 'account_verify';
75
+ /** The flat denial body both spines must return, for a given surface. */
76
+ const surface_denial_body = (surface) => ({
77
+ error: ERROR_TOKEN_SCOPE_REQUIRED,
78
+ required_scope: `surface:${surface}`
79
+ });
80
+ /**
81
+ * Mint a narrowed bearer token over a session, through the production
82
+ * `account_token_create` path.
83
+ *
84
+ * Deliberately not a fixture-level credential: minting it here means the token
85
+ * is produced by the same handler a real minter would use, on whichever
86
+ * account the caller chose — which is what lets the ordering case put a
87
+ * narrowed token on an account that lacks the gating role.
88
+ */
89
+ const mint_narrowed_token = async (transport, rpc_path, session_headers) => {
90
+ const res = await cross_rpc_call(transport, rpc_path, account_token_create_action_spec.method, { scope: { kind: 'methods', methods: [ADMITTED_METHOD] } }, session_headers);
91
+ assert.ok(res.ok, `minting a narrowed token failed: ${JSON.stringify(res.error)}`);
92
+ const token = res.result?.token;
93
+ assert.ok(typeof token === 'string' && token.length > 0, 'mint returned no token');
94
+ return token;
95
+ };
96
+ /**
97
+ * Assert a bearer GET is refused with the canonical surface denial.
98
+ *
99
+ * `origin: null` so the bearer credential isn't discarded as browser context,
100
+ * and a fresh jar so no session cookie rides along and answers instead.
101
+ */
102
+ const assert_surface_denied = async (fresh_transport, path, token, surface) => {
103
+ const res = await fresh_transport({ origin: null })(path, {
104
+ headers: { authorization: `Bearer ${token}` }
105
+ });
106
+ assert.strictEqual(res.status, 403, `${path} must refuse a narrowed token with 403`);
107
+ const body = await res.json();
108
+ assert.deepStrictEqual(body, surface_denial_body(surface), `${path} must return the canonical flat surface denial`);
109
+ };
110
+ export const describe_token_scope_surface_cross_tests = (options) => {
111
+ const { setup_test, capabilities } = options;
112
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
113
+ const sse_path = options.sse_path ?? DEFAULT_SSE_PATH;
114
+ describe('token scope non-RPC surface parity', () => {
115
+ /**
116
+ * The non-vacuity control for every denial below: a narrowed token is
117
+ * still a working credential. Without this, a spine that rejected the
118
+ * token outright — or never minted it — would pass all four negatives.
119
+ */
120
+ test_if(true, 'a narrowed token still reaches the method it names', async () => {
121
+ const fixture = await setup_test();
122
+ const token = await mint_narrowed_token(fixture.transport, rpc_path, fixture.create_session_headers());
123
+ const res = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, ADMITTED_METHOD, undefined, { authorization: `Bearer ${token}` });
124
+ assert.ok(res.ok, `admitted method must succeed: ${JSON.stringify(res.error)}`);
125
+ });
126
+ test_if(capabilities.sse, 'a narrowed token is refused the audit stream (surface:audit_stream)', async () => {
127
+ const fixture = await setup_test();
128
+ const token = await mint_narrowed_token(fixture.transport, rpc_path, fixture.create_session_headers());
129
+ await assert_surface_denied(fixture.fresh_transport, sse_path, token, 'audit_stream');
130
+ });
131
+ test_if(capabilities.fact_serving, 'a narrowed token is refused the bare-hash fact read (surface:fact_bare)', async () => {
132
+ const fixture = await setup_test();
133
+ const token = await mint_narrowed_token(fixture.transport, rpc_path, fixture.create_session_headers());
134
+ await assert_surface_denied(fixture.fresh_transport, `/api/facts/${ABSENT_FACT_HASH}`, token, 'fact_bare');
135
+ });
136
+ /**
137
+ * The surface-level non-vacuity control. A **full** bearer on the same
138
+ * route must get past the scope gate — proving the denial above is the
139
+ * scope refusing a narrowed token, not the route refusing every bearer.
140
+ * A 404 (no such fact) is the expected outcome; anything that isn't the
141
+ * scope denial satisfies the property, so the assertion is on what the
142
+ * response must *not* be.
143
+ */
144
+ test_if(capabilities.fact_serving, 'a full token is not refused by the scope gate on the same route', async () => {
145
+ const fixture = await setup_test();
146
+ const res = await fixture.fresh_transport({ origin: null })(`/api/facts/${ABSENT_FACT_HASH}`, { headers: fixture.create_bearer_headers() });
147
+ const body = (await res.json().catch(() => undefined));
148
+ assert.notStrictEqual(body?.error, ERROR_TOKEN_SCOPE_REQUIRED, 'a full-authority bearer must not be refused on scope grounds');
149
+ });
150
+ /**
151
+ * **Scope outranks role**, and this is the case that had no cross-impl
152
+ * pin while the two spines disagreed about it.
153
+ *
154
+ * The surfaces are role-gated, so a narrowed token on an account without
155
+ * the role could be refused by either gate. The scope gate must answer:
156
+ * it is the coarser fact about a credential that was never going to be
157
+ * admitted, and `insufficient_permissions` would instead report on the
158
+ * deployment's role structure to that credential. Every other
159
+ * scoped-token probe in the slate uses the keeper's token, whose account
160
+ * *holds* the role — so both orderings look identical there, which is
161
+ * exactly why this divergence survived as long as it did.
162
+ */
163
+ test_if(capabilities.fact_serving, 'scope outranks role — a narrowed token lacking the role hears about its scope', async () => {
164
+ const fixture = await setup_test();
165
+ const non_admin = await fixture.create_account({ username: 'scope_before_role' });
166
+ const token = await mint_narrowed_token(fixture.fresh_transport(), rpc_path, non_admin.create_session_headers());
167
+ await assert_surface_denied(fixture.fresh_transport, `/api/facts/${ABSENT_FACT_HASH}`, token, 'fact_bare');
168
+ });
169
+ });
170
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IA66CF,CAAC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IA86CF,CAAC"}
@@ -685,8 +685,9 @@ export const describe_standard_integration_tests = (options) => {
685
685
  // Pick an admin REST route where a clean role-denial 403 is
686
686
  // reachable: account-grain (`actor: 'none'`, so the authorization
687
687
  // phase doesn't first demand an acting actor → `actor_required`)
688
- // and input/param/query-free (so input validation doesn't 400
689
- // first, per the 401 → 400 → 403 phase order). Admin surfaces are
688
+ // and input/param/query-free (params and query still validate ahead
689
+ // of the auth phase — only body validation moved behind the gates,
690
+ // per the 401 → authz → 403 → 400 order). Admin surfaces are
690
691
  // RPC-first now; a consumer may expose no such REST route (fuz_app
691
692
  // itself, zzz) — then this REST-era check is a no-op and RPC denial
692
693
  // is covered by `describe_rpc_attack_surface_tests`.
@@ -11,7 +11,6 @@ import './assert_dev_env.js';
11
11
  import { vi } from 'vitest';
12
12
  import { Hono } from 'hono';
13
13
  import type { z } from 'zod';
14
- import type { RateLimiter } from '../rate_limiter.js';
15
14
  import { type RequestContext } from '../auth/request_context.js';
16
15
  /** Mock configuration for bearer auth middleware test setup. */
17
16
  export interface BearerAuthTestOptions {
@@ -80,7 +79,7 @@ export declare const TEST_CLIENT_IP = "127.0.0.1";
80
79
  * The route handler at `/api/test` returns the resolved context in the response body,
81
80
  * enabling assertions on `REQUEST_CONTEXT_KEY` and `CREDENTIAL_TYPE_KEY`.
82
81
  */
83
- export declare const create_bearer_auth_test_app: (tc: BearerAuthTestOptions, ip_rate_limiter?: RateLimiter | null) => {
82
+ export declare const create_bearer_auth_test_app: (tc: BearerAuthTestOptions) => {
84
83
  app: Hono;
85
84
  mocks: BearerAuthMocks;
86
85
  };
@@ -89,7 +88,7 @@ export declare const create_bearer_auth_test_app: (tc: BearerAuthTestOptions, ip
89
88
  *
90
89
  * Generates one `test()` per case inside a `describe()` block.
91
90
  */
92
- export declare const describe_bearer_auth_cases: (suite_name: string, cases: Array<BearerAuthTestCase>, ip_rate_limiter?: RateLimiter | null) => void;
91
+ export declare const describe_bearer_auth_cases: (suite_name: string, cases: Array<BearerAuthTestCase>) => void;
93
92
  /** Path used by the echo route in `create_test_middleware_stack_app`. */
94
93
  export declare const TEST_MIDDLEWARE_PATH = "/api/test";
95
94
  /** Options for `create_test_middleware_stack_app`. */
@@ -100,8 +99,6 @@ export interface TestMiddlewareStackOptions {
100
99
  allowed_origins?: string;
101
100
  /** Connection IP or factory. @default first trusted proxy */
102
101
  connection_ip?: string | (() => string | undefined);
103
- /** Rate limiter for bearer auth. @default `null` */
104
- ip_rate_limiter?: RateLimiter | null;
105
102
  }
106
103
  /** Return type of `create_test_middleware_stack_app`. */
107
104
  export interface TestMiddlewareStackApp {
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/middleware.ts"],"names":[],"mappings":"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"}
@@ -91,9 +91,9 @@ export const TEST_CLIENT_IP = '127.0.0.1';
91
91
  * The route handler at `/api/test` returns the resolved context in the response body,
92
92
  * enabling assertions on `REQUEST_CONTEXT_KEY` and `CREDENTIAL_TYPE_KEY`.
93
93
  */
94
- export const create_bearer_auth_test_app = (tc, ip_rate_limiter = null) => {
94
+ export const create_bearer_auth_test_app = (tc) => {
95
95
  const mocks = create_bearer_auth_mocks(tc);
96
- const bearer_middleware = create_bearer_auth_middleware(STUB_DEPS, ip_rate_limiter, new Logger('test', { level: 'off' }));
96
+ const bearer_middleware = create_bearer_auth_middleware(STUB_DEPS, new Logger('test', { level: 'off' }));
97
97
  const app = new Hono();
98
98
  // inject pre-existing session identity if the test case specifies one.
99
99
  // `pre_context` simulates the session middleware having authenticated
@@ -144,11 +144,11 @@ export const create_bearer_auth_test_app = (tc, ip_rate_limiter = null) => {
144
144
  *
145
145
  * Generates one `test()` per case inside a `describe()` block.
146
146
  */
147
- export const describe_bearer_auth_cases = (suite_name, cases, ip_rate_limiter = null) => {
147
+ export const describe_bearer_auth_cases = (suite_name, cases) => {
148
148
  describe(suite_name, () => {
149
149
  for (const tc of cases) {
150
150
  test(tc.name, async () => {
151
- const { app, mocks } = create_bearer_auth_test_app(tc, ip_rate_limiter);
151
+ const { app, mocks } = create_bearer_auth_test_app(tc);
152
152
  const res = await app.request('/api/test', {
153
153
  method: 'GET',
154
154
  headers: tc.headers
@@ -227,7 +227,7 @@ export const create_test_middleware_stack_app = (options) => {
227
227
  });
228
228
  const allowed_patterns = parse_allowed_origins(allowed_origins_str);
229
229
  const origin_mw = verify_request_source(allowed_patterns);
230
- const bearer_mw = create_bearer_auth_middleware(STUB_DEPS, options?.ip_rate_limiter ?? null, new Logger('test', { level: 'off' }));
230
+ const bearer_mw = create_bearer_auth_middleware(STUB_DEPS, new Logger('test', { level: 'off' }));
231
231
  const app = new Hono();
232
232
  app.use('*', proxy_mw);
233
233
  app.use('/api/*', origin_mw);
@@ -4,7 +4,7 @@ import type { AppServerContext } from '../server/app_server_context.js';
4
4
  import type { RouteSpec } from '../http/route_spec.js';
5
5
  import { type SuiteAppOptions } from './app_server.js';
6
6
  import { type DbFactory } from './db.js';
7
- import { type RpcEndpointsSuiteOption } from './rpc_helpers.js';
7
+ import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
8
8
  /**
9
9
  * Configuration for `describe_rate_limiting_tests`.
10
10
  */
@@ -25,37 +25,31 @@ export interface RateLimitingTestOptions {
25
25
  */
26
26
  max_attempts?: number;
27
27
  /**
28
- * RPC endpoint specs — required so the bearer-auth rate limiting test
29
- * can probe an authenticated method via the `account_verify` RPC
30
- * action. Hard-fails via `require_rpc_endpoint_path` on setup.
28
+ * RPC endpoint specs mounted on the test app. Optional — both suites below
29
+ * exercise REST auth routes, so nothing here needs an RPC path.
31
30
  *
32
31
  * Accepts either an array (eager) or a factory
33
32
  * `(ctx: AppServerContext) => Array<RpcEndpointSpec>` — the factory form
34
33
  * is required when action handlers must close over the per-test
35
- * `ctx.deps`. The factory must return the same
36
- * endpoint `path` regardless of ctx — it is invoked once at setup with
37
- * a stub ctx for path lookup and again per-test by `create_app_server`
38
- * for live dispatch.
34
+ * `ctx.deps`.
39
35
  */
40
- rpc_endpoints: RpcEndpointsSuiteOption;
36
+ rpc_endpoints?: RpcEndpointsSuiteOption;
41
37
  }
42
38
  /**
43
39
  * Standard rate limiting integration test suite.
44
40
  *
45
- * Creates 3 test groups:
41
+ * Creates 2 test groups:
46
42
  * 1. IP rate limiting on login — fires `max_attempts + 1` login requests,
47
43
  * verifies the last returns 429 with a valid `RateLimitError` body.
48
44
  * 2. Per-account rate limiting on login — fires `max_attempts + 1` login
49
45
  * requests with the same username, verifies the last returns 429.
50
- * 3. Bearer auth IP rate limiting — fires `max_attempts + 1` bearer requests
51
- * with an invalid token, verifies the last returns 429.
46
+ *
47
+ * There is deliberately no bearer-auth group: the bearer path carries no rate
48
+ * limiter on either spine, because an API token's entropy — not throttling —
49
+ * is what bounds guessing it. See `auth/bearer_auth.ts`.
52
50
  *
53
51
  * Each test group asserts that required routes exist, failing with a descriptive
54
52
  * message if the consumer's route specs are misconfigured.
55
- *
56
- * @throws Error at setup time when `options.rpc_endpoints` is empty — the
57
- * bearer-auth rate-limit test probes via the `account_verify` RPC action,
58
- * so the suite hard-fails via `require_rpc_endpoint_path`.
59
53
  */
60
54
  export declare const describe_rate_limiting_tests: (options: RateLimitingTestOptions) => void;
61
55
  //# sourceMappingURL=rate_limiting.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate_limiting.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rate_limiting.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA0B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAEjB,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAK1B;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,uBAAuB,KAAG,IAgQ/E,CAAC"}
1
+ {"version":3,"file":"rate_limiting.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rate_limiting.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA0B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAIhE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACxC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,uBAAuB,KAAG,IAqK/E,CAAC"}
@@ -27,35 +27,25 @@ import { auth_migration_ns } from '../auth/migrations.js';
27
27
  import { create_test_app } from './app_server.js';
28
28
  import { create_pglite_factory, create_describe_db, auth_integration_truncate_tables } from './db.js';
29
29
  import { find_auth_route, assert_rate_limit_retry_after_header } from './integration_helpers.js';
30
- import { rpc_call_non_browser, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup } from './rpc_helpers.js';
31
30
  import { run_migrations } from '../db/migrate.js';
32
- import { account_verify_action_spec } from '../auth/account_action_specs.js';
33
31
  /**
34
32
  * Standard rate limiting integration test suite.
35
33
  *
36
- * Creates 3 test groups:
34
+ * Creates 2 test groups:
37
35
  * 1. IP rate limiting on login — fires `max_attempts + 1` login requests,
38
36
  * verifies the last returns 429 with a valid `RateLimitError` body.
39
37
  * 2. Per-account rate limiting on login — fires `max_attempts + 1` login
40
38
  * requests with the same username, verifies the last returns 429.
41
- * 3. Bearer auth IP rate limiting — fires `max_attempts + 1` bearer requests
42
- * with an invalid token, verifies the last returns 429.
39
+ *
40
+ * There is deliberately no bearer-auth group: the bearer path carries no rate
41
+ * limiter on either spine, because an API token's entropy — not throttling —
42
+ * is what bounds guessing it. See `auth/bearer_auth.ts`.
43
43
  *
44
44
  * Each test group asserts that required routes exist, failing with a descriptive
45
45
  * message if the consumer's route specs are misconfigured.
46
- *
47
- * @throws Error at setup time when `options.rpc_endpoints` is empty — the
48
- * bearer-auth rate-limit test probes via the `account_verify` RPC action,
49
- * so the suite hard-fails via `require_rpc_endpoint_path`.
50
46
  */
51
47
  export const describe_rate_limiting_tests = (options) => {
52
48
  const max_attempts = options.max_attempts ?? 2;
53
- // Hard-fail early so consumers see a clear setup error instead of a
54
- // confusing test failure when `rpc_endpoints` is missing. Factory-form
55
- // callers are resolved with a stub ctx purely to extract the endpoint
56
- // path; real handlers run per-test via the top-level `rpc_endpoints` slot on `CreateTestAppOptions`.
57
- const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
58
- const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
59
49
  const init_schema = async (db) => {
60
50
  await run_migrations(db, [auth_migration_ns]);
61
51
  };
@@ -67,7 +57,7 @@ export const describe_rate_limiting_tests = (options) => {
67
57
  // --- 1. IP rate limiting on login ---
68
58
  describe('IP rate limiting on login', () => {
69
59
  test(`login is blocked after ${max_attempts} failed attempts`, async () => {
70
- const ip_rate_limiter = create_test_rate_limiter();
60
+ const login_ip_rate_limiter = create_test_rate_limiter();
71
61
  try {
72
62
  const test_app = await create_test_app({
73
63
  session_options: options.session_options,
@@ -76,9 +66,8 @@ export const describe_rate_limiting_tests = (options) => {
76
66
  rpc_endpoints: options.rpc_endpoints,
77
67
  app_options: {
78
68
  ...options.app_options,
79
- ip_rate_limiter,
80
- login_account_rate_limiter: null,
81
- bearer_ip_rate_limiter: null
69
+ login_ip_rate_limiter,
70
+ login_account_rate_limiter: null
82
71
  }
83
72
  });
84
73
  const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
@@ -113,7 +102,7 @@ export const describe_rate_limiting_tests = (options) => {
113
102
  assert_rate_limit_retry_after_header(blocked_res, body);
114
103
  }
115
104
  finally {
116
- ip_rate_limiter.dispose();
105
+ login_ip_rate_limiter.dispose();
117
106
  }
118
107
  });
119
108
  });
@@ -129,9 +118,8 @@ export const describe_rate_limiting_tests = (options) => {
129
118
  rpc_endpoints: options.rpc_endpoints,
130
119
  app_options: {
131
120
  ...options.app_options,
132
- ip_rate_limiter: null,
133
- login_account_rate_limiter,
134
- bearer_ip_rate_limiter: null
121
+ login_ip_rate_limiter: null,
122
+ login_account_rate_limiter
135
123
  }
136
124
  });
137
125
  const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
@@ -182,77 +170,5 @@ export const describe_rate_limiting_tests = (options) => {
182
170
  }
183
171
  });
184
172
  });
185
- // --- 3. Bearer auth IP rate limiting ---
186
- describe('bearer auth IP rate limiting', () => {
187
- test(`bearer auth is blocked after ${max_attempts} invalid token attempts`, async () => {
188
- const bearer_ip_rate_limiter = create_test_rate_limiter();
189
- try {
190
- const test_app = await create_test_app({
191
- session_options: options.session_options,
192
- create_route_specs: options.create_route_specs,
193
- db: get_db(),
194
- rpc_endpoints: options.rpc_endpoints,
195
- app_options: {
196
- ...options.app_options,
197
- ip_rate_limiter: null,
198
- login_account_rate_limiter: null,
199
- bearer_ip_rate_limiter
200
- }
201
- });
202
- // Probe `account_verify` via RPC with an invalid bearer token.
203
- // The REST `/api/account/verify` shim is status-only (empty body
204
- // for nginx `auth_request`), so we use the RPC surface to exercise
205
- // a typed authenticated method. The bearer_auth rate limiter
206
- // increments per attempt regardless of the route's own auth outcome.
207
- // Use `rpc_call_non_browser` so the default `origin` header is
208
- // suppressed — bearer_auth discards the token when Origin or
209
- // Referer is present (browser context), which would short-circuit
210
- // before the rate limiter records the attempt.
211
- //
212
- // Note: the rate limiter short-circuits before the RPC dispatcher,
213
- // so the 429 response is a REST-shaped `RateLimitError`, not a
214
- // JSON-RPC envelope. We use the underlying `app.request` for the
215
- // blocked probe so `rpc_call_non_browser` doesn't throw on the
216
- // non-envelope body.
217
- const bearer_probe_headers = {
218
- authorization: 'Bearer secret_fuz_token_invalid'
219
- };
220
- // Fire max_attempts invalid bearer requests (sequential — must exhaust the window)
221
- for (let i = 0; i < max_attempts; i++) {
222
- const res = await rpc_call_non_browser({
223
- app: test_app.app,
224
- path: rpc_path,
225
- method: account_verify_action_spec.method,
226
- id: 'rl-probe',
227
- headers: bearer_probe_headers
228
- });
229
- assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
230
- }
231
- // The next request should be rate limited. The 429 body is REST-shape
232
- // (middleware short-circuits before the RPC dispatcher), so go
233
- // direct — `rpc_call_non_browser` would throw on the non-envelope body.
234
- const blocked_res = await test_app.app.request(rpc_path, {
235
- method: 'POST',
236
- headers: {
237
- host: 'localhost',
238
- 'content-type': 'application/json',
239
- ...bearer_probe_headers
240
- },
241
- body: JSON.stringify({
242
- jsonrpc: '2.0',
243
- method: account_verify_action_spec.method,
244
- id: 'rl-probe-blocked'
245
- })
246
- });
247
- assert.strictEqual(blocked_res.status, 429);
248
- const body = await blocked_res.json();
249
- RateLimitError.parse(body);
250
- assert_rate_limit_retry_after_header(blocked_res, body);
251
- }
252
- finally {
253
- bearer_ip_rate_limiter.dispose();
254
- }
255
- });
256
- });
257
173
  });
258
174
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rpc_attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,OAAO,KAAK,EAGX,cAAc,EACd,MAAM,oBAAoB,CAAC;AA0B5B,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACvC,+FAA+F;IAC/F,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AA4dD;;;;;;;;;GASG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,uBAAuB,KAAG,IAOpF,CAAC"}
1
+ {"version":3,"file":"rpc_attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,OAAO,KAAK,EAGX,cAAc,EACd,MAAM,oBAAoB,CAAC;AA0B5B,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACvC,+FAA+F;IAC/F,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AA8dD;;;;;;;;;GASG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,uBAAuB,KAAG,IAOpF,CAAC"}
@@ -83,8 +83,10 @@ const describe_rpc_auth = (options) => {
83
83
  const app = apps.by_role.get(wrong_role);
84
84
  if (!app)
85
85
  throw new Error(`No test app for role '${wrong_role}'`);
86
- // Send valid params so we trip the role gate (403), not input
87
- // validation (400). Dispatcher ordering: 401 → 400 → 403.
86
+ // Send valid params so the probe varies only the role. Dispatcher
87
+ // ordering is 401 → authz → 403 → 400, so the role gate trips
88
+ // ahead of validation regardless; valid params also keep the
89
+ // authorization phase from answering `actor_required` first.
88
90
  const action = find_rpc_action(rpc_endpoint_specs, endpoint.path, method.name);
89
91
  const params = action ? generate_valid_body(action.spec.input) : undefined;
90
92
  const res = await app.request(endpoint.path, create_rpc_post_init(method.name, params));
@@ -1 +1 @@
1
- {"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
@@ -199,7 +199,9 @@ export const create_stub_app_server_context = (session_options) => {
199
199
  },
200
200
  bootstrap_status: { available: false, token_path: null },
201
201
  session_options,
202
- ip_rate_limiter: null,
202
+ login_ip_rate_limiter: null,
203
+ signup_ip_rate_limiter: null,
204
+ bootstrap_ip_rate_limiter: null,
203
205
  login_account_rate_limiter: null,
204
206
  signup_account_rate_limiter: null,
205
207
  action_ip_rate_limiter: null,
@@ -20,7 +20,7 @@ import './assert_dev_env.js';
20
20
  * import {TestingRateLimiter} from '@fuzdev/fuz_app/testing/testing_rate_limiter.ts';
21
21
  *
22
22
  * const limiter = new TestingRateLimiter(default_login_ip_rate_limit);
23
- * await create_app_server({backend, ip_rate_limiter: limiter, ...});
23
+ * await create_app_server({backend, login_ip_rate_limiter: limiter, ...});
24
24
  *
25
25
  * // Inside the `_testing_reset` handler's `reset_state`:
26
26
  * limiter.reset_all();
@@ -45,6 +45,12 @@ export declare class TestingRateLimiter extends RateLimiter {
45
45
  * safe to call before any check/record activity. Designed to be invoked
46
46
  * from a `_testing_reset` handler's `reset_state` callback so the test
47
47
  * binary's rate-limit buckets don't leak across test cases.
48
+ *
49
+ * This is the **one sanctioned caller that clears IP-aggregate keys** —
50
+ * `RateLimiter.reset` forbids that for production code, because refunding
51
+ * the IP bucket on a successful auth reopens the distributed-spray path.
52
+ * Here it is test-state teardown between cases, not success-forgiveness, so
53
+ * the rule doesn't apply.
48
54
  */
49
55
  reset_all(): void;
50
56
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"testing_rate_limiter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/testing_rate_limiter.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEvE;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;;IAGzC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe;IAKjD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe;IAKlD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKjC;;;;;OAKG;IACH,SAAS,IAAI,IAAI;IAOjB;;;;;;OAMG;IACH,IAAI,YAAY,IAAI,WAAW,CAAC,MAAM,CAAC,CAEtC;CACD"}
1
+ {"version":3,"file":"testing_rate_limiter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/testing_rate_limiter.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEvE;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;;IAGzC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe;IAKjD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe;IAKlD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKjC;;;;;;;;;;;OAWG;IACH,SAAS,IAAI,IAAI;IAOjB;;;;;;OAMG;IACH,IAAI,YAAY,IAAI,WAAW,CAAC,MAAM,CAAC,CAEtC;CACD"}