@fuzdev/fuz_app 0.55.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 +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 +56 -34
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +63 -28
- 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 +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 +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
|
@@ -10,6 +10,7 @@ import { z } from 'zod';
|
|
|
10
10
|
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
11
11
|
import { Blake3Hash } from '@fuzdev/fuz_util/hash_blake3.js';
|
|
12
12
|
import { AuthSessionJson } from './account_schema.js';
|
|
13
|
+
import { Email } from '../primitive_schemas.js';
|
|
13
14
|
import { ApiTokenId } from './api_token.js';
|
|
14
15
|
/**
|
|
15
16
|
* All tracked auth event types. Frozen to convert accidental in-process
|
|
@@ -28,14 +29,14 @@ export const AUDIT_EVENT_TYPES = Object.freeze([
|
|
|
28
29
|
'token_create',
|
|
29
30
|
'token_revoke',
|
|
30
31
|
'token_revoke_all',
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
32
|
+
'role_grant_create',
|
|
33
|
+
'role_grant_revoke',
|
|
34
|
+
'role_grant_offer_create',
|
|
35
|
+
'role_grant_offer_accept',
|
|
36
|
+
'role_grant_offer_decline',
|
|
37
|
+
'role_grant_offer_retract',
|
|
38
|
+
'role_grant_offer_expire',
|
|
39
|
+
'role_grant_offer_supersede',
|
|
39
40
|
'invite_create',
|
|
40
41
|
'invite_delete',
|
|
41
42
|
'app_settings_update',
|
|
@@ -74,19 +75,31 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
74
75
|
})
|
|
75
76
|
.nullable(),
|
|
76
77
|
signup: z.looseObject({
|
|
77
|
-
username: z.string().meta({ description: 'Username
|
|
78
|
+
username: z.string().meta({ description: 'Username submitted at signup.' }),
|
|
78
79
|
invite_id: Uuid.optional().meta({
|
|
79
|
-
description: 'Invite consumed by this signup
|
|
80
|
+
description: 'Invite consumed by this signup. Set on success and on `race_lost` / `signup_conflict` failure rows when an invite was matched at attempt time.',
|
|
80
81
|
}),
|
|
81
82
|
open_signup: z.boolean().optional().meta({
|
|
82
|
-
description: 'True when the signup occurred via the `open_signup` setting (no invite required).',
|
|
83
|
+
description: 'True when the signup occurred via the `open_signup` setting (no invite required). Set on success rows under `open_signup` and on failure rows when the attempt was made under `open_signup`.',
|
|
84
|
+
}),
|
|
85
|
+
reason: z.string().optional().meta({
|
|
86
|
+
description: 'Failure category: `no_match` (no unclaimed invite matched), `race_lost` (invite was claimed between find and claim), `signup_conflict` (username/email already exists). Set only on `outcome=failure`.',
|
|
87
|
+
}),
|
|
88
|
+
email: Email.optional().meta({
|
|
89
|
+
description: 'Email submitted at signup — recorded on failure rows for forensic correlation. Omitted on success rows because the email is already tied to the resulting account.',
|
|
83
90
|
}),
|
|
84
91
|
}),
|
|
85
92
|
password_change: z
|
|
86
93
|
.looseObject({
|
|
87
|
-
sessions_revoked: z
|
|
88
|
-
.
|
|
89
|
-
|
|
94
|
+
sessions_revoked: z.number().optional().meta({
|
|
95
|
+
description: 'Number of sessions revoked as a side effect of the password change. Present on `outcome=success`.',
|
|
96
|
+
}),
|
|
97
|
+
tokens_revoked: z.number().optional().meta({
|
|
98
|
+
description: 'Number of API tokens revoked as a side effect of the password change. Present on `outcome=success`.',
|
|
99
|
+
}),
|
|
100
|
+
reason: z.enum(['concurrent_change']).optional().meta({
|
|
101
|
+
description: 'Failure category. `concurrent_change` indicates another password change committed first against the same starting hash (verify-write race loser). Absent for typed-wrong-password failures.',
|
|
102
|
+
}),
|
|
90
103
|
})
|
|
91
104
|
.nullable(),
|
|
92
105
|
session_revoke: z.looseObject({
|
|
@@ -128,19 +141,19 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
128
141
|
description: 'Probed account id when the target lookup missed (FK constraint forces `target_account_id` to null).',
|
|
129
142
|
}),
|
|
130
143
|
}),
|
|
131
|
-
// `
|
|
132
|
-
// (e.g.
|
|
144
|
+
// `role_grant_id` is optional on `role_grant_create` because failed grants
|
|
145
|
+
// (e.g. admin-grant-path denied) never produce a role_grant row.
|
|
133
146
|
// `self_service: true` is set by the self-service role toggle in
|
|
134
147
|
// `self_service_role_actions.ts` — declared explicitly rather than
|
|
135
148
|
// riding on `z.looseObject` permissiveness so the field is part of
|
|
136
149
|
// the documented schema surface.
|
|
137
|
-
|
|
150
|
+
role_grant_create: z.looseObject({
|
|
138
151
|
role: z.string().meta({ description: 'Role being granted.' }),
|
|
139
|
-
|
|
140
|
-
description: 'Id of the resulting
|
|
152
|
+
role_grant_id: Uuid.optional().meta({
|
|
153
|
+
description: 'Id of the resulting role_grant row. Omitted when the grant failed (e.g. admin-grant-path denial).',
|
|
141
154
|
}),
|
|
142
155
|
scope_id: Uuid.nullish().meta({
|
|
143
|
-
description: 'Scope of the granted
|
|
156
|
+
description: 'Scope of the granted role_grant; null for global role_grants.',
|
|
144
157
|
}),
|
|
145
158
|
source_offer_id: Uuid.optional().meta({
|
|
146
159
|
description: 'Offer this grant resolved, when the grant originated from an accepted offer.',
|
|
@@ -149,11 +162,11 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
149
162
|
description: 'True when the grant came from the self-service role toggle.',
|
|
150
163
|
}),
|
|
151
164
|
}),
|
|
152
|
-
|
|
165
|
+
role_grant_revoke: z.looseObject({
|
|
153
166
|
role: z.string().meta({ description: 'Role being revoked.' }),
|
|
154
|
-
|
|
167
|
+
role_grant_id: Uuid.meta({ description: 'Id of the revoked role_grant row.' }),
|
|
155
168
|
scope_id: Uuid.nullish().meta({
|
|
156
|
-
description: 'Scope of the revoked
|
|
169
|
+
description: 'Scope of the revoked role_grant; null for global role_grants.',
|
|
157
170
|
}),
|
|
158
171
|
reason: z
|
|
159
172
|
.string()
|
|
@@ -163,9 +176,9 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
163
176
|
description: 'True when the revoke came from the self-service role toggle.',
|
|
164
177
|
}),
|
|
165
178
|
}),
|
|
166
|
-
// `offer_id` is optional because failed creates (e.g.
|
|
179
|
+
// `offer_id` is optional because failed creates (e.g. admin-grant-path
|
|
167
180
|
// denied, `authorize` callback denied) never produce an offer row.
|
|
168
|
-
|
|
181
|
+
role_grant_offer_create: z.looseObject({
|
|
169
182
|
offer_id: Uuid.optional().meta({
|
|
170
183
|
description: 'Id of the created offer row. Omitted when the create failed before insert.',
|
|
171
184
|
}),
|
|
@@ -175,17 +188,17 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
175
188
|
}),
|
|
176
189
|
to_account_id: Uuid.meta({ description: 'Account the offer is directed to.' }),
|
|
177
190
|
}),
|
|
178
|
-
// `
|
|
179
|
-
// design: offer-lifecycle audit +
|
|
180
|
-
|
|
191
|
+
// `role_grant_create` is emitted alongside on accept — two events per accept by
|
|
192
|
+
// design: offer-lifecycle audit + role-grant-lifecycle audit.
|
|
193
|
+
role_grant_offer_accept: z.looseObject({
|
|
181
194
|
offer_id: Uuid.meta({ description: 'Id of the accepted offer.' }),
|
|
182
|
-
|
|
195
|
+
role_grant_id: Uuid.meta({ description: 'Id of the resulting role_grant row.' }),
|
|
183
196
|
role: z.string().meta({ description: 'Role granted by the offer.' }),
|
|
184
197
|
scope_id: Uuid.nullish().meta({
|
|
185
|
-
description: 'Scope of the resulting
|
|
198
|
+
description: 'Scope of the resulting role_grant; null for global role_grants.',
|
|
186
199
|
}),
|
|
187
200
|
}),
|
|
188
|
-
|
|
201
|
+
role_grant_offer_decline: z.looseObject({
|
|
189
202
|
offer_id: Uuid.meta({ description: 'Id of the declined offer.' }),
|
|
190
203
|
role: z.string().meta({ description: 'Role that was offered.' }),
|
|
191
204
|
scope_id: Uuid.nullish().meta({
|
|
@@ -196,14 +209,14 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
196
209
|
.optional()
|
|
197
210
|
.meta({ description: 'Optional decline reason text from the recipient.' }),
|
|
198
211
|
}),
|
|
199
|
-
|
|
212
|
+
role_grant_offer_retract: z.looseObject({
|
|
200
213
|
offer_id: Uuid.meta({ description: 'Id of the retracted offer.' }),
|
|
201
214
|
role: z.string().meta({ description: 'Role that was offered.' }),
|
|
202
215
|
scope_id: Uuid.nullish().meta({
|
|
203
216
|
description: 'Scope of the offered role; null for global offers.',
|
|
204
217
|
}),
|
|
205
218
|
}),
|
|
206
|
-
|
|
219
|
+
role_grant_offer_expire: z.looseObject({
|
|
207
220
|
offer_id: Uuid.meta({ description: 'Id of the expired offer.' }),
|
|
208
221
|
role: z.string().meta({ description: 'Role that was offered.' }),
|
|
209
222
|
scope_id: Uuid.nullish().meta({
|
|
@@ -212,19 +225,19 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
212
225
|
}),
|
|
213
226
|
// Emitted when an offer is obsoleted by an external event. `reason`
|
|
214
227
|
// distinguishes the trigger; `cause_id` points to the accepted offer
|
|
215
|
-
// (for `sibling_accepted`), the revoked
|
|
228
|
+
// (for `sibling_accepted`), the revoked role_grant (for `role_grant_revoked`),
|
|
216
229
|
// or the destroyed parent scope row (for `scope_destroyed`).
|
|
217
|
-
|
|
230
|
+
role_grant_offer_supersede: z.looseObject({
|
|
218
231
|
offer_id: Uuid.meta({ description: 'Id of the superseded offer.' }),
|
|
219
232
|
role: z.string().meta({ description: 'Role that was offered.' }),
|
|
220
233
|
scope_id: Uuid.nullish().meta({
|
|
221
234
|
description: 'Scope of the offered role; null for global offers.',
|
|
222
235
|
}),
|
|
223
|
-
reason: z.enum(['sibling_accepted', '
|
|
224
|
-
description: 'Trigger that obsoleted the offer: a sibling offer was accepted, the resulting
|
|
236
|
+
reason: z.enum(['sibling_accepted', 'role_grant_revoked', 'scope_destroyed']).meta({
|
|
237
|
+
description: 'Trigger that obsoleted the offer: a sibling offer was accepted, the resulting role_grant was revoked, or the parent scope row was destroyed.',
|
|
225
238
|
}),
|
|
226
239
|
cause_id: Uuid.meta({
|
|
227
|
-
description: 'Row that caused the supersede: accepted offer (`sibling_accepted`), revoked
|
|
240
|
+
description: 'Row that caused the supersede: accepted offer (`sibling_accepted`), revoked role_grant (`role_grant_revoked`), or destroyed parent scope row (`scope_destroyed`).',
|
|
228
241
|
}),
|
|
229
242
|
}),
|
|
230
243
|
invite_create: z.looseObject({
|
|
@@ -260,9 +273,9 @@ export const BUILTIN_AUDIT_LOG_CONFIG = Object.freeze({
|
|
|
260
273
|
* Throws when an `extra_events` key collides with a builtin event type, or
|
|
261
274
|
* fails `AuditEventTypeName` format validation.
|
|
262
275
|
*
|
|
263
|
-
* Call once at startup; pass the result to
|
|
264
|
-
* `
|
|
265
|
-
* pick up `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
276
|
+
* Call once at startup; pass the result to `create_app_backend` (which
|
|
277
|
+
* threads it into `AppDeps.audit`). Builtin handlers omit the
|
|
278
|
+
* `audit_log_config` slot and pick up `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
266
279
|
*
|
|
267
280
|
* @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
|
|
268
281
|
*/
|
|
@@ -323,8 +336,8 @@ export const AuditLogEventWithUsernamesJson = AuditLogEventJson.extend({
|
|
|
323
336
|
username: z.string().nullable(),
|
|
324
337
|
target_username: z.string().nullable(),
|
|
325
338
|
});
|
|
326
|
-
/** Zod schema for
|
|
327
|
-
export const
|
|
339
|
+
/** Zod schema for role_grant history events with resolved usernames. */
|
|
340
|
+
export const RoleGrantHistoryEventJson = AuditLogEventJson.extend({
|
|
328
341
|
username: z.string().nullable(),
|
|
329
342
|
target_username: z.string().nullable(),
|
|
330
343
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth guard resolver for the route spec system.
|
|
3
|
+
*
|
|
4
|
+
* Maps the four-axis `RouteAuth` (`account` / `actor` / `roles` /
|
|
5
|
+
* `credential_types`) to two-phase middleware sets that
|
|
6
|
+
* `apply_route_specs` weaves into the per-route pipeline:
|
|
7
|
+
*
|
|
8
|
+
* - `pre_validation` runs before input validation. `require_auth` lands
|
|
9
|
+
* here whenever `auth.account === 'required'` or `auth.actor ===
|
|
10
|
+
* 'required'` (per registry-time invariant 3, `actor: 'required'`
|
|
11
|
+
* today implies a credential — accountless actors are out of scope
|
|
12
|
+
* for v1). Pre-validation 401 fires before any body parsing so
|
|
13
|
+
* unauthenticated callers never see route-shape information from
|
|
14
|
+
* parse failures.
|
|
15
|
+
* - `post_authorization` runs after the dispatcher's authorization
|
|
16
|
+
* phase has populated `RequestContext`. `require_role(roles)` fires
|
|
17
|
+
* whenever `auth.roles?.length`. `require_credential_types(types)`
|
|
18
|
+
* fires whenever `auth.credential_types?.length`.
|
|
19
|
+
*
|
|
20
|
+
* Public routes (`auth.account === 'none' && auth.actor === 'none'`)
|
|
21
|
+
* yield empty guard arrays. `'optional'` axes contribute no
|
|
22
|
+
* pre-validation 401; the authorization phase sets `RequestContext`
|
|
23
|
+
* to whatever the credential supports and the post-authorization
|
|
24
|
+
* gates decide whether the actor's role_grants / credential type match.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
import type { AuthGuardResolver } from '../http/route_spec.js';
|
|
29
|
+
/**
|
|
30
|
+
* Standard auth guard resolver for fuz_app.
|
|
31
|
+
*
|
|
32
|
+
* Reads each axis of the four-axis `RouteAuth` shape and emits the
|
|
33
|
+
* corresponding middleware:
|
|
34
|
+
*
|
|
35
|
+
* - `account === 'required'` or `actor === 'required'` → pre-validation `require_auth`
|
|
36
|
+
* - `roles?.length` → post-authorization `require_role(roles)` (multi-role any-of)
|
|
37
|
+
* - `credential_types?.length` → post-authorization `require_credential_types(types)`
|
|
38
|
+
*
|
|
39
|
+
* Multiple post-authorization guards run in declaration order: credential
|
|
40
|
+
* type check first (since failing it implies the request can never
|
|
41
|
+
* resolve a usable identity), role check second.
|
|
42
|
+
*/
|
|
43
|
+
export declare const fuz_auth_guard_resolver: AuthGuardResolver;
|
|
44
|
+
//# sourceMappingURL=auth_guard_resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth_guard_resolver.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/auth_guard_resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,EAAE,iBAerC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth guard resolver for the route spec system.
|
|
3
|
+
*
|
|
4
|
+
* Maps the four-axis `RouteAuth` (`account` / `actor` / `roles` /
|
|
5
|
+
* `credential_types`) to two-phase middleware sets that
|
|
6
|
+
* `apply_route_specs` weaves into the per-route pipeline:
|
|
7
|
+
*
|
|
8
|
+
* - `pre_validation` runs before input validation. `require_auth` lands
|
|
9
|
+
* here whenever `auth.account === 'required'` or `auth.actor ===
|
|
10
|
+
* 'required'` (per registry-time invariant 3, `actor: 'required'`
|
|
11
|
+
* today implies a credential — accountless actors are out of scope
|
|
12
|
+
* for v1). Pre-validation 401 fires before any body parsing so
|
|
13
|
+
* unauthenticated callers never see route-shape information from
|
|
14
|
+
* parse failures.
|
|
15
|
+
* - `post_authorization` runs after the dispatcher's authorization
|
|
16
|
+
* phase has populated `RequestContext`. `require_role(roles)` fires
|
|
17
|
+
* whenever `auth.roles?.length`. `require_credential_types(types)`
|
|
18
|
+
* fires whenever `auth.credential_types?.length`.
|
|
19
|
+
*
|
|
20
|
+
* Public routes (`auth.account === 'none' && auth.actor === 'none'`)
|
|
21
|
+
* yield empty guard arrays. `'optional'` axes contribute no
|
|
22
|
+
* pre-validation 401; the authorization phase sets `RequestContext`
|
|
23
|
+
* to whatever the credential supports and the post-authorization
|
|
24
|
+
* gates decide whether the actor's role_grants / credential type match.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
import { require_auth, require_credential_types, require_role } from './request_context.js';
|
|
29
|
+
/**
|
|
30
|
+
* Standard auth guard resolver for fuz_app.
|
|
31
|
+
*
|
|
32
|
+
* Reads each axis of the four-axis `RouteAuth` shape and emits the
|
|
33
|
+
* corresponding middleware:
|
|
34
|
+
*
|
|
35
|
+
* - `account === 'required'` or `actor === 'required'` → pre-validation `require_auth`
|
|
36
|
+
* - `roles?.length` → post-authorization `require_role(roles)` (multi-role any-of)
|
|
37
|
+
* - `credential_types?.length` → post-authorization `require_credential_types(types)`
|
|
38
|
+
*
|
|
39
|
+
* Multiple post-authorization guards run in declaration order: credential
|
|
40
|
+
* type check first (since failing it implies the request can never
|
|
41
|
+
* resolve a usable identity), role check second.
|
|
42
|
+
*/
|
|
43
|
+
export const fuz_auth_guard_resolver = (auth) => {
|
|
44
|
+
const pre_validation = [];
|
|
45
|
+
const post_authorization = [];
|
|
46
|
+
if (auth.account === 'required' || auth.actor === 'required') {
|
|
47
|
+
pre_validation.push(require_auth);
|
|
48
|
+
}
|
|
49
|
+
if (auth.credential_types?.length) {
|
|
50
|
+
post_authorization.push(require_credential_types(auth.credential_types));
|
|
51
|
+
}
|
|
52
|
+
if (auth.roles?.length) {
|
|
53
|
+
post_authorization.push(require_role(auth.roles));
|
|
54
|
+
}
|
|
55
|
+
return { pre_validation, post_authorization };
|
|
56
|
+
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
10
10
|
import type { PasswordHashDeps } from './password.js';
|
|
11
11
|
import { ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING } from '../http/error_schemas.js';
|
|
12
|
-
import type { Account, Actor,
|
|
12
|
+
import type { Account, Actor, RoleGrant } from './account_schema.js';
|
|
13
13
|
import type { Db } from '../db/db.js';
|
|
14
14
|
/** Input for the bootstrap account creation. */
|
|
15
15
|
export interface BootstrapAccountInput {
|
|
@@ -21,9 +21,9 @@ export interface BootstrapAccountSuccess {
|
|
|
21
21
|
ok: true;
|
|
22
22
|
account: Account;
|
|
23
23
|
actor: Actor;
|
|
24
|
-
|
|
25
|
-
keeper:
|
|
26
|
-
admin:
|
|
24
|
+
role_grants: {
|
|
25
|
+
keeper: RoleGrant;
|
|
26
|
+
admin: RoleGrant;
|
|
27
27
|
};
|
|
28
28
|
/** Whether the bootstrap token file was successfully deleted after account creation. */
|
|
29
29
|
token_file_deleted: boolean;
|
|
@@ -70,15 +70,15 @@ export interface BootstrapAccountDeps {
|
|
|
70
70
|
* 2. Hash the password (CPU-intensive, before transaction)
|
|
71
71
|
* 3. Acquire the bootstrap lock atomically (inside transaction)
|
|
72
72
|
* 4. Create account + actor
|
|
73
|
-
* 5. Grant keeper and admin
|
|
73
|
+
* 5. Grant keeper and admin role_grants (no expiry, `granted_by = null`)
|
|
74
74
|
* 6. Delete the token file (after commit, reported via `token_file_deleted`)
|
|
75
75
|
*
|
|
76
76
|
* @param deps - database, token path, filesystem callbacks, and password hashing
|
|
77
77
|
* @param provided_token - the bootstrap token from the user
|
|
78
78
|
* @param input - username and password
|
|
79
|
-
* @returns the created account, actor, and
|
|
79
|
+
* @returns the created account, actor, and role_grants — or a bootstrap failure
|
|
80
80
|
* @mutates `bootstrap_lock` row - flips `bootstrapped` to `true` atomically
|
|
81
|
-
* @mutates `account` / `actor` / `
|
|
81
|
+
* @mutates `account` / `actor` / `role_grant` tables - inserts the bootstrap account, actor, and the keeper + admin role_grants
|
|
82
82
|
* @mutates filesystem - deletes the bootstrap token file after commit (reported via `token_file_deleted`)
|
|
83
83
|
*/
|
|
84
84
|
export declare const bootstrap_account: (deps: BootstrapAccountDeps, provided_token: string, input: BootstrapAccountInput) => Promise<BootstrapAccountResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap_account.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_account.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC;AACpD,OAAO,EACN,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAC,OAAO,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"bootstrap_account.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_account.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC;AACpD,OAAO,EACN,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAGnE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,gDAAgD;AAChD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE;QAAC,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAC,CAAC;IACnD,wFAAwF;IACxF,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED,gCAAgC;AAChC,MAAM,MAAM,uBAAuB,GAChC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,0BAA0B,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAClE;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,wBAAwB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAChE;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,CAAC;AAE/D,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,GAAG,uBAAuB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,EAAE,CAAC;IACP,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAClD,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,oBAAoB,EAC1B,gBAAgB,MAAM,EACtB,OAAO,qBAAqB,KAC1B,OAAO,CAAC,sBAAsB,CA4EhC,CAAC"}
|
|
@@ -10,7 +10,7 @@ import { timingSafeEqual } from 'node:crypto';
|
|
|
10
10
|
import { ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING, } from '../http/error_schemas.js';
|
|
11
11
|
import { ROLE_ADMIN, ROLE_KEEPER } from './role_schema.js';
|
|
12
12
|
import { query_create_account_with_actor, query_account_has_any } from './account_queries.js';
|
|
13
|
-
import {
|
|
13
|
+
import { query_create_role_grant } from './role_grant_queries.js';
|
|
14
14
|
/**
|
|
15
15
|
* Bootstrap the first account with keeper and admin privileges.
|
|
16
16
|
*
|
|
@@ -21,15 +21,15 @@ import { query_grant_permit } from './permit_queries.js';
|
|
|
21
21
|
* 2. Hash the password (CPU-intensive, before transaction)
|
|
22
22
|
* 3. Acquire the bootstrap lock atomically (inside transaction)
|
|
23
23
|
* 4. Create account + actor
|
|
24
|
-
* 5. Grant keeper and admin
|
|
24
|
+
* 5. Grant keeper and admin role_grants (no expiry, `granted_by = null`)
|
|
25
25
|
* 6. Delete the token file (after commit, reported via `token_file_deleted`)
|
|
26
26
|
*
|
|
27
27
|
* @param deps - database, token path, filesystem callbacks, and password hashing
|
|
28
28
|
* @param provided_token - the bootstrap token from the user
|
|
29
29
|
* @param input - username and password
|
|
30
|
-
* @returns the created account, actor, and
|
|
30
|
+
* @returns the created account, actor, and role_grants — or a bootstrap failure
|
|
31
31
|
* @mutates `bootstrap_lock` row - flips `bootstrapped` to `true` atomically
|
|
32
|
-
* @mutates `account` / `actor` / `
|
|
32
|
+
* @mutates `account` / `actor` / `role_grant` tables - inserts the bootstrap account, actor, and the keeper + admin role_grants
|
|
33
33
|
* @mutates filesystem - deletes the bootstrap token file after commit (reported via `token_file_deleted`)
|
|
34
34
|
*/
|
|
35
35
|
export const bootstrap_account = async (deps, provided_token, input) => {
|
|
@@ -66,13 +66,13 @@ export const bootstrap_account = async (deps, provided_token, input) => {
|
|
|
66
66
|
username: input.username,
|
|
67
67
|
password_hash,
|
|
68
68
|
});
|
|
69
|
-
const
|
|
69
|
+
const keeper_role_grant = await query_create_role_grant(tx_deps, {
|
|
70
70
|
actor_id: actor.id,
|
|
71
71
|
role: ROLE_KEEPER,
|
|
72
72
|
granted_by: null,
|
|
73
73
|
expires_at: null,
|
|
74
74
|
});
|
|
75
|
-
const
|
|
75
|
+
const admin_role_grant = await query_create_role_grant(tx_deps, {
|
|
76
76
|
actor_id: actor.id,
|
|
77
77
|
role: ROLE_ADMIN,
|
|
78
78
|
granted_by: null,
|
|
@@ -82,7 +82,7 @@ export const bootstrap_account = async (deps, provided_token, input) => {
|
|
|
82
82
|
ok: true,
|
|
83
83
|
account,
|
|
84
84
|
actor,
|
|
85
|
-
|
|
85
|
+
role_grants: { keeper: keeper_role_grant, admin: admin_role_grant },
|
|
86
86
|
};
|
|
87
87
|
});
|
|
88
88
|
if (!tx_result.ok)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAGvF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAGvF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAYnD,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;kBAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,4EAA4E;IAC5E,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,EAAE,EAAE,EAAE,CAAC;IACP,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,KAClC,OAAO,CAAC,eAAe,CAwBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAiHjB,CAAC"}
|
|
@@ -7,15 +7,14 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
-
import { create_session_and_set_cookie } from './
|
|
10
|
+
import { create_session_and_set_cookie } from './session_middleware.js';
|
|
11
11
|
import { bootstrap_account } from './bootstrap_account.js';
|
|
12
|
-
import { Username } from '
|
|
12
|
+
import { Username } from '../primitive_schemas.js';
|
|
13
13
|
import { Password } from './password.js';
|
|
14
14
|
import { get_route_input } from '../http/route_spec.js';
|
|
15
15
|
import { get_client_ip } from '../http/proxy.js';
|
|
16
16
|
import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
17
17
|
import { ERROR_BOOTSTRAP_NOT_CONFIGURED, ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
|
|
18
|
-
import { audit_log_fire_and_forget } from './audit_log_queries.js';
|
|
19
18
|
// -- Input/output schemas ---------------------------------------------------
|
|
20
19
|
/** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
|
|
21
20
|
export const BootstrapInput = z.strictObject({
|
|
@@ -74,7 +73,7 @@ export const create_bootstrap_route_specs = (deps, options) => {
|
|
|
74
73
|
{
|
|
75
74
|
method: 'POST',
|
|
76
75
|
path: '/bootstrap',
|
|
77
|
-
auth: {
|
|
76
|
+
auth: { account: 'none', actor: 'none' },
|
|
78
77
|
description: 'Create initial keeper account (one-shot)',
|
|
79
78
|
transaction: false, // bootstrap_account manages its own transaction
|
|
80
79
|
input: BootstrapInput,
|
|
@@ -107,8 +106,10 @@ export const create_bootstrap_route_specs = (deps, options) => {
|
|
|
107
106
|
if (token_path === null) {
|
|
108
107
|
return c.json({ error: ERROR_BOOTSTRAP_NOT_CONFIGURED }, 404);
|
|
109
108
|
}
|
|
109
|
+
// `transaction: false` makes `route.db` the pool. `bootstrap_account`
|
|
110
|
+
// manages its own transaction internally.
|
|
110
111
|
const result = await bootstrap_account({
|
|
111
|
-
db: route.
|
|
112
|
+
db: route.db,
|
|
112
113
|
token_path,
|
|
113
114
|
read_text_file: deps.read_text_file,
|
|
114
115
|
delete_file: deps.delete_file,
|
|
@@ -118,12 +119,12 @@ export const create_bootstrap_route_specs = (deps, options) => {
|
|
|
118
119
|
if (!result.ok) {
|
|
119
120
|
if (ip_rate_limiter && ip)
|
|
120
121
|
ip_rate_limiter.record(ip);
|
|
121
|
-
|
|
122
|
+
deps.audit.emit(route, {
|
|
122
123
|
event_type: 'bootstrap',
|
|
123
124
|
outcome: 'failure',
|
|
124
125
|
ip: get_client_ip(c),
|
|
125
126
|
metadata: { error: result.error },
|
|
126
|
-
}
|
|
127
|
+
});
|
|
127
128
|
return c.json({ error: result.error }, result.status);
|
|
128
129
|
}
|
|
129
130
|
// Successful bootstrap — update state immediately
|
|
@@ -132,7 +133,7 @@ export const create_bootstrap_route_specs = (deps, options) => {
|
|
|
132
133
|
bootstrap_status.available = false;
|
|
133
134
|
await create_session_and_set_cookie({
|
|
134
135
|
keyring,
|
|
135
|
-
deps: { db: route.
|
|
136
|
+
deps: { db: route.db },
|
|
136
137
|
c,
|
|
137
138
|
account_id: result.account.id,
|
|
138
139
|
session_options,
|
|
@@ -145,12 +146,12 @@ export const create_bootstrap_route_specs = (deps, options) => {
|
|
|
145
146
|
deps.log.error(`on_bootstrap callback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
|
-
|
|
149
|
+
deps.audit.emit(route, {
|
|
149
150
|
event_type: 'bootstrap',
|
|
150
151
|
actor_id: result.actor.id,
|
|
151
152
|
account_id: result.account.id,
|
|
152
153
|
ip: get_client_ip(c),
|
|
153
|
-
}
|
|
154
|
+
});
|
|
154
155
|
// CRITICAL: If token file deletion failed, throw to force operator attention.
|
|
155
156
|
// All success work (session, on_bootstrap, audit) has completed above.
|
|
156
157
|
// The error response alerts the operator to delete the token file manually.
|
package/dist/auth/cleanup.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Periodic auth cleanup — sweeps expired sessions and
|
|
2
|
+
* Periodic auth cleanup — sweeps expired sessions and role_grant offers.
|
|
3
3
|
*
|
|
4
4
|
* Single entry point for consumers scheduling auth maintenance. Internally
|
|
5
5
|
* runs every known sweep and emits the corresponding audit events so
|
|
6
6
|
* consumer code only manages cadence, not per-task wiring.
|
|
7
7
|
*
|
|
8
8
|
* The per-task primitives remain exported from their home modules
|
|
9
|
-
* (`query_session_cleanup_expired`, `
|
|
10
|
-
* `
|
|
11
|
-
* `
|
|
9
|
+
* (`query_session_cleanup_expired`, `query_role_grant_offer_sweep_expired`);
|
|
10
|
+
* `cleanup_expired_role_grant_offers` here wraps the latter with the required
|
|
11
|
+
* `role_grant_offer_expire` audit emission and is the piece most likely to be
|
|
12
12
|
* reused in a consumer's bespoke scheduler.
|
|
13
13
|
*
|
|
14
|
-
* Idempotency: the audit log has no tombstone on `
|
|
14
|
+
* Idempotency: the audit log has no tombstone on `role_grant_offer_expire`, so
|
|
15
15
|
* concurrent sweep runs double-audit. The expected deployment pattern is a
|
|
16
16
|
* single scheduled invocation per instance — matching
|
|
17
17
|
* `query_session_cleanup_expired`.
|
|
@@ -20,57 +20,51 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
22
22
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
23
|
-
import type {
|
|
23
|
+
import type { AuditEmitter } from './audit_emitter.js';
|
|
24
24
|
/** Dependencies for the cleanup helpers. */
|
|
25
25
|
export interface AuthCleanupDeps extends QueryDeps {
|
|
26
26
|
log: Logger;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Bound audit emitter. `cleanup_expired_role_grant_offers` writes via
|
|
29
|
+
* `audit.emit_pool` (the captured pool + config + listener chain), so
|
|
30
|
+
* one slot covers both row persistence and SSE/WS fan-out. Required —
|
|
31
|
+
* production wiring always has a bound emitter on `AppDeps.audit`, and
|
|
32
|
+
* tests that need a no-op pass `create_test_audit_emitter()`.
|
|
31
33
|
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Audit-log config. Only the builtin `permit_offer_expire` event type is
|
|
35
|
-
* emitted here, so omitting this is safe — the field exists so consumers
|
|
36
|
-
* threading the same `AppDeps` bundle to scheduled cleanup keep using
|
|
37
|
-
* their registered config (and consumer extensions to the
|
|
38
|
-
* `permit_offer_expire` metadata schema get validated).
|
|
39
|
-
*/
|
|
40
|
-
audit_log_config?: AuditLogConfig;
|
|
34
|
+
audit: AuditEmitter;
|
|
41
35
|
}
|
|
42
36
|
/** Result of `run_auth_cleanup`. */
|
|
43
37
|
export interface AuthCleanupResult {
|
|
44
38
|
/** Number of expired session rows deleted. */
|
|
45
39
|
expired_sessions: number;
|
|
46
|
-
/** Number of expired
|
|
40
|
+
/** Number of expired role_grant offer rows audit-stamped. */
|
|
47
41
|
expired_offers: number;
|
|
48
42
|
}
|
|
49
43
|
/**
|
|
50
|
-
* Sweep expired
|
|
44
|
+
* Sweep expired role_grant offers and emit one `role_grant_offer_expire` audit
|
|
51
45
|
* event per row.
|
|
52
46
|
*
|
|
53
47
|
* Returns the count of offers audit-stamped. The offer rows themselves are
|
|
54
48
|
* preserved — offers carry audit value for the history view even after
|
|
55
|
-
* expiry, and accepted rows are the provenance for the resulting
|
|
49
|
+
* expiry, and accepted rows are the provenance for the resulting role_grant
|
|
56
50
|
* (deleting expired rows would not threaten that, but keeping them uniform
|
|
57
51
|
* with the retention policy for terminal rows is simpler).
|
|
58
52
|
*
|
|
59
|
-
* @mutates `audit_log` table - inserts one `
|
|
53
|
+
* @mutates `audit_log` table - inserts one `role_grant_offer_expire` row per swept offer
|
|
60
54
|
*/
|
|
61
|
-
export declare const
|
|
55
|
+
export declare const cleanup_expired_role_grant_offers: (deps: AuthCleanupDeps) => Promise<number>;
|
|
62
56
|
/**
|
|
63
|
-
* Run every auth cleanup sweep — expired sessions and expired
|
|
57
|
+
* Run every auth cleanup sweep — expired sessions and expired role_grant
|
|
64
58
|
* offers — and return the counts.
|
|
65
59
|
*
|
|
66
60
|
* Consumers call this from a scheduled task (setInterval, cron, etc.)
|
|
67
61
|
* alongside their own domain cleanup. Errors from individual sweeps are
|
|
68
62
|
* re-thrown so the caller's scheduler can log/alert; use the per-task
|
|
69
|
-
* helpers (`query_session_cleanup_expired`, `
|
|
63
|
+
* helpers (`query_session_cleanup_expired`, `cleanup_expired_role_grant_offers`)
|
|
70
64
|
* directly if you need finer error isolation.
|
|
71
65
|
*
|
|
72
66
|
* @mutates `auth_session` table - deletes expired sessions
|
|
73
|
-
* @mutates `audit_log` table - emits `
|
|
67
|
+
* @mutates `audit_log` table - emits `role_grant_offer_expire` rows for expired offers
|
|
74
68
|
* @throws Error re-thrown from any sweep that fails (no per-sweep isolation here)
|
|
75
69
|
*/
|
|
76
70
|
export declare const run_auth_cleanup: (deps: AuthCleanupDeps) => Promise<AuthCleanupResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAGnD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,4CAA4C;AAC5C,MAAM,WAAW,eAAgB,SAAQ,SAAS;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,KAAK,EAAE,YAAY,CAAC;CACpB;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,CAuB7F,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,iBAAiB,CAIvF,CAAC"}
|