@fuzdev/fuz_app 0.145.0 → 0.146.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 (59) hide show
  1. package/dist/actions/CLAUDE.md +7 -6
  2. package/dist/auth/CLAUDE.md +5 -2
  3. package/dist/auth/account_action_specs.d.ts.map +1 -1
  4. package/dist/auth/account_action_specs.js +2 -1
  5. package/dist/auth/account_route_schema.d.ts.map +1 -1
  6. package/dist/auth/account_route_schema.js +9 -2
  7. package/dist/auth/account_routes.d.ts +8 -0
  8. package/dist/auth/account_routes.d.ts.map +1 -1
  9. package/dist/auth/account_routes.js +12 -3
  10. package/dist/auth/request_context.d.ts +23 -0
  11. package/dist/auth/request_context.d.ts.map +1 -1
  12. package/dist/auth/request_context.js +32 -1
  13. package/dist/auth/token_scope.d.ts +22 -14
  14. package/dist/auth/token_scope.d.ts.map +1 -1
  15. package/dist/auth/token_scope.js +23 -14
  16. package/dist/http/CLAUDE.md +2 -2
  17. package/dist/http/auth_shape.d.ts.map +1 -1
  18. package/dist/http/auth_shape.js +11 -6
  19. package/dist/http/proxy.d.ts +22 -21
  20. package/dist/http/proxy.d.ts.map +1 -1
  21. package/dist/http/proxy.js +28 -33
  22. package/dist/http/route_spec.d.ts +8 -0
  23. package/dist/http/route_spec.d.ts.map +1 -1
  24. package/dist/server/env.d.ts.map +1 -1
  25. package/dist/server/env.js +1 -1
  26. package/dist/server/fact_security_headers.d.ts +5 -0
  27. package/dist/server/fact_security_headers.d.ts.map +1 -1
  28. package/dist/server/fact_security_headers.js +5 -0
  29. package/dist/server/serve_fact_route.d.ts +12 -7
  30. package/dist/server/serve_fact_route.d.ts.map +1 -1
  31. package/dist/server/serve_fact_route.js +30 -23
  32. package/dist/server/x_accel.d.ts +37 -9
  33. package/dist/server/x_accel.d.ts.map +1 -1
  34. package/dist/server/x_accel.js +136 -19
  35. package/dist/testing/CLAUDE.md +73 -22
  36. package/dist/testing/cross_backend/capabilities.d.ts +0 -18
  37. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  38. package/dist/testing/cross_backend/capabilities.js +1 -4
  39. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  40. package/dist/testing/cross_backend/default_backend_configs.js +0 -7
  41. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  42. package/dist/testing/cross_backend/fact_serving.js +85 -12
  43. package/dist/testing/cross_backend/identity_parity.d.ts +11 -0
  44. package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
  45. package/dist/testing/cross_backend/identity_parity.js +124 -9
  46. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  47. package/dist/testing/cross_backend/login_security.js +28 -0
  48. package/dist/testing/cross_backend/revocation_drivers.d.ts +70 -0
  49. package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -0
  50. package/dist/testing/cross_backend/revocation_drivers.js +104 -0
  51. package/dist/testing/cross_backend/sse_round_trip.d.ts +6 -1
  52. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  53. package/dist/testing/cross_backend/sse_round_trip.js +52 -16
  54. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  55. package/dist/testing/cross_backend/token_scope_surface.js +33 -0
  56. package/dist/testing/cross_backend/ws_round_trip.d.ts +13 -1
  57. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  58. package/dist/testing/cross_backend/ws_round_trip.js +155 -39
  59. package/package.json +1 -1
@@ -159,10 +159,10 @@ if a consumer genuinely owns protocol actions in their typed API.
159
159
 
160
160
  **Consumer tiers.** Single-source consumers (zzz) drop into the helpers
161
161
  and accept the default `* as specs` namespace import. Multi-source
162
- consumers (fuz_forge, visiones — stitching local specs with
163
- `all_admin_action_specs` / `all_role_grant_offer_action_specs` /
164
- `all_account_action_specs` / `all_self_service_role_action_specs` from
165
- fuz_app) call `create_namespace_qualifier` once, then pass the returned
162
+ consumers (stitching local specs with `all_admin_action_specs` /
163
+ `all_role_grant_offer_action_specs` / `all_account_action_specs` /
164
+ `all_self_service_role_action_specs` from fuz_app) call
165
+ `create_namespace_qualifier` once, then pass the returned
166
166
  `qualify_spec` callback to multi-source helpers.
167
167
 
168
168
  **Tier 1** (HTTP-only) emits a smaller surface — typically
@@ -962,8 +962,9 @@ answer. The close hands the frames still waiting in the connection's outbound
962
962
  queue to the socket ahead of the close frame while the socket's buffer is below
963
963
  `WS_WRITE_HIGH_WATER_BYTES`, so the reply arrives when it was queued behind
964
964
  another frame of the same run on a socket that is keeping up, and is discarded
965
- when the socket is backed up past that mark. (The Rust spine does flush first, and drops the response: a divergence
966
- the cross-backend WS suite records as `capabilities.ws_self_revocation_reply`.)
965
+ when the socket is backed up past that mark. (The Rust spine drains first but marks the reply owed beforehand, and its
966
+ socket loop writes that reply ahead of the close — the same order on the wire,
967
+ held by the cross-backend WS suite.)
967
968
 
968
969
  **Lifecycle hooks.** `on_socket_open({ws, connection_id, identity, notify, signal})`
969
970
  fires once the connection is admitted and before the first message dispatches
@@ -220,7 +220,7 @@ Everything else listed under §RPC action surfaces.
220
220
  ### Middleware
221
221
 
222
222
  - `auth/middleware.ts` — `create_auth_middleware_specs(deps, options)` assembles `[origin, session, request_context, bearer_auth]` + optional `daemon_token` (the content-type refusal isn't a middleware: `apply_route_specs` checks every `POST` route, see `http/simple_request.ts`). The origin check mounts on every path (`ORIGIN_MIDDLEWARE_PATH`, `'*'`), less its `origin_carve_outs` — each a `MiddlewareCarveOut` (`{path, reason}`, copied and frozen at assembly by `freeze_origin_carve_outs`, refused by `assert_origin_carve_outs_valid` without a reason or when it overlaps the auth scope — `middleware_paths_overlap` against `AUTH_MIDDLEWARE_PATH`, which holds every credential-reading and credential-minting route — and recorded on the origin spec so the surface leaves its `403` off the carved routes); `AUTH_MIDDLEWARE_PATH` (`'/api/*'`) is where `create_app_server` mounts the rest of the stack; assembly asserts lean on it: `assert_endpoint_in_auth_scope` (an `rpc_endpoints` / `ws_endpoints` path, or the bootstrap route under `bootstrap.route_prefix`, must sit inside it — so no carve-out can reach one), `assert_credential_routes_in_auth_scope(route_specs)` (every registered route that reads a credential — `auth.account` not `'none'`, or marked `reads_credential`, as account status is — or is marked `mints_credential` — login, signup, bootstrap — must sit inside it too, so a consumer prefixing the relative account or signup specs outside `/api/` is refused), and `assert_middleware_stack_mounted(stack_specs, middleware_specs)`, run on a `transform_middleware` result against the pre-transform stack (`[proxy, ...auth]`) — each stack spec, matched by its `handler` closure (not its spoofable `name`), mounted exactly once at its original path (`'*'` for the trusted proxy and the origin check, `AUTH_MIDDLEWARE_PATH` for the rest of auth), in its original order; other specs may sit anywhere, including before the proxy and between the auth layers. The proxy is in the stack because dropping it turns every `get_client_ip` into `'unknown'`, one shared bucket for every IP-keyed limiter. The transform receives shallow copies in a fresh array, so an in-place mutation can't rewrite the originals the check reads. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access. Both run in `create_test_app_surface_spec` too.
223
- - `auth/request_context.ts` — `RequestContext`, `resolve_acting_actor`, `build_request_context`, predicates (`has_role`, `has_scoped_role`, `has_any_scoped_role` — the dispatcher's role gate and every builtin-role check read the **global** grant via `has_scoped_role(_, role, null)` / `has_any_scoped_role(_, roles, null)`; scope-blind `has_role` is never the gate for a builtin role), guards (`require_auth`, `require_role`, `require_credential_types`, `require_token_scope`), `token_scope_surface_denial` (the direct-call form, for surfaces that aren't route specs), `refresh_role_grants`.
223
+ - `auth/request_context.ts` — `RequestContext`, `resolve_acting_actor`, `build_request_context`, predicates (`has_role`, `has_scoped_role`, `has_any_scoped_role` — the dispatcher's role gate and every builtin-role check read the **global** grant via `has_scoped_role(_, role, null)` / `has_any_scoped_role(_, roles, null)`; scope-blind `has_role` is never the gate for a builtin role), guards (`require_auth`, `require_role`, `require_credential_types`, `require_token_scope`), `token_scope_surface_denial` (the direct-call form, for surfaces that aren't route specs), `get_public_account_id` (the account id a `reads_credential` route may read — `null` for a scope-narrowed token, which such a route answers as anonymous; twin of the Rust `AuthGate::public_request_context` downgrade), `refresh_role_grants`.
224
224
  - `auth/session_middleware.ts` — `process_session_cookie` integration, `create_session_and_set_cookie` (shared by login / signup / bootstrap; returns the token hashes of the sessions the cap evicted, which the caller closes after its commit — only login passes a cap).
225
225
  - `auth/resolved_auth.ts` — `ResolvedAuth` (the resolved credential as one value: `account_id`, `credential_type`, `token_hash`, `api_token_id`, `credential_deadline`), `get_resolved_auth(c)` (gathers it from the context keys the auth middleware set), and `revalidate_resolved_auth(deps, resolved)`, the no-touch re-read a long-lived connection runs once it is registered: session row / token row (by id) still live and still on the account, account not soft-deleted; a daemon token re-reads its account only. Reuses the middleware's own queries and predicates. Called by the WS upgrade (`actions/register_action_ws.ts`) and the audit SSE route between registering a connection pending and admitting it. Twin of the Rust `revalidate_resolved_auth`.
226
226
  - `auth/credential_deadline.ts` — when a resolved credential stops authenticating, which a WebSocket or audit stream opened on it closes at (expiry writes nothing a revocation listener hears): `credential_deadline_after` (the earliest remaining lifetime, anchored to `performance.now()`), `EXPIRES_IN_MS_SQL` (a row's `expires_at - NOW()`, read on the database clock in the statement that checks it, so the session and token validation queries return `expires_in_ms`), and `schedule_credential_deadline` (a chain of timers of at most `CREDENTIAL_DEADLINE_TIMER_CAP_MS`, an hour — a 30-day session is past `setTimeout`'s ceiling — each wake-up also checking a wall-clock copy of the deadline, so a host suspend, which stops the monotonic clock, delays the close by at most one timer after the resume). The middleware sets `CREDENTIAL_DEADLINE_KEY`: session — the earlier of the cookie's signed expiry (`SESSION_COOKIE_EXPIRES_AT_KEY`, set by the session middleware) and the row's; bearer — the row's, none for an eternal token; daemon token — none. Twin of the Rust `ResolvedAuth::credential_deadline`.
@@ -811,7 +811,10 @@ Options: `roles?: RoleSchemaResult` (drives admin-grant-path lookup),
811
811
 
812
812
  `account_verify` is intentionally on both surfaces: the REST `GET /verify`
813
813
  shim is a status-only nginx probe; the RPC action returns
814
- `SessionAccountJson` for programmatic callers.
814
+ `SessionAccountJson` for programmatic callers. Only the RPC action is open to a
815
+ narrowed token that names it: the REST probe is the `surface:account_verify`
816
+ rule-3 surface, refused to every narrowed token, and `GET /status` answers one
817
+ as the anonymous caller.
815
818
 
816
819
  `session_id` validates as `Blake3Hash`; `token_id` as `ApiTokenId`
817
820
  (`tok_[A-Za-z0-9_-]{12}`).
@@ -1 +1 @@
1
- {"version":3,"file":"account_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_action_specs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_action_specs.ts"],"names":[],"mappings":"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"}
@@ -68,7 +68,8 @@ export const TokenCreateInput = z.strictObject({
68
68
  scope: TokenScopeInput.meta({
69
69
  description: "What the token may do. `{kind:'full'}` for full account authority, or " +
70
70
  "`{kind:'methods',methods:[…]}` to narrow it to named RPC methods — a " +
71
- 'narrowed token is RPC-only and reaches no non-RPC surface.'
71
+ 'narrowed token is RPC-only and reaches no non-RPC surface that reads ' +
72
+ 'account data or carries authority.'
72
73
  }),
73
74
  lifetime: TokenLifetimeInput.meta({
74
75
  description: "How long the token lives. `{kind:'eternal'}` never expires; " +
@@ -1 +1 @@
1
- {"version":3,"file":"account_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4CAA4C;AAC5C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAI9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,4EAA4E;AAC5E,eAAO,MAAM,iCAAiC;;;iBAG5C,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAElG,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;kBAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,WAAW;;kBAEtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wFAAwF;AACxF,eAAO,MAAM,YAAY;;;kBAGvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,sHAAsH;AACtH,eAAO,MAAM,mBAAmB;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,uGAAuG;AACvG,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaD,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACxC,8FAA8F;IAC9F,0BAA0B,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,wBAAwB,KAC/B,CACF,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CA6D1B,CAAC"}
1
+ {"version":3,"file":"account_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4CAA4C;AAC5C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAI9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,4EAA4E;AAC5E,eAAO,MAAM,iCAAiC;;;iBAG5C,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAElG,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;kBAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,WAAW;;kBAEtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wFAAwF;AACxF,eAAO,MAAM,YAAY;;;kBAGvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,sHAAsH;AACtH,eAAO,MAAM,mBAAmB;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,uGAAuG;AACvG,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaD,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACxC,8FAA8F;IAC9F,0BAA0B,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,wBAAwB,KAC/B,CACF,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAqE1B,CAAC"}
@@ -109,8 +109,15 @@ export const create_account_route_shapes = (options) => [
109
109
  {
110
110
  method: 'GET',
111
111
  path: '/verify',
112
- auth: { account: 'required', actor: 'none' },
113
- description: 'Session-validity probe for nginx auth_request (empty body, 200 or 401)',
112
+ // a non-RPC surface: the probe answers 200 for the account behind a
113
+ // credential, so a narrowed token is refused it (it would otherwise pass
114
+ // an nginx `auth_request` gate as its account)
115
+ auth: {
116
+ account: 'required',
117
+ actor: 'none',
118
+ required_scope: 'surface:account_verify'
119
+ },
120
+ description: 'Session-validity probe for nginx auth_request (empty body; 200, 401, or 403 for a narrowed token)',
114
121
  input: z.null(),
115
122
  output: z.null()
116
123
  },
@@ -32,6 +32,14 @@ import type { RouteFactoryDeps } from './deps.js';
32
32
  * - Authenticated: returns `{account}` with 200
33
33
  * - Unauthenticated: returns 401 with optional `bootstrap_available` flag
34
34
  *
35
+ * A scope-narrowed API token is answered as the unauthenticated caller, byte
36
+ * for byte. A narrowed token reaches no non-RPC surface
37
+ * (`token_scope_admits_non_rpc`), and this one hands out the account's email,
38
+ * its verification state, and the acting actor's role grants; the route
39
+ * already answers anonymous callers, so that answer is the refusal — a 403
40
+ * would tell the holder nothing that dropping the header doesn't. The handler
41
+ * reads the credential through `get_public_account_id`, which applies it.
42
+ *
35
43
  * This eliminates the need for a separate `/health` fetch on page load —
36
44
  * the frontend gets both session state and bootstrap availability in one request.
37
45
  *
@@ -1 +1 @@
1
- {"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA4B1D,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIxE,OAAO,EAGN,KAAK,WAAW,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAOlD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gCAAgC,GAAI,UAAU,oBAAoB,KAAG,SAkFhF,CAAC;AAEH,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAQ/C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IACnE;;;;;;;OAOG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,4FAA4F;IAC5F,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,gBAAgB,EACtB,SAAS,mBAAmB,KAC1B,KAAK,CAAC,SAAS,CA+VjB,CAAC"}
1
+ {"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA6B1D,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIxE,OAAO,EAGN,KAAK,WAAW,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAOlD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC,GAAI,UAAU,oBAAoB,KAAG,SAmFhF,CAAC;AAEH,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAQ/C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IACnE;;;;;;;OAOG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,4FAA4F;IAC5F,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,gBAAgB,EACtB,SAAS,mBAAmB,KAC1B,KAAK,CAAC,SAAS,CA+VjB,CAAC"}
@@ -27,8 +27,8 @@ import { account_status_route_shape, create_account_route_shapes, DEFAULT_MAX_SE
27
27
  import { hash_session_token, query_session_revoke_all_for_account, query_session_revoke_by_hash_unscoped } from './session_queries.js';
28
28
  import { query_account_by_username_or_email, query_update_account_password } from './account_queries.js';
29
29
  import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
30
- import { build_account_context, build_request_context, get_request_context, require_request_context, resolve_acting_actor } from './request_context.js';
31
- import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
30
+ import { build_account_context, build_request_context, get_public_account_id, get_request_context, require_request_context, resolve_acting_actor } from './request_context.js';
31
+ import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
32
32
  import { get_route_input } from '../http/route_spec.js';
33
33
  import { get_client_ip } from '../http/client_ip.js';
34
34
  import { rate_limit_ip_key } from '../http/ip_canonical.js';
@@ -43,6 +43,14 @@ import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INVALID_CREDENTIALS } from '../htt
43
43
  * - Authenticated: returns `{account}` with 200
44
44
  * - Unauthenticated: returns 401 with optional `bootstrap_available` flag
45
45
  *
46
+ * A scope-narrowed API token is answered as the unauthenticated caller, byte
47
+ * for byte. A narrowed token reaches no non-RPC surface
48
+ * (`token_scope_admits_non_rpc`), and this one hands out the account's email,
49
+ * its verification state, and the acting actor's role grants; the route
50
+ * already answers anonymous callers, so that answer is the refusal — a 403
51
+ * would tell the holder nothing that dropping the header doesn't. The handler
52
+ * reads the credential through `get_public_account_id`, which applies it.
53
+ *
46
54
  * This eliminates the need for a separate `/health` fetch on page load —
47
55
  * the frontend gets both session state and bootstrap availability in one request.
48
56
  *
@@ -53,7 +61,8 @@ export const create_account_status_route_spec = (options) => ({
53
61
  ...account_status_route_shape,
54
62
  path: options?.path ?? account_status_route_shape.path,
55
63
  handler: async (c, route) => {
56
- const account_id = c.get(ACCOUNT_ID_KEY) ?? null;
64
+ // a narrowed token is the anonymous caller here, never a refusal
65
+ const account_id = get_public_account_id(c);
57
66
  if (!account_id) {
58
67
  return c.json({
59
68
  error: ERROR_AUTHENTICATION_REQUIRED,
@@ -268,6 +268,29 @@ export declare const create_request_context_middleware: (deps: QueryDeps, sessio
268
268
  * @returns the 403 to return, or `null` when the caller may proceed
269
269
  */
270
270
  export declare const token_scope_surface_denial: (c: Context, capability: string) => Response | null;
271
+ /**
272
+ * The account id a public route — one that answers anonymous callers and reads
273
+ * the credential (`reads_credential`) — may act on: `null` for the anonymous
274
+ * caller **and for a scope-narrowed API token**. Rule 3 in its
275
+ * anonymous-admitting disposition — the downgrade the Rust
276
+ * `AuthGate::public_request_context` applies.
277
+ *
278
+ * A narrowed token reaches no non-RPC surface that reads account data
279
+ * (`token_scope_admits_non_rpc`). On a route that refuses anonymous callers
280
+ * that is a 403 (`auth.required_scope`); on one that answers them, a 403
281
+ * would enforce nothing — the holder drops the `Authorization` header and
282
+ * reads the anonymous answer anyway — so the narrowed token *is* the
283
+ * anonymous caller there. Read the credential through this on any
284
+ * `reads_credential` route instead of `ACCOUNT_ID_KEY` directly, so the
285
+ * downgrade is one decision rather than an `&&` hand-rolled per handler.
286
+ *
287
+ * The RPC endpoint is the exception: its dispatcher gates each method against
288
+ * the scope, so it reads the full credential.
289
+ *
290
+ * @param c - the request context, after the auth middleware chain
291
+ * @returns the account id, or `null` for an anonymous caller or a narrowed token
292
+ */
293
+ export declare const get_public_account_id: (c: Context) => string | null;
271
294
  /**
272
295
  * Create middleware refusing a route to a credential whose scope does not admit
273
296
  * `capability`.
@@ -1 +1 @@
1
- {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}
@@ -40,7 +40,7 @@ import { query_account_by_id, query_actor_by_id, query_active_actors_by_account
40
40
  import { query_role_grant_find_active_for_actor } from './role_grant_queries.js';
41
41
  import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_DEADLINE_KEY, CREDENTIAL_TYPE_KEY, SESSION_COOKIE_EXPIRES_AT_KEY, TOKEN_SCOPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
42
42
  import { credential_deadline_after } from './credential_deadline.js';
43
- import { parse_token_scope_capability, token_scope_admits_capability, token_scope_denied_body, token_scope_full } from './token_scope.js';
43
+ import { parse_token_scope_capability, token_scope_admits_capability, token_scope_admits_non_rpc, token_scope_denied_body, token_scope_full } from './token_scope.js';
44
44
  import { is_public_auth, needs_actor, parse_acting } from '../http/auth_shape.js';
45
45
  import { is_null_schema } from '../http/schema_helpers.js';
46
46
  import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_ACTOR_REQUIRED, ERROR_ACTOR_NOT_ON_ACCOUNT, ERROR_NO_ACTORS_ON_ACCOUNT, ERROR_ACCOUNT_VANISHED } from '../http/error_schemas.js';
@@ -308,6 +308,37 @@ const token_scope_denial = (c, capability) => {
308
308
  * @returns the 403 to return, or `null` when the caller may proceed
309
309
  */
310
310
  export const token_scope_surface_denial = (c, capability) => token_scope_denial(c, { kind: 'surface', capability });
311
+ /**
312
+ * The account id a public route — one that answers anonymous callers and reads
313
+ * the credential (`reads_credential`) — may act on: `null` for the anonymous
314
+ * caller **and for a scope-narrowed API token**. Rule 3 in its
315
+ * anonymous-admitting disposition — the downgrade the Rust
316
+ * `AuthGate::public_request_context` applies.
317
+ *
318
+ * A narrowed token reaches no non-RPC surface that reads account data
319
+ * (`token_scope_admits_non_rpc`). On a route that refuses anonymous callers
320
+ * that is a 403 (`auth.required_scope`); on one that answers them, a 403
321
+ * would enforce nothing — the holder drops the `Authorization` header and
322
+ * reads the anonymous answer anyway — so the narrowed token *is* the
323
+ * anonymous caller there. Read the credential through this on any
324
+ * `reads_credential` route instead of `ACCOUNT_ID_KEY` directly, so the
325
+ * downgrade is one decision rather than an `&&` hand-rolled per handler.
326
+ *
327
+ * The RPC endpoint is the exception: its dispatcher gates each method against
328
+ * the scope, so it reads the full credential.
329
+ *
330
+ * @param c - the request context, after the auth middleware chain
331
+ * @returns the account id, or `null` for an anonymous caller or a narrowed token
332
+ */
333
+ export const get_public_account_id = (c) => {
334
+ const account_id = c.get(ACCOUNT_ID_KEY) ?? null;
335
+ if (!account_id)
336
+ return null;
337
+ const scope = c.get(TOKEN_SCOPE_KEY);
338
+ if (scope && !token_scope_admits_non_rpc(scope))
339
+ return null;
340
+ return account_id;
341
+ };
311
342
  /**
312
343
  * Create middleware refusing a route to a credential whose scope does not admit
313
344
  * `capability`.
@@ -19,9 +19,12 @@
19
19
  * 2. **`methods` denies every method it does not list.** An empty list is legal
20
20
  * and renders the token inert for RPC — unlike an empty `credential_types`
21
21
  * allowlist, which is rejected because it renders an *action* unreachable.
22
- * 3. **A narrowed token is RPC-only.** `methods` denies every non-RPC spine
23
- * surface outright — the db-admin browser, the bare-hash fact read, the
24
- * audit SSE stream, and the WS upgrade. See `token_scope_admits_non_rpc`.
22
+ * 3. **A narrowed token is RPC-only.** `methods` denies outright every non-RPC
23
+ * spine surface that reads account data or carries authority — the
24
+ * db-admin browser, the bare-hash fact read, the audit SSE stream, the WS
25
+ * upgrade, the surface explorer, and the `auth_request` probe. See
26
+ * `token_scope_admits_non_rpc`, and `docs/security.md` §Token scoping for
27
+ * the reads that answer it as anonymous and the server status read.
25
28
  *
26
29
  * @module
27
30
  */
@@ -94,10 +97,10 @@ export declare const token_scope_admits_method: (scope: TokenScope, method: stri
94
97
  /**
95
98
  * Does `scope` admit a non-RPC spine surface?
96
99
  *
97
- * **A narrowed token reaches no non-RPC surface at all** — the load-bearing
98
- * half of token scoping, and what "rule 3" names wherever this module's
99
- * callers and the Rust twin use that shorthand. See `docs/security.md`
100
- * §Token scoping.
100
+ * **A narrowed token reaches no non-RPC surface that reads account data or
101
+ * carries authority** — the load-bearing half of token scoping, and what
102
+ * "rule 3" names wherever this module's callers and the Rust twin use that
103
+ * shorthand. See `docs/security.md` §Token scoping.
101
104
  *
102
105
  * A method-name allowlist alone would have been a false promise: the db-admin
103
106
  * browser serves paginated rows of any `public` table (including
@@ -105,16 +108,21 @@ export declare const token_scope_admits_method: (scope: TokenScope, method: stri
105
108
  * gated on a global role a bearer credential satisfies. A token whose UI badge
106
109
  * read "scoped to `cell_get`" could still delete an account row.
107
110
  *
108
- * Denying every non-RPC surface outright is strictly more restrictive than
109
- * naming each one, keeps no vocabulary to maintain, and fits in one sentence a
110
- * user can hold: *a scoped token is RPC-only*.
111
+ * The predicate is all-or-nothing: no per-surface vocabulary decides anything,
112
+ * so it fits in one sentence a user can hold — *a scoped token is RPC-only*.
113
+ * Placement is per site: a route that refuses anonymous callers declares
114
+ * `auth.required_scope`, one that answers them reads the credential through
115
+ * `get_public_account_id` and answers a narrowed token as anonymous, and the
116
+ * surface census pins every site.
111
117
  */
112
118
  export declare const token_scope_admits_non_rpc: (scope: TokenScope) => boolean;
113
119
  /**
114
120
  * The non-RPC surfaces the **spine itself** mounts (rule 3). Twin of the Rust
115
- * `TokenSurface` enum, name for name, except `app_surface` — the opt-in
116
- * `GET /api/surface` explorer (`server/surface_route.ts`), which only the TS
117
- * spine serves.
121
+ * `TokenSurface` enum, name for name, except the two only the TS spine serves:
122
+ * `app_surface`, the opt-in `GET /api/surface` explorer
123
+ * (`server/surface_route.ts`), and `account_verify`, the
124
+ * `GET /api/account/verify` nginx `auth_request` probe
125
+ * (`auth/account_route_schema.ts`).
118
126
  *
119
127
  * Not a registry a consumer registers into. `RouteAuth.required_scope` accepts
120
128
  * any well-formed `surface:<name>` (see `parse_token_scope_capability`), so a
@@ -128,7 +136,7 @@ export declare const token_scope_admits_non_rpc: (scope: TokenScope) => boolean;
128
136
  * refused, in the `required_scope` a denial reports. That is the same field
129
137
  * whose `rpc:<method>` arm has always carried arbitrary consumer method names.
130
138
  */
131
- export declare const TOKEN_SURFACES: readonly ["db_admin", "fact_bare", "audit_stream", "ws_upgrade", "app_surface"];
139
+ export declare const TOKEN_SURFACES: readonly ["db_admin", "fact_bare", "audit_stream", "ws_upgrade", "app_surface", "account_verify"];
132
140
  export type TokenSurface = (typeof TOKEN_SURFACES)[number];
133
141
  /**
134
142
  * Is `value` one of the surfaces the spine mounts?
@@ -1 +1 @@
1
- {"version":3,"file":"token_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/token_scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAW,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU;;;;;;;;;4BAYrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;;;4BAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,QAAO,UAGlC,CAAC;AAEH,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,SAAS,KAAK,CAAC,MAAM,CAAC,KAAG,UAI3D,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,eAAe,KAAG,UAC8B,CAAC;AAE/F;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,UAAU,EAAE,QAAQ,MAAM,KAAG,OACtB,CAAC;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,UAAU,KAAG,OAAgC,CAAC;AAEhG;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,iFAMjB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,KAAK,IAAI,YACA,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,QAAgC,CAAC;AAQtE;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,MAAM,KAAG,MAC3B,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAC/B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/F;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GAAI,OAAO,MAAM,KAAG,oBAAoB,GAAG,IAUnF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GACzC,OAAO,UAAU,EACjB,YAAY,oBAAoB,KAC9B,OAGkC,CAAC;AAEtC;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GACnC,YAAY,MAAM,KAChB;IAAE,KAAK,EAAE,sBAAsB,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAGxD,CAAC;AAEH,4CAA4C;AAC5C,eAAO,MAAM,iBAAiB,GAAI,OAAO,UAAU,KAAG,MAGrD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,UAAU,GAAG,IAU5D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,UAAU,KAAG,MAQzD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,UAAU,GAAG,IAQtE,CAAC"}
1
+ {"version":3,"file":"token_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/token_scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAW,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU;;;;;;;;;4BAYrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;;;4BAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,QAAO,UAGlC,CAAC;AAEH,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,SAAS,KAAK,CAAC,MAAM,CAAC,KAAG,UAI3D,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,eAAe,KAAG,UAC8B,CAAC;AAE/F;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,UAAU,EAAE,QAAQ,MAAM,KAAG,OACtB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,UAAU,KAAG,OAAgC,CAAC;AAEhG;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc,mGAOjB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,KAAK,IAAI,YACA,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,QAAgC,CAAC;AAQtE;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,MAAM,KAAG,MAC3B,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAC/B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/F;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GAAI,OAAO,MAAM,KAAG,oBAAoB,GAAG,IAUnF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GACzC,OAAO,UAAU,EACjB,YAAY,oBAAoB,KAC9B,OAGkC,CAAC;AAEtC;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GACnC,YAAY,MAAM,KAChB;IAAE,KAAK,EAAE,sBAAsB,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAGxD,CAAC;AAEH,4CAA4C;AAC5C,eAAO,MAAM,iBAAiB,GAAI,OAAO,UAAU,KAAG,MAGrD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,UAAU,GAAG,IAU5D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,UAAU,KAAG,MAQzD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,UAAU,GAAG,IAQtE,CAAC"}
@@ -19,9 +19,12 @@
19
19
  * 2. **`methods` denies every method it does not list.** An empty list is legal
20
20
  * and renders the token inert for RPC — unlike an empty `credential_types`
21
21
  * allowlist, which is rejected because it renders an *action* unreachable.
22
- * 3. **A narrowed token is RPC-only.** `methods` denies every non-RPC spine
23
- * surface outright — the db-admin browser, the bare-hash fact read, the
24
- * audit SSE stream, and the WS upgrade. See `token_scope_admits_non_rpc`.
22
+ * 3. **A narrowed token is RPC-only.** `methods` denies outright every non-RPC
23
+ * spine surface that reads account data or carries authority — the
24
+ * db-admin browser, the bare-hash fact read, the audit SSE stream, the WS
25
+ * upgrade, the surface explorer, and the `auth_request` probe. See
26
+ * `token_scope_admits_non_rpc`, and `docs/security.md` §Token scoping for
27
+ * the reads that answer it as anonymous and the server status read.
25
28
  *
26
29
  * @module
27
30
  */
@@ -100,10 +103,10 @@ export const token_scope_admits_method = (scope, method) => scope.kind === 'full
100
103
  /**
101
104
  * Does `scope` admit a non-RPC spine surface?
102
105
  *
103
- * **A narrowed token reaches no non-RPC surface at all** — the load-bearing
104
- * half of token scoping, and what "rule 3" names wherever this module's
105
- * callers and the Rust twin use that shorthand. See `docs/security.md`
106
- * §Token scoping.
106
+ * **A narrowed token reaches no non-RPC surface that reads account data or
107
+ * carries authority** — the load-bearing half of token scoping, and what
108
+ * "rule 3" names wherever this module's callers and the Rust twin use that
109
+ * shorthand. See `docs/security.md` §Token scoping.
107
110
  *
108
111
  * A method-name allowlist alone would have been a false promise: the db-admin
109
112
  * browser serves paginated rows of any `public` table (including
@@ -111,16 +114,21 @@ export const token_scope_admits_method = (scope, method) => scope.kind === 'full
111
114
  * gated on a global role a bearer credential satisfies. A token whose UI badge
112
115
  * read "scoped to `cell_get`" could still delete an account row.
113
116
  *
114
- * Denying every non-RPC surface outright is strictly more restrictive than
115
- * naming each one, keeps no vocabulary to maintain, and fits in one sentence a
116
- * user can hold: *a scoped token is RPC-only*.
117
+ * The predicate is all-or-nothing: no per-surface vocabulary decides anything,
118
+ * so it fits in one sentence a user can hold — *a scoped token is RPC-only*.
119
+ * Placement is per site: a route that refuses anonymous callers declares
120
+ * `auth.required_scope`, one that answers them reads the credential through
121
+ * `get_public_account_id` and answers a narrowed token as anonymous, and the
122
+ * surface census pins every site.
117
123
  */
118
124
  export const token_scope_admits_non_rpc = (scope) => scope.kind === 'full';
119
125
  /**
120
126
  * The non-RPC surfaces the **spine itself** mounts (rule 3). Twin of the Rust
121
- * `TokenSurface` enum, name for name, except `app_surface` — the opt-in
122
- * `GET /api/surface` explorer (`server/surface_route.ts`), which only the TS
123
- * spine serves.
127
+ * `TokenSurface` enum, name for name, except the two only the TS spine serves:
128
+ * `app_surface`, the opt-in `GET /api/surface` explorer
129
+ * (`server/surface_route.ts`), and `account_verify`, the
130
+ * `GET /api/account/verify` nginx `auth_request` probe
131
+ * (`auth/account_route_schema.ts`).
124
132
  *
125
133
  * Not a registry a consumer registers into. `RouteAuth.required_scope` accepts
126
134
  * any well-formed `surface:<name>` (see `parse_token_scope_capability`), so a
@@ -139,7 +147,8 @@ export const TOKEN_SURFACES = [
139
147
  'fact_bare',
140
148
  'audit_stream',
141
149
  'ws_upgrade',
142
- 'app_surface'
150
+ 'app_surface',
151
+ 'account_verify'
143
152
  ];
144
153
  /**
145
154
  * Is `value` one of the surfaces the spine mounts?
@@ -52,7 +52,7 @@ consumers.
52
52
  - `input: z.ZodType` — request body schema; `z.null()` for no-body (GET/DELETE)
53
53
  - `output: z.ZodType` — success response schema
54
54
  - `mints_credential?: boolean` — the route issues a session cookie while declaring no auth (login, signup, bootstrap); assembly refuses it outside `AUTH_MIDDLEWARE_PATH`, as it does any route whose `auth.account` is not `'none'` (`assert_credential_routes_in_auth_scope` in `auth/middleware.ts`)
55
- - `reads_credential?: boolean` — the handler reads the resolved account while the route declares no auth gate, answering an anonymous caller itself (account status, the cell-scoped fact read, `create_rpc_endpoint`'s pair, whose dispatcher gates per action); held inside `AUTH_MIDDLEWARE_PATH` by the same assertion
55
+ - `reads_credential?: boolean` — the handler reads the resolved account while the route declares no auth gate, answering an anonymous caller itself (account status, the cell-scoped fact read, `create_rpc_endpoint`'s pair, whose dispatcher gates per action); held inside `AUTH_MIDDLEWARE_PATH` by the same assertion. Such a handler reads the account through `get_public_account_id` (`auth/request_context.ts`), which answers a scope-narrowed token as anonymous — the RPC pair excepted, its dispatcher gating each method
56
56
  - `rate_limit?: RateLimitKey` — metadata only (`'ip' | 'account' | 'both'`); auto-derives 429
57
57
  - `errors?: RouteErrorSchemas` — handler-specific error schemas keyed by HTTP status
58
58
  - `transaction?: boolean` — declarative transaction wrapping (see below)
@@ -315,7 +315,7 @@ rate-limiting middleware (see root ../../../CLAUDE.md §Middleware Ordering).
315
315
 
316
316
  Per-symbol semantics on TSDoc; the cross-cutting properties:
317
317
 
318
- - **Rightmost-first XFF walk**, skipping trusted entries AND entries that fail strict validation. Closes a rate-limit-key-poisoning surface where an attacker who controls XFF and transits through a trusted proxy could rotate garbage strings to evade per-IP limits.
318
+ - **Rightmost-first XFF walk, skipping trusted entries; the first entry that fails strict validation stops the walk and the request keys on the connection IP.** Stopping, not skipping, keeps a client-written entry left of a non-bare-IP hop (`ip:port`, `unknown`) from picking the bucket.
319
319
  - **`validate_ip_strict(ip)`** is defensive against Hono's lax `distinctRemoteAddr` (which classifies anything-with-colons as IPv6 and accepts `'[::1]:8080'`, `'::1\n'` as binary-valid). Two-layer check: character-set pre-filter + round-trip through `convertIPv*ToBinary`.
320
320
  - **`normalize_ip(ip)`** delegates to `canonicalize_ip` from `http/ip_canonical.ts` — RFC 5952 lowercase + longest-zero-run compression, IPv4-mapped IPv6 stripped to plain IPv4 so buckets collapse. Idempotent, safe on non-IP strings, strict char-set filter preserves malformed forms unchanged.
321
321
  - **Three-branch middleware logic**: no XFF → use connection IP; XFF + connection untrusted → ignore XFF, use connection IP (spoof-proof, debug log); XFF + connection trusted → resolve from header, warn if all entries turn out trusted.
@@ -1 +1 @@
1
- {"version":3,"file":"auth_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/auth_shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,6DAGtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAGtD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;EAA2C,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;kBAyEnB,CAAC;AACJ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAUlD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACA,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAG,OAAgC,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,KAAG,OAAkC,CAAC;AAEnF,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,KAAG,OAA+B,CAAC;AAE/E,8FAA8F;AAC9F,eAAO,MAAM,wBAAwB,GAAI,MAAM,SAAS,KAAG,OAC3B,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACQ,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,SAAS,KAAG,OACwB,CAAC;AAYvF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAIhE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,MAAM,SAAS,EACf,OAAO,WAAW,EAClB,SAAS,MAAM,KACb,IAmBF,CAAC"}
1
+ {"version":3,"file":"auth_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/auth_shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,6DAGtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAGtD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;EAA2C,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;kBA8EnB,CAAC;AACJ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAUlD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACA,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAG,OAAgC,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,KAAG,OAAkC,CAAC;AAEnF,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,KAAG,OAA+B,CAAC;AAE/E,8FAA8F;AAC9F,eAAO,MAAM,wBAAwB,GAAI,MAAM,SAAS,KAAG,OAC3B,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACQ,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,SAAS,KAAG,OACwB,CAAC;AAYvF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAIhE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,MAAM,SAAS,EACf,OAAO,WAAW,EAClB,SAAS,MAAM,KACb,IAmBF,CAAC"}
@@ -138,9 +138,9 @@ export const RouteAuth = z
138
138
  *
139
139
  * - `surface:<name>` — a non-RPC surface, refused to every narrowed token
140
140
  * whatever its method list says (token scoping's rule that a narrowed
141
- * token is RPC-only). `surface:audit_stream`, `surface:fact_bare`, and
142
- * `surface:app_surface` are the spine's route-spec ones; a consumer names
143
- * its own without registering it.
141
+ * token is RPC-only). `surface:audit_stream`, `surface:fact_bare`,
142
+ * `surface:account_verify`, and `surface:app_surface` are the spine's
143
+ * route-spec ones; a consumer names its own without registering it.
144
144
  * - `rpc:<method>` — one action method, refused unless the token lists it.
145
145
  * What a route bridged from an `ActionSpec` declares: it carries the
146
146
  * method's identity but never reaches the dispatcher's per-method gate.
@@ -151,9 +151,14 @@ export const RouteAuth = z
151
151
  * account lacks the role would be told about the role rather than about the
152
152
  * scope).
153
153
  *
154
- * Opt-in rather than blanket: the account REST routes cross no boundary, and
155
- * the RPC endpoint runs its own per-method check inside the dispatcher — and
156
- * both are route specs too.
154
+ * Opt-in rather than blanket: no narrowed token reaches login (which reads
155
+ * no credential) or logout and the password change (session-only), the
156
+ * RPC endpoint runs its own per-method check inside the dispatcher, the
157
+ * `reads_credential` routes that answer anonymous callers (account status,
158
+ * the cell-scoped fact read) can't take the field and answer a narrowed
159
+ * token as anonymous in their handler (`get_public_account_id`), and the
160
+ * server-status route admits a narrowed token by design — and all are
161
+ * route specs too.
157
162
  *
158
163
  * Typed as a plain string here to keep `http/` from importing `auth/`; the
159
164
  * resolver parses it at registration and throws on a malformed one.
@@ -102,30 +102,30 @@ export declare const is_trusted_ip: (ip: string, proxies: Array<ParsedProxy>) =>
102
102
  /**
103
103
  * Resolve the real client IP from an `X-Forwarded-For` header value.
104
104
  *
105
- * Walks right-to-left, skipping trusted proxy entries AND any entry
106
- * that fails strict IP validation (`validate_ip_strict`). The first
107
- * untrusted, strictly-valid entry is the client IP. If every walked
108
- * entry is trusted or malformed, returns the leftmost strictly-valid
109
- * (trusted) entry (likely-misconfigured all-trusted case) or
110
- * `undefined` (everything was malformed — middleware falls back to
111
- * the connection IP). All entries are normalized before matching and
112
- * in the returned value.
113
- *
114
- * Skipping malformed entries is the rate-limit-key fix for the
115
- * "attacker controls XFF and the proxy passes it through" surface —
116
- * without the skip, an attacker could rotate arbitrary strings (incl.
105
+ * Walks right-to-left, skipping trusted proxy entries. The first untrusted
106
+ * entry is the client IP. The walk **stops** at the first entry that fails
107
+ * strict IP validation (`validate_ip_strict`) and returns `undefined`, so the
108
+ * middleware falls back to the connection IP: an entry is only as
109
+ * trustworthy as the hop that wrote it, and everything left of a malformed
110
+ * one was written before any trusted hop vouched for it. If every entry is
111
+ * trusted, returns the leftmost one (likely-misconfigured all-trusted case).
112
+ * Empty entries are dropped before the walk. All entries are normalized
113
+ * before matching and in the returned value.
114
+ *
115
+ * Stopping, not skipping, is the rate-limit-key rule. Returning a malformed
116
+ * entry would let an attacker rotate arbitrary strings (incl.
117
117
  * `'attacker:controlled'`, which Hono's lax `distinctRemoteAddr`
118
- * misclassifies as IPv6) as XFF values to get fresh per-IP rate-limit
119
- * buckets. Tradeoff: legitimate non-standard proxies that include
120
- * ports in XFF entries (e.g. `203.0.113.1:8080`) also fail strict
121
- * validation, so those entries get skipped and the rate-limit bucket
122
- * collapses to the proxy's connection IP (one bucket for everyone
123
- * behind that proxy). Standard proxies (nginx, cloud LBs) don't
124
- * include ports.
118
+ * misclassifies as IPv6) into fresh per-IP rate-limit buckets; skipping it
119
+ * and walking on would hand the key to the entry the client wrote whenever a
120
+ * trusted hop appends a non-bare-IP token (`ip:port`, `[v6]:port`,
121
+ * `unknown`, `unix:`). Tradeoff: behind such a proxy every client collapses
122
+ * to the proxy's connection IP in rate limiting (one bucket for everyone
123
+ * behind it). Standard proxies (nginx's `$remote_addr`, cloud LBs) append
124
+ * bare IPs.
125
125
  *
126
126
  * @param forwarded_for - the `X-Forwarded-For` header value
127
127
  * @param proxies - parsed trusted proxy entries
128
- * @returns the normalized client IP, or `undefined` if the header is empty / all entries malformed
128
+ * @returns the normalized client IP, or `undefined` if the header is empty or the walk met a malformed entry
129
129
  */
130
130
  export declare const resolve_client_ip: (forwarded_for: string, proxies: Array<ParsedProxy>) => string | undefined;
131
131
  /**
@@ -139,7 +139,8 @@ export declare const resolve_client_ip: (forwarded_for: string, proxies: Array<P
139
139
  * 2. `X-Forwarded-For` present but connection is untrusted → ignore header
140
140
  * (spoofed by a direct attacker), use connection IP.
141
141
  * 3. `X-Forwarded-For` present and connection is trusted → walk header
142
- * right-to-left, strip trusted entries, use first untrusted entry.
142
+ * right-to-left, strip trusted entries, use first untrusted entry; a
143
+ * malformed entry stops the walk and the connection IP is used.
143
144
  *
144
145
  * @param options - trusted proxy configuration
145
146
  * @mutates `c.var.client_ip` - set to the resolved (or `'unknown'`) client IP per request
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,sFAAsF;IACtF,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,+DAA+D;IAC/D,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAEpF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,WA6CjD,CAAC;AAiBF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,MAAM,KAAG,MAAM,GAAG,MAAM,GAAG,SAWjE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,EAAE,SAAS,KAAK,CAAC,WAAW,CAAC,KAAG,OAqBvE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,iBAAiB,GAC7B,eAAe,MAAM,EACrB,SAAS,KAAK,CAAC,WAAW,CAAC,KACzB,MAAM,GAAG,SA0BX,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,YAAY,KAAG,iBAyC/D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,YAAY,KAAG,cAInE,CAAC"}
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,GAAI,IAAI,MAAM,KAAG,MAA6B,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,sFAAsF;IACtF,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,+DAA+D;IAC/D,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAEpF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,WA6CjD,CAAC;AAiBF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,MAAM,KAAG,MAAM,GAAG,MAAM,GAAG,SAWjE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,EAAE,SAAS,KAAK,CAAC,WAAW,CAAC,KAAG,OAqBvE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,iBAAiB,GAC7B,eAAe,MAAM,EACrB,SAAS,KAAK,CAAC,WAAW,CAAC,KACzB,MAAM,GAAG,SAqBX,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,YAAY,KAAG,iBAyC/D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,YAAY,KAAG,cAInE,CAAC"}