@fuzdev/fuz_app 0.55.0 → 0.57.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 +211 -155
- 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 +19 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +20 -14
- 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 +110 -44
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +92 -287
- 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 +44 -38
- 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 +2 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +32 -10
- 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 +673 -442
- 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 +8 -14
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +26 -32
- package/dist/auth/account_queries.d.ts +46 -13
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +73 -33
- package/dist/auth/account_routes.d.ts +4 -3
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +58 -33
- package/dist/auth/account_schema.d.ts +46 -54
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +21 -48
- package/dist/auth/admin_action_specs.d.ts +55 -21
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +42 -26
- package/dist/auth/admin_actions.d.ts +14 -21
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +47 -44
- 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 -87
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +17 -96
- 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 +48 -42
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +56 -43
- 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/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 -47
- 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 +1 -1
- package/dist/auth/daemon_token_middleware.js +3 -3
- 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 -32
- 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 +5 -2
- package/dist/auth/migrations.d.ts +22 -7
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +64 -25
- package/dist/auth/request_context.d.ts +157 -170
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +224 -268
- package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +130 -100
- 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/{permit_offer_actions.js → role_grant_offer_actions.js} +153 -140
- package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +80 -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/{permit_offer_queries.d.ts → role_grant_offer_queries.d.ts} +64 -64
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
- package/dist/auth/{permit_offer_queries.js → role_grant_offer_queries.js} +136 -123
- 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} +55 -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 +4 -1
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +2 -2
- package/dist/auth/self_service_role_actions.d.ts +35 -29
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +58 -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 +1 -1
- package/dist/db/migrate.js +1 -1
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +4 -4
- package/dist/env/load.d.ts +1 -1
- package/dist/env/load.js +1 -1
- package/dist/hono_context.d.ts +27 -45
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +14 -28
- package/dist/http/CLAUDE.md +235 -121
- 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 +72 -39
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +81 -33
- 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 +89 -75
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +54 -72
- package/dist/http/schema_helpers.d.ts +3 -14
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +2 -14
- package/dist/http/surface.d.ts +2 -10
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +3 -4
- 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 +35 -40
- package/dist/server/validate_nginx.d.ts +1 -1
- package/dist/server/validate_nginx.js +1 -1
- package/dist/testing/CLAUDE.md +50 -38
- 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 +87 -85
- 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 +16 -15
- 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 +36 -36
- 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 +22 -19
- 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 +8 -7
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +21 -18
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +13 -14
- package/dist/testing/integration_helpers.d.ts +4 -4
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +20 -18
- package/dist/testing/middleware.d.ts +4 -4
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +12 -11
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +40 -24
- 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 +14 -6
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +119 -43
- 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 +19 -11
- 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 +60 -60
- package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
- package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
- 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 +18 -18
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +16 -16
- 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} +30 -30
- 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} +18 -18
- 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 -258
- 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_notifications.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.js +0 -182
- package/dist/auth/permit_offer_queries.d.ts.map +0 -1
- package/dist/auth/permit_offer_schema.d.ts +0 -125
- package/dist/auth/permit_offer_schema.d.ts.map +0 -1
- package/dist/auth/permit_queries.d.ts +0 -222
- package/dist/auth/permit_queries.d.ts.map +0 -1
- package/dist/auth/permit_queries.js +0 -305
- 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 -27
- package/dist/auth/route_guards.d.ts.map +0 -1
- package/dist/auth/route_guards.js +0 -38
- 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.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,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grant-path registry — the surfaces through which a role can be
|
|
3
|
+
* granted to an actor.
|
|
4
|
+
*
|
|
5
|
+
* Four builtins:
|
|
6
|
+
*
|
|
7
|
+
* - `admin` — granted by an admin via `role_grant_offer_create` (subject to
|
|
8
|
+
* the consumer's `authorize` callback) or admin-side direct grant.
|
|
9
|
+
* - `self_service` — toggled by the holder themselves via
|
|
10
|
+
* `self_service_role_set` (allowlisted by `eligible_roles`).
|
|
11
|
+
* - `system` — granted by system code paths (signup, automation, etc.)
|
|
12
|
+
* that don't fit either of the above.
|
|
13
|
+
* - `bootstrap` — granted exactly once during the bootstrap flow
|
|
14
|
+
* (`keeper`, `admin` on a fresh install).
|
|
15
|
+
*
|
|
16
|
+
* Open registry on top so consumers can declare additional paths
|
|
17
|
+
* (e.g. `'invite_only'`, `'sso_assertion'`) without an upstream release.
|
|
18
|
+
* `RoleSpec.grant_paths` references entries from this registry; the
|
|
19
|
+
* default for `admin_actions.grantable_roles` is `grant_paths.includes('admin')`,
|
|
20
|
+
* the default for `self_service_role_actions` eligibility is
|
|
21
|
+
* `grant_paths.includes('self_service')`. Mirrors the open-registry
|
|
22
|
+
* pattern used for `RoleName`, `ScopeKindName`, `CredentialTypeName`,
|
|
23
|
+
* and `AuditEventTypeName`.
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
/**
|
|
29
|
+
* Letter (lowercase a-z) start and end (or single letter), with letters
|
|
30
|
+
* and underscores in between. Mirrors `RoleName`, `ScopeKindName`,
|
|
31
|
+
* `CredentialTypeName`. Rejects empty strings, leading or trailing
|
|
32
|
+
* underscores, uppercase, and digits.
|
|
33
|
+
*/
|
|
34
|
+
export const GRANT_PATH_NAME_REGEX = /^[a-z][a-z_]*[a-z]$|^[a-z]$/;
|
|
35
|
+
/** Zod schema for valid grant-path name strings. */
|
|
36
|
+
export const GrantPathName = z
|
|
37
|
+
.string()
|
|
38
|
+
.regex(GRANT_PATH_NAME_REGEX, 'Grant-path names must be lowercase letters and underscores (a-z_), no leading/trailing underscore');
|
|
39
|
+
// Builtin grant paths — provided by fuz_app, always available.
|
|
40
|
+
/** Admin-mediated grant — `role_grant_offer_create` plus admin-direct flows. */
|
|
41
|
+
export const GRANT_PATH_ADMIN = 'admin';
|
|
42
|
+
/** Self-service grant — caller toggles their own role_grant via `self_service_role_set`. */
|
|
43
|
+
export const GRANT_PATH_SELF_SERVICE = 'self_service';
|
|
44
|
+
/** System-mediated grant — signup hooks, automation, internal service flows. */
|
|
45
|
+
export const GRANT_PATH_SYSTEM = 'system';
|
|
46
|
+
/** Bootstrap grant — one-shot flow during the keep's first-run bootstrap. */
|
|
47
|
+
export const GRANT_PATH_BOOTSTRAP = 'bootstrap';
|
|
48
|
+
/** The builtin grant-path names as a const tuple. */
|
|
49
|
+
export const BUILTIN_GRANT_PATHS = [
|
|
50
|
+
GRANT_PATH_ADMIN,
|
|
51
|
+
GRANT_PATH_SELF_SERVICE,
|
|
52
|
+
GRANT_PATH_SYSTEM,
|
|
53
|
+
GRANT_PATH_BOOTSTRAP,
|
|
54
|
+
];
|
|
55
|
+
/** Zod enum for builtin grant paths only. */
|
|
56
|
+
export const BuiltinGrantPath = z.enum(BUILTIN_GRANT_PATHS);
|
|
57
|
+
/**
|
|
58
|
+
* Builtin grant-path metadata. Not overridable by consumers.
|
|
59
|
+
*
|
|
60
|
+
* Typed `ReadonlyMap` for the contract — but JS Maps don't honor
|
|
61
|
+
* `Object.freeze` for `.set` / `.delete` / `.clear` (they mutate
|
|
62
|
+
* internal slots, not own properties), so freeze adds no runtime guard
|
|
63
|
+
* here. Read once at startup by `create_grant_path_schema`; runtime
|
|
64
|
+
* mutation has no effect on already-built schemas.
|
|
65
|
+
*/
|
|
66
|
+
export const BUILTIN_GRANT_PATH_META = new Map([
|
|
67
|
+
[
|
|
68
|
+
GRANT_PATH_ADMIN,
|
|
69
|
+
{
|
|
70
|
+
description: 'Admin-mediated grant — admin offers via `role_grant_offer_create` or direct grant.',
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
GRANT_PATH_SELF_SERVICE,
|
|
75
|
+
{
|
|
76
|
+
description: 'Self-service grant — caller toggles their own role_grant via `self_service_role_set`.',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
GRANT_PATH_SYSTEM,
|
|
81
|
+
{ description: 'System-mediated grant — signup, automation, or internal service flows.' },
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
GRANT_PATH_BOOTSTRAP,
|
|
85
|
+
{ description: 'Bootstrap grant — one-shot flow during the keep’s first-run bootstrap.' },
|
|
86
|
+
],
|
|
87
|
+
]);
|
|
88
|
+
/**
|
|
89
|
+
* Create a grant-path schema from the builtin set plus optional
|
|
90
|
+
* consumer-declared additions.
|
|
91
|
+
*
|
|
92
|
+
* Builtins (`admin`, `self_service`, `system`, `bootstrap`) are always
|
|
93
|
+
* present; consumer entries that collide with a builtin name throw at
|
|
94
|
+
* construction. Pass the result into `create_role_schema`'s optional
|
|
95
|
+
* `grant_paths` parameter so each role's `grant_paths` entries are
|
|
96
|
+
* validated against this set at construction time.
|
|
97
|
+
*
|
|
98
|
+
* @param consumer_paths - optional consumer-declared grant-path set with optional metadata
|
|
99
|
+
* @returns `{GrantPath, grant_paths}` — Zod schema and metadata map
|
|
100
|
+
*
|
|
101
|
+
* @throws Error if any `consumer_paths` key fails the `GrantPathName` regex, collides with a builtin name, or appears more than once
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* // simple — builtins only
|
|
106
|
+
* const {GrantPath, grant_paths} = create_grant_path_schema();
|
|
107
|
+
*
|
|
108
|
+
* // with consumer extensions
|
|
109
|
+
* const {GrantPath} = create_grant_path_schema({
|
|
110
|
+
* invite_only: {description: 'Granted by claiming a consumer-issued invite.'},
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export const create_grant_path_schema = (consumer_paths = {}) => {
|
|
115
|
+
const consumer_names = Object.keys(consumer_paths);
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
for (const name of consumer_names) {
|
|
118
|
+
const parsed = GrantPathName.safeParse(name);
|
|
119
|
+
if (!parsed.success) {
|
|
120
|
+
throw new Error(`Invalid grant-path name "${name}": ${parsed.error.issues[0].message}`);
|
|
121
|
+
}
|
|
122
|
+
if (BUILTIN_GRANT_PATH_META.has(name)) {
|
|
123
|
+
throw new Error(`Consumer grant-path "${name}" collides with builtin grant-path`);
|
|
124
|
+
}
|
|
125
|
+
if (seen.has(name)) {
|
|
126
|
+
throw new Error(`Duplicate grant-path name "${name}"`);
|
|
127
|
+
}
|
|
128
|
+
seen.add(name);
|
|
129
|
+
}
|
|
130
|
+
const all_names = [...BUILTIN_GRANT_PATHS, ...consumer_names];
|
|
131
|
+
const GrantPath = z.enum(all_names);
|
|
132
|
+
const grant_paths = new Map(BUILTIN_GRANT_PATH_META);
|
|
133
|
+
for (const name of consumer_names) {
|
|
134
|
+
grant_paths.set(name, consumer_paths[name]);
|
|
135
|
+
}
|
|
136
|
+
return { GrantPath, grant_paths };
|
|
137
|
+
};
|
|
@@ -41,13 +41,24 @@ export declare const query_invite_find_unclaimed_match: (deps: QueryDeps, email:
|
|
|
41
41
|
/**
|
|
42
42
|
* Claim an invite by setting the claimed_by and claimed_at fields.
|
|
43
43
|
*
|
|
44
|
+
* The `_unscoped` suffix is the safety signal — the SQL only checks the
|
|
45
|
+
* row state (`claimed_at IS NULL`), not whether the claiming account's
|
|
46
|
+
* email or username matches the invite. Callers must scope the lookup
|
|
47
|
+
* upstream via `query_invite_find_unclaimed_match`; the production caller
|
|
48
|
+
* (`auth/signup_routes.ts`) does this. Skipping the find step lets a
|
|
49
|
+
* caller claim any unclaimed invite by id.
|
|
50
|
+
*
|
|
51
|
+
* Mirrors the `query_session_revoke_by_hash_unscoped` precedent — there
|
|
52
|
+
* is no scoped sibling because the scoping is provided by a separate
|
|
53
|
+
* find query, not by an alternate variant of this query.
|
|
54
|
+
*
|
|
44
55
|
* @param deps - query dependencies
|
|
45
56
|
* @param invite_id - the invite to claim
|
|
46
57
|
* @param account_id - the account claiming the invite
|
|
47
58
|
* @returns true if the invite was claimed, false if already claimed or not found
|
|
48
59
|
* @mutates `invite` row - sets `claimed_by` and `claimed_at` when still unclaimed
|
|
49
60
|
*/
|
|
50
|
-
export declare const
|
|
61
|
+
export declare const query_invite_claim_unscoped: (deps: QueryDeps, invite_id: string, account_id: string) => Promise<boolean>;
|
|
51
62
|
/**
|
|
52
63
|
* List all invites, newest first.
|
|
53
64
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAC;AAE3F;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,OAAO,MAAM,GAAG,IAAI,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAe5B,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAC;AAE3F;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,OAAO,MAAM,GAAG,IAAI,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAe5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,WAAW,MAAM,EACjB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAElF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAUxC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,OAAO,CAMjB,CAAC"}
|
|
@@ -61,13 +61,24 @@ export const query_invite_find_unclaimed_match = async (deps, email, username) =
|
|
|
61
61
|
/**
|
|
62
62
|
* Claim an invite by setting the claimed_by and claimed_at fields.
|
|
63
63
|
*
|
|
64
|
+
* The `_unscoped` suffix is the safety signal — the SQL only checks the
|
|
65
|
+
* row state (`claimed_at IS NULL`), not whether the claiming account's
|
|
66
|
+
* email or username matches the invite. Callers must scope the lookup
|
|
67
|
+
* upstream via `query_invite_find_unclaimed_match`; the production caller
|
|
68
|
+
* (`auth/signup_routes.ts`) does this. Skipping the find step lets a
|
|
69
|
+
* caller claim any unclaimed invite by id.
|
|
70
|
+
*
|
|
71
|
+
* Mirrors the `query_session_revoke_by_hash_unscoped` precedent — there
|
|
72
|
+
* is no scoped sibling because the scoping is provided by a separate
|
|
73
|
+
* find query, not by an alternate variant of this query.
|
|
74
|
+
*
|
|
64
75
|
* @param deps - query dependencies
|
|
65
76
|
* @param invite_id - the invite to claim
|
|
66
77
|
* @param account_id - the account claiming the invite
|
|
67
78
|
* @returns true if the invite was claimed, false if already claimed or not found
|
|
68
79
|
* @mutates `invite` row - sets `claimed_by` and `claimed_at` when still unclaimed
|
|
69
80
|
*/
|
|
70
|
-
export const
|
|
81
|
+
export const query_invite_claim_unscoped = async (deps, invite_id, account_id) => {
|
|
71
82
|
const rows = await deps.db.query(`UPDATE invite SET claimed_by = $1, claimed_at = NOW()
|
|
72
83
|
WHERE id = $2 AND claimed_at IS NULL
|
|
73
84
|
RETURNING id`, [account_id, invite_id]);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
11
|
-
import { Username, Email } from '
|
|
11
|
+
import { Username, Email } from '../primitive_schemas.js';
|
|
12
12
|
/** Invite row from the database. */
|
|
13
13
|
export interface Invite {
|
|
14
14
|
id: Uuid;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE5C,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"invite_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE5C,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,yBAAyB,CAAC;AAExD,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACtB,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED,8CAA8C;AAC9C,eAAO,MAAM,UAAU;;;;;;;;kBAQrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB;;;;;;;;;;kBAGlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
11
|
-
import { Username, Email } from '
|
|
11
|
+
import { Username, Email } from '../primitive_schemas.js';
|
|
12
12
|
/** Zod schema for client-safe invite data. */
|
|
13
13
|
export const InviteJson = z.strictObject({
|
|
14
14
|
id: Uuid,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,oFAAoF;IACpF,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,oFAAoF;IACpF,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAwE/B,CAAC"}
|
package/dist/auth/middleware.js
CHANGED
|
@@ -23,8 +23,11 @@ export const create_auth_middleware_specs = async (deps, options) => {
|
|
|
23
23
|
const { keyring, db } = deps;
|
|
24
24
|
const { allowed_origins, session_options, path = '/api/*', daemon_token_state, bearer_ip_rate_limiter, } = options;
|
|
25
25
|
const query_deps = { db };
|
|
26
|
-
// Dynamic imports
|
|
27
|
-
//
|
|
26
|
+
// Dynamic imports preserve the bundle-split for type-only consumers of
|
|
27
|
+
// this module (`MiddlewareSpec`, `AuthMiddlewareOptions`, etc.). The
|
|
28
|
+
// runtime chain pulls session_queries + blake3 transitively via
|
|
29
|
+
// session_middleware.js, but type-only imports are erased at compile
|
|
30
|
+
// time and stay free.
|
|
28
31
|
const [{ verify_request_source }, { create_session_middleware }, { create_request_context_middleware }, { create_bearer_auth_middleware },] = await Promise.all([
|
|
29
32
|
import('../http/origin.js'),
|
|
30
33
|
import('./session_middleware.js'),
|
|
@@ -39,18 +39,33 @@
|
|
|
39
39
|
import type { Migration, MigrationNamespace } from '../db/migrate.js';
|
|
40
40
|
/** Namespace identifier for fuz_app auth migrations. */
|
|
41
41
|
export declare const AUTH_MIGRATION_NAMESPACE = "fuz_auth";
|
|
42
|
+
/**
|
|
43
|
+
* Migration namespaces reserved by fuz_app. Consumers passing
|
|
44
|
+
* `migration_namespaces` to `create_app_backend` must choose a name not in
|
|
45
|
+
* this list — the runtime check rejects matches with a thrown error. Typed
|
|
46
|
+
* as `ReadonlyArray<string>` (not a literal tuple) so `.includes()` accepts
|
|
47
|
+
* any consumer-supplied namespace string without a cast.
|
|
48
|
+
*/
|
|
49
|
+
export declare const RESERVED_MIGRATION_NAMESPACES: ReadonlyArray<string>;
|
|
42
50
|
/**
|
|
43
51
|
* Auth schema migrations in order.
|
|
44
52
|
*
|
|
45
|
-
* - v0: Full auth schema — account (with email_verified), actor,
|
|
53
|
+
* - v0: Full auth schema — account (with email_verified), actor, role_grant,
|
|
46
54
|
* auth_session, api_token, audit_log (with seq), bootstrap_lock, invite,
|
|
47
55
|
* app_settings, plus all indexes and seeds.
|
|
48
|
-
* - v1: `
|
|
49
|
-
* `source_offer_id` / `revoked_reason` to `
|
|
50
|
-
* `(actor_id, role)` partial unique index for a scope-aware
|
|
51
|
-
* the
|
|
52
|
-
*
|
|
53
|
-
* `
|
|
56
|
+
* - v1: `role_grant_offer` table for consentful grants; adds `scope_id` /
|
|
57
|
+
* `scope_kind` / `source_offer_id` / `revoked_reason` to `role_grant` and
|
|
58
|
+
* swaps the `(actor_id, role)` partial unique index for a scope-aware
|
|
59
|
+
* variant using the index-side `'GLOBAL'` token + all-zeros sentinel
|
|
60
|
+
* UUID. The `(scope_kind, scope_id)` pair is enforced paired-null by
|
|
61
|
+
* `role_grant_scope_kind_paired` / `role_grant_offer_scope_kind_paired` CHECK
|
|
62
|
+
* constraints — both null for global, both non-null for scoped. The
|
|
63
|
+
* `role_grant_offer` table carries a `superseded_at` terminal state; its
|
|
64
|
+
* partial unique index is scoped by
|
|
65
|
+
* `(to_account, role, scope_kind, scope, from_actor)` so multiple
|
|
66
|
+
* grantors may coexist. `scope_kind` is informative-only in v1
|
|
67
|
+
* (registry-membership validation against `create_scope_kind_schema`);
|
|
68
|
+
* v2 may add INSERT-time `(role, scope_kind)` enforcement.
|
|
54
69
|
*/
|
|
55
70
|
export declare const AUTH_MIGRATIONS: Array<Migration>;
|
|
56
71
|
/** Pre-composed migration namespace for auth tables. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AA8BH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AAEpE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,MAAM,CAA8B,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAqF5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
|
package/dist/auth/migrations.js
CHANGED
|
@@ -36,23 +36,38 @@
|
|
|
36
36
|
*
|
|
37
37
|
* @module
|
|
38
38
|
*/
|
|
39
|
-
import { ACCOUNT_SCHEMA, ACCOUNT_EMAIL_INDEX, ACCOUNT_USERNAME_CI_INDEX, ACTOR_SCHEMA, ACTOR_INDEX,
|
|
39
|
+
import { ACCOUNT_SCHEMA, ACCOUNT_EMAIL_INDEX, ACCOUNT_USERNAME_CI_INDEX, ACTOR_SCHEMA, ACTOR_INDEX, ROLE_GRANT_SCHEMA, ROLE_GRANT_INDEXES, AUTH_SESSION_SCHEMA, AUTH_SESSION_INDEXES, API_TOKEN_SCHEMA, API_TOKEN_INDEX, BOOTSTRAP_LOCK_SCHEMA, BOOTSTRAP_LOCK_SEED, INVITE_SCHEMA, INVITE_INDEXES, APP_SETTINGS_SCHEMA, APP_SETTINGS_SEED, } from './ddl.js';
|
|
40
40
|
import { AUDIT_LOG_SCHEMA, AUDIT_LOG_INDEXES } from './audit_log_schema.js';
|
|
41
|
-
import {
|
|
41
|
+
import { ROLE_GRANT_OFFER_SCHEMA, ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX, ROLE_GRANT_OFFER_INBOX_INDEX, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, } from './role_grant_offer_schema.js';
|
|
42
42
|
/** Namespace identifier for fuz_app auth migrations. */
|
|
43
43
|
export const AUTH_MIGRATION_NAMESPACE = 'fuz_auth';
|
|
44
|
+
/**
|
|
45
|
+
* Migration namespaces reserved by fuz_app. Consumers passing
|
|
46
|
+
* `migration_namespaces` to `create_app_backend` must choose a name not in
|
|
47
|
+
* this list — the runtime check rejects matches with a thrown error. Typed
|
|
48
|
+
* as `ReadonlyArray<string>` (not a literal tuple) so `.includes()` accepts
|
|
49
|
+
* any consumer-supplied namespace string without a cast.
|
|
50
|
+
*/
|
|
51
|
+
export const RESERVED_MIGRATION_NAMESPACES = [AUTH_MIGRATION_NAMESPACE];
|
|
44
52
|
/**
|
|
45
53
|
* Auth schema migrations in order.
|
|
46
54
|
*
|
|
47
|
-
* - v0: Full auth schema — account (with email_verified), actor,
|
|
55
|
+
* - v0: Full auth schema — account (with email_verified), actor, role_grant,
|
|
48
56
|
* auth_session, api_token, audit_log (with seq), bootstrap_lock, invite,
|
|
49
57
|
* app_settings, plus all indexes and seeds.
|
|
50
|
-
* - v1: `
|
|
51
|
-
* `source_offer_id` / `revoked_reason` to `
|
|
52
|
-
* `(actor_id, role)` partial unique index for a scope-aware
|
|
53
|
-
* the
|
|
54
|
-
*
|
|
55
|
-
* `
|
|
58
|
+
* - v1: `role_grant_offer` table for consentful grants; adds `scope_id` /
|
|
59
|
+
* `scope_kind` / `source_offer_id` / `revoked_reason` to `role_grant` and
|
|
60
|
+
* swaps the `(actor_id, role)` partial unique index for a scope-aware
|
|
61
|
+
* variant using the index-side `'GLOBAL'` token + all-zeros sentinel
|
|
62
|
+
* UUID. The `(scope_kind, scope_id)` pair is enforced paired-null by
|
|
63
|
+
* `role_grant_scope_kind_paired` / `role_grant_offer_scope_kind_paired` CHECK
|
|
64
|
+
* constraints — both null for global, both non-null for scoped. The
|
|
65
|
+
* `role_grant_offer` table carries a `superseded_at` terminal state; its
|
|
66
|
+
* partial unique index is scoped by
|
|
67
|
+
* `(to_account, role, scope_kind, scope, from_actor)` so multiple
|
|
68
|
+
* grantors may coexist. `scope_kind` is informative-only in v1
|
|
69
|
+
* (registry-membership validation against `create_scope_kind_schema`);
|
|
70
|
+
* v2 may add INSERT-time `(role, scope_kind)` enforcement.
|
|
56
71
|
*/
|
|
57
72
|
export const AUTH_MIGRATIONS = [
|
|
58
73
|
// v0: full auth schema — all IF NOT EXISTS, safe for existing databases
|
|
@@ -64,8 +79,8 @@ export const AUTH_MIGRATIONS = [
|
|
|
64
79
|
await db.query(ACCOUNT_USERNAME_CI_INDEX);
|
|
65
80
|
await db.query(ACTOR_SCHEMA);
|
|
66
81
|
await db.query(ACTOR_INDEX);
|
|
67
|
-
await db.query(
|
|
68
|
-
for (const sql of
|
|
82
|
+
await db.query(ROLE_GRANT_SCHEMA);
|
|
83
|
+
for (const sql of ROLE_GRANT_INDEXES) {
|
|
69
84
|
await db.query(sql);
|
|
70
85
|
}
|
|
71
86
|
await db.query(AUTH_SESSION_SCHEMA);
|
|
@@ -88,24 +103,48 @@ export const AUTH_MIGRATIONS = [
|
|
|
88
103
|
await db.query(APP_SETTINGS_SEED);
|
|
89
104
|
},
|
|
90
105
|
},
|
|
91
|
-
// v1: consentful
|
|
106
|
+
// v1: consentful role_grants — role_grant_offer table + scoped role_grants
|
|
92
107
|
{
|
|
93
108
|
name: 'permit_offer_and_scoped_permits',
|
|
94
109
|
up: async (db) => {
|
|
95
|
-
await db.query(
|
|
96
|
-
await db.query(
|
|
97
|
-
await db.query(
|
|
98
|
-
await db.query('ALTER TABLE
|
|
99
|
-
await db.query('ALTER TABLE
|
|
100
|
-
await db.query('ALTER TABLE
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
await db.query(ROLE_GRANT_OFFER_SCHEMA);
|
|
111
|
+
await db.query(ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX);
|
|
112
|
+
await db.query(ROLE_GRANT_OFFER_INBOX_INDEX);
|
|
113
|
+
await db.query('ALTER TABLE role_grant ADD COLUMN IF NOT EXISTS scope_id UUID NULL');
|
|
114
|
+
await db.query('ALTER TABLE role_grant ADD COLUMN IF NOT EXISTS scope_kind TEXT NULL');
|
|
115
|
+
await db.query('ALTER TABLE role_grant ADD COLUMN IF NOT EXISTS source_offer_id UUID NULL REFERENCES role_grant_offer(id) ON DELETE SET NULL');
|
|
116
|
+
await db.query('ALTER TABLE role_grant ADD COLUMN IF NOT EXISTS revoked_reason TEXT NULL');
|
|
117
|
+
// Paired-null CHECK on `(scope_kind, scope_id)` — both null encodes
|
|
118
|
+
// the global case; both non-null encodes a scoped grant. The DO
|
|
119
|
+
// block makes constraint addition idempotent across migration
|
|
120
|
+
// re-runs (Postgres has no `ADD CONSTRAINT IF NOT EXISTS` for
|
|
121
|
+
// CHECK constraints — `pg_constraint` lookup is the established
|
|
122
|
+
// shape).
|
|
123
|
+
await db.query(`DO $$ BEGIN
|
|
124
|
+
IF NOT EXISTS (
|
|
125
|
+
SELECT 1 FROM pg_constraint WHERE conname = 'role_grant_scope_kind_paired'
|
|
126
|
+
) THEN
|
|
127
|
+
ALTER TABLE role_grant
|
|
128
|
+
ADD CONSTRAINT role_grant_scope_kind_paired
|
|
129
|
+
CHECK ((scope_kind IS NULL) = (scope_id IS NULL));
|
|
130
|
+
END IF;
|
|
131
|
+
END $$`);
|
|
132
|
+
// Swap the (actor_id, role) partial unique for a scope-aware variant.
|
|
133
|
+
// Existing rows have `scope_id = NULL` (and `scope_kind = NULL` per
|
|
134
|
+
// the pair invariant) and collapse to the index-side `'GLOBAL'`
|
|
135
|
+
// token + all-zeros sentinel UUID.
|
|
136
|
+
await db.query('DROP INDEX IF EXISTS role_grant_actor_role_active_unique');
|
|
137
|
+
await db.query('DROP INDEX IF EXISTS role_grant_actor_role_scope_active_unique');
|
|
138
|
+
await db.query(`CREATE UNIQUE INDEX IF NOT EXISTS role_grant_actor_role_scope_active_unique
|
|
139
|
+
ON role_grant (
|
|
140
|
+
actor_id,
|
|
141
|
+
role,
|
|
142
|
+
COALESCE(scope_kind, '${ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN}'),
|
|
143
|
+
COALESCE(scope_id, '${ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID}'::uuid)
|
|
144
|
+
)
|
|
106
145
|
WHERE revoked_at IS NULL`);
|
|
107
|
-
await db.query(`CREATE INDEX IF NOT EXISTS
|
|
108
|
-
ON
|
|
146
|
+
await db.query(`CREATE INDEX IF NOT EXISTS role_grant_scope_active
|
|
147
|
+
ON role_grant (actor_id, role, scope_id)
|
|
109
148
|
WHERE revoked_at IS NULL`);
|
|
110
149
|
},
|
|
111
150
|
},
|