@fuzdev/fuz_app 0.54.0 → 0.56.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 +214 -103
- package/dist/actions/action_bridge.d.ts +8 -5
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +1 -11
- package/dist/actions/action_codegen.d.ts +32 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +35 -15
- package/dist/actions/action_registry.d.ts.map +1 -1
- package/dist/actions/action_registry.js +5 -2
- package/dist/actions/action_rpc.d.ts +141 -22
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +106 -187
- package/dist/actions/action_spec.d.ts +55 -16
- package/dist/actions/action_spec.d.ts.map +1 -1
- package/dist/actions/action_spec.js +16 -11
- package/dist/actions/action_types.d.ts +28 -60
- package/dist/actions/action_types.d.ts.map +1 -1
- package/dist/actions/action_types.js +13 -5
- package/dist/actions/broadcast_api.d.ts +2 -2
- package/dist/actions/broadcast_api.js +2 -2
- package/dist/actions/compile_action_registry.d.ts +50 -0
- package/dist/actions/compile_action_registry.d.ts.map +1 -0
- package/dist/actions/compile_action_registry.js +69 -0
- package/dist/actions/heartbeat.d.ts +8 -4
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -4
- package/dist/actions/perform_action.d.ts +145 -0
- package/dist/actions/perform_action.d.ts.map +1 -0
- package/dist/actions/perform_action.js +258 -0
- package/dist/actions/register_action_ws.d.ts +46 -40
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +101 -159
- package/dist/actions/register_ws_endpoint.d.ts +15 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +54 -7
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +0 -4
- package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
- package/dist/actions/transports_ws_auth_guard.js +1 -1
- package/dist/actions/transports_ws_backend.d.ts +1 -1
- package/dist/actions/transports_ws_backend.js +1 -1
- package/dist/auth/CLAUDE.md +794 -410
- package/dist/auth/account_action_specs.d.ts +28 -7
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +7 -7
- package/dist/auth/account_actions.d.ts +7 -13
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +26 -35
- package/dist/auth/account_queries.d.ts +52 -16
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +87 -38
- package/dist/auth/account_routes.d.ts +9 -11
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +118 -46
- package/dist/auth/account_schema.d.ts +46 -35
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +21 -28
- package/dist/auth/admin_action_specs.d.ts +100 -32
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +64 -33
- package/dist/auth/admin_actions.d.ts +13 -19
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +37 -41
- package/dist/auth/audit_emitter.d.ts +160 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -0
- package/dist/auth/audit_emitter.js +83 -0
- package/dist/auth/audit_log_queries.d.ts +17 -48
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +20 -56
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +7 -3
- package/dist/auth/audit_log_schema.d.ts +92 -32
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +75 -46
- package/dist/auth/auth_guard_resolver.d.ts +44 -0
- package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
- package/dist/auth/auth_guard_resolver.js +56 -0
- package/dist/auth/bearer_auth.d.ts +9 -7
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +13 -21
- package/dist/auth/bootstrap_account.d.ts +7 -7
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +7 -7
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +11 -10
- package/dist/auth/cleanup.d.ts +20 -26
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +33 -42
- package/dist/auth/credential_type_schema.d.ts +115 -0
- package/dist/auth/credential_type_schema.d.ts.map +1 -0
- package/dist/auth/credential_type_schema.js +127 -0
- package/dist/auth/daemon_token_middleware.d.ts +23 -11
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +28 -22
- package/dist/auth/ddl.d.ts +2 -2
- package/dist/auth/ddl.d.ts.map +1 -1
- package/dist/auth/ddl.js +6 -6
- package/dist/auth/deps.d.ts +7 -18
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/grant_path_schema.d.ts +117 -0
- package/dist/auth/grant_path_schema.d.ts.map +1 -0
- package/dist/auth/grant_path_schema.js +137 -0
- package/dist/auth/invite_queries.d.ts +12 -1
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +12 -1
- package/dist/auth/invite_schema.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +9 -4
- package/dist/auth/migrations.d.ts +37 -14
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +79 -32
- package/dist/auth/request_context.d.ts +331 -61
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +378 -95
- package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +163 -94
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_action_specs.js +262 -0
- package/dist/auth/role_grant_offer_actions.d.ts +104 -0
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_actions.js +473 -0
- package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +90 -70
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_notifications.js +182 -0
- package/dist/auth/role_grant_offer_queries.d.ts +242 -0
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_queries.js +533 -0
- package/dist/auth/role_grant_offer_schema.d.ts +150 -0
- package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
- package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +60 -36
- package/dist/auth/role_grant_queries.d.ts +231 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -0
- package/dist/auth/role_grant_queries.js +320 -0
- package/dist/auth/role_schema.d.ts +150 -40
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +144 -45
- package/dist/auth/scope_kind_schema.d.ts +96 -0
- package/dist/auth/scope_kind_schema.d.ts.map +1 -0
- package/dist/auth/scope_kind_schema.js +94 -0
- package/dist/auth/self_service_role_action_specs.d.ts +6 -1
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +3 -1
- package/dist/auth/self_service_role_actions.d.ts +34 -27
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +68 -48
- package/dist/auth/session_cookie.d.ts +43 -6
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +31 -5
- package/dist/auth/session_middleware.d.ts +37 -3
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +33 -7
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +48 -19
- package/dist/auth/standard_action_specs.d.ts +2 -2
- package/dist/auth/standard_action_specs.js +4 -4
- package/dist/auth/standard_rpc_actions.d.ts +23 -19
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +12 -12
- package/dist/db/migrate.d.ts +12 -8
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +10 -7
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +9 -7
- package/dist/env/load.d.ts +1 -1
- package/dist/env/load.js +1 -1
- package/dist/hono_context.d.ts +64 -5
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +38 -2
- package/dist/http/CLAUDE.md +264 -87
- package/dist/http/auth_shape.d.ts +191 -0
- package/dist/http/auth_shape.d.ts.map +1 -0
- package/dist/http/auth_shape.js +237 -0
- package/dist/http/common_routes.js +3 -3
- package/dist/http/db_routes.d.ts +4 -0
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +44 -7
- package/dist/http/error_schemas.d.ts +132 -19
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +132 -40
- package/dist/http/jsonrpc_errors.d.ts +27 -2
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +26 -2
- package/dist/http/pending_effects.d.ts +71 -18
- package/dist/http/pending_effects.d.ts.map +1 -1
- package/dist/http/pending_effects.js +87 -18
- package/dist/http/proxy.d.ts +52 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +92 -14
- package/dist/http/route_spec.d.ts +113 -41
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +130 -52
- package/dist/http/schema_helpers.d.ts +3 -2
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +9 -2
- package/dist/http/surface.d.ts +2 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +1 -2
- package/dist/http/surface_query.d.ts +39 -35
- package/dist/http/surface_query.d.ts.map +1 -1
- package/dist/http/surface_query.js +79 -36
- package/dist/primitive_schemas.d.ts +39 -0
- package/dist/primitive_schemas.d.ts.map +1 -0
- package/dist/primitive_schemas.js +40 -0
- package/dist/realtime/sse_auth_guard.d.ts +5 -5
- package/dist/realtime/sse_auth_guard.js +9 -9
- package/dist/runtime/mock.d.ts +1 -1
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +14 -11
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +12 -8
- package/dist/server/app_server.d.ts +7 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +36 -31
- package/dist/server/validate_nginx.d.ts +1 -1
- package/dist/server/validate_nginx.js +1 -1
- package/dist/testing/CLAUDE.md +73 -55
- package/dist/testing/admin_integration.d.ts +5 -6
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +100 -96
- package/dist/testing/adversarial_headers.js +1 -1
- package/dist/testing/app_server.d.ts +11 -14
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +18 -17
- package/dist/testing/assertions.d.ts.map +1 -1
- package/dist/testing/assertions.js +2 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +15 -9
- package/dist/testing/audit_completeness.d.ts +2 -2
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +53 -39
- package/dist/testing/auth_apps.d.ts +5 -4
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +28 -22
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +5 -5
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +4 -4
- package/dist/testing/db_entities.d.ts +22 -0
- package/dist/testing/db_entities.d.ts.map +1 -0
- package/dist/testing/db_entities.js +28 -0
- package/dist/testing/entities.d.ts +10 -8
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +22 -18
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +13 -14
- package/dist/testing/integration_helpers.d.ts +8 -6
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +29 -23
- package/dist/testing/middleware.d.ts +15 -11
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +75 -32
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +40 -24
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +3 -1
- package/dist/testing/rpc_round_trip.d.ts +1 -1
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +14 -13
- package/dist/testing/sse_round_trip.d.ts +3 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +7 -11
- package/dist/testing/standard.d.ts +1 -1
- package/dist/testing/stubs.d.ts +25 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +43 -2
- package/dist/testing/surface_invariants.d.ts +2 -2
- package/dist/testing/ws_round_trip.d.ts +12 -13
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +24 -12
- package/dist/ui/AdminAccounts.svelte +23 -20
- package/dist/ui/AdminOverview.svelte +15 -13
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
- package/dist/ui/BootstrapForm.svelte +1 -1
- package/dist/ui/CLAUDE.md +65 -59
- package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +37 -22
- package/dist/ui/RoleGrantOfferForm.svelte.d.ts +20 -0
- package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
- package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
- package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/SignupForm.svelte +1 -1
- package/dist/ui/SurfaceExplorer.svelte +35 -15
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +2 -3
- package/dist/ui/admin_accounts_state.svelte.d.ts +25 -18
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +28 -17
- package/dist/ui/admin_rpc_adapters.d.ts +20 -20
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +17 -17
- package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
- package/dist/ui/admin_sessions_state.svelte.js +2 -2
- package/dist/ui/audit_log_state.svelte.d.ts +7 -7
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +6 -6
- package/dist/ui/auth_state.svelte.d.ts +3 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +6 -6
- package/dist/ui/format_scope.d.ts +2 -2
- package/dist/ui/format_scope.js +2 -2
- package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +39 -31
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +25 -19
- package/dist/ui/ui_format.js +2 -2
- package/package.json +3 -3
- package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
- package/dist/auth/permit_offer_action_specs.js +0 -227
- package/dist/auth/permit_offer_actions.d.ts +0 -110
- package/dist/auth/permit_offer_actions.d.ts.map +0 -1
- package/dist/auth/permit_offer_actions.js +0 -452
- package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.js +0 -182
- package/dist/auth/permit_offer_queries.d.ts +0 -183
- package/dist/auth/permit_offer_queries.d.ts.map +0 -1
- package/dist/auth/permit_offer_queries.js +0 -408
- package/dist/auth/permit_offer_schema.d.ts +0 -103
- package/dist/auth/permit_offer_schema.d.ts.map +0 -1
- package/dist/auth/permit_queries.d.ts +0 -210
- package/dist/auth/permit_queries.d.ts.map +0 -1
- package/dist/auth/permit_queries.js +0 -294
- package/dist/auth/require_keeper.d.ts +0 -20
- package/dist/auth/require_keeper.d.ts.map +0 -1
- package/dist/auth/require_keeper.js +0 -35
- package/dist/auth/route_guards.d.ts +0 -21
- package/dist/auth/route_guards.d.ts.map +0 -1
- package/dist/auth/route_guards.js +0 -32
- package/dist/auth/session_lifecycle.d.ts +0 -37
- package/dist/auth/session_lifecycle.d.ts.map +0 -1
- package/dist/auth/session_lifecycle.js +0 -29
- package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferForm.svelte.d.ts +0 -14
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
- package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical four-axis auth shape for action specs and route specs.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the discriminated `'public' | 'authenticated' | 'keeper' | {role}`
|
|
5
|
+
* literal that conflated authentication, account resolution, actor resolution,
|
|
6
|
+
* and authorization into a single value. The flat record names each axis
|
|
7
|
+
* the dispatcher actually walks:
|
|
8
|
+
*
|
|
9
|
+
* - `account` — does the dispatcher require / load / skip the account?
|
|
10
|
+
* - `actor` — does the dispatcher require / load / skip the acting actor?
|
|
11
|
+
* - `roles` — disjunction of permitted roles (any-of); absent = no role check.
|
|
12
|
+
* - `credential_types` — restricts the credential channel (e.g. daemon_token);
|
|
13
|
+
* absent = any authenticated credential.
|
|
14
|
+
*
|
|
15
|
+
* The same shape governs both `ActionSpec.auth` (in `actions/action_spec.ts`)
|
|
16
|
+
* and `RouteSpec.auth` (in `http/route_spec.ts`). The canonical schema
|
|
17
|
+
* lives here in `http/` because that preserves the existing
|
|
18
|
+
* `actions → http` dependency direction (and `error_schemas.ts` /
|
|
19
|
+
* `surface.ts` consume the type).
|
|
20
|
+
*
|
|
21
|
+
* Registry-time invariants 1, 3, and 4 live on the schema's
|
|
22
|
+
* `.superRefine` so any spec that fails them throws at the Zod parse
|
|
23
|
+
* boundary. Invariant 2 (the `actor !== 'none' ⟺ input or query
|
|
24
|
+
* declares acting?: ActingActor` biconditional) needs introspection of
|
|
25
|
+
* the spec's input/query schemas, so it is enforced at registration
|
|
26
|
+
* time inside the dispatcher loops (`apply_route_specs`,
|
|
27
|
+
* `create_rpc_endpoint`, `register_action_ws`) via the
|
|
28
|
+
* `assert_route_auth_acting_biconditional` helper exported below.
|
|
29
|
+
*
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
import { z } from 'zod';
|
|
33
|
+
/**
|
|
34
|
+
* `acting` field shared by every input that needs the caller's acting actor.
|
|
35
|
+
* Declaring `acting: ActingActor` on a route or action input signals to the
|
|
36
|
+
* dispatcher's authorization phase to resolve an actor against the
|
|
37
|
+
* authenticated account: it runs `resolve_acting_actor`, builds the
|
|
38
|
+
* actor-bound `RequestContext`, and loads role_grants before auth guards fire.
|
|
39
|
+
*
|
|
40
|
+
* Resolution rules: omitted + 1 actor → use it; omitted + multiple actors →
|
|
41
|
+
* `actor_required` with the available list; supplied + on the account → use
|
|
42
|
+
* it; supplied + foreign actor → `actor_not_on_account`.
|
|
43
|
+
*
|
|
44
|
+
* Account-grain routes — input doesn't declare `acting` and auth doesn't
|
|
45
|
+
* require role_grants — skip resolution entirely; their `RequestContext.actor`
|
|
46
|
+
* is `null` and the audit envelope's `actor_id` stays null.
|
|
47
|
+
*
|
|
48
|
+
* Lives next to `RouteAuth` because the two are paired by registry-time
|
|
49
|
+
* invariant 2: `auth.actor !== 'none'` ⟺ input (or query, on REST GETs)
|
|
50
|
+
* declares `acting?: ActingActor`. Keeping the contract in one module
|
|
51
|
+
* removes the http/ → auth/ import that an earlier split forced.
|
|
52
|
+
*/
|
|
53
|
+
export declare const ActingActor: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
54
|
+
export type ActingActor = z.infer<typeof ActingActor>;
|
|
55
|
+
/**
|
|
56
|
+
* Per-axis auth state — names the dispatcher's behavior on `account` and
|
|
57
|
+
* `actor` independently:
|
|
58
|
+
*
|
|
59
|
+
* - `'none'` — explicitly skipped, even when the credential provides it.
|
|
60
|
+
* Public actions (no auth surface) and notifications declare this.
|
|
61
|
+
* - `'optional'` — surfaced if the credential provides it, null otherwise.
|
|
62
|
+
* Identity-aware reads with anonymous fallback (cell_get-style) declare
|
|
63
|
+
* this on `account` / `actor`.
|
|
64
|
+
* - `'required'` — must be resolved; the dispatcher rejects requests that
|
|
65
|
+
* fail to provide it (401 for `account === 'required'` without a
|
|
66
|
+
* credential; the authorization phase 4xx for `actor === 'required'`
|
|
67
|
+
* without an actor binding).
|
|
68
|
+
*/
|
|
69
|
+
export declare const AuthAxisState: z.ZodEnum<{
|
|
70
|
+
optional: "optional";
|
|
71
|
+
none: "none";
|
|
72
|
+
required: "required";
|
|
73
|
+
}>;
|
|
74
|
+
export type AuthAxisState = z.infer<typeof AuthAxisState>;
|
|
75
|
+
/**
|
|
76
|
+
* The canonical four-axis auth shape used by both `ActionSpec.auth` and
|
|
77
|
+
* `RouteSpec.auth`.
|
|
78
|
+
*
|
|
79
|
+
* Cross-axis registry invariants enforced via `.superRefine`:
|
|
80
|
+
*
|
|
81
|
+
* 1. **Roles imply actor.** `roles?.length` ⟹ `actor === 'required'`.
|
|
82
|
+
* Role checks read the actor's role_grants, so a role-gated spec without
|
|
83
|
+
* a resolved actor would have nothing to check.
|
|
84
|
+
* 3. **No accountless actors yet.** `account === 'none' && actor !== 'none'`
|
|
85
|
+
* is invalid in v1. The credential resolver always binds account before
|
|
86
|
+
* actor today; agent-token / group-actor credentials will lift this.
|
|
87
|
+
* 4. **Unrestricted is leaf.** `account === 'none' && actor === 'none'`
|
|
88
|
+
* ⟹ no `roles`, no `credential_types` (nothing left to gate).
|
|
89
|
+
*
|
|
90
|
+
* Invariant 2 — the `actor !== 'none' ⟺ input or query declares
|
|
91
|
+
* acting?: ActingActor` biconditional — needs introspection of the
|
|
92
|
+
* spec's input/query schemas, so it is checked at registration time,
|
|
93
|
+
* not on this schema. See `assert_route_auth_acting_biconditional`
|
|
94
|
+
* below.
|
|
95
|
+
*/
|
|
96
|
+
export declare const RouteAuth: z.ZodObject<{
|
|
97
|
+
account: z.ZodEnum<{
|
|
98
|
+
optional: "optional";
|
|
99
|
+
none: "none";
|
|
100
|
+
required: "required";
|
|
101
|
+
}>;
|
|
102
|
+
actor: z.ZodEnum<{
|
|
103
|
+
optional: "optional";
|
|
104
|
+
none: "none";
|
|
105
|
+
required: "required";
|
|
106
|
+
}>;
|
|
107
|
+
roles: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
108
|
+
credential_types: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
109
|
+
}, z.core.$strict>;
|
|
110
|
+
export type RouteAuth = z.infer<typeof RouteAuth>;
|
|
111
|
+
/**
|
|
112
|
+
* True iff the route is fully public — both account and actor axes
|
|
113
|
+
* are `'none'`. Public routes skip the dispatcher's authorization
|
|
114
|
+
* phase entirely (per registry-time invariant 4 they also cannot
|
|
115
|
+
* declare roles or credential gates).
|
|
116
|
+
*/
|
|
117
|
+
export declare const is_public_auth: (auth: RouteAuth) => boolean;
|
|
118
|
+
/**
|
|
119
|
+
* True iff the route declares an actor axis (`'optional'` or
|
|
120
|
+
* `'required'`). Equivalent to "the dispatcher's authorization phase
|
|
121
|
+
* may resolve an actor for this request" — which by registry-time
|
|
122
|
+
* invariant 2 also means the input (or query, on REST GETs) declares
|
|
123
|
+
* `acting?: ActingActor`.
|
|
124
|
+
*/
|
|
125
|
+
export declare const needs_actor: (auth: RouteAuth) => boolean;
|
|
126
|
+
/**
|
|
127
|
+
* True iff the route declares an account axis (`'optional'` or
|
|
128
|
+
* `'required'`). Per registry-time invariant 3 this is implied by
|
|
129
|
+
* `needs_actor(auth)` in v1 (no accountless actors yet).
|
|
130
|
+
*/
|
|
131
|
+
export declare const needs_account: (auth: RouteAuth) => boolean;
|
|
132
|
+
/** True iff the route declares any role gate (`auth.roles?.length`). */
|
|
133
|
+
export declare const is_role_auth: (auth: RouteAuth) => boolean;
|
|
134
|
+
/** True iff the route declares any credential-type gate (`auth.credential_types?.length`). */
|
|
135
|
+
export declare const is_credential_gated_auth: (auth: RouteAuth) => boolean;
|
|
136
|
+
/**
|
|
137
|
+
* True iff the route is the keeper bucket — credential gate restricted
|
|
138
|
+
* to `daemon_token`. Keeper is the only credential gate today; if more
|
|
139
|
+
* land, this filter widens. Knows the `'daemon_token'` literal directly
|
|
140
|
+
* (the keeper composition is fuz_app's only registered credential gate).
|
|
141
|
+
*/
|
|
142
|
+
export declare const is_keeper_auth: (auth: RouteAuth) => boolean;
|
|
143
|
+
/**
|
|
144
|
+
* True iff the route is plain authenticated — `account === 'required'`
|
|
145
|
+
* with no role gate and no credential gate. Account-grain authenticated
|
|
146
|
+
* routes (logout, password change, account self-service) fall here.
|
|
147
|
+
*/
|
|
148
|
+
export declare const is_plain_authenticated_auth: (auth: RouteAuth) => boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Whether a schema declares the canonical `acting?: ActingActor` field.
|
|
151
|
+
* Reference-equality on the exported `ActingActor` schema — consumer
|
|
152
|
+
* schemas with unrelated `acting` fields don't trip this check.
|
|
153
|
+
*
|
|
154
|
+
* Peels through Zod wrappers (`optional`, `nullable`, `default`,
|
|
155
|
+
* `transform`, `pipe`, `prefault`) via `zod_unwrap_to_object` so a spec
|
|
156
|
+
* authored as `z.optional(z.strictObject({acting: ActingActor}))` or
|
|
157
|
+
* `z.strictObject({acting: ActingActor}).default({})` still trips the
|
|
158
|
+
* predicate.
|
|
159
|
+
*/
|
|
160
|
+
export declare const input_schema_declares_acting: (schema: z.ZodType) => boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Slots where a spec may declare the `acting?: ActingActor` field —
|
|
163
|
+
* input for both REST + actions; query for REST GETs that bi-locate
|
|
164
|
+
* `acting` on the query schema (actions have no `query` shape, so the
|
|
165
|
+
* field is omitted on action call sites).
|
|
166
|
+
*/
|
|
167
|
+
export interface ActingSlots {
|
|
168
|
+
input: z.ZodType;
|
|
169
|
+
query?: z.ZodType;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Registry-time biconditional check: `auth.actor !== 'none' ⟺ some
|
|
173
|
+
* supplied slot declares acting?: ActingActor`. Throws on violation.
|
|
174
|
+
*
|
|
175
|
+
* The slot set differs by surface: REST passes `{input, query}` (both
|
|
176
|
+
* locatable, query only set for GETs); action dispatchers pass `{input}`
|
|
177
|
+
* (no query shape on `ActionSpec`). The throw message lists the slots
|
|
178
|
+
* that were actually in play, so an actor-required action without
|
|
179
|
+
* `acting` doesn't point the operator at a query slot that doesn't
|
|
180
|
+
* exist on their spec.
|
|
181
|
+
*
|
|
182
|
+
* Called by every dispatcher registration loop (`apply_route_specs`,
|
|
183
|
+
* `compile_action_registry`) on every spec it accepts.
|
|
184
|
+
*
|
|
185
|
+
* @param auth - the route/action's auth shape
|
|
186
|
+
* @param slots - the spec's `acting`-bearing schemas; `query` omitted on action call sites
|
|
187
|
+
* @param context - identifier for the throwing message (route key, RPC method, etc.)
|
|
188
|
+
* @throws Error when the biconditional is violated
|
|
189
|
+
*/
|
|
190
|
+
export declare const assert_route_auth_acting_biconditional: (auth: RouteAuth, slots: ActingSlots, context: string) => void;
|
|
191
|
+
//# sourceMappingURL=auth_shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/auth_shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,6DAGtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;EAA2C,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;kBA6CnB,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;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACQ,CAAC;AAE1D;;;;GAIG;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,IAeF,CAAC"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical four-axis auth shape for action specs and route specs.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the discriminated `'public' | 'authenticated' | 'keeper' | {role}`
|
|
5
|
+
* literal that conflated authentication, account resolution, actor resolution,
|
|
6
|
+
* and authorization into a single value. The flat record names each axis
|
|
7
|
+
* the dispatcher actually walks:
|
|
8
|
+
*
|
|
9
|
+
* - `account` — does the dispatcher require / load / skip the account?
|
|
10
|
+
* - `actor` — does the dispatcher require / load / skip the acting actor?
|
|
11
|
+
* - `roles` — disjunction of permitted roles (any-of); absent = no role check.
|
|
12
|
+
* - `credential_types` — restricts the credential channel (e.g. daemon_token);
|
|
13
|
+
* absent = any authenticated credential.
|
|
14
|
+
*
|
|
15
|
+
* The same shape governs both `ActionSpec.auth` (in `actions/action_spec.ts`)
|
|
16
|
+
* and `RouteSpec.auth` (in `http/route_spec.ts`). The canonical schema
|
|
17
|
+
* lives here in `http/` because that preserves the existing
|
|
18
|
+
* `actions → http` dependency direction (and `error_schemas.ts` /
|
|
19
|
+
* `surface.ts` consume the type).
|
|
20
|
+
*
|
|
21
|
+
* Registry-time invariants 1, 3, and 4 live on the schema's
|
|
22
|
+
* `.superRefine` so any spec that fails them throws at the Zod parse
|
|
23
|
+
* boundary. Invariant 2 (the `actor !== 'none' ⟺ input or query
|
|
24
|
+
* declares acting?: ActingActor` biconditional) needs introspection of
|
|
25
|
+
* the spec's input/query schemas, so it is enforced at registration
|
|
26
|
+
* time inside the dispatcher loops (`apply_route_specs`,
|
|
27
|
+
* `create_rpc_endpoint`, `register_action_ws`) via the
|
|
28
|
+
* `assert_route_auth_acting_biconditional` helper exported below.
|
|
29
|
+
*
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
import { z } from 'zod';
|
|
33
|
+
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
34
|
+
import { zod_unwrap_to_object } from '@fuzdev/fuz_util/zod.js';
|
|
35
|
+
/**
|
|
36
|
+
* `acting` field shared by every input that needs the caller's acting actor.
|
|
37
|
+
* Declaring `acting: ActingActor` on a route or action input signals to the
|
|
38
|
+
* dispatcher's authorization phase to resolve an actor against the
|
|
39
|
+
* authenticated account: it runs `resolve_acting_actor`, builds the
|
|
40
|
+
* actor-bound `RequestContext`, and loads role_grants before auth guards fire.
|
|
41
|
+
*
|
|
42
|
+
* Resolution rules: omitted + 1 actor → use it; omitted + multiple actors →
|
|
43
|
+
* `actor_required` with the available list; supplied + on the account → use
|
|
44
|
+
* it; supplied + foreign actor → `actor_not_on_account`.
|
|
45
|
+
*
|
|
46
|
+
* Account-grain routes — input doesn't declare `acting` and auth doesn't
|
|
47
|
+
* require role_grants — skip resolution entirely; their `RequestContext.actor`
|
|
48
|
+
* is `null` and the audit envelope's `actor_id` stays null.
|
|
49
|
+
*
|
|
50
|
+
* Lives next to `RouteAuth` because the two are paired by registry-time
|
|
51
|
+
* invariant 2: `auth.actor !== 'none'` ⟺ input (or query, on REST GETs)
|
|
52
|
+
* declares `acting?: ActingActor`. Keeping the contract in one module
|
|
53
|
+
* removes the http/ → auth/ import that an earlier split forced.
|
|
54
|
+
*/
|
|
55
|
+
export const ActingActor = Uuid.optional().meta({
|
|
56
|
+
description: 'Actor on the authenticated account that this request acts as. Omit on single-actor accounts; required on multi-actor.',
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Per-axis auth state — names the dispatcher's behavior on `account` and
|
|
60
|
+
* `actor` independently:
|
|
61
|
+
*
|
|
62
|
+
* - `'none'` — explicitly skipped, even when the credential provides it.
|
|
63
|
+
* Public actions (no auth surface) and notifications declare this.
|
|
64
|
+
* - `'optional'` — surfaced if the credential provides it, null otherwise.
|
|
65
|
+
* Identity-aware reads with anonymous fallback (cell_get-style) declare
|
|
66
|
+
* this on `account` / `actor`.
|
|
67
|
+
* - `'required'` — must be resolved; the dispatcher rejects requests that
|
|
68
|
+
* fail to provide it (401 for `account === 'required'` without a
|
|
69
|
+
* credential; the authorization phase 4xx for `actor === 'required'`
|
|
70
|
+
* without an actor binding).
|
|
71
|
+
*/
|
|
72
|
+
export const AuthAxisState = z.enum(['none', 'optional', 'required']);
|
|
73
|
+
/**
|
|
74
|
+
* The canonical four-axis auth shape used by both `ActionSpec.auth` and
|
|
75
|
+
* `RouteSpec.auth`.
|
|
76
|
+
*
|
|
77
|
+
* Cross-axis registry invariants enforced via `.superRefine`:
|
|
78
|
+
*
|
|
79
|
+
* 1. **Roles imply actor.** `roles?.length` ⟹ `actor === 'required'`.
|
|
80
|
+
* Role checks read the actor's role_grants, so a role-gated spec without
|
|
81
|
+
* a resolved actor would have nothing to check.
|
|
82
|
+
* 3. **No accountless actors yet.** `account === 'none' && actor !== 'none'`
|
|
83
|
+
* is invalid in v1. The credential resolver always binds account before
|
|
84
|
+
* actor today; agent-token / group-actor credentials will lift this.
|
|
85
|
+
* 4. **Unrestricted is leaf.** `account === 'none' && actor === 'none'`
|
|
86
|
+
* ⟹ no `roles`, no `credential_types` (nothing left to gate).
|
|
87
|
+
*
|
|
88
|
+
* Invariant 2 — the `actor !== 'none' ⟺ input or query declares
|
|
89
|
+
* acting?: ActingActor` biconditional — needs introspection of the
|
|
90
|
+
* spec's input/query schemas, so it is checked at registration time,
|
|
91
|
+
* not on this schema. See `assert_route_auth_acting_biconditional`
|
|
92
|
+
* below.
|
|
93
|
+
*/
|
|
94
|
+
export const RouteAuth = z
|
|
95
|
+
.strictObject({
|
|
96
|
+
account: AuthAxisState,
|
|
97
|
+
actor: AuthAxisState,
|
|
98
|
+
roles: z.array(z.string()).readonly().optional(),
|
|
99
|
+
credential_types: z.array(z.string()).readonly().optional(),
|
|
100
|
+
})
|
|
101
|
+
.superRefine((value, ctx) => {
|
|
102
|
+
// invariant 1: roles imply actor
|
|
103
|
+
if (value.roles?.length && value.actor !== 'required') {
|
|
104
|
+
ctx.addIssue({
|
|
105
|
+
code: 'custom',
|
|
106
|
+
message: "auth.roles requires auth.actor === 'required' (role checks read the actor's role_grants)",
|
|
107
|
+
path: ['roles'],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// invariant 3: no accountless actors yet
|
|
111
|
+
if (value.account === 'none' && value.actor !== 'none') {
|
|
112
|
+
ctx.addIssue({
|
|
113
|
+
code: 'custom',
|
|
114
|
+
message: "auth.account === 'none' && auth.actor !== 'none' is not yet supported — accountless credentials (agent-token, group-actor) are out of scope for v1",
|
|
115
|
+
path: ['actor'],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// invariant 4: unrestricted is leaf
|
|
119
|
+
if (value.account === 'none' && value.actor === 'none') {
|
|
120
|
+
if (value.roles?.length) {
|
|
121
|
+
ctx.addIssue({
|
|
122
|
+
code: 'custom',
|
|
123
|
+
message: "unrestricted auth (account === 'none' && actor === 'none') cannot declare roles — nothing to gate",
|
|
124
|
+
path: ['roles'],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (value.credential_types?.length) {
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: 'custom',
|
|
130
|
+
message: "unrestricted auth (account === 'none' && actor === 'none') cannot declare credential_types — nothing to gate",
|
|
131
|
+
path: ['credential_types'],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// --- Predicates over the four-axis shape ---
|
|
137
|
+
//
|
|
138
|
+
// Pure derived reads of `RouteAuth`. Live here so every consumer that
|
|
139
|
+
// branches on the shape (the dispatcher's authorization phase, route
|
|
140
|
+
// guards, `merge_error_schemas`, `surface_query`, the surface explorer
|
|
141
|
+
// UI, and the testing harnesses) goes through one source of truth
|
|
142
|
+
// instead of inlining axis comparisons that drift over time.
|
|
143
|
+
/**
|
|
144
|
+
* True iff the route is fully public — both account and actor axes
|
|
145
|
+
* are `'none'`. Public routes skip the dispatcher's authorization
|
|
146
|
+
* phase entirely (per registry-time invariant 4 they also cannot
|
|
147
|
+
* declare roles or credential gates).
|
|
148
|
+
*/
|
|
149
|
+
export const is_public_auth = (auth) => auth.account === 'none' && auth.actor === 'none';
|
|
150
|
+
/**
|
|
151
|
+
* True iff the route declares an actor axis (`'optional'` or
|
|
152
|
+
* `'required'`). Equivalent to "the dispatcher's authorization phase
|
|
153
|
+
* may resolve an actor for this request" — which by registry-time
|
|
154
|
+
* invariant 2 also means the input (or query, on REST GETs) declares
|
|
155
|
+
* `acting?: ActingActor`.
|
|
156
|
+
*/
|
|
157
|
+
export const needs_actor = (auth) => auth.actor !== 'none';
|
|
158
|
+
/**
|
|
159
|
+
* True iff the route declares an account axis (`'optional'` or
|
|
160
|
+
* `'required'`). Per registry-time invariant 3 this is implied by
|
|
161
|
+
* `needs_actor(auth)` in v1 (no accountless actors yet).
|
|
162
|
+
*/
|
|
163
|
+
export const needs_account = (auth) => auth.account !== 'none';
|
|
164
|
+
/** True iff the route declares any role gate (`auth.roles?.length`). */
|
|
165
|
+
export const is_role_auth = (auth) => !!auth.roles?.length;
|
|
166
|
+
/** True iff the route declares any credential-type gate (`auth.credential_types?.length`). */
|
|
167
|
+
export const is_credential_gated_auth = (auth) => !!auth.credential_types?.length;
|
|
168
|
+
/**
|
|
169
|
+
* True iff the route is the keeper bucket — credential gate restricted
|
|
170
|
+
* to `daemon_token`. Keeper is the only credential gate today; if more
|
|
171
|
+
* land, this filter widens. Knows the `'daemon_token'` literal directly
|
|
172
|
+
* (the keeper composition is fuz_app's only registered credential gate).
|
|
173
|
+
*/
|
|
174
|
+
export const is_keeper_auth = (auth) => auth.credential_types?.includes('daemon_token') ?? false;
|
|
175
|
+
/**
|
|
176
|
+
* True iff the route is plain authenticated — `account === 'required'`
|
|
177
|
+
* with no role gate and no credential gate. Account-grain authenticated
|
|
178
|
+
* routes (logout, password change, account self-service) fall here.
|
|
179
|
+
*/
|
|
180
|
+
export const is_plain_authenticated_auth = (auth) => auth.account === 'required' && !is_role_auth(auth) && !is_credential_gated_auth(auth);
|
|
181
|
+
// --- Registry-time invariant 2 enforcement ---
|
|
182
|
+
//
|
|
183
|
+
// The biconditional `auth.actor !== 'none' ⟺ input (or query, on REST
|
|
184
|
+
// GETs) declares acting?: ActingActor`. Cannot live on the `RouteAuth`
|
|
185
|
+
// schema's `.superRefine` because it requires introspecting the spec's
|
|
186
|
+
// input/query schemas for reference equality with the canonical
|
|
187
|
+
// `ActingActor` schema above. Enforced at registration time by every
|
|
188
|
+
// dispatcher loop (`apply_route_specs`, `compile_action_registry` for
|
|
189
|
+
// `create_rpc_endpoint` + `register_action_ws`).
|
|
190
|
+
/**
|
|
191
|
+
* Whether a schema declares the canonical `acting?: ActingActor` field.
|
|
192
|
+
* Reference-equality on the exported `ActingActor` schema — consumer
|
|
193
|
+
* schemas with unrelated `acting` fields don't trip this check.
|
|
194
|
+
*
|
|
195
|
+
* Peels through Zod wrappers (`optional`, `nullable`, `default`,
|
|
196
|
+
* `transform`, `pipe`, `prefault`) via `zod_unwrap_to_object` so a spec
|
|
197
|
+
* authored as `z.optional(z.strictObject({acting: ActingActor}))` or
|
|
198
|
+
* `z.strictObject({acting: ActingActor}).default({})` still trips the
|
|
199
|
+
* predicate.
|
|
200
|
+
*/
|
|
201
|
+
export const input_schema_declares_acting = (schema) => {
|
|
202
|
+
const obj = zod_unwrap_to_object(schema);
|
|
203
|
+
if (!obj)
|
|
204
|
+
return false;
|
|
205
|
+
return obj.shape.acting === ActingActor;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Registry-time biconditional check: `auth.actor !== 'none' ⟺ some
|
|
209
|
+
* supplied slot declares acting?: ActingActor`. Throws on violation.
|
|
210
|
+
*
|
|
211
|
+
* The slot set differs by surface: REST passes `{input, query}` (both
|
|
212
|
+
* locatable, query only set for GETs); action dispatchers pass `{input}`
|
|
213
|
+
* (no query shape on `ActionSpec`). The throw message lists the slots
|
|
214
|
+
* that were actually in play, so an actor-required action without
|
|
215
|
+
* `acting` doesn't point the operator at a query slot that doesn't
|
|
216
|
+
* exist on their spec.
|
|
217
|
+
*
|
|
218
|
+
* Called by every dispatcher registration loop (`apply_route_specs`,
|
|
219
|
+
* `compile_action_registry`) on every spec it accepts.
|
|
220
|
+
*
|
|
221
|
+
* @param auth - the route/action's auth shape
|
|
222
|
+
* @param slots - the spec's `acting`-bearing schemas; `query` omitted on action call sites
|
|
223
|
+
* @param context - identifier for the throwing message (route key, RPC method, etc.)
|
|
224
|
+
* @throws Error when the biconditional is violated
|
|
225
|
+
*/
|
|
226
|
+
export const assert_route_auth_acting_biconditional = (auth, slots, context) => {
|
|
227
|
+
const wants_actor = needs_actor(auth);
|
|
228
|
+
const declares_acting = input_schema_declares_acting(slots.input) ||
|
|
229
|
+
(slots.query !== undefined && input_schema_declares_acting(slots.query));
|
|
230
|
+
if (wants_actor === declares_acting)
|
|
231
|
+
return;
|
|
232
|
+
const slot_phrase = slots.query !== undefined ? 'input or query schema' : 'input schema';
|
|
233
|
+
if (wants_actor) {
|
|
234
|
+
throw new Error(`${context}: auth.actor === '${auth.actor}' requires the ${slot_phrase} to declare 'acting?: ActingActor' (registry-time invariant 2)`);
|
|
235
|
+
}
|
|
236
|
+
throw new Error(`${context}: ${slot_phrase} declares 'acting?: ActingActor' but auth.actor === 'none' (registry-time invariant 2)`);
|
|
237
|
+
};
|
|
@@ -16,7 +16,7 @@ import { z } from 'zod';
|
|
|
16
16
|
export const create_health_route_spec = () => ({
|
|
17
17
|
method: 'GET',
|
|
18
18
|
path: '/health',
|
|
19
|
-
auth: {
|
|
19
|
+
auth: { account: 'none', actor: 'none' },
|
|
20
20
|
handler: (c) => c.json({ status: 'ok' }),
|
|
21
21
|
description: 'Health check',
|
|
22
22
|
input: z.null(),
|
|
@@ -31,7 +31,7 @@ export const create_health_route_spec = () => ({
|
|
|
31
31
|
export const create_server_status_route_spec = (options) => ({
|
|
32
32
|
method: 'GET',
|
|
33
33
|
path: '/api/server/status',
|
|
34
|
-
auth: {
|
|
34
|
+
auth: { account: 'required', actor: 'none' },
|
|
35
35
|
handler: (c) => c.json({ version: options.version, uptime_ms: options.get_uptime_ms() }),
|
|
36
36
|
description: 'Server version and uptime',
|
|
37
37
|
input: z.null(),
|
|
@@ -46,7 +46,7 @@ export const create_server_status_route_spec = (options) => ({
|
|
|
46
46
|
export const create_surface_route_spec = (options) => ({
|
|
47
47
|
method: 'GET',
|
|
48
48
|
path: '/api/surface',
|
|
49
|
-
auth: {
|
|
49
|
+
auth: { account: 'required', actor: 'none' },
|
|
50
50
|
handler: (c) => c.json(options.surface),
|
|
51
51
|
description: 'Application surface (routes, middleware, schemas)',
|
|
52
52
|
input: z.null(),
|
package/dist/http/db_routes.d.ts
CHANGED
|
@@ -36,6 +36,10 @@ export interface ColumnInfo {
|
|
|
36
36
|
data_type: string;
|
|
37
37
|
is_nullable: string;
|
|
38
38
|
}
|
|
39
|
+
/** Default page size for `GET /tables/:name` rows. */
|
|
40
|
+
export declare const DB_TABLE_ROWS_DEFAULT_LIMIT = 100;
|
|
41
|
+
/** Maximum page size for `GET /tables/:name` rows. */
|
|
42
|
+
export declare const DB_TABLE_ROWS_LIMIT_MAX = 1000;
|
|
39
43
|
/**
|
|
40
44
|
* Per-factory configuration for db routes.
|
|
41
45
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/db_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,
|
|
1
|
+
{"version":3,"file":"db_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/db_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAoC,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAalF;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,sDAAsD;AACtD,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,sDAAsD;AACtD,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,cAAc,KAAG,KAAK,CAAC,SAAS,CA0P9E,CAAC"}
|
package/dist/http/db_routes.js
CHANGED
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
-
import { get_route_params } from './route_spec.js';
|
|
10
|
+
import { get_route_params, get_route_query } from './route_spec.js';
|
|
11
|
+
import { ActingActor } from './auth_shape.js';
|
|
11
12
|
import { ForeignKeyError, ERROR_TABLE_NOT_FOUND, ERROR_TABLE_NO_PRIMARY_KEY, ERROR_ROW_NOT_FOUND, ERROR_FOREIGN_KEY_VIOLATION, ERROR_INVALID_ROUTE_PARAMS, ERROR_DATABASE_CONNECTION_FAILED, } from './error_schemas.js';
|
|
12
13
|
import { assert_valid_sql_identifier, VALID_SQL_IDENTIFIER } from '../db/sql_identifier.js';
|
|
14
|
+
/** Default page size for `GET /tables/:name` rows. */
|
|
15
|
+
export const DB_TABLE_ROWS_DEFAULT_LIMIT = 100;
|
|
16
|
+
/** Maximum page size for `GET /tables/:name` rows. */
|
|
17
|
+
export const DB_TABLE_ROWS_LIMIT_MAX = 1000;
|
|
13
18
|
/**
|
|
14
19
|
* Create the db API route specs.
|
|
15
20
|
*/
|
|
@@ -19,8 +24,14 @@ export const create_db_route_specs = (options) => {
|
|
|
19
24
|
{
|
|
20
25
|
method: 'GET',
|
|
21
26
|
path: '/health',
|
|
22
|
-
auth: {
|
|
27
|
+
auth: {
|
|
28
|
+
account: 'required',
|
|
29
|
+
actor: 'required',
|
|
30
|
+
roles: ['keeper'],
|
|
31
|
+
credential_types: ['daemon_token'],
|
|
32
|
+
},
|
|
23
33
|
description: 'Database health and stats',
|
|
34
|
+
query: z.strictObject({ acting: ActingActor }),
|
|
24
35
|
input: z.null(),
|
|
25
36
|
output: z.looseObject({ connected: z.boolean() }),
|
|
26
37
|
errors: {
|
|
@@ -53,8 +64,14 @@ export const create_db_route_specs = (options) => {
|
|
|
53
64
|
{
|
|
54
65
|
method: 'GET',
|
|
55
66
|
path: '/tables',
|
|
56
|
-
auth: {
|
|
67
|
+
auth: {
|
|
68
|
+
account: 'required',
|
|
69
|
+
actor: 'required',
|
|
70
|
+
roles: ['keeper'],
|
|
71
|
+
credential_types: ['daemon_token'],
|
|
72
|
+
},
|
|
57
73
|
description: 'List public tables with row counts',
|
|
74
|
+
query: z.strictObject({ acting: ActingActor }),
|
|
58
75
|
input: z.null(),
|
|
59
76
|
output: z.looseObject({
|
|
60
77
|
tables: z.array(z.strictObject({ name: z.string(), row_count: z.number() })),
|
|
@@ -77,9 +94,24 @@ export const create_db_route_specs = (options) => {
|
|
|
77
94
|
{
|
|
78
95
|
method: 'GET',
|
|
79
96
|
path: '/tables/:name',
|
|
80
|
-
auth: {
|
|
97
|
+
auth: {
|
|
98
|
+
account: 'required',
|
|
99
|
+
actor: 'required',
|
|
100
|
+
roles: ['keeper'],
|
|
101
|
+
credential_types: ['daemon_token'],
|
|
102
|
+
},
|
|
81
103
|
description: 'Get table columns and rows (paginated)',
|
|
82
104
|
params: z.strictObject({ name: z.string().regex(VALID_SQL_IDENTIFIER) }),
|
|
105
|
+
query: z.strictObject({
|
|
106
|
+
acting: ActingActor,
|
|
107
|
+
offset: z.coerce.number().int().min(0).default(0),
|
|
108
|
+
limit: z.coerce
|
|
109
|
+
.number()
|
|
110
|
+
.int()
|
|
111
|
+
.min(1)
|
|
112
|
+
.max(DB_TABLE_ROWS_LIMIT_MAX)
|
|
113
|
+
.default(DB_TABLE_ROWS_DEFAULT_LIMIT),
|
|
114
|
+
}),
|
|
83
115
|
input: z.null(),
|
|
84
116
|
errors: {
|
|
85
117
|
400: z.looseObject({ error: z.literal(ERROR_INVALID_ROUTE_PARAMS) }),
|
|
@@ -95,8 +127,7 @@ export const create_db_route_specs = (options) => {
|
|
|
95
127
|
}),
|
|
96
128
|
handler: async (c, route) => {
|
|
97
129
|
const { name } = get_route_params(c);
|
|
98
|
-
const offset =
|
|
99
|
-
const limit = Math.min(1000, Math.max(1, parseInt(c.req.query('limit') ?? '100', 10) || 100));
|
|
130
|
+
const { offset, limit } = get_route_query(c);
|
|
100
131
|
const exists = await route.db.query_one(`SELECT table_name FROM information_schema.tables
|
|
101
132
|
WHERE table_schema = 'public' AND table_name = $1`, [name]);
|
|
102
133
|
if (!exists) {
|
|
@@ -125,12 +156,18 @@ export const create_db_route_specs = (options) => {
|
|
|
125
156
|
{
|
|
126
157
|
method: 'DELETE',
|
|
127
158
|
path: '/tables/:name/rows/:id',
|
|
128
|
-
auth: {
|
|
159
|
+
auth: {
|
|
160
|
+
account: 'required',
|
|
161
|
+
actor: 'required',
|
|
162
|
+
roles: ['keeper'],
|
|
163
|
+
credential_types: ['daemon_token'],
|
|
164
|
+
},
|
|
129
165
|
description: 'Delete a row by primary key',
|
|
130
166
|
params: z.strictObject({
|
|
131
167
|
name: z.string().regex(VALID_SQL_IDENTIFIER),
|
|
132
168
|
id: z.string(),
|
|
133
169
|
}),
|
|
170
|
+
query: z.strictObject({ acting: ActingActor }),
|
|
134
171
|
input: z.null(),
|
|
135
172
|
output: z.looseObject({ success: z.boolean() }),
|
|
136
173
|
errors: {
|