@fuzdev/fuz_app 0.38.1 → 0.39.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/auth/CLAUDE.md +48 -32
- package/dist/auth/audit_log_queries.d.ts +19 -17
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +49 -36
- package/dist/auth/audit_log_schema.d.ts +81 -10
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +67 -10
- package/dist/auth/role_schema.d.ts +10 -1
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +10 -1
- package/dist/http/jsonrpc_errors.d.ts +27 -75
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +16 -9
- package/dist/server/app_backend.d.ts +17 -6
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +17 -6
- package/dist/server/app_server.d.ts +6 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +16 -29
- package/dist/ui/AdminAccounts.svelte +19 -0
- package/dist/ui/AdminAccounts.svelte.d.ts +2 -17
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminPermitHistory.svelte +23 -2
- package/dist/ui/AdminPermitHistory.svelte.d.ts +2 -17
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +11 -0
- package/dist/ui/PermitOfferHistory.svelte +11 -5
- package/dist/ui/PermitOfferHistory.svelte.d.ts +7 -1
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -1
- package/dist/ui/PermitOfferInbox.svelte +12 -7
- package/dist/ui/PermitOfferInbox.svelte.d.ts +8 -3
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.d.ts +16 -1
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +12 -1
- package/dist/ui/format_scope.d.ts +45 -0
- package/dist/ui/format_scope.d.ts.map +1 -0
- package/dist/ui/format_scope.js +34 -0
- package/package.json +1 -1
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -162,22 +162,40 @@ Separated from runtime types to isolate DDL concerns. Consumed by
|
|
|
162
162
|
`_decline` / `_retract` / `_expire` / `_supersede`.
|
|
163
163
|
- `AuditEventType` (Zod enum), `AuditOutcome` (`'success' | 'failure'`).
|
|
164
164
|
- `AUDIT_METADATA_SCHEMAS` — per-type `z.looseObject`. Notable shapes:
|
|
165
|
-
- `permit_grant`
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
- `
|
|
169
|
-
- `
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
- `AuditLogEvent` (row), `AuditLogInput<T>` (narrow metadata), `AuditLogListOptions`
|
|
175
|
-
(supports `since_seq` for SSE reconnection gap fill).
|
|
165
|
+
- `permit_grant` — `scope_id`, optional `permit_id` (failed grants
|
|
166
|
+
omit — `web_grantable` denial never produces a row), optional `source_offer_id`.
|
|
167
|
+
- `permit_revoke` — `scope_id`, optional `reason`.
|
|
168
|
+
- `permit_offer_create` — optional `offer_id` (failed creates omit).
|
|
169
|
+
- `permit_offer_supersede` — `reason: 'sibling_accepted' | 'permit_revoked'`
|
|
170
|
+
plus `cause_id` (accepted offer id or revoked permit id).
|
|
171
|
+
- `AuditLogEvent` (row); `AuditLogInput<T extends string = AuditEventType>`
|
|
172
|
+
(narrow metadata when `T` is builtin, generic record otherwise);
|
|
173
|
+
`AuditLogListOptions` (supports `since_seq` for SSE reconnection gap fill).
|
|
176
174
|
- Client-safe: `AuditLogEventJson`, `AuditLogEventWithUsernamesJson`,
|
|
177
175
|
`PermitHistoryEventJson`, `AdminSessionJson`.
|
|
178
|
-
- `get_audit_metadata(event)` type-narrows
|
|
176
|
+
- `get_audit_metadata(event)` type-narrows after checking `event_type`.
|
|
179
177
|
- DDL: `AUDIT_LOG_SCHEMA` (includes monotonically-increasing `seq SERIAL`
|
|
180
|
-
|
|
178
|
+
for cursor-based gap fill), `AUDIT_LOG_INDEXES`.
|
|
179
|
+
- **Consumer extensibility**: `create_audit_log_config({extra_events})`
|
|
180
|
+
builds an `AuditLogConfig` merging builtins with consumer event-type
|
|
181
|
+
strings keyed to a Zod schema (validates metadata) or `null` (registers
|
|
182
|
+
without validation). Pass the result as the trailing `config` argument
|
|
183
|
+
to `audit_log_fire_and_forget` / `query_audit_log`; both default to
|
|
184
|
+
`BUILTIN_AUDIT_LOG_CONFIG`. Builtin collisions and `AuditEventTypeName`
|
|
185
|
+
format failures throw at construction. The DB column is `TEXT NOT NULL`
|
|
186
|
+
(no enum), so consumer types round-trip through list queries and SSE
|
|
187
|
+
identically to builtins. The `audit_log_list` RPC filter still uses the
|
|
188
|
+
closed `AuditEventType` — widening that is future work.
|
|
189
|
+
- **Drift counters**: `audit_metadata_validation_failures` (schema mismatch)
|
|
190
|
+
and `audit_unknown_event_type_failures` (`event_type` not in active
|
|
191
|
+
config). Both fail-open. Independent in implementation; under the
|
|
192
|
+
factory they track the same config, but a hand-rolled `AuditLogConfig`
|
|
193
|
+
(or a cast escape) can fire both on a single emission. Sample via
|
|
194
|
+
`get_*` getters; `reset_*` are test-only. `AUDIT_EVENT_TYPES`,
|
|
195
|
+
`AUDIT_METADATA_SCHEMAS`, `BUILTIN_AUDIT_LOG_CONFIG`, and the configs
|
|
196
|
+
returned by `create_audit_log_config` are `Object.freeze`'d to convert
|
|
197
|
+
accidental mutation (bugs, test cross-contamination, cast escapes)
|
|
198
|
+
into loud TypeErrors — not a security boundary.
|
|
181
199
|
|
|
182
200
|
### Permit offer (`permit_offer_schema.ts`)
|
|
183
201
|
|
|
@@ -422,31 +440,29 @@ run'` if the seed somehow missed (defensive — migrations always seed).
|
|
|
422
440
|
### `audit_log_queries.ts`
|
|
423
441
|
|
|
424
442
|
- `AUDIT_LOG_DEFAULT_LIMIT = 50`.
|
|
425
|
-
- `query_audit_log<T>(deps, input)` —
|
|
426
|
-
`
|
|
427
|
-
|
|
428
|
-
`
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
`
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
restart); operators thread it into a future `/metrics` surface or a
|
|
436
|
-
debug RPC handler when external observability is needed.
|
|
443
|
+
- `query_audit_log<T>(deps, input, config?)` — `config` defaults to
|
|
444
|
+
`BUILTIN_AUDIT_LOG_CONFIG`. Membership check runs against
|
|
445
|
+
`config.event_types`; metadata validation runs independently against
|
|
446
|
+
`config.metadata_schemas[event_type]` when present. Mismatches and
|
|
447
|
+
unknown types log + bump their counters (see schema section);
|
|
448
|
+
never throws. Returns the inserted row via `RETURNING *`.
|
|
449
|
+
- Drift counters live alongside in this module:
|
|
450
|
+
`get_audit_metadata_validation_failures()` /
|
|
451
|
+
`get_audit_unknown_event_type_failures()` (read);
|
|
452
|
+
`reset_*` (test-only). In-process; reset on restart.
|
|
437
453
|
- `query_audit_log_list(deps, options?)` — supports `event_type`,
|
|
438
|
-
`event_type_in`, `account_id` (matches
|
|
454
|
+
`event_type_in`, `account_id` (matches `account_id` OR
|
|
439
455
|
`target_account_id`), `outcome`, `since_seq`, `limit`, `offset`.
|
|
440
456
|
- `query_audit_log_list_with_usernames` — joins twice to `account`.
|
|
441
457
|
- `query_audit_log_list_for_account`, `query_audit_log_list_permit_history`
|
|
442
458
|
(filters to `permit_grant` / `permit_revoke`).
|
|
443
459
|
- `query_audit_log_cleanup_before`.
|
|
444
|
-
- **`audit_log_fire_and_forget(route, input, log, on_event)`** —
|
|
445
|
-
`route.background_db` (pool-level),
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
460
|
+
- **`audit_log_fire_and_forget(route, input, log, on_event, config?)`** —
|
|
461
|
+
writes to `route.background_db` (pool-level), so audit entries persist
|
|
462
|
+
even when the request transaction rolls back. Write and `on_event`
|
|
463
|
+
callback failures are logged separately. Pushes onto
|
|
464
|
+
`route.pending_effects` for test flushing. Pass a consumer `config`
|
|
465
|
+
built once at startup; builtin handlers omit the argument.
|
|
450
466
|
|
|
451
467
|
### `migrations.ts`
|
|
452
468
|
|
|
@@ -14,30 +14,32 @@
|
|
|
14
14
|
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
15
15
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
16
16
|
import type { RouteContext } from '../http/route_spec.js';
|
|
17
|
-
import { type
|
|
17
|
+
import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type PermitHistoryEventJson } from './audit_log_schema.js';
|
|
18
18
|
/** Default limit for audit log listings. */
|
|
19
19
|
export declare const AUDIT_LOG_DEFAULT_LIMIT = 50;
|
|
20
20
|
/** Number of audit metadata validation failures observed since process start. */
|
|
21
21
|
export declare const get_audit_metadata_validation_failures: () => number;
|
|
22
|
-
/** Reset the counter — for tests only
|
|
22
|
+
/** Reset the counter — for tests only. */
|
|
23
23
|
export declare const reset_audit_metadata_validation_failures: () => void;
|
|
24
|
+
/** Number of audit unknown-event-type failures observed since process start. */
|
|
25
|
+
export declare const get_audit_unknown_event_type_failures: () => number;
|
|
26
|
+
/** Reset the counter — for tests only. */
|
|
27
|
+
export declare const reset_audit_unknown_event_type_failures: () => void;
|
|
24
28
|
/**
|
|
25
29
|
* Insert an audit log entry.
|
|
26
30
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `audit_metadata_validation_failures` (sampled via the exported getter)
|
|
33
|
-
* but never throw — the audit row is still written. Schema-vs-runtime
|
|
34
|
-
* drift is an operator signal, not a request-failing condition.
|
|
31
|
+
* `RETURNING *` so callers receive DB-assigned fields (`id`, `seq`,
|
|
32
|
+
* `created_at`). Validates `metadata` against `config.metadata_schemas`;
|
|
33
|
+
* unknown `event_type` and metadata mismatches log + bump their counters
|
|
34
|
+
* but write the row anyway. Consumers extend the recognized set via
|
|
35
|
+
* `create_audit_log_config({extra_events})`.
|
|
35
36
|
*
|
|
36
37
|
* @param deps - query dependencies
|
|
37
38
|
* @param input - the audit event to record
|
|
39
|
+
* @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
38
40
|
* @returns the inserted audit log row
|
|
39
41
|
*/
|
|
40
|
-
export declare const query_audit_log: <T extends
|
|
42
|
+
export declare const query_audit_log: <T extends string>(deps: QueryDeps, input: AuditLogInput<T>, config?: AuditLogConfig) => Promise<AuditLogEvent>;
|
|
41
43
|
/**
|
|
42
44
|
* List audit log entries, newest first.
|
|
43
45
|
*
|
|
@@ -82,16 +84,16 @@ export declare const query_audit_log_cleanup_before: (deps: QueryDeps, before: D
|
|
|
82
84
|
/**
|
|
83
85
|
* Log an audit event without blocking the caller.
|
|
84
86
|
*
|
|
85
|
-
* Errors are logged
|
|
86
|
-
*
|
|
87
|
-
* Write
|
|
88
|
-
* so the error message indicates which phase failed.
|
|
87
|
+
* Errors are logged — audit logging never breaks auth flows. Uses
|
|
88
|
+
* `background_db` so entries persist even when the request transaction
|
|
89
|
+
* rolls back. Write and `on_event` callback failures are logged separately.
|
|
89
90
|
*
|
|
90
91
|
* @param route - `background_db` and `pending_effects` from the route context
|
|
91
92
|
* @param input - the audit event to record
|
|
92
93
|
* @param log - the logger instance
|
|
93
94
|
* @param on_event - callback invoked with the inserted row after a successful write
|
|
94
|
-
* @
|
|
95
|
+
* @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
96
|
+
* @returns the settled promise (callers may ignore it)
|
|
95
97
|
*/
|
|
96
|
-
export declare const audit_log_fire_and_forget: <T extends
|
|
98
|
+
export declare const audit_log_fire_and_forget: <T extends string>(route: Pick<RouteContext, "background_db" | "pending_effects">, input: AuditLogInput<T>, log: Logger, on_event: (event: AuditLogEvent) => void, config?: AuditLogConfig) => Promise<void>;
|
|
97
99
|
//# 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,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,4CAA4C;AAC5C,eAAO,MAAM,uBAAuB,KAAK,CAAC;
|
|
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,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,4CAA4C;AAC5C,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAa1C,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;;;;;;;;;;;;;GAaG;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,CAmCvB,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;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAA+B,KAC7B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAO9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAYvC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,MAAM,EACzD,OAAO,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,iBAAiB,CAAC,EAC9D,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,KAAK,MAAM,EACX,UAAU,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EACxC,SAAQ,cAAyC,KAC/C,OAAO,CAAC,IAAI,CAcd,CAAC"}
|
|
@@ -12,54 +12,67 @@
|
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
import { assert_row } from '../db/assert_row.js';
|
|
15
|
-
import {
|
|
15
|
+
import { BUILTIN_AUDIT_LOG_CONFIG, } from './audit_log_schema.js';
|
|
16
16
|
/** Default limit for audit log listings. */
|
|
17
17
|
export const AUDIT_LOG_DEFAULT_LIMIT = 50;
|
|
18
18
|
/**
|
|
19
|
-
* Process-wide counter for audit metadata validation failures.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* the counter via `get_audit_metadata_validation_failures()` (e.g. from
|
|
27
|
-
* a future `/metrics` surface or a debug RPC handler).
|
|
28
|
-
*
|
|
29
|
-
* Single-process scope: fuz_app's runtime state lives in-process (rate
|
|
30
|
-
* limiter, daemon token state); this counter follows the same pattern
|
|
31
|
-
* and resets on server restart.
|
|
19
|
+
* Process-wide counter for audit metadata validation failures. `query_audit_log`
|
|
20
|
+
* increments on `safeParse` mismatch and writes the row anyway (fail-open —
|
|
21
|
+
* schema drift should not break auth flows). Independent of the
|
|
22
|
+
* unknown-event-type counter — `create_audit_log_config` keeps the two in
|
|
23
|
+
* sync, but a hand-rolled `AuditLogConfig` (or a cast escape) can have a
|
|
24
|
+
* schema entry without a matching `event_types` entry, in which case both
|
|
25
|
+
* counters bump on a single emission. In-process; resets on restart.
|
|
32
26
|
*/
|
|
33
27
|
let audit_metadata_validation_failures = 0;
|
|
34
28
|
/** Number of audit metadata validation failures observed since process start. */
|
|
35
29
|
export const get_audit_metadata_validation_failures = () => audit_metadata_validation_failures;
|
|
36
|
-
/** Reset the counter — for tests only
|
|
30
|
+
/** Reset the counter — for tests only. */
|
|
37
31
|
export const reset_audit_metadata_validation_failures = () => {
|
|
38
32
|
audit_metadata_validation_failures = 0;
|
|
39
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Process-wide counter for audit-log emissions whose `event_type` is missing
|
|
36
|
+
* from the active config. Same fail-open posture as the metadata counter;
|
|
37
|
+
* orthogonal in implementation — metadata validation runs regardless of
|
|
38
|
+
* registration — though under the factory both counters track the same
|
|
39
|
+
* config (see `audit_metadata_validation_failures`). Catches typos and
|
|
40
|
+
* missing `extra_events` registrations.
|
|
41
|
+
*/
|
|
42
|
+
let audit_unknown_event_type_failures = 0;
|
|
43
|
+
/** Number of audit unknown-event-type failures observed since process start. */
|
|
44
|
+
export const get_audit_unknown_event_type_failures = () => audit_unknown_event_type_failures;
|
|
45
|
+
/** Reset the counter — for tests only. */
|
|
46
|
+
export const reset_audit_unknown_event_type_failures = () => {
|
|
47
|
+
audit_unknown_event_type_failures = 0;
|
|
48
|
+
};
|
|
40
49
|
/**
|
|
41
50
|
* Insert an audit log entry.
|
|
42
51
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `audit_metadata_validation_failures` (sampled via the exported getter)
|
|
49
|
-
* but never throw — the audit row is still written. Schema-vs-runtime
|
|
50
|
-
* drift is an operator signal, not a request-failing condition.
|
|
52
|
+
* `RETURNING *` so callers receive DB-assigned fields (`id`, `seq`,
|
|
53
|
+
* `created_at`). Validates `metadata` against `config.metadata_schemas`;
|
|
54
|
+
* unknown `event_type` and metadata mismatches log + bump their counters
|
|
55
|
+
* but write the row anyway. Consumers extend the recognized set via
|
|
56
|
+
* `create_audit_log_config({extra_events})`.
|
|
51
57
|
*
|
|
52
58
|
* @param deps - query dependencies
|
|
53
59
|
* @param input - the audit event to record
|
|
60
|
+
* @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
54
61
|
* @returns the inserted audit log row
|
|
55
62
|
*/
|
|
56
|
-
export const query_audit_log = async (deps, input) => {
|
|
63
|
+
export const query_audit_log = async (deps, input, config = BUILTIN_AUDIT_LOG_CONFIG) => {
|
|
64
|
+
if (!config.event_types.includes(input.event_type)) {
|
|
65
|
+
audit_unknown_event_type_failures++;
|
|
66
|
+
console.error(`[audit_log] unknown event_type '${input.event_type}' — register via create_audit_log_config({extra_events})`);
|
|
67
|
+
}
|
|
57
68
|
if (input.metadata != null) {
|
|
58
|
-
const schema =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
const schema = config.metadata_schemas[input.event_type];
|
|
70
|
+
if (schema) {
|
|
71
|
+
const result = schema.safeParse(input.metadata);
|
|
72
|
+
if (!result.success) {
|
|
73
|
+
audit_metadata_validation_failures++;
|
|
74
|
+
console.error(`[audit_log] metadata mismatch for '${input.event_type}':`, result.error.issues);
|
|
75
|
+
}
|
|
63
76
|
}
|
|
64
77
|
}
|
|
65
78
|
const rows = await deps.db.query(`INSERT INTO audit_log (event_type, outcome, actor_id, account_id, target_account_id, ip, metadata)
|
|
@@ -201,19 +214,19 @@ export const query_audit_log_cleanup_before = async (deps, before) => {
|
|
|
201
214
|
/**
|
|
202
215
|
* Log an audit event without blocking the caller.
|
|
203
216
|
*
|
|
204
|
-
* Errors are logged
|
|
205
|
-
*
|
|
206
|
-
* Write
|
|
207
|
-
* so the error message indicates which phase failed.
|
|
217
|
+
* Errors are logged — audit logging never breaks auth flows. Uses
|
|
218
|
+
* `background_db` so entries persist even when the request transaction
|
|
219
|
+
* rolls back. Write and `on_event` callback failures are logged separately.
|
|
208
220
|
*
|
|
209
221
|
* @param route - `background_db` and `pending_effects` from the route context
|
|
210
222
|
* @param input - the audit event to record
|
|
211
223
|
* @param log - the logger instance
|
|
212
224
|
* @param on_event - callback invoked with the inserted row after a successful write
|
|
213
|
-
* @
|
|
225
|
+
* @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
226
|
+
* @returns the settled promise (callers may ignore it)
|
|
214
227
|
*/
|
|
215
|
-
export const audit_log_fire_and_forget = (route, input, log, on_event) => {
|
|
216
|
-
const p = query_audit_log({ db: route.background_db }, input)
|
|
228
|
+
export const audit_log_fire_and_forget = (route, input, log, on_event, config = BUILTIN_AUDIT_LOG_CONFIG) => {
|
|
229
|
+
const p = query_audit_log({ db: route.background_db }, input, config)
|
|
217
230
|
.then((event) => {
|
|
218
231
|
try {
|
|
219
232
|
on_event(event);
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { Uuid } from '../uuid.js';
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* All tracked auth event types. Frozen to convert accidental in-process
|
|
13
|
+
* mutation (test cross-contamination, cast escapes) into loud TypeErrors.
|
|
14
|
+
* Not a security boundary — in-process code has many other paths to subvert
|
|
15
|
+
* audit logging.
|
|
16
|
+
*/
|
|
12
17
|
export declare const AUDIT_EVENT_TYPES: readonly ["login", "logout", "bootstrap", "signup", "password_change", "session_revoke", "session_revoke_all", "token_create", "token_revoke", "token_revoke_all", "permit_grant", "permit_revoke", "permit_offer_create", "permit_offer_accept", "permit_offer_decline", "permit_offer_retract", "permit_offer_expire", "permit_offer_supersede", "invite_create", "invite_delete", "app_settings_update"];
|
|
13
18
|
/** Zod schema for audit event types. */
|
|
14
19
|
export declare const AuditEventType: z.ZodEnum<{
|
|
@@ -35,6 +40,15 @@ export declare const AuditEventType: z.ZodEnum<{
|
|
|
35
40
|
app_settings_update: "app_settings_update";
|
|
36
41
|
}>;
|
|
37
42
|
export type AuditEventType = z.infer<typeof AuditEventType>;
|
|
43
|
+
/**
|
|
44
|
+
* Letter start, then letters, digits, `_`, `.`, `/`, `-`. Accepts snake_case,
|
|
45
|
+
* dotted, and namespaced consumer conventions; rejects empty strings, leading
|
|
46
|
+
* separators, whitespace, and control characters.
|
|
47
|
+
*/
|
|
48
|
+
export declare const AUDIT_EVENT_TYPE_NAME_REGEX: RegExp;
|
|
49
|
+
/** Zod schema for valid audit event-type name strings. */
|
|
50
|
+
export declare const AuditEventTypeName: z.ZodString;
|
|
51
|
+
export type AuditEventTypeName = z.infer<typeof AuditEventTypeName>;
|
|
38
52
|
/** Zod schema for audit event outcomes. */
|
|
39
53
|
export declare const AuditOutcome: z.ZodEnum<{
|
|
40
54
|
success: "success";
|
|
@@ -42,13 +56,13 @@ export declare const AuditOutcome: z.ZodEnum<{
|
|
|
42
56
|
}>;
|
|
43
57
|
export type AuditOutcome = z.infer<typeof AuditOutcome>;
|
|
44
58
|
/**
|
|
45
|
-
* Per-event-type metadata Zod schemas.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
59
|
+
* Per-event-type metadata Zod schemas. `z.looseObject` so consumers can
|
|
60
|
+
* add fields while known ones are validated. The record is frozen to
|
|
61
|
+
* catch mutation bugs at the key level (e.g. tests that try to swap in a
|
|
62
|
+
* stub schema); the Zod schemas themselves are reachable and mutable —
|
|
63
|
+
* freeze isn't a security boundary.
|
|
50
64
|
*/
|
|
51
|
-
export declare const AUDIT_METADATA_SCHEMAS: {
|
|
65
|
+
export declare const AUDIT_METADATA_SCHEMAS: Readonly<{
|
|
52
66
|
login: z.ZodNullable<z.ZodObject<{
|
|
53
67
|
username: z.ZodString;
|
|
54
68
|
}, z.core.$loose>>;
|
|
@@ -147,7 +161,7 @@ export declare const AUDIT_METADATA_SCHEMAS: {
|
|
|
147
161
|
old_value: z.ZodUnknown;
|
|
148
162
|
new_value: z.ZodUnknown;
|
|
149
163
|
}, z.core.$loose>;
|
|
150
|
-
}
|
|
164
|
+
}>;
|
|
151
165
|
/** Mapped type of metadata shapes per event type, derived from Zod schemas. */
|
|
152
166
|
export type AuditMetadataMap = {
|
|
153
167
|
[K in AuditEventType]: z.infer<(typeof AUDIT_METADATA_SCHEMAS)[K]>;
|
|
@@ -174,15 +188,72 @@ export declare const get_audit_metadata: <T extends AuditEventType>(event: Audit
|
|
|
174
188
|
event_type: T;
|
|
175
189
|
}) => AuditMetadataMap[T] | null;
|
|
176
190
|
/** Input for creating an audit log entry. */
|
|
177
|
-
export interface AuditLogInput<T extends
|
|
191
|
+
export interface AuditLogInput<T extends string = AuditEventType> {
|
|
178
192
|
event_type: T;
|
|
179
193
|
outcome?: AuditOutcome;
|
|
180
194
|
actor_id?: Uuid | null;
|
|
181
195
|
account_id?: Uuid | null;
|
|
182
196
|
target_account_id?: Uuid | null;
|
|
183
197
|
ip?: string | null;
|
|
184
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Per-event-type metadata. Builtin `T` narrows to `AuditMetadataMap[T]`;
|
|
200
|
+
* consumer strings widen to a generic record (validation runs against
|
|
201
|
+
* `AuditLogConfig.metadata_schemas` at insert time).
|
|
202
|
+
*/
|
|
203
|
+
metadata?: T extends AuditEventType ? (AuditMetadataMap[T] & Record<string, unknown>) | null : Record<string, unknown> | null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Configuration bundle for audit-log event types and metadata schemas.
|
|
207
|
+
*
|
|
208
|
+
* Lets consumers extend the closed `AUDIT_EVENT_TYPES` enum with their own
|
|
209
|
+
* event strings (and metadata Zod schemas) without forking. Pass to
|
|
210
|
+
* `audit_log_fire_and_forget` / `query_audit_log` as the optional `config`
|
|
211
|
+
* argument; both default to `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
212
|
+
*
|
|
213
|
+
* The DB column is `TEXT NOT NULL` and never enforced an enum, so consumer
|
|
214
|
+
* event types round-trip through `query_audit_log_list` and SSE identically
|
|
215
|
+
* to builtins.
|
|
216
|
+
*
|
|
217
|
+
* Constructed configs are deep-frozen (wrapper, `event_types`,
|
|
218
|
+
* `metadata_schemas`) to catch accidental mutation bugs early. Not a
|
|
219
|
+
* security boundary against in-process code, which can subvert audit
|
|
220
|
+
* logging through other paths.
|
|
221
|
+
*/
|
|
222
|
+
export interface AuditLogConfig {
|
|
223
|
+
/** All recognized event-type strings — fuz_app builtins plus consumer extras. */
|
|
224
|
+
readonly event_types: ReadonlyArray<string>;
|
|
225
|
+
/**
|
|
226
|
+
* Per-event-type metadata schemas. Missing entries skip metadata
|
|
227
|
+
* validation for that type (row still written; metadata stored as raw JSONB).
|
|
228
|
+
*/
|
|
229
|
+
readonly metadata_schemas: Readonly<Record<string, z.ZodType>>;
|
|
230
|
+
}
|
|
231
|
+
/** Builtin fuz_app audit-log config — every existing event type and its metadata schema. */
|
|
232
|
+
export declare const BUILTIN_AUDIT_LOG_CONFIG: AuditLogConfig;
|
|
233
|
+
/** Options for `create_audit_log_config`. */
|
|
234
|
+
export interface CreateAuditLogConfigOptions {
|
|
235
|
+
/**
|
|
236
|
+
* Extra event types keyed by event-type string. Value is a Zod metadata
|
|
237
|
+
* schema, or `null` to register the type without validation (row still
|
|
238
|
+
* written, metadata stored as raw JSONB).
|
|
239
|
+
*
|
|
240
|
+
* Collisions with builtin event-type strings throw at construction.
|
|
241
|
+
* Schemas are run via `safeParse` at insert time; mismatches log + count
|
|
242
|
+
* but never throw (fail-open — see the drift counters in `audit_log_queries.ts`).
|
|
243
|
+
*/
|
|
244
|
+
extra_events?: Readonly<Record<string, z.ZodType | null>>;
|
|
185
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Build an `AuditLogConfig` by merging fuz_app builtins with consumer extras.
|
|
248
|
+
*
|
|
249
|
+
* Throws when an `extra_events` key collides with a builtin event type, or
|
|
250
|
+
* fails `AuditEventTypeName` format validation.
|
|
251
|
+
*
|
|
252
|
+
* Call once at startup; pass the result to consumer-emitted
|
|
253
|
+
* `audit_log_fire_and_forget` calls. Builtin handlers omit the argument and
|
|
254
|
+
* pick up `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
255
|
+
*/
|
|
256
|
+
export declare const create_audit_log_config: (options?: CreateAuditLogConfigOptions) => AuditLogConfig;
|
|
186
257
|
/** Options for listing audit log entries. */
|
|
187
258
|
export interface AuditLogListOptions {
|
|
188
259
|
limit?: number;
|
|
@@ -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;AAEtB,OAAO,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AAGhC
|
|
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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"}
|
|
@@ -9,8 +9,13 @@
|
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { Uuid } from '../uuid.js';
|
|
11
11
|
import { AuthSessionJson } from './account_schema.js';
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* All tracked auth event types. Frozen to convert accidental in-process
|
|
14
|
+
* mutation (test cross-contamination, cast escapes) into loud TypeErrors.
|
|
15
|
+
* Not a security boundary — in-process code has many other paths to subvert
|
|
16
|
+
* audit logging.
|
|
17
|
+
*/
|
|
18
|
+
export const AUDIT_EVENT_TYPES = Object.freeze([
|
|
14
19
|
'login',
|
|
15
20
|
'logout',
|
|
16
21
|
'bootstrap',
|
|
@@ -32,19 +37,29 @@ export const AUDIT_EVENT_TYPES = [
|
|
|
32
37
|
'invite_create',
|
|
33
38
|
'invite_delete',
|
|
34
39
|
'app_settings_update',
|
|
35
|
-
];
|
|
40
|
+
]);
|
|
36
41
|
/** Zod schema for audit event types. */
|
|
37
42
|
export const AuditEventType = z.enum(AUDIT_EVENT_TYPES);
|
|
43
|
+
/**
|
|
44
|
+
* Letter start, then letters, digits, `_`, `.`, `/`, `-`. Accepts snake_case,
|
|
45
|
+
* dotted, and namespaced consumer conventions; rejects empty strings, leading
|
|
46
|
+
* separators, whitespace, and control characters.
|
|
47
|
+
*/
|
|
48
|
+
export const AUDIT_EVENT_TYPE_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_./-]*$/;
|
|
49
|
+
/** Zod schema for valid audit event-type name strings. */
|
|
50
|
+
export const AuditEventTypeName = z.string().regex(AUDIT_EVENT_TYPE_NAME_REGEX, {
|
|
51
|
+
message: 'must start with a letter; only letters, digits, _ . / - allowed',
|
|
52
|
+
});
|
|
38
53
|
/** Zod schema for audit event outcomes. */
|
|
39
54
|
export const AuditOutcome = z.enum(['success', 'failure']);
|
|
40
55
|
/**
|
|
41
|
-
* Per-event-type metadata Zod schemas.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
56
|
+
* Per-event-type metadata Zod schemas. `z.looseObject` so consumers can
|
|
57
|
+
* add fields while known ones are validated. The record is frozen to
|
|
58
|
+
* catch mutation bugs at the key level (e.g. tests that try to swap in a
|
|
59
|
+
* stub schema); the Zod schemas themselves are reachable and mutable —
|
|
60
|
+
* freeze isn't a security boundary.
|
|
46
61
|
*/
|
|
47
|
-
export const AUDIT_METADATA_SCHEMAS = {
|
|
62
|
+
export const AUDIT_METADATA_SCHEMAS = Object.freeze({
|
|
48
63
|
login: z.looseObject({ username: z.string() }).nullable(),
|
|
49
64
|
logout: z.null(),
|
|
50
65
|
bootstrap: z.looseObject({ error: z.string() }).nullable(),
|
|
@@ -139,7 +154,7 @@ export const AUDIT_METADATA_SCHEMAS = {
|
|
|
139
154
|
old_value: z.unknown(),
|
|
140
155
|
new_value: z.unknown(),
|
|
141
156
|
}),
|
|
142
|
-
};
|
|
157
|
+
});
|
|
143
158
|
/**
|
|
144
159
|
* Narrow metadata type for a known event type.
|
|
145
160
|
*
|
|
@@ -148,6 +163,48 @@ export const AUDIT_METADATA_SCHEMAS = {
|
|
|
148
163
|
export const get_audit_metadata = (event) => {
|
|
149
164
|
return event.metadata;
|
|
150
165
|
};
|
|
166
|
+
/** Builtin fuz_app audit-log config — every existing event type and its metadata schema. */
|
|
167
|
+
export const BUILTIN_AUDIT_LOG_CONFIG = Object.freeze({
|
|
168
|
+
event_types: AUDIT_EVENT_TYPES,
|
|
169
|
+
metadata_schemas: AUDIT_METADATA_SCHEMAS,
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* Build an `AuditLogConfig` by merging fuz_app builtins with consumer extras.
|
|
173
|
+
*
|
|
174
|
+
* Throws when an `extra_events` key collides with a builtin event type, or
|
|
175
|
+
* fails `AuditEventTypeName` format validation.
|
|
176
|
+
*
|
|
177
|
+
* Call once at startup; pass the result to consumer-emitted
|
|
178
|
+
* `audit_log_fire_and_forget` calls. Builtin handlers omit the argument and
|
|
179
|
+
* pick up `BUILTIN_AUDIT_LOG_CONFIG`.
|
|
180
|
+
*/
|
|
181
|
+
export const create_audit_log_config = (options) => {
|
|
182
|
+
const extras = options?.extra_events;
|
|
183
|
+
if (!extras)
|
|
184
|
+
return BUILTIN_AUDIT_LOG_CONFIG;
|
|
185
|
+
const extra_entries = Object.entries(extras);
|
|
186
|
+
if (extra_entries.length === 0)
|
|
187
|
+
return BUILTIN_AUDIT_LOG_CONFIG;
|
|
188
|
+
const builtin_set = new Set(AUDIT_EVENT_TYPES);
|
|
189
|
+
const extra_keys = [];
|
|
190
|
+
const metadata_schemas = { ...AUDIT_METADATA_SCHEMAS };
|
|
191
|
+
for (const [t, schema] of extra_entries) {
|
|
192
|
+
if (builtin_set.has(t)) {
|
|
193
|
+
throw new Error(`extra_events key "${t}" collides with a builtin event type — pick a distinct string (e.g. "app_${t}")`);
|
|
194
|
+
}
|
|
195
|
+
const name_check = AuditEventTypeName.safeParse(t);
|
|
196
|
+
if (!name_check.success) {
|
|
197
|
+
throw new Error(`extra_events key "${t}" has invalid format: ${name_check.error.issues[0].message}`);
|
|
198
|
+
}
|
|
199
|
+
extra_keys.push(t);
|
|
200
|
+
if (schema !== null)
|
|
201
|
+
metadata_schemas[t] = schema;
|
|
202
|
+
}
|
|
203
|
+
return Object.freeze({
|
|
204
|
+
event_types: Object.freeze([...AUDIT_EVENT_TYPES, ...extra_keys]),
|
|
205
|
+
metadata_schemas: Object.freeze(metadata_schemas),
|
|
206
|
+
});
|
|
207
|
+
};
|
|
151
208
|
/** Zod schema for client-safe audit log event. */
|
|
152
209
|
export const AuditLogEventJson = z.strictObject({
|
|
153
210
|
id: Uuid,
|
|
@@ -35,7 +35,16 @@ export interface RoleOptions {
|
|
|
35
35
|
/** If true, admins can grant this role via the web UI. Default `true`. */
|
|
36
36
|
web_grantable?: boolean;
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Builtin role configs. Not overridable by consumers.
|
|
40
|
+
*
|
|
41
|
+
* Typed `ReadonlyMap` for the contract — but JS Maps don't honor
|
|
42
|
+
* `Object.freeze` for `.set` / `.delete` / `.clear` (they mutate internal
|
|
43
|
+
* slots, not own properties), so freeze adds no runtime guard here. Read
|
|
44
|
+
* once at startup by `create_role_schema` and the admin / permit-offer
|
|
45
|
+
* action factories; runtime mutation has no effect on already-built role
|
|
46
|
+
* schemas.
|
|
47
|
+
*/
|
|
39
48
|
export declare const BUILTIN_ROLE_OPTIONS: ReadonlyMap<string, Required<RoleOptions>>;
|
|
40
49
|
/** The result of `create_role_schema` — a Zod schema and config map for all roles. */
|
|
41
50
|
export interface RoleSchemaResult {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,0FAA0F;AAC1F,eAAO,MAAM,QAAQ,aAKnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAIhD,sFAAsF;AACtF,eAAO,MAAM,WAAW,WAAW,CAAC;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,UAAU,UAAU,CAAC;AAElC,+CAA+C;AAC/C,eAAO,MAAM,aAAa,8BAAqC,CAAC;AAEhE,yCAAyC;AACzC,eAAO,MAAM,WAAW;;;EAAwB,CAAC;AACjD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAItD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,iGAAiG;IACjG,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,0EAA0E;IAC1E,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED
|
|
1
|
+
{"version":3,"file":"role_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,0FAA0F;AAC1F,eAAO,MAAM,QAAQ,aAKnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAIhD,sFAAsF;AACtF,eAAO,MAAM,WAAW,WAAW,CAAC;AAEpC,+EAA+E;AAC/E,eAAO,MAAM,UAAU,UAAU,CAAC;AAElC,+CAA+C;AAC/C,eAAO,MAAM,aAAa,8BAAqC,CAAC;AAEhE,yCAAyC;AACzC,eAAO,MAAM,WAAW;;;EAAwB,CAAC;AACjD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAItD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,iGAAiG;IACjG,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,0EAA0E;IAC1E,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAG1E,CAAC;AAEH,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAChC,sGAAsG;IACtG,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,2EAA2E;IAC3E,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;CACzD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,EAClD,WAAW,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,KAC/B,gBAwBF,CAAC"}
|
package/dist/auth/role_schema.js
CHANGED
|
@@ -21,7 +21,16 @@ export const ROLE_ADMIN = 'admin';
|
|
|
21
21
|
export const BUILTIN_ROLES = [ROLE_KEEPER, ROLE_ADMIN];
|
|
22
22
|
/** Zod schema for builtin roles only. */
|
|
23
23
|
export const BuiltinRole = z.enum(BUILTIN_ROLES);
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Builtin role configs. Not overridable by consumers.
|
|
26
|
+
*
|
|
27
|
+
* Typed `ReadonlyMap` for the contract — but JS Maps don't honor
|
|
28
|
+
* `Object.freeze` for `.set` / `.delete` / `.clear` (they mutate internal
|
|
29
|
+
* slots, not own properties), so freeze adds no runtime guard here. Read
|
|
30
|
+
* once at startup by `create_role_schema` and the admin / permit-offer
|
|
31
|
+
* action factories; runtime mutation has no effect on already-built role
|
|
32
|
+
* schemas.
|
|
33
|
+
*/
|
|
25
34
|
export const BUILTIN_ROLE_OPTIONS = new Map([
|
|
26
35
|
[ROLE_KEEPER, { requires_daemon_token: true, web_grantable: false }],
|
|
27
36
|
[ROLE_ADMIN, { requires_daemon_token: false, web_grantable: true }],
|