@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
|
@@ -5,59 +5,62 @@
|
|
|
5
5
|
*
|
|
6
6
|
* - Account management: `admin_account_list`, `admin_session_list`,
|
|
7
7
|
* `admin_session_revoke_all`, `admin_token_revoke_all`.
|
|
8
|
-
* - Audit log reads: `audit_log_list`, `
|
|
8
|
+
* - Audit log reads: `audit_log_list`, `audit_log_role_grant_history`.
|
|
9
9
|
* - Invite CRUD: `invite_create`, `invite_list`, `invite_delete`.
|
|
10
10
|
* - App settings: `app_settings_get`, `app_settings_update` (registered only
|
|
11
11
|
* when `AdminActionOptions.app_settings` is provided — the mutable ref is
|
|
12
12
|
* owned by the server context and shared with signup middleware).
|
|
13
13
|
*
|
|
14
14
|
* The action specs themselves live in `auth/admin_action_specs.ts`. Mutations
|
|
15
|
-
* emit matching audit events via `
|
|
15
|
+
* emit matching audit events via `deps.audit.emit`.
|
|
16
16
|
*
|
|
17
17
|
* Authorization is declared at the spec level (`auth: {role: 'admin'}`) so
|
|
18
18
|
* the RPC dispatcher enforces it before the handler runs and the generated
|
|
19
|
-
* surface accurately reports the requirement. `
|
|
20
|
-
* `auth/
|
|
19
|
+
* surface accurately reports the requirement. `role_grant_revoke` in
|
|
20
|
+
* `auth/role_grant_offer_actions.ts` uses the same spec-level pattern even though its
|
|
21
21
|
* sibling methods are authenticated-but-not-admin — the dispatcher checks
|
|
22
22
|
* auth per-spec, so mixed-auth endpoints compose cleanly. Handler-level
|
|
23
23
|
* gates are reserved for input-dependent elevation (e.g.
|
|
24
|
-
* `
|
|
24
|
+
* `role_grant_offer_list`/`_history` elevate to admin only when the caller
|
|
25
25
|
* passes an `account_id` other than their own — an input-dependent check
|
|
26
26
|
* the spec can't express).
|
|
27
27
|
*
|
|
28
28
|
* @module
|
|
29
29
|
*/
|
|
30
|
-
import {
|
|
30
|
+
import { rpc_action } from '../actions/action_rpc.js';
|
|
31
31
|
import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
32
|
-
import {
|
|
32
|
+
import { BUILTIN_ROLE_SPECS_BY_NAME, list_roles_with_grant_path, } from './role_schema.js';
|
|
33
|
+
import { GRANT_PATH_ADMIN } from './grant_path_schema.js';
|
|
33
34
|
import { query_account_by_email, query_account_by_id, query_account_by_username, query_admin_account_list, } from './account_queries.js';
|
|
34
35
|
import { query_session_list_all_active, query_session_revoke_all_for_account, } from './session_queries.js';
|
|
35
36
|
import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
|
|
36
|
-
import {
|
|
37
|
+
import { query_audit_log_list_role_grant_history, query_audit_log_list_with_usernames, } from './audit_log_queries.js';
|
|
37
38
|
import { AUDIT_LOG_DEFAULT_LIMIT } from './audit_log_schema.js';
|
|
38
39
|
import { query_create_invite, query_invite_delete_unclaimed, query_invite_list_all_with_usernames, } from './invite_queries.js';
|
|
39
40
|
import {} from './app_settings_schema.js';
|
|
40
41
|
import { query_app_settings_load_with_username, query_app_settings_update, } from './app_settings_queries.js';
|
|
41
42
|
import { is_pg_unique_violation } from '../db/pg_error.js';
|
|
42
43
|
import { ERROR_ACCOUNT_NOT_FOUND, ERROR_INVITE_ACCOUNT_EXISTS_EMAIL, ERROR_INVITE_ACCOUNT_EXISTS_USERNAME, ERROR_INVITE_DUPLICATE, ERROR_INVITE_MISSING_IDENTIFIER, ERROR_INVITE_NOT_FOUND, } from '../http/error_schemas.js';
|
|
43
|
-
import { admin_account_list_action_spec, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec,
|
|
44
|
+
import { admin_account_list_action_spec, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec, audit_log_role_grant_history_action_spec, invite_create_action_spec, invite_list_action_spec, invite_delete_action_spec, app_settings_get_action_spec, app_settings_update_action_spec, } from './admin_action_specs.js';
|
|
44
45
|
/**
|
|
45
46
|
* Create the admin-only RPC actions.
|
|
46
47
|
*
|
|
47
|
-
* @param deps - `
|
|
48
|
+
* @param deps - `RouteFactoryDeps` (`log`, `audit`, …). `log` drives RPC-
|
|
49
|
+
* internal error logging; `audit.emit` writes audit rows via the captured
|
|
50
|
+
* pool. The bound emitter encapsulates `on_audit_event` fan-out and the
|
|
51
|
+
* optional `AuditLogConfig`.
|
|
48
52
|
* @param options - role schema for `grantable_roles` derivation
|
|
49
53
|
* @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
|
|
50
54
|
* @mutates `options.app_settings` ref - `app_settings_update` writes `open_signup`, `updated_at`, and `updated_by` so signup middleware reads without a DB round trip
|
|
51
55
|
*/
|
|
52
56
|
export const create_admin_actions = (deps, options = {}) => {
|
|
53
|
-
const
|
|
54
|
-
const grantable_roles =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const accounts = await query_admin_account_list(ctx);
|
|
57
|
+
const role_specs = options.roles?.role_specs ?? BUILTIN_ROLE_SPECS_BY_NAME;
|
|
58
|
+
const grantable_roles = list_roles_with_grant_path(role_specs, GRANT_PATH_ADMIN);
|
|
59
|
+
const account_list_handler = async (input, ctx) => {
|
|
60
|
+
const accounts = await query_admin_account_list(ctx, {
|
|
61
|
+
limit: input.limit,
|
|
62
|
+
offset: input.offset,
|
|
63
|
+
});
|
|
61
64
|
return { accounts, grantable_roles };
|
|
62
65
|
};
|
|
63
66
|
const session_list_handler = async (_input, ctx) => {
|
|
@@ -68,7 +71,7 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
68
71
|
const auth = ctx.auth;
|
|
69
72
|
const account = await query_account_by_id(ctx, input.account_id);
|
|
70
73
|
if (!account) {
|
|
71
|
-
|
|
74
|
+
deps.audit.emit(ctx, {
|
|
72
75
|
event_type: 'session_revoke_all',
|
|
73
76
|
outcome: 'failure',
|
|
74
77
|
account_id: auth.account.id,
|
|
@@ -81,24 +84,24 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
81
84
|
reason: ERROR_ACCOUNT_NOT_FOUND,
|
|
82
85
|
attempted_account_id: input.account_id,
|
|
83
86
|
},
|
|
84
|
-
}
|
|
87
|
+
});
|
|
85
88
|
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
86
89
|
}
|
|
87
90
|
const count = await query_session_revoke_all_for_account(ctx, input.account_id);
|
|
88
|
-
|
|
91
|
+
deps.audit.emit(ctx, {
|
|
89
92
|
event_type: 'session_revoke_all',
|
|
90
93
|
account_id: auth.account.id,
|
|
91
94
|
target_account_id: input.account_id,
|
|
92
95
|
ip: ctx.client_ip,
|
|
93
96
|
metadata: { count },
|
|
94
|
-
}
|
|
97
|
+
});
|
|
95
98
|
return { ok: true, count };
|
|
96
99
|
};
|
|
97
100
|
const token_revoke_all_handler = async (input, ctx) => {
|
|
98
101
|
const auth = ctx.auth;
|
|
99
102
|
const account = await query_account_by_id(ctx, input.account_id);
|
|
100
103
|
if (!account) {
|
|
101
|
-
|
|
104
|
+
deps.audit.emit(ctx, {
|
|
102
105
|
event_type: 'token_revoke_all',
|
|
103
106
|
outcome: 'failure',
|
|
104
107
|
account_id: auth.account.id,
|
|
@@ -110,17 +113,17 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
110
113
|
reason: ERROR_ACCOUNT_NOT_FOUND,
|
|
111
114
|
attempted_account_id: input.account_id,
|
|
112
115
|
},
|
|
113
|
-
}
|
|
116
|
+
});
|
|
114
117
|
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
115
118
|
}
|
|
116
119
|
const count = await query_revoke_all_api_tokens_for_account(ctx, input.account_id);
|
|
117
|
-
|
|
120
|
+
deps.audit.emit(ctx, {
|
|
118
121
|
event_type: 'token_revoke_all',
|
|
119
122
|
account_id: auth.account.id,
|
|
120
123
|
target_account_id: input.account_id,
|
|
121
124
|
ip: ctx.client_ip,
|
|
122
125
|
metadata: { count },
|
|
123
|
-
}
|
|
126
|
+
});
|
|
124
127
|
return { ok: true, count };
|
|
125
128
|
};
|
|
126
129
|
const audit_log_list_handler = async (input, ctx) => {
|
|
@@ -134,8 +137,8 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
134
137
|
});
|
|
135
138
|
return { events };
|
|
136
139
|
};
|
|
137
|
-
const
|
|
138
|
-
const events = await
|
|
140
|
+
const audit_log_role_grant_history_handler = async (input, ctx) => {
|
|
141
|
+
const events = await query_audit_log_list_role_grant_history(ctx, input.limit ?? AUDIT_LOG_DEFAULT_LIMIT, input.offset ?? 0);
|
|
139
142
|
return { events };
|
|
140
143
|
};
|
|
141
144
|
const invite_create_handler = async (input, ctx) => {
|
|
@@ -179,12 +182,12 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
179
182
|
}
|
|
180
183
|
throw err;
|
|
181
184
|
}
|
|
182
|
-
|
|
185
|
+
deps.audit.emit(ctx, {
|
|
183
186
|
event_type: 'invite_create',
|
|
184
187
|
account_id: auth.account.id,
|
|
185
188
|
ip: ctx.client_ip,
|
|
186
189
|
metadata: { invite_id: invite.id, email, username },
|
|
187
|
-
}
|
|
190
|
+
});
|
|
188
191
|
return { ok: true, invite };
|
|
189
192
|
};
|
|
190
193
|
const invite_list_handler = async (_input, ctx) => {
|
|
@@ -197,24 +200,24 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
197
200
|
if (!deleted) {
|
|
198
201
|
throw jsonrpc_errors.not_found('invite', { reason: ERROR_INVITE_NOT_FOUND });
|
|
199
202
|
}
|
|
200
|
-
|
|
203
|
+
deps.audit.emit(ctx, {
|
|
201
204
|
event_type: 'invite_delete',
|
|
202
205
|
account_id: auth.account.id,
|
|
203
206
|
ip: ctx.client_ip,
|
|
204
207
|
metadata: { invite_id: input.invite_id },
|
|
205
|
-
}
|
|
208
|
+
});
|
|
206
209
|
return { ok: true };
|
|
207
210
|
};
|
|
208
211
|
const actions = [
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
rpc_action(admin_account_list_action_spec, account_list_handler),
|
|
213
|
+
rpc_action(admin_session_list_action_spec, session_list_handler),
|
|
214
|
+
rpc_action(admin_session_revoke_all_action_spec, session_revoke_all_handler),
|
|
215
|
+
rpc_action(admin_token_revoke_all_action_spec, token_revoke_all_handler),
|
|
216
|
+
rpc_action(audit_log_list_action_spec, audit_log_list_handler),
|
|
217
|
+
rpc_action(audit_log_role_grant_history_action_spec, audit_log_role_grant_history_handler),
|
|
218
|
+
rpc_action(invite_create_action_spec, invite_create_handler),
|
|
219
|
+
rpc_action(invite_list_action_spec, invite_list_handler),
|
|
220
|
+
rpc_action(invite_delete_action_spec, invite_delete_handler),
|
|
218
221
|
];
|
|
219
222
|
const { app_settings } = options;
|
|
220
223
|
if (app_settings) {
|
|
@@ -231,7 +234,7 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
231
234
|
app_settings.open_signup = updated.open_signup;
|
|
232
235
|
app_settings.updated_at = updated.updated_at;
|
|
233
236
|
app_settings.updated_by = updated.updated_by;
|
|
234
|
-
|
|
237
|
+
deps.audit.emit(ctx, {
|
|
235
238
|
event_type: 'app_settings_update',
|
|
236
239
|
account_id: auth.account.id,
|
|
237
240
|
ip: ctx.client_ip,
|
|
@@ -240,11 +243,11 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
240
243
|
old_value,
|
|
241
244
|
new_value: input.open_signup,
|
|
242
245
|
},
|
|
243
|
-
}
|
|
246
|
+
});
|
|
244
247
|
const settings = await query_app_settings_load_with_username(ctx);
|
|
245
248
|
return { ok: true, settings };
|
|
246
249
|
};
|
|
247
|
-
actions.push(
|
|
250
|
+
actions.push(rpc_action(app_settings_get_action_spec, app_settings_get_handler), rpc_action(app_settings_update_action_spec, app_settings_update_handler));
|
|
248
251
|
}
|
|
249
252
|
return actions;
|
|
250
253
|
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bound audit-emit capability.
|
|
3
|
+
*
|
|
4
|
+
* `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
|
|
5
|
+
* subscriber chain, and the optional `AuditLogConfig` at backend-assembly
|
|
6
|
+
* time. Consumers reach for `deps.audit.emit(ctx, input)` and never see the
|
|
7
|
+
* pool — handlers cannot accidentally emit an audit event against the
|
|
8
|
+
* request's transactional `db` (which would be rolled back with the parent
|
|
9
|
+
* on a handler throw).
|
|
10
|
+
*
|
|
11
|
+
* Four methods cover every fan-out shape the auth domain needs:
|
|
12
|
+
*
|
|
13
|
+
* - `emit(ctx, input)` — fire-and-forget pool write. Pushes the in-flight
|
|
14
|
+
* promise onto `ctx.pending_effects` for post-response flushing. Errors are
|
|
15
|
+
* logged, never thrown. Returns `void` so callers don't pile up `void`
|
|
16
|
+
* keywords or accidentally `await` something whose handle is already in
|
|
17
|
+
* `pending_effects`.
|
|
18
|
+
* - `emit_role_grant_target(ctx, auth, input)` — wrapper that lifts the
|
|
19
|
+
* `actor_id` / `account_id` / `ip` boilerplate every role-grant-shape audit
|
|
20
|
+
* site repeated. Delegates to `emit`.
|
|
21
|
+
* - `emit_pool(input)` — awaitable pool write for code paths without a
|
|
22
|
+
* `pending_effects` queue (cleanup sweeps, ad-hoc maintenance scripts).
|
|
23
|
+
* Same write-then-notify semantics as `emit`, just synchronous-with-await.
|
|
24
|
+
* - `notify(event)` — fan out an already-written audit row (e.g. rows
|
|
25
|
+
* returned by `query_accept_offer` that were inserted in-transaction by
|
|
26
|
+
* the query layer). Runs every listener on the chain; per-listener throws
|
|
27
|
+
* are isolated.
|
|
28
|
+
*
|
|
29
|
+
* The chain is mutable so server assembly can append additional listeners
|
|
30
|
+
* (e.g. the audit-log SSE registry composed by `create_app_server`) after
|
|
31
|
+
* the backend is built but before the first request runs.
|
|
32
|
+
*
|
|
33
|
+
* @module
|
|
34
|
+
*/
|
|
35
|
+
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
36
|
+
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
37
|
+
import type { Db } from '../db/db.js';
|
|
38
|
+
import type { RequestActorContext } from './request_context.js';
|
|
39
|
+
import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput } from './audit_log_schema.js';
|
|
40
|
+
/**
|
|
41
|
+
* Per-request context required by `AuditEmitter.emit` — just the eager
|
|
42
|
+
* `pending_effects` queue. The bound emitter carries its own `log`
|
|
43
|
+
* reference inside the closure, so per-call contexts don't need one.
|
|
44
|
+
*
|
|
45
|
+
* Audit emits are eager-only by design: the bound emitter fires the
|
|
46
|
+
* pool write immediately and pushes the in-flight `Promise<void>` here.
|
|
47
|
+
* They never go through `emit_after_commit` — pool-routed audit writes
|
|
48
|
+
* are already rollback-resilient because they run outside the request
|
|
49
|
+
* transaction, so the post-commit timing the deferred queue provides
|
|
50
|
+
* would only delay forensic visibility without any safety benefit.
|
|
51
|
+
*
|
|
52
|
+
* Both `RouteContext` and `ActionContext` structurally satisfy this
|
|
53
|
+
* shape (they each carry `pending_effects`), so handlers pass `route`
|
|
54
|
+
* / `ctx` directly.
|
|
55
|
+
*/
|
|
56
|
+
export interface AuditEmitterContext {
|
|
57
|
+
pending_effects: Array<Promise<void>>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Context required by `AuditEmitter.emit_role_grant_target` — adds
|
|
61
|
+
* `client_ip` so the helper can lift the `ip: ctx.client_ip`
|
|
62
|
+
* boilerplate every role-grant-shape emit site repeated.
|
|
63
|
+
*/
|
|
64
|
+
export interface AuditEmitRoleGrantContext extends AuditEmitterContext {
|
|
65
|
+
/** Resolved client IP from the trusted-proxy middleware — `'unknown'` if not resolved. */
|
|
66
|
+
client_ip: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Bound audit-emit capability. Built once at backend assembly via
|
|
70
|
+
* `create_audit_emitter`; lives on `AppDeps.audit` so factories never see
|
|
71
|
+
* the pool.
|
|
72
|
+
*/
|
|
73
|
+
export interface AuditEmitter {
|
|
74
|
+
/**
|
|
75
|
+
* Fire-and-forget audit write via the captured pool.
|
|
76
|
+
*
|
|
77
|
+
* The in-flight promise is pushed onto `ctx.pending_effects` so tests
|
|
78
|
+
* with `await_pending_effects: true` can assert side effects inline.
|
|
79
|
+
* Errors are logged, never thrown. Successful writes fan out to every
|
|
80
|
+
* listener on the chain (`notify`).
|
|
81
|
+
*
|
|
82
|
+
* Returns `void` deliberately — the in-flight promise is already on
|
|
83
|
+
* `ctx.pending_effects`, and exposing it would tempt callers to `await`
|
|
84
|
+
* (sequencing audit writes onto the response hot path) or sprinkle
|
|
85
|
+
* `void` to placate `no-floating-promises`. For awaitable writes from
|
|
86
|
+
* code paths without `pending_effects`, use `emit_pool`.
|
|
87
|
+
*
|
|
88
|
+
* @mutates `audit_log` table - inserts the row via the captured pool
|
|
89
|
+
* @mutates `ctx.pending_effects` - appends the in-flight settled promise
|
|
90
|
+
*/
|
|
91
|
+
emit<T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>): void;
|
|
92
|
+
/**
|
|
93
|
+
* Emit a role-grant-shape audit event with `actor_id` / `account_id` /
|
|
94
|
+
* `ip` lifted from `auth` + `ctx`. Delegates to `emit`.
|
|
95
|
+
*
|
|
96
|
+
* Use for any event populating one of the `target_*_id` columns.
|
|
97
|
+
* Reach for the lower-level `emit` only when the event is non-role-grant
|
|
98
|
+
* shape (e.g. `app_settings_update`, bootstrap, signup).
|
|
99
|
+
*/
|
|
100
|
+
emit_role_grant_target<T extends string>(ctx: AuditEmitRoleGrantContext, auth: RequestActorContext, input: {
|
|
101
|
+
event_type: T;
|
|
102
|
+
target_account_id: Uuid | null;
|
|
103
|
+
target_actor_id: Uuid | null;
|
|
104
|
+
metadata: AuditLogInput<T>['metadata'];
|
|
105
|
+
outcome?: 'success' | 'failure';
|
|
106
|
+
}): void;
|
|
107
|
+
/**
|
|
108
|
+
* Awaitable pool write for code paths without a `pending_effects` queue.
|
|
109
|
+
*
|
|
110
|
+
* Same write-then-notify semantics as `emit`. Errors are logged and
|
|
111
|
+
* swallowed (resolved void), so callers can sequence sweeps with
|
|
112
|
+
* `await audit.emit_pool(...)` without try/catch boilerplate. The
|
|
113
|
+
* primary user is `auth/cleanup.ts` — sweeps have no per-request
|
|
114
|
+
* `pending_effects` to attach to.
|
|
115
|
+
*
|
|
116
|
+
* @mutates `audit_log` table - inserts the row via the captured pool
|
|
117
|
+
*/
|
|
118
|
+
emit_pool<T extends string>(input: AuditLogInput<T>): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Fan out an already-written audit row to the listener chain.
|
|
121
|
+
*
|
|
122
|
+
* Use only when the row was inserted in-transaction by a query helper
|
|
123
|
+
* that returned the `AuditLogEvent` (e.g. `query_accept_offer.audit_events`).
|
|
124
|
+
* Per-listener exceptions are caught and logged; one failing listener
|
|
125
|
+
* does not starve siblings.
|
|
126
|
+
*/
|
|
127
|
+
notify(event: AuditLogEvent): void;
|
|
128
|
+
/**
|
|
129
|
+
* Mutable subscriber chain. Append at server assembly to compose the
|
|
130
|
+
* factory-managed audit-log SSE on top of the consumer's
|
|
131
|
+
* `on_audit_event` callback without shallow-copying `AppDeps`.
|
|
132
|
+
*/
|
|
133
|
+
readonly on_event_chain: Array<(event: AuditLogEvent) => void>;
|
|
134
|
+
}
|
|
135
|
+
/** Options for `create_audit_emitter`. */
|
|
136
|
+
export interface CreateAuditEmitterOptions {
|
|
137
|
+
/** Pool-level `Db`. Captured by every emit call. */
|
|
138
|
+
db: Db;
|
|
139
|
+
/** Logger for write + listener-callback failures. */
|
|
140
|
+
log: Logger;
|
|
141
|
+
/**
|
|
142
|
+
* Initial subscriber appended to `on_event_chain`. Omit for backends
|
|
143
|
+
* that compose listeners post-assembly (e.g. via `audit_log_sse`).
|
|
144
|
+
*/
|
|
145
|
+
on_audit_event?: ((event: AuditLogEvent) => void) | null;
|
|
146
|
+
/**
|
|
147
|
+
* Audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`. Consumer-
|
|
148
|
+
* extended configs from `create_audit_log_config({extra_events})` get
|
|
149
|
+
* registered here once at backend assembly.
|
|
150
|
+
*/
|
|
151
|
+
audit_log_config?: AuditLogConfig;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Build a bound `AuditEmitter`. Called once at `create_app_backend` time.
|
|
155
|
+
*
|
|
156
|
+
* @param options - pool, logger, optional initial subscriber, optional config
|
|
157
|
+
* @returns the bound emitter; closes over the pool + config + listener chain
|
|
158
|
+
*/
|
|
159
|
+
export declare const create_audit_emitter: (options: CreateAuditEmitterOptions) => AuditEmitter;
|
|
160
|
+
//# sourceMappingURL=audit_emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,mBAAmB;IACnC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACrE,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChF;;;;;;;OAOG;IACH,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACtC,GAAG,EAAE,yBAAyB,EAC9B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE;QACN,UAAU,EAAE,CAAC,CAAC;QACd,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;QAC7B,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACvC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;KAChC,GACC,IAAI,CAAC;IACR;;;;;;;;;;OAUG;IACH,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;CAC/D;AAED,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACzC,oDAAoD;IACpD,EAAE,EAAE,EAAE,CAAC;IACP,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,yBAAyB,KAAG,YAoDzE,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bound audit-emit capability.
|
|
3
|
+
*
|
|
4
|
+
* `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
|
|
5
|
+
* subscriber chain, and the optional `AuditLogConfig` at backend-assembly
|
|
6
|
+
* time. Consumers reach for `deps.audit.emit(ctx, input)` and never see the
|
|
7
|
+
* pool — handlers cannot accidentally emit an audit event against the
|
|
8
|
+
* request's transactional `db` (which would be rolled back with the parent
|
|
9
|
+
* on a handler throw).
|
|
10
|
+
*
|
|
11
|
+
* Four methods cover every fan-out shape the auth domain needs:
|
|
12
|
+
*
|
|
13
|
+
* - `emit(ctx, input)` — fire-and-forget pool write. Pushes the in-flight
|
|
14
|
+
* promise onto `ctx.pending_effects` for post-response flushing. Errors are
|
|
15
|
+
* logged, never thrown. Returns `void` so callers don't pile up `void`
|
|
16
|
+
* keywords or accidentally `await` something whose handle is already in
|
|
17
|
+
* `pending_effects`.
|
|
18
|
+
* - `emit_role_grant_target(ctx, auth, input)` — wrapper that lifts the
|
|
19
|
+
* `actor_id` / `account_id` / `ip` boilerplate every role-grant-shape audit
|
|
20
|
+
* site repeated. Delegates to `emit`.
|
|
21
|
+
* - `emit_pool(input)` — awaitable pool write for code paths without a
|
|
22
|
+
* `pending_effects` queue (cleanup sweeps, ad-hoc maintenance scripts).
|
|
23
|
+
* Same write-then-notify semantics as `emit`, just synchronous-with-await.
|
|
24
|
+
* - `notify(event)` — fan out an already-written audit row (e.g. rows
|
|
25
|
+
* returned by `query_accept_offer` that were inserted in-transaction by
|
|
26
|
+
* the query layer). Runs every listener on the chain; per-listener throws
|
|
27
|
+
* are isolated.
|
|
28
|
+
*
|
|
29
|
+
* The chain is mutable so server assembly can append additional listeners
|
|
30
|
+
* (e.g. the audit-log SSE registry composed by `create_app_server`) after
|
|
31
|
+
* the backend is built but before the first request runs.
|
|
32
|
+
*
|
|
33
|
+
* @module
|
|
34
|
+
*/
|
|
35
|
+
import { query_audit_log } from './audit_log_queries.js';
|
|
36
|
+
import { BUILTIN_AUDIT_LOG_CONFIG, } from './audit_log_schema.js';
|
|
37
|
+
/**
|
|
38
|
+
* Build a bound `AuditEmitter`. Called once at `create_app_backend` time.
|
|
39
|
+
*
|
|
40
|
+
* @param options - pool, logger, optional initial subscriber, optional config
|
|
41
|
+
* @returns the bound emitter; closes over the pool + config + listener chain
|
|
42
|
+
*/
|
|
43
|
+
export const create_audit_emitter = (options) => {
|
|
44
|
+
const { db, log, audit_log_config = BUILTIN_AUDIT_LOG_CONFIG } = options;
|
|
45
|
+
const on_event_chain = [];
|
|
46
|
+
if (options.on_audit_event)
|
|
47
|
+
on_event_chain.push(options.on_audit_event);
|
|
48
|
+
const notify = (event) => {
|
|
49
|
+
for (const listener of on_event_chain) {
|
|
50
|
+
try {
|
|
51
|
+
listener(event);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
log.error('Audit log listener failed:', err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const emit_pool = async (input) => {
|
|
59
|
+
try {
|
|
60
|
+
const event = await query_audit_log({ db }, input, audit_log_config);
|
|
61
|
+
notify(event);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
log.error('Audit log write failed:', err);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const emit = (ctx, input) => {
|
|
68
|
+
ctx.pending_effects.push(emit_pool(input));
|
|
69
|
+
};
|
|
70
|
+
const emit_role_grant_target = (ctx, auth, input) => {
|
|
71
|
+
emit(ctx, {
|
|
72
|
+
event_type: input.event_type,
|
|
73
|
+
actor_id: auth.actor.id,
|
|
74
|
+
account_id: auth.account.id,
|
|
75
|
+
outcome: input.outcome,
|
|
76
|
+
target_account_id: input.target_account_id,
|
|
77
|
+
target_actor_id: input.target_actor_id,
|
|
78
|
+
ip: ctx.client_ip,
|
|
79
|
+
metadata: input.metadata,
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
return { emit, emit_role_grant_target, emit_pool, notify, on_event_chain };
|
|
83
|
+
};
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Audit log database queries.
|
|
3
3
|
*
|
|
4
|
-
* Records and retrieves auth mutation events for security monitoring.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Records and retrieves auth mutation events for security monitoring. The
|
|
5
|
+
* canonical fire-and-forget entry point is `AppDeps.audit.emit(ctx, input)`
|
|
6
|
+
* (see `auth/audit_emitter.ts`) — it closes over the pool so audit rows
|
|
7
|
+
* persist even when the request transaction rolls back. This module only
|
|
8
|
+
* exposes the in-transaction `query_*` primitives and the drift counters;
|
|
9
|
+
* the bound emitter writes through `query_audit_log` against its captured
|
|
10
|
+
* pool.
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
15
|
-
import type
|
|
16
|
-
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
17
|
-
import type { AuditEmitDeps } from './deps.js';
|
|
18
|
-
import type { RequestActorContext } from './request_context.js';
|
|
19
|
-
import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type PermitHistoryEventJson } from './audit_log_schema.js';
|
|
15
|
+
import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type RoleGrantHistoryEventJson } from './audit_log_schema.js';
|
|
20
16
|
/** Number of audit metadata validation failures observed since process start. */
|
|
21
17
|
export declare const get_audit_metadata_validation_failures: () => number;
|
|
22
18
|
/** Reset the counter — for tests only. */
|
|
@@ -34,6 +30,12 @@ export declare const reset_audit_unknown_event_type_failures: () => void;
|
|
|
34
30
|
* but write the row anyway. Consumers extend the recognized set via
|
|
35
31
|
* `create_audit_log_config({extra_events})`.
|
|
36
32
|
*
|
|
33
|
+
* In-transaction call site for query helpers that must atomically write the
|
|
34
|
+
* row alongside other mutations (e.g. `query_accept_offer`). Fire-and-forget
|
|
35
|
+
* call sites should reach for `AppDeps.audit.emit` instead — that wrapper
|
|
36
|
+
* closes over the pool so audit rows persist when the parent transaction
|
|
37
|
+
* rolls back.
|
|
38
|
+
*
|
|
37
39
|
* @param deps - query dependencies
|
|
38
40
|
* @param input - the audit event to record
|
|
39
41
|
* @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
@@ -59,22 +61,14 @@ export declare const query_audit_log_list: (deps: QueryDeps, options?: AuditLogL
|
|
|
59
61
|
*/
|
|
60
62
|
export declare const query_audit_log_list_with_usernames: (deps: QueryDeps, options?: AuditLogListOptions) => Promise<Array<AuditLogEventWithUsernamesJson>>;
|
|
61
63
|
/**
|
|
62
|
-
* List
|
|
63
|
-
*
|
|
64
|
-
* @param deps - query dependencies
|
|
65
|
-
* @param account_id - the account to query for
|
|
66
|
-
* @param limit - maximum entries to return
|
|
67
|
-
*/
|
|
68
|
-
export declare const query_audit_log_list_for_account: (deps: QueryDeps, account_id: string, limit?: number) => Promise<Array<AuditLogEvent>>;
|
|
69
|
-
/**
|
|
70
|
-
* List permit grant/revoke events with resolved usernames.
|
|
64
|
+
* List role_grant grant/revoke events with resolved usernames.
|
|
71
65
|
*
|
|
72
66
|
* @param deps - query dependencies
|
|
73
67
|
* @param limit - maximum entries to return
|
|
74
68
|
* @param offset - number of entries to skip
|
|
75
|
-
* @returns
|
|
69
|
+
* @returns role_grant history events with `username` and `target_username`
|
|
76
70
|
*/
|
|
77
|
-
export declare const
|
|
71
|
+
export declare const query_audit_log_list_role_grant_history: (deps: QueryDeps, limit?: number, offset?: number) => Promise<Array<RoleGrantHistoryEventJson>>;
|
|
78
72
|
/**
|
|
79
73
|
* Delete audit log entries older than the given date.
|
|
80
74
|
*
|
|
@@ -84,68 +78,4 @@ export declare const query_audit_log_list_permit_history: (deps: QueryDeps, limi
|
|
|
84
78
|
* @mutates `audit_log` table - deletes every row with `created_at < before`
|
|
85
79
|
*/
|
|
86
80
|
export declare const query_audit_log_cleanup_before: (deps: QueryDeps, before: Date) => Promise<number>;
|
|
87
|
-
/**
|
|
88
|
-
* Log an audit event without blocking the caller.
|
|
89
|
-
*
|
|
90
|
-
* Errors are logged — audit logging never breaks auth flows. Uses
|
|
91
|
-
* `background_db` so entries persist even when the request transaction
|
|
92
|
-
* rolls back. Write and `on_audit_event` callback failures are logged separately.
|
|
93
|
-
*
|
|
94
|
-
* `deps` is the shared `AuditEmitDeps` bundle (`log`, `on_audit_event`,
|
|
95
|
-
* optional `audit_log_config`) so call sites pass the surrounding deps
|
|
96
|
-
* object directly. The bundled shape replaces the prior `(log,
|
|
97
|
-
* on_audit_event, config?)` positional args — consumers that forgot the
|
|
98
|
-
* trailing `config` would silently fall back to `BUILTIN_AUDIT_LOG_CONFIG`
|
|
99
|
-
* and skip metadata validation for their own event types.
|
|
100
|
-
*
|
|
101
|
-
* @param route - `background_db` and `pending_effects` from the route context
|
|
102
|
-
* @param input - the audit event to record
|
|
103
|
-
* @param deps - logger, `on_audit_event` callback, and optional `audit_log_config`
|
|
104
|
-
* @returns the settled promise (callers may ignore it)
|
|
105
|
-
* @mutates `audit_log` table - inserts a row via `background_db` (independent of the request transaction)
|
|
106
|
-
* @mutates `route.pending_effects` - pushes the in-flight settled promise for test flushing
|
|
107
|
-
*/
|
|
108
|
-
export declare const audit_log_fire_and_forget: <T extends string>(route: Pick<RouteContext, "background_db" | "pending_effects">, input: AuditLogInput<T>, deps: AuditEmitDeps) => Promise<void>;
|
|
109
|
-
/**
|
|
110
|
-
* Per-request context required by `emit_permit_target_event` —
|
|
111
|
-
* `RouteContext` plus the resolved `client_ip` (lives on `ActionContext`
|
|
112
|
-
* for RPC handlers and on the route's Hono context for REST). Declared
|
|
113
|
-
* locally rather than reaching into `actions/action_rpc.ts` so the helper
|
|
114
|
-
* stays usable from REST handlers that haven't promoted to RPC yet.
|
|
115
|
-
*/
|
|
116
|
-
export type EmitPermitTargetEventContext = Pick<RouteContext, 'background_db' | 'pending_effects'> & {
|
|
117
|
-
client_ip: string;
|
|
118
|
-
};
|
|
119
|
-
/**
|
|
120
|
-
* Stamp a permit-shape audit event with both `target_account_id` (drives
|
|
121
|
-
* SSE/WS socket-close — sessions are account-grain) and `target_actor_id`
|
|
122
|
-
* (the actor-grain forensic field). Both target fields nullable so emit
|
|
123
|
-
* sites without a recipient binding (e.g. `permit_revoke` on a missing
|
|
124
|
-
* account, offer-shape events with no `to_actor_id`) can call through
|
|
125
|
-
* uniformly.
|
|
126
|
-
*
|
|
127
|
-
* Lifts the six-site `{actor_id: auth.actor.id, account_id: auth.account.id,
|
|
128
|
-
* ip: ctx.client_ip, ...}` boilerplate around `audit_log_fire_and_forget`
|
|
129
|
-
* so callers thread auth + ctx + deps once and the event metadata once,
|
|
130
|
-
* without re-derivable plumbing.
|
|
131
|
-
*
|
|
132
|
-
* Outcome defaults to `'success'`; pass `'failure'` for denial-shape
|
|
133
|
-
* events. Other audit envelope shapes (target_*-by-actor-id-only events,
|
|
134
|
-
* non-permit-shape events) should call `audit_log_fire_and_forget`
|
|
135
|
-
* directly — this helper deliberately narrows to the permit-target shape.
|
|
136
|
-
*
|
|
137
|
-
* @param ctx - request context with `background_db`, `pending_effects`, `client_ip`
|
|
138
|
-
* @param auth - the resolved `RequestActorContext` for the current handler — actor invariant captured in the type so the helper stops needing `auth.actor!`
|
|
139
|
-
* @param deps - `log`, `on_audit_event`, optional `audit_log_config`
|
|
140
|
-
* @param input - event type, target columns, metadata, optional outcome
|
|
141
|
-
* @returns the settled promise (callers may ignore it)
|
|
142
|
-
* @mutates `audit_log` table - inserts a row via `background_db`
|
|
143
|
-
*/
|
|
144
|
-
export declare const emit_permit_target_event: <T extends string>(ctx: EmitPermitTargetEventContext, auth: RequestActorContext, deps: AuditEmitDeps, input: {
|
|
145
|
-
event_type: T;
|
|
146
|
-
target_account_id: Uuid | null;
|
|
147
|
-
target_actor_id: Uuid | null;
|
|
148
|
-
metadata: AuditLogInput<T>["metadata"];
|
|
149
|
-
outcome?: "success" | "failure";
|
|
150
|
-
}) => Promise<void>;
|
|
151
81
|
//# sourceMappingURL=audit_log_queries.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,
|
|
1
|
+
{"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC9B,MAAM,uBAAuB,CAAC;AAa/B,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,QAAO,MACvB,CAAC;AAEpC,0CAA0C;AAC1C,eAAO,MAAM,wCAAwC,QAAO,IAE3D,CAAC;AAYF,gFAAgF;AAChF,eAAO,MAAM,qCAAqC,QAAO,MACvB,CAAC;AAEnC,0CAA0C;AAC1C,eAAO,MAAM,uCAAuC,QAAO,IAE1D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,MAAM,EACrD,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,SAAQ,cAAyC,KAC/C,OAAO,CAAC,aAAa,CAoCvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAwC9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CA8C/C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAY1C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC"}
|