@fuzdev/fuz_app 0.54.0 → 0.55.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 +68 -13
- package/dist/actions/action_codegen.d.ts +13 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +15 -1
- package/dist/actions/action_rpc.d.ts +60 -7
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +158 -44
- package/dist/actions/register_action_ws.d.ts +4 -4
- package/dist/actions/register_action_ws.js +6 -6
- package/dist/actions/register_ws_endpoint.d.ts +20 -7
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +30 -5
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +0 -4
- package/dist/auth/CLAUDE.md +219 -66
- package/dist/auth/account_actions.d.ts +6 -6
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +8 -11
- package/dist/auth/account_queries.d.ts +6 -3
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +14 -5
- package/dist/auth/account_routes.d.ts +7 -10
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +70 -23
- package/dist/auth/account_schema.d.ts +19 -0
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +20 -0
- package/dist/auth/admin_action_specs.d.ts +45 -11
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +23 -8
- package/dist/auth/admin_actions.d.ts +8 -7
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +11 -18
- package/dist/auth/audit_log_queries.d.ts +53 -14
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +45 -2
- package/dist/auth/audit_log_schema.d.ts +55 -1
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +19 -3
- package/dist/auth/bearer_auth.d.ts +9 -7
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +13 -21
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +5 -0
- package/dist/auth/daemon_token_middleware.d.ts +23 -11
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +26 -20
- package/dist/auth/deps.d.ts +14 -0
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +4 -2
- package/dist/auth/migrations.d.ts +15 -7
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +15 -7
- package/dist/auth/permit_offer_action_specs.d.ts +45 -6
- package/dist/auth/permit_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/permit_offer_action_specs.js +38 -7
- package/dist/auth/permit_offer_actions.d.ts +2 -2
- package/dist/auth/permit_offer_actions.d.ts.map +1 -1
- package/dist/auth/permit_offer_actions.js +98 -90
- package/dist/auth/permit_offer_notifications.d.ts +10 -0
- package/dist/auth/permit_offer_notifications.d.ts.map +1 -1
- package/dist/auth/permit_offer_queries.d.ts +68 -9
- package/dist/auth/permit_offer_queries.d.ts.map +1 -1
- package/dist/auth/permit_offer_queries.js +147 -35
- package/dist/auth/permit_offer_schema.d.ts +23 -1
- package/dist/auth/permit_offer_schema.d.ts.map +1 -1
- package/dist/auth/permit_offer_schema.js +5 -0
- package/dist/auth/permit_queries.d.ts +17 -5
- package/dist/auth/permit_queries.d.ts.map +1 -1
- package/dist/auth/permit_queries.js +19 -8
- package/dist/auth/request_context.d.ts +321 -38
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +393 -66
- package/dist/auth/route_guards.d.ts +10 -4
- package/dist/auth/route_guards.d.ts.map +1 -1
- package/dist/auth/route_guards.js +14 -8
- package/dist/auth/self_service_role_action_specs.d.ts +2 -0
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +2 -0
- package/dist/auth/self_service_role_actions.d.ts +6 -5
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +18 -8
- package/dist/db/migrate.d.ts +11 -7
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +9 -6
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +5 -3
- package/dist/hono_context.d.ts +77 -0
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +50 -0
- package/dist/http/CLAUDE.md +80 -17
- package/dist/http/error_schemas.d.ts +92 -1
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +73 -16
- package/dist/http/jsonrpc_errors.d.ts +27 -2
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +26 -2
- package/dist/http/route_spec.d.ts +62 -4
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +117 -21
- package/dist/http/schema_helpers.d.ts +13 -1
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +21 -2
- package/dist/http/surface.d.ts +10 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +2 -2
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +11 -1
- package/dist/testing/CLAUDE.md +23 -17
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +15 -13
- package/dist/testing/adversarial_headers.js +1 -1
- package/dist/testing/app_server.js +2 -2
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +21 -7
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +6 -3
- package/dist/testing/entities.d.ts +2 -1
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +1 -0
- package/dist/testing/integration_helpers.d.ts +4 -2
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +9 -5
- package/dist/testing/middleware.d.ts +12 -8
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +67 -25
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +3 -1
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +5 -1
- package/dist/ui/CLAUDE.md +16 -10
- package/dist/ui/PermitOfferForm.svelte +14 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts +6 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.d.ts +8 -1
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +14 -3
- package/dist/ui/permit_offers_state.svelte.d.ts +9 -1
- package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -1
- package/dist/ui/permit_offers_state.svelte.js +7 -1
- package/package.json +1 -1
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
import { type RpcAction } from '../actions/action_rpc.js';
|
|
31
31
|
import { type RoleSchemaResult } from './role_schema.js';
|
|
32
32
|
import { type AppSettings } from './app_settings_schema.js';
|
|
33
|
-
import type {
|
|
33
|
+
import type { AuditEmitDeps } from './deps.js';
|
|
34
34
|
/** Options for `create_admin_actions`. */
|
|
35
35
|
export interface AdminActionOptions {
|
|
36
36
|
/**
|
|
@@ -52,13 +52,14 @@ export interface AdminActionOptions {
|
|
|
52
52
|
/**
|
|
53
53
|
* Dependencies for `create_admin_actions`.
|
|
54
54
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* `
|
|
58
|
-
*
|
|
59
|
-
* `
|
|
55
|
+
* Aliases the shared `AuditEmitDeps` (the `log` / `on_audit_event` /
|
|
56
|
+
* optional `audit_log_config` slice every audit-emitting site picks).
|
|
57
|
+
* `log` drives RPC-internal error logging; `on_audit_event` is wired by
|
|
58
|
+
* the two revoke-all mutations so SSE fan-out mirrors the former
|
|
59
|
+
* REST-route behavior; `audit_log_config` is consumed by
|
|
60
|
+
* `audit_log_fire_and_forget`.
|
|
60
61
|
*/
|
|
61
|
-
export type AdminActionDeps =
|
|
62
|
+
export type AdminActionDeps = AuditEmitDeps;
|
|
62
63
|
/**
|
|
63
64
|
* Create the admin-only RPC actions.
|
|
64
65
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAA4C,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAEnG,OAAO,EAAuB,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAuB7E,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAK1D,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,WAAW,CAAC;AA8C7C,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,aAAa,CAAC;AAE5C;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,eAAe,EACrB,UAAS,kBAAuB,KAC9B,KAAK,CAAC,SAAS,CA4RjB,CAAC"}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*
|
|
28
28
|
* @module
|
|
29
29
|
*/
|
|
30
|
-
import {
|
|
30
|
+
import { rpc_actor_action } from '../actions/action_rpc.js';
|
|
31
31
|
import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
32
32
|
import { BUILTIN_ROLE_OPTIONS } from './role_schema.js';
|
|
33
33
|
import { query_account_by_email, query_account_by_id, query_account_by_username, query_admin_account_list, } from './account_queries.js';
|
|
@@ -71,7 +71,6 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
71
71
|
void audit_log_fire_and_forget(ctx, {
|
|
72
72
|
event_type: 'session_revoke_all',
|
|
73
73
|
outcome: 'failure',
|
|
74
|
-
actor_id: auth.actor.id,
|
|
75
74
|
account_id: auth.account.id,
|
|
76
75
|
// `target_account_id` is null: the FK to `account` would reject
|
|
77
76
|
// a probe for a non-existent id. The probed value is preserved
|
|
@@ -88,7 +87,6 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
88
87
|
const count = await query_session_revoke_all_for_account(ctx, input.account_id);
|
|
89
88
|
void audit_log_fire_and_forget(ctx, {
|
|
90
89
|
event_type: 'session_revoke_all',
|
|
91
|
-
actor_id: auth.actor.id,
|
|
92
90
|
account_id: auth.account.id,
|
|
93
91
|
target_account_id: input.account_id,
|
|
94
92
|
ip: ctx.client_ip,
|
|
@@ -103,7 +101,6 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
103
101
|
void audit_log_fire_and_forget(ctx, {
|
|
104
102
|
event_type: 'token_revoke_all',
|
|
105
103
|
outcome: 'failure',
|
|
106
|
-
actor_id: auth.actor.id,
|
|
107
104
|
account_id: auth.account.id,
|
|
108
105
|
// See `session_revoke_all_handler` — FK forces null here; the
|
|
109
106
|
// probed id lives under `metadata.attempted_account_id`.
|
|
@@ -119,7 +116,6 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
119
116
|
const count = await query_revoke_all_api_tokens_for_account(ctx, input.account_id);
|
|
120
117
|
void audit_log_fire_and_forget(ctx, {
|
|
121
118
|
event_type: 'token_revoke_all',
|
|
122
|
-
actor_id: auth.actor.id,
|
|
123
119
|
account_id: auth.account.id,
|
|
124
120
|
target_account_id: input.account_id,
|
|
125
121
|
ip: ctx.client_ip,
|
|
@@ -185,7 +181,6 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
185
181
|
}
|
|
186
182
|
void audit_log_fire_and_forget(ctx, {
|
|
187
183
|
event_type: 'invite_create',
|
|
188
|
-
actor_id: auth.actor.id,
|
|
189
184
|
account_id: auth.account.id,
|
|
190
185
|
ip: ctx.client_ip,
|
|
191
186
|
metadata: { invite_id: invite.id, email, username },
|
|
@@ -204,7 +199,6 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
204
199
|
}
|
|
205
200
|
void audit_log_fire_and_forget(ctx, {
|
|
206
201
|
event_type: 'invite_delete',
|
|
207
|
-
actor_id: auth.actor.id,
|
|
208
202
|
account_id: auth.account.id,
|
|
209
203
|
ip: ctx.client_ip,
|
|
210
204
|
metadata: { invite_id: input.invite_id },
|
|
@@ -212,15 +206,15 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
212
206
|
return { ok: true };
|
|
213
207
|
};
|
|
214
208
|
const actions = [
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
209
|
+
rpc_actor_action(admin_account_list_action_spec, account_list_handler),
|
|
210
|
+
rpc_actor_action(admin_session_list_action_spec, session_list_handler),
|
|
211
|
+
rpc_actor_action(admin_session_revoke_all_action_spec, session_revoke_all_handler),
|
|
212
|
+
rpc_actor_action(admin_token_revoke_all_action_spec, token_revoke_all_handler),
|
|
213
|
+
rpc_actor_action(audit_log_list_action_spec, audit_log_list_handler),
|
|
214
|
+
rpc_actor_action(audit_log_permit_history_action_spec, audit_log_permit_history_handler),
|
|
215
|
+
rpc_actor_action(invite_create_action_spec, invite_create_handler),
|
|
216
|
+
rpc_actor_action(invite_list_action_spec, invite_list_handler),
|
|
217
|
+
rpc_actor_action(invite_delete_action_spec, invite_delete_handler),
|
|
224
218
|
];
|
|
225
219
|
const { app_settings } = options;
|
|
226
220
|
if (app_settings) {
|
|
@@ -239,7 +233,6 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
239
233
|
app_settings.updated_by = updated.updated_by;
|
|
240
234
|
void audit_log_fire_and_forget(ctx, {
|
|
241
235
|
event_type: 'app_settings_update',
|
|
242
|
-
actor_id: auth.actor.id,
|
|
243
236
|
account_id: auth.account.id,
|
|
244
237
|
ip: ctx.client_ip,
|
|
245
238
|
metadata: {
|
|
@@ -251,7 +244,7 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
251
244
|
const settings = await query_app_settings_load_with_username(ctx);
|
|
252
245
|
return { ok: true, settings };
|
|
253
246
|
};
|
|
254
|
-
actions.push(
|
|
247
|
+
actions.push(rpc_actor_action(app_settings_get_action_spec, app_settings_get_handler), rpc_actor_action(app_settings_update_action_spec, app_settings_update_handler));
|
|
255
248
|
}
|
|
256
249
|
return actions;
|
|
257
250
|
};
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
15
15
|
import type { RouteContext } from '../http/route_spec.js';
|
|
16
|
-
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';
|
|
17
19
|
import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type PermitHistoryEventJson } from './audit_log_schema.js';
|
|
18
20
|
/** Number of audit metadata validation failures observed since process start. */
|
|
19
21
|
export declare const get_audit_metadata_validation_failures: () => number;
|
|
@@ -82,18 +84,6 @@ export declare const query_audit_log_list_permit_history: (deps: QueryDeps, limi
|
|
|
82
84
|
* @mutates `audit_log` table - deletes every row with `created_at < before`
|
|
83
85
|
*/
|
|
84
86
|
export declare const query_audit_log_cleanup_before: (deps: QueryDeps, before: Date) => Promise<number>;
|
|
85
|
-
/**
|
|
86
|
-
* Capabilities required by `audit_log_fire_and_forget`.
|
|
87
|
-
*
|
|
88
|
-
* Defined as a slice of `AppDeps` so call sites can pass the surrounding deps
|
|
89
|
-
* bundle directly without a structural-compatibility coincidence. The bundled
|
|
90
|
-
* shape replaces the prior `(log, on_audit_event, config?)` positional args
|
|
91
|
-
* — consumers that forgot the trailing `config` would silently fall back to
|
|
92
|
-
* `BUILTIN_AUDIT_LOG_CONFIG` and skip metadata validation for their own
|
|
93
|
-
* event types. `audit_log_config` is optional on `AppDeps` and defaults to
|
|
94
|
-
* `BUILTIN_AUDIT_LOG_CONFIG` inside `audit_log_fire_and_forget` when absent.
|
|
95
|
-
*/
|
|
96
|
-
export type AuditLogFireAndForgetDeps = Pick<AppDeps, 'log' | 'on_audit_event' | 'audit_log_config'>;
|
|
97
87
|
/**
|
|
98
88
|
* Log an audit event without blocking the caller.
|
|
99
89
|
*
|
|
@@ -101,6 +91,13 @@ export type AuditLogFireAndForgetDeps = Pick<AppDeps, 'log' | 'on_audit_event' |
|
|
|
101
91
|
* `background_db` so entries persist even when the request transaction
|
|
102
92
|
* rolls back. Write and `on_audit_event` callback failures are logged separately.
|
|
103
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
|
+
*
|
|
104
101
|
* @param route - `background_db` and `pending_effects` from the route context
|
|
105
102
|
* @param input - the audit event to record
|
|
106
103
|
* @param deps - logger, `on_audit_event` callback, and optional `audit_log_config`
|
|
@@ -108,5 +105,47 @@ export type AuditLogFireAndForgetDeps = Pick<AppDeps, 'log' | 'on_audit_event' |
|
|
|
108
105
|
* @mutates `audit_log` table - inserts a row via `background_db` (independent of the request transaction)
|
|
109
106
|
* @mutates `route.pending_effects` - pushes the in-flight settled promise for test flushing
|
|
110
107
|
*/
|
|
111
|
-
export declare const audit_log_fire_and_forget: <T extends string>(route: Pick<RouteContext, "background_db" | "pending_effects">, input: AuditLogInput<T>, deps:
|
|
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>;
|
|
112
151
|
//# 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,KAAK,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,14 +75,15 @@ export const query_audit_log = async (deps, input, config = BUILTIN_AUDIT_LOG_CO
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
const rows = await deps.db.query(`INSERT INTO audit_log (event_type, outcome, actor_id, account_id, target_account_id, ip, metadata)
|
|
79
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
78
|
+
const rows = await deps.db.query(`INSERT INTO audit_log (event_type, outcome, actor_id, account_id, target_account_id, target_actor_id, ip, metadata)
|
|
79
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
80
80
|
RETURNING *`, [
|
|
81
81
|
input.event_type,
|
|
82
82
|
input.outcome ?? 'success',
|
|
83
83
|
input.actor_id ?? null,
|
|
84
84
|
input.account_id ?? null,
|
|
85
85
|
input.target_account_id ?? null,
|
|
86
|
+
input.target_actor_id ?? null,
|
|
86
87
|
input.ip ?? null,
|
|
87
88
|
input.metadata ? JSON.stringify(input.metadata) : null,
|
|
88
89
|
]);
|
|
@@ -219,6 +220,13 @@ export const query_audit_log_cleanup_before = async (deps, before) => {
|
|
|
219
220
|
* `background_db` so entries persist even when the request transaction
|
|
220
221
|
* rolls back. Write and `on_audit_event` callback failures are logged separately.
|
|
221
222
|
*
|
|
223
|
+
* `deps` is the shared `AuditEmitDeps` bundle (`log`, `on_audit_event`,
|
|
224
|
+
* optional `audit_log_config`) so call sites pass the surrounding deps
|
|
225
|
+
* object directly. The bundled shape replaces the prior `(log,
|
|
226
|
+
* on_audit_event, config?)` positional args — consumers that forgot the
|
|
227
|
+
* trailing `config` would silently fall back to `BUILTIN_AUDIT_LOG_CONFIG`
|
|
228
|
+
* and skip metadata validation for their own event types.
|
|
229
|
+
*
|
|
222
230
|
* @param route - `background_db` and `pending_effects` from the route context
|
|
223
231
|
* @param input - the audit event to record
|
|
224
232
|
* @param deps - logger, `on_audit_event` callback, and optional `audit_log_config`
|
|
@@ -243,3 +251,38 @@ export const audit_log_fire_and_forget = (route, input, deps) => {
|
|
|
243
251
|
route.pending_effects.push(p);
|
|
244
252
|
return p;
|
|
245
253
|
};
|
|
254
|
+
/**
|
|
255
|
+
* Stamp a permit-shape audit event with both `target_account_id` (drives
|
|
256
|
+
* SSE/WS socket-close — sessions are account-grain) and `target_actor_id`
|
|
257
|
+
* (the actor-grain forensic field). Both target fields nullable so emit
|
|
258
|
+
* sites without a recipient binding (e.g. `permit_revoke` on a missing
|
|
259
|
+
* account, offer-shape events with no `to_actor_id`) can call through
|
|
260
|
+
* uniformly.
|
|
261
|
+
*
|
|
262
|
+
* Lifts the six-site `{actor_id: auth.actor.id, account_id: auth.account.id,
|
|
263
|
+
* ip: ctx.client_ip, ...}` boilerplate around `audit_log_fire_and_forget`
|
|
264
|
+
* so callers thread auth + ctx + deps once and the event metadata once,
|
|
265
|
+
* without re-derivable plumbing.
|
|
266
|
+
*
|
|
267
|
+
* Outcome defaults to `'success'`; pass `'failure'` for denial-shape
|
|
268
|
+
* events. Other audit envelope shapes (target_*-by-actor-id-only events,
|
|
269
|
+
* non-permit-shape events) should call `audit_log_fire_and_forget`
|
|
270
|
+
* directly — this helper deliberately narrows to the permit-target shape.
|
|
271
|
+
*
|
|
272
|
+
* @param ctx - request context with `background_db`, `pending_effects`, `client_ip`
|
|
273
|
+
* @param auth - the resolved `RequestActorContext` for the current handler — actor invariant captured in the type so the helper stops needing `auth.actor!`
|
|
274
|
+
* @param deps - `log`, `on_audit_event`, optional `audit_log_config`
|
|
275
|
+
* @param input - event type, target columns, metadata, optional outcome
|
|
276
|
+
* @returns the settled promise (callers may ignore it)
|
|
277
|
+
* @mutates `audit_log` table - inserts a row via `background_db`
|
|
278
|
+
*/
|
|
279
|
+
export const emit_permit_target_event = (ctx, auth, deps, input) => audit_log_fire_and_forget(ctx, {
|
|
280
|
+
event_type: input.event_type,
|
|
281
|
+
actor_id: auth.actor.id,
|
|
282
|
+
account_id: auth.account.id,
|
|
283
|
+
outcome: input.outcome,
|
|
284
|
+
target_account_id: input.target_account_id,
|
|
285
|
+
target_actor_id: input.target_actor_id,
|
|
286
|
+
ip: ctx.client_ip,
|
|
287
|
+
metadata: input.metadata,
|
|
288
|
+
}, deps);
|
|
@@ -175,9 +175,59 @@ export interface AuditLogEvent {
|
|
|
175
175
|
seq: number;
|
|
176
176
|
event_type: AuditEventTypeName;
|
|
177
177
|
outcome: AuditOutcome;
|
|
178
|
+
/**
|
|
179
|
+
* Operator (the actor that initiated the event) — populated when the
|
|
180
|
+
* request resolved an acting actor.
|
|
181
|
+
*
|
|
182
|
+
* Resolution is driven per-request by the route-spec wrapper / RPC
|
|
183
|
+
* dispatcher; a route gets an acting actor when its input schema
|
|
184
|
+
* declares `acting?: ActingActor` or its auth requires permits
|
|
185
|
+
* (`role` / `keeper`). Account-grain operations declare neither,
|
|
186
|
+
* so no actor is resolved and `actor_id` is null: login (also
|
|
187
|
+
* pre-credential), logout, signup, bootstrap, password_change,
|
|
188
|
+
* session/token revoke, app_settings_update, invite events.
|
|
189
|
+
* Permit events, admin actions, and actor-targeted offers
|
|
190
|
+
* populate this with the initiator's actor.
|
|
191
|
+
*/
|
|
178
192
|
actor_id: Uuid | null;
|
|
179
193
|
account_id: Uuid | null;
|
|
180
194
|
target_account_id: Uuid | null;
|
|
195
|
+
/**
|
|
196
|
+
* Actor-grain target — populated when the event subject is bound to
|
|
197
|
+
* a specific actor.
|
|
198
|
+
*
|
|
199
|
+
* Concretely:
|
|
200
|
+
* - Always populated: `permit_revoke` and `permit_grant`
|
|
201
|
+
* (admin direct-grant, self-service toggle, and in-tx
|
|
202
|
+
* `permit_offer_accept` all populate both target columns — the
|
|
203
|
+
* permit's grantee is the actor-grain subject regardless of who
|
|
204
|
+
* initiated the grant), `permit_offer_accept` on accept (the
|
|
205
|
+
* accept binds the actor deterministically), `permit_offer_decline`
|
|
206
|
+
* (the grantor actor — decline is *to* the offering actor).
|
|
207
|
+
* - Conditionally populated: offer-shape events
|
|
208
|
+
* (`permit_offer_create`, `_expire`, `_retract`, `_supersede`)
|
|
209
|
+
* carry the actor when the offer was actor-targeted at create time
|
|
210
|
+
* (`permit_offer.to_actor_id` set), null when the offer was
|
|
211
|
+
* account-grain (any actor on `to_account_id` may accept).
|
|
212
|
+
* - Not populated: admin actions, account-shape events (login,
|
|
213
|
+
* logout, signup, bootstrap, password_change, session/token
|
|
214
|
+
* revoke, app_settings_update, invite events) — subject is the
|
|
215
|
+
* account or no specific resource, not an actor-bound permit.
|
|
216
|
+
* - Not populated: events whose principal isn't an actor-bound
|
|
217
|
+
* resource (e.g. consumer events that name a non-actor scope in
|
|
218
|
+
* metadata).
|
|
219
|
+
*
|
|
220
|
+
* Multi-actor invariants this column relies on: when both
|
|
221
|
+
* `target_actor_id` and `target_account_id` are populated they refer
|
|
222
|
+
* to the same account (`actor.account_id`-derivable). The invariant
|
|
223
|
+
* holds uniformly across every populated event including decline
|
|
224
|
+
* (the grantor's account is joined into the decline RETURNING) and
|
|
225
|
+
* the supersede cascade (the recipient account is known on
|
|
226
|
+
* `permit_offer.to_account_id`). `target_account_id` stays the
|
|
227
|
+
* SSE/WS socket-close key because sessions remain account-grain
|
|
228
|
+
* after multi-actor lands.
|
|
229
|
+
*/
|
|
230
|
+
target_actor_id: Uuid | null;
|
|
181
231
|
ip: string | null;
|
|
182
232
|
created_at: string;
|
|
183
233
|
metadata: Record<string, unknown> | null;
|
|
@@ -197,6 +247,7 @@ export interface AuditLogInput<T extends string = AuditEventType> {
|
|
|
197
247
|
actor_id?: Uuid | null;
|
|
198
248
|
account_id?: Uuid | null;
|
|
199
249
|
target_account_id?: Uuid | null;
|
|
250
|
+
target_actor_id?: Uuid | null;
|
|
200
251
|
ip?: string | null;
|
|
201
252
|
/**
|
|
202
253
|
* Per-event-type metadata. Builtin `T` narrows to `AuditMetadataMap[T]`;
|
|
@@ -298,6 +349,7 @@ export declare const AuditLogEventJson: z.ZodObject<{
|
|
|
298
349
|
actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
299
350
|
account_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
300
351
|
target_account_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
352
|
+
target_actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
301
353
|
ip: z.ZodNullable<z.ZodString>;
|
|
302
354
|
created_at: z.ZodString;
|
|
303
355
|
metadata: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -315,6 +367,7 @@ export declare const AuditLogEventWithUsernamesJson: z.ZodObject<{
|
|
|
315
367
|
actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
316
368
|
account_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
317
369
|
target_account_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
370
|
+
target_actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
318
371
|
ip: z.ZodNullable<z.ZodString>;
|
|
319
372
|
created_at: z.ZodString;
|
|
320
373
|
metadata: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -334,6 +387,7 @@ export declare const PermitHistoryEventJson: z.ZodObject<{
|
|
|
334
387
|
actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
335
388
|
account_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
336
389
|
target_account_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
390
|
+
target_actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
337
391
|
ip: z.ZodNullable<z.ZodString>;
|
|
338
392
|
created_at: z.ZodString;
|
|
339
393
|
metadata: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -351,6 +405,6 @@ export declare const AdminSessionJson: z.ZodObject<{
|
|
|
351
405
|
username: z.ZodString;
|
|
352
406
|
}, z.core.$strict>;
|
|
353
407
|
export type AdminSessionJson = z.infer<typeof AdminSessionJson>;
|
|
354
|
-
export declare const AUDIT_LOG_SCHEMA = "\nCREATE TABLE IF NOT EXISTS audit_log (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n seq SERIAL NOT NULL,\n event_type TEXT NOT NULL,\n outcome TEXT NOT NULL DEFAULT 'success',\n actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n ip TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB\n)";
|
|
408
|
+
export declare const AUDIT_LOG_SCHEMA = "\nCREATE TABLE IF NOT EXISTS audit_log (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n seq SERIAL NOT NULL,\n event_type TEXT NOT NULL,\n outcome TEXT NOT NULL DEFAULT 'success',\n actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n ip TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB\n)";
|
|
355
409
|
export declare const AUDIT_LOG_INDEXES: string[];
|
|
356
410
|
//# sourceMappingURL=audit_log_schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
11
|
+
import { Blake3Hash } from '@fuzdev/fuz_util/hash_blake3.js';
|
|
11
12
|
import { AuthSessionJson } from './account_schema.js';
|
|
13
|
+
import { ApiTokenId } from './api_token.js';
|
|
12
14
|
/**
|
|
13
15
|
* All tracked auth event types. Frozen to convert accidental in-process
|
|
14
16
|
* mutation (test cross-contamination, cast escapes) into loud TypeErrors.
|
|
@@ -88,7 +90,7 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
88
90
|
})
|
|
89
91
|
.nullable(),
|
|
90
92
|
session_revoke: z.looseObject({
|
|
91
|
-
session_id:
|
|
93
|
+
session_id: Blake3Hash.meta({ description: 'Blake3 hash identifying the revoked session row.' }),
|
|
92
94
|
}),
|
|
93
95
|
session_revoke_all: z.looseObject({
|
|
94
96
|
// Omitted on `outcome='failure'` (no revocation attempted — e.g. target
|
|
@@ -107,11 +109,11 @@ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
|
107
109
|
}),
|
|
108
110
|
}),
|
|
109
111
|
token_create: z.looseObject({
|
|
110
|
-
token_id:
|
|
112
|
+
token_id: ApiTokenId.meta({ description: 'Public id of the created API token (`tok_…`).' }),
|
|
111
113
|
name: z.string().meta({ description: 'Operator-supplied label for the token.' }),
|
|
112
114
|
}),
|
|
113
115
|
token_revoke: z.looseObject({
|
|
114
|
-
token_id:
|
|
116
|
+
token_id: ApiTokenId.meta({ description: 'Public id of the revoked API token (`tok_…`).' }),
|
|
115
117
|
}),
|
|
116
118
|
token_revoke_all: z.looseObject({
|
|
117
119
|
// Same shape as `session_revoke_all` for failures.
|
|
@@ -311,6 +313,7 @@ export const AuditLogEventJson = z.strictObject({
|
|
|
311
313
|
actor_id: Uuid.nullable(),
|
|
312
314
|
account_id: Uuid.nullable(),
|
|
313
315
|
target_account_id: Uuid.nullable(),
|
|
316
|
+
target_actor_id: Uuid.nullable(),
|
|
314
317
|
ip: z.string().nullable(),
|
|
315
318
|
created_at: z.string(),
|
|
316
319
|
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
@@ -330,6 +333,17 @@ export const AdminSessionJson = AuthSessionJson.extend({
|
|
|
330
333
|
username: z.string(),
|
|
331
334
|
});
|
|
332
335
|
// Schema DDL
|
|
336
|
+
//
|
|
337
|
+
// Multi-actor invariants the envelope columns assume:
|
|
338
|
+
// - `actor_id` + `account_id`, when both populated, refer to the same
|
|
339
|
+
// account (derivable via `actor.account_id`). Denormalized for
|
|
340
|
+
// indexed audit queries; do not let them disagree.
|
|
341
|
+
// - `target_actor_id` + `target_account_id`, same rule when both populated.
|
|
342
|
+
// - `target_account_id` is the SSE/WS socket-close key — sessions stay
|
|
343
|
+
// account-grain after multi-actor lands, so this column carries
|
|
344
|
+
// the routing identity even on actor-bound events.
|
|
345
|
+
// - `target_actor_id` is populated iff the event subject is actor-bound
|
|
346
|
+
// (see `AuditLogEvent.target_actor_id` doc-comment for the rule).
|
|
333
347
|
export const AUDIT_LOG_SCHEMA = `
|
|
334
348
|
CREATE TABLE IF NOT EXISTS audit_log (
|
|
335
349
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
@@ -339,6 +353,7 @@ CREATE TABLE IF NOT EXISTS audit_log (
|
|
|
339
353
|
actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,
|
|
340
354
|
account_id UUID REFERENCES account(id) ON DELETE SET NULL,
|
|
341
355
|
target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,
|
|
356
|
+
target_actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,
|
|
342
357
|
ip TEXT,
|
|
343
358
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
344
359
|
metadata JSONB
|
|
@@ -348,4 +363,5 @@ export const AUDIT_LOG_INDEXES = [
|
|
|
348
363
|
`CREATE INDEX IF NOT EXISTS idx_audit_log_account ON audit_log(account_id)`,
|
|
349
364
|
`CREATE INDEX IF NOT EXISTS idx_audit_log_event_type ON audit_log(event_type)`,
|
|
350
365
|
`CREATE INDEX IF NOT EXISTS idx_audit_log_target_account ON audit_log(target_account_id)`,
|
|
366
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_log_target_actor ON audit_log(target_actor_id)`,
|
|
351
367
|
];
|
|
@@ -18,18 +18,20 @@ import { type RateLimiter } from '../rate_limiter.js';
|
|
|
18
18
|
* Create middleware that authenticates via bearer token.
|
|
19
19
|
*
|
|
20
20
|
* Soft-fails for invalid, expired, or empty tokens — calls `next()` without
|
|
21
|
-
* setting
|
|
22
|
-
*
|
|
23
|
-
* route-level error. This
|
|
21
|
+
* setting account identity, letting downstream auth enforcement (the RPC
|
|
22
|
+
* dispatcher's pre-validation / post-authorization auth gates or
|
|
23
|
+
* `require_auth`) return a consistent JSON-RPC or route-level error. This
|
|
24
|
+
* avoids leaking token-specific diagnostics
|
|
24
25
|
* (`invalid_token`, `account_not_found`) that could aid enumeration attacks,
|
|
25
26
|
* and ensures public actions are not blocked by bad credentials.
|
|
26
27
|
*
|
|
27
28
|
* Rejects bearer tokens when an `Origin` or `Referer` header is present —
|
|
28
29
|
* browsers must use cookie auth to reduce attack surface.
|
|
29
30
|
* Auth scheme matching is case-insensitive per RFC 7235.
|
|
30
|
-
* On success,
|
|
31
|
-
* and
|
|
32
|
-
* (e.g. by session middleware).
|
|
31
|
+
* On success, sets `c.var.auth_account_id`, `CREDENTIAL_TYPE_KEY = 'api_token'`,
|
|
32
|
+
* and `AUTH_API_TOKEN_ID_KEY`. Skips when an account is already authenticated
|
|
33
|
+
* (e.g. by session middleware). Acting-actor resolution + `RequestContext`
|
|
34
|
+
* construction are deferred to the dispatcher's authorization phase.
|
|
33
35
|
*
|
|
34
36
|
* Rate limiting (429) is the only hard-fail — it's a throttling concern
|
|
35
37
|
* independent of auth identity.
|
|
@@ -37,7 +39,7 @@ import { type RateLimiter } from '../rate_limiter.js';
|
|
|
37
39
|
* @param deps - query dependencies (pool-level db for middleware)
|
|
38
40
|
* @param ip_rate_limiter - per-IP rate limiter for bearer token attempts (null to disable)
|
|
39
41
|
* @param log - the logger instance
|
|
40
|
-
* @mutates Hono context - sets `
|
|
42
|
+
* @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on success
|
|
41
43
|
* @mutates `ip_rate_limiter` - records on attempt; resets on a valid token
|
|
42
44
|
*/
|
|
43
45
|
export declare const create_bearer_auth_middleware: (deps: QueryDeps, ip_rate_limiter: RateLimiter | null, log: Logger) => MiddlewareHandler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAIpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,iBAAiB,WAAW,GAAG,IAAI,EACnC,KAAK,MAAM,KACT,iBA4EF,CAAC"}
|
package/dist/auth/bearer_auth.js
CHANGED
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import { AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
13
|
+
import { AUTH_API_TOKEN_ID_KEY, ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
15
14
|
import { query_validate_api_token } from './api_token_queries.js';
|
|
16
15
|
import { get_client_ip } from '../http/proxy.js';
|
|
17
16
|
import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
@@ -19,18 +18,20 @@ import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
|
19
18
|
* Create middleware that authenticates via bearer token.
|
|
20
19
|
*
|
|
21
20
|
* Soft-fails for invalid, expired, or empty tokens — calls `next()` without
|
|
22
|
-
* setting
|
|
23
|
-
*
|
|
24
|
-
* route-level error. This
|
|
21
|
+
* setting account identity, letting downstream auth enforcement (the RPC
|
|
22
|
+
* dispatcher's pre-validation / post-authorization auth gates or
|
|
23
|
+
* `require_auth`) return a consistent JSON-RPC or route-level error. This
|
|
24
|
+
* avoids leaking token-specific diagnostics
|
|
25
25
|
* (`invalid_token`, `account_not_found`) that could aid enumeration attacks,
|
|
26
26
|
* and ensures public actions are not blocked by bad credentials.
|
|
27
27
|
*
|
|
28
28
|
* Rejects bearer tokens when an `Origin` or `Referer` header is present —
|
|
29
29
|
* browsers must use cookie auth to reduce attack surface.
|
|
30
30
|
* Auth scheme matching is case-insensitive per RFC 7235.
|
|
31
|
-
* On success,
|
|
32
|
-
* and
|
|
33
|
-
* (e.g. by session middleware).
|
|
31
|
+
* On success, sets `c.var.auth_account_id`, `CREDENTIAL_TYPE_KEY = 'api_token'`,
|
|
32
|
+
* and `AUTH_API_TOKEN_ID_KEY`. Skips when an account is already authenticated
|
|
33
|
+
* (e.g. by session middleware). Acting-actor resolution + `RequestContext`
|
|
34
|
+
* construction are deferred to the dispatcher's authorization phase.
|
|
34
35
|
*
|
|
35
36
|
* Rate limiting (429) is the only hard-fail — it's a throttling concern
|
|
36
37
|
* independent of auth identity.
|
|
@@ -38,13 +39,13 @@ import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
|
38
39
|
* @param deps - query dependencies (pool-level db for middleware)
|
|
39
40
|
* @param ip_rate_limiter - per-IP rate limiter for bearer token attempts (null to disable)
|
|
40
41
|
* @param log - the logger instance
|
|
41
|
-
* @mutates Hono context - sets `
|
|
42
|
+
* @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on success
|
|
42
43
|
* @mutates `ip_rate_limiter` - records on attempt; resets on a valid token
|
|
43
44
|
*/
|
|
44
45
|
export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
|
|
45
46
|
return async (c, next) => {
|
|
46
|
-
// Skip if already authenticated
|
|
47
|
-
if (c.get(
|
|
47
|
+
// Skip if an account is already authenticated (e.g. by session middleware)
|
|
48
|
+
if (c.get(ACCOUNT_ID_KEY) != null) {
|
|
48
49
|
await next();
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
@@ -97,16 +98,7 @@ export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
|
|
|
97
98
|
// Valid token — reset rate limit counter
|
|
98
99
|
if (ip_rate_limiter)
|
|
99
100
|
ip_rate_limiter.reset(ip);
|
|
100
|
-
|
|
101
|
-
const ctx = await build_request_context(deps, api_token.account_id);
|
|
102
|
-
if (!ctx) {
|
|
103
|
-
// Token exists but account/actor missing — soft-fail to avoid
|
|
104
|
-
// leaking account lifecycle information.
|
|
105
|
-
log.debug('bearer auth soft-fail: account or actor not found for token');
|
|
106
|
-
await next();
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
c.set(REQUEST_CONTEXT_KEY, ctx);
|
|
101
|
+
c.set(ACCOUNT_ID_KEY, api_token.account_id);
|
|
110
102
|
c.set(CREDENTIAL_TYPE_KEY, 'api_token');
|
|
111
103
|
c.set(AUTH_API_TOKEN_ID_KEY, api_token.id);
|
|
112
104
|
await next();
|
|
@@ -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;AAInD,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEzE,4CAA4C;AAC5C,MAAM,WAAW,eAAgB,SAAQ,SAAS;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;CAClC;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,
|
|
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;AAInD,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEzE,4CAA4C;AAC5C,MAAM,WAAW,eAAgB,SAAQ,SAAS;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;CAClC;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,CAsCzF,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,iBAAiB,CAIvF,CAAC"}
|