@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.
- package/dist/actions/CLAUDE.md +7 -6
- package/dist/auth/CLAUDE.md +5 -2
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +2 -1
- package/dist/auth/account_route_schema.d.ts.map +1 -1
- package/dist/auth/account_route_schema.js +9 -2
- package/dist/auth/account_routes.d.ts +8 -0
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +12 -3
- package/dist/auth/request_context.d.ts +23 -0
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +32 -1
- package/dist/auth/token_scope.d.ts +22 -14
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +23 -14
- package/dist/http/CLAUDE.md +2 -2
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +11 -6
- package/dist/http/proxy.d.ts +22 -21
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +28 -33
- package/dist/http/route_spec.d.ts +8 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +1 -1
- package/dist/server/fact_security_headers.d.ts +5 -0
- package/dist/server/fact_security_headers.d.ts.map +1 -1
- package/dist/server/fact_security_headers.js +5 -0
- package/dist/server/serve_fact_route.d.ts +12 -7
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +30 -23
- package/dist/server/x_accel.d.ts +37 -9
- package/dist/server/x_accel.d.ts.map +1 -1
- package/dist/server/x_accel.js +136 -19
- package/dist/testing/CLAUDE.md +73 -22
- package/dist/testing/cross_backend/capabilities.d.ts +0 -18
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +1 -4
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +0 -7
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/fact_serving.js +85 -12
- package/dist/testing/cross_backend/identity_parity.d.ts +11 -0
- package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
- package/dist/testing/cross_backend/identity_parity.js +124 -9
- package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
- package/dist/testing/cross_backend/login_security.js +28 -0
- package/dist/testing/cross_backend/revocation_drivers.d.ts +70 -0
- package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -0
- package/dist/testing/cross_backend/revocation_drivers.js +104 -0
- package/dist/testing/cross_backend/sse_round_trip.d.ts +6 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +52 -16
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +33 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts +13 -1
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +155 -39
- package/package.json +1 -1
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -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 (
|
|
163
|
-
`
|
|
164
|
-
`
|
|
165
|
-
|
|
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
|
|
966
|
-
the
|
|
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
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -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":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAW3E,6EAA6E;AAC7E,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,uDAAuD;AACvD,eAAO,MAAM,gBAAgB,WAAW,CAAC;AACzC,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,yCAAyC;AACzC,eAAO,MAAM,iBAAiB;;;;;;;kBAE5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB;;kBAE7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,iFAAiF;AACjF,eAAO,MAAM,mBAAmB;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,6DAA6D;AAC7D,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAC9C,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,sBAAsB;;;kBAGjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;
|
|
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,
|
|
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
|
-
|
|
113
|
-
|
|
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;
|
|
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 {
|
|
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
|
-
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,KAAK,EAEV,KAAK,SAAS,EACd,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
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
|
|
23
|
-
* surface
|
|
24
|
-
* audit SSE stream,
|
|
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
|
|
98
|
-
* half of token scoping, and what
|
|
99
|
-
* callers and the Rust twin use that
|
|
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
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
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
|
|
116
|
-
* `GET /api/surface` explorer
|
|
117
|
-
*
|
|
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
|
|
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"}
|
package/dist/auth/token_scope.js
CHANGED
|
@@ -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
|
|
23
|
-
* surface
|
|
24
|
-
* audit SSE stream,
|
|
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
|
|
104
|
-
* half of token scoping, and what
|
|
105
|
-
* callers and the Rust twin use that
|
|
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
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
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
|
|
122
|
-
* `GET /api/surface` explorer
|
|
123
|
-
*
|
|
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?
|
package/dist/http/CLAUDE.md
CHANGED
|
@@ -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
|
|
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;;;;;;;;;;;;;;
|
|
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"}
|
package/dist/http/auth_shape.js
CHANGED
|
@@ -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`,
|
|
142
|
-
* `surface:app_surface` are the spine's
|
|
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:
|
|
155
|
-
*
|
|
156
|
-
*
|
|
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.
|
package/dist/http/proxy.d.ts
CHANGED
|
@@ -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
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
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)
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* behind
|
|
124
|
-
*
|
|
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
|
|
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
|
package/dist/http/proxy.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|