@fuzdev/fuz_app 0.38.1 → 0.40.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.
Files changed (70) hide show
  1. package/dist/auth/CLAUDE.md +124 -36
  2. package/dist/auth/account_actions.d.ts +5 -3
  3. package/dist/auth/account_actions.d.ts.map +1 -1
  4. package/dist/auth/account_actions.js +5 -6
  5. package/dist/auth/account_routes.d.ts.map +1 -1
  6. package/dist/auth/account_routes.js +7 -7
  7. package/dist/auth/admin_action_specs.d.ts +6 -138
  8. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  9. package/dist/auth/admin_action_specs.js +4 -2
  10. package/dist/auth/admin_actions.d.ts +4 -3
  11. package/dist/auth/admin_actions.d.ts.map +1 -1
  12. package/dist/auth/admin_actions.js +8 -9
  13. package/dist/auth/audit_log_queries.d.ts +32 -20
  14. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  15. package/dist/auth/audit_log_queries.js +52 -40
  16. package/dist/auth/audit_log_schema.d.ts +105 -84
  17. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  18. package/dist/auth/audit_log_schema.js +84 -12
  19. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  20. package/dist/auth/bootstrap_routes.js +3 -3
  21. package/dist/auth/cleanup.d.ts +9 -1
  22. package/dist/auth/cleanup.d.ts.map +1 -1
  23. package/dist/auth/cleanup.js +2 -2
  24. package/dist/auth/deps.d.ts +13 -1
  25. package/dist/auth/deps.d.ts.map +1 -1
  26. package/dist/auth/permit_offer_actions.d.ts +16 -2
  27. package/dist/auth/permit_offer_actions.d.ts.map +1 -1
  28. package/dist/auth/permit_offer_actions.js +26 -8
  29. package/dist/auth/role_schema.d.ts +10 -1
  30. package/dist/auth/role_schema.d.ts.map +1 -1
  31. package/dist/auth/role_schema.js +10 -1
  32. package/dist/auth/self_service_role_actions.d.ts +136 -0
  33. package/dist/auth/self_service_role_actions.d.ts.map +1 -0
  34. package/dist/auth/self_service_role_actions.js +198 -0
  35. package/dist/auth/signup_routes.d.ts.map +1 -1
  36. package/dist/auth/signup_routes.js +2 -2
  37. package/dist/auth/standard_rpc_actions.d.ts +1 -1
  38. package/dist/auth/standard_rpc_actions.js +1 -1
  39. package/dist/http/jsonrpc_errors.d.ts +27 -75
  40. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  41. package/dist/http/jsonrpc_errors.js +16 -9
  42. package/dist/server/app_backend.d.ts +26 -7
  43. package/dist/server/app_backend.d.ts.map +1 -1
  44. package/dist/server/app_backend.js +29 -7
  45. package/dist/server/app_server.d.ts +6 -7
  46. package/dist/server/app_server.d.ts.map +1 -1
  47. package/dist/server/app_server.js +16 -29
  48. package/dist/ui/AdminAccounts.svelte +19 -0
  49. package/dist/ui/AdminAccounts.svelte.d.ts +2 -17
  50. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  51. package/dist/ui/AdminPermitHistory.svelte +23 -2
  52. package/dist/ui/AdminPermitHistory.svelte.d.ts +2 -17
  53. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
  54. package/dist/ui/CLAUDE.md +11 -0
  55. package/dist/ui/PermitOfferHistory.svelte +11 -5
  56. package/dist/ui/PermitOfferHistory.svelte.d.ts +7 -1
  57. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -1
  58. package/dist/ui/PermitOfferInbox.svelte +12 -7
  59. package/dist/ui/PermitOfferInbox.svelte.d.ts +8 -3
  60. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -1
  61. package/dist/ui/admin_rpc_adapters.d.ts +16 -1
  62. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  63. package/dist/ui/admin_rpc_adapters.js +12 -1
  64. package/dist/ui/format_scope.d.ts +45 -0
  65. package/dist/ui/format_scope.d.ts.map +1 -0
  66. package/dist/ui/format_scope.js +34 -0
  67. package/dist/ui/ui_format.d.ts +2 -3
  68. package/dist/ui/ui_format.d.ts.map +1 -1
  69. package/dist/ui/ui_format.js +1 -1
  70. package/package.json +1 -1
@@ -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
- /** All tracked auth event types. */
13
- export const AUDIT_EVENT_TYPES = [
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
- * Uses `z.looseObject` so consumers can add extra fields
44
- * (e.g. visiones `self_service`) while known fields are validated.
45
- * Events with outcome-dependent metadata use a union with `z.null()`.
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(),
@@ -74,17 +89,23 @@ export const AUDIT_METADATA_SCHEMAS = {
74
89
  }),
75
90
  // `permit_id` is optional on `permit_grant` because failed grants
76
91
  // (e.g. `web_grantable` denied) never produce a permit row.
92
+ // `self_service: true` is set by the self-service role toggle in
93
+ // `self_service_role_actions.ts` — declared explicitly rather than
94
+ // riding on `z.looseObject` permissiveness so the field is part of
95
+ // the documented schema surface.
77
96
  permit_grant: z.looseObject({
78
97
  role: z.string(),
79
98
  permit_id: Uuid.optional(),
80
99
  scope_id: Uuid.nullish(),
81
100
  source_offer_id: Uuid.optional(),
101
+ self_service: z.boolean().optional(),
82
102
  }),
83
103
  permit_revoke: z.looseObject({
84
104
  role: z.string(),
85
105
  permit_id: Uuid,
86
106
  scope_id: Uuid.nullish(),
87
107
  reason: z.string().optional(),
108
+ self_service: z.boolean().optional(),
88
109
  }),
89
110
  // `offer_id` is optional because failed creates (e.g. `web_grantable`
90
111
  // denied, `authorize` callback denied) never produce an offer row.
@@ -139,7 +160,7 @@ export const AUDIT_METADATA_SCHEMAS = {
139
160
  old_value: z.unknown(),
140
161
  new_value: z.unknown(),
141
162
  }),
142
- };
163
+ });
143
164
  /**
144
165
  * Narrow metadata type for a known event type.
145
166
  *
@@ -148,11 +169,62 @@ export const AUDIT_METADATA_SCHEMAS = {
148
169
  export const get_audit_metadata = (event) => {
149
170
  return event.metadata;
150
171
  };
151
- /** Zod schema for client-safe audit log event. */
172
+ /** Builtin fuz_app audit-log config every existing event type and its metadata schema. */
173
+ export const BUILTIN_AUDIT_LOG_CONFIG = Object.freeze({
174
+ event_types: AUDIT_EVENT_TYPES,
175
+ metadata_schemas: AUDIT_METADATA_SCHEMAS,
176
+ });
177
+ /**
178
+ * Build an `AuditLogConfig` by merging fuz_app builtins with consumer extras.
179
+ *
180
+ * Throws when an `extra_events` key collides with a builtin event type, or
181
+ * fails `AuditEventTypeName` format validation.
182
+ *
183
+ * Call once at startup; pass the result to consumer-emitted
184
+ * `audit_log_fire_and_forget` calls. Builtin handlers omit the argument and
185
+ * pick up `BUILTIN_AUDIT_LOG_CONFIG`.
186
+ */
187
+ export const create_audit_log_config = (options) => {
188
+ const extras = options?.extra_events;
189
+ if (!extras)
190
+ return BUILTIN_AUDIT_LOG_CONFIG;
191
+ const extra_entries = Object.entries(extras);
192
+ if (extra_entries.length === 0)
193
+ return BUILTIN_AUDIT_LOG_CONFIG;
194
+ const builtin_set = new Set(AUDIT_EVENT_TYPES);
195
+ const extra_keys = [];
196
+ const metadata_schemas = { ...AUDIT_METADATA_SCHEMAS };
197
+ for (const [t, schema] of extra_entries) {
198
+ if (builtin_set.has(t)) {
199
+ throw new Error(`extra_events key "${t}" collides with a builtin event type — pick a distinct string (e.g. "app_${t}")`);
200
+ }
201
+ const name_check = AuditEventTypeName.safeParse(t);
202
+ if (!name_check.success) {
203
+ throw new Error(`extra_events key "${t}" has invalid format: ${name_check.error.issues[0].message}`);
204
+ }
205
+ extra_keys.push(t);
206
+ if (schema !== null)
207
+ metadata_schemas[t] = schema;
208
+ }
209
+ return Object.freeze({
210
+ event_types: Object.freeze([...AUDIT_EVENT_TYPES, ...extra_keys]),
211
+ metadata_schemas: Object.freeze(metadata_schemas),
212
+ });
213
+ };
214
+ /**
215
+ * Zod schema for client-safe audit log event.
216
+ *
217
+ * `event_type` is `AuditEventTypeName` (regex-validated string) — matches
218
+ * the `AuditLogEvent` row and the DB's `TEXT NOT NULL` column. Consumer
219
+ * types registered via `create_audit_log_config({extra_events})` round-trip
220
+ * through queries, `on_audit_event` callbacks, and JSON-RPC responses
221
+ * identically to builtins. `AuditLogInput<T>` stays parameterized on the
222
+ * write side so `AuditMetadataMap` narrowing via `get_audit_metadata` works.
223
+ */
152
224
  export const AuditLogEventJson = z.strictObject({
153
225
  id: Uuid,
154
226
  seq: z.number().int(),
155
- event_type: AuditEventType,
227
+ event_type: AuditEventTypeName,
156
228
  outcome: AuditOutcome,
157
229
  actor_id: Uuid.nullable(),
158
230
  account_id: Uuid.nullable(),
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAGvF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAanD,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;kBAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,4EAA4E;IAC5E,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,EAAE,EAAE,EAAE,CAAC;IACP,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,KAClC,OAAO,CAAC,eAAe,CAwBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAyHjB,CAAC"}
1
+ {"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAGvF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAanD,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;kBAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,4EAA4E;IAC5E,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,EAAE,EAAE,EAAE,CAAC;IACP,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,KAClC,OAAO,CAAC,eAAe,CAwBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAuHjB,CAAC"}
@@ -67,7 +67,7 @@ export const check_bootstrap_status = async (deps, options) => {
67
67
  * @returns route specs (not yet applied to Hono)
68
68
  */
69
69
  export const create_bootstrap_route_specs = (deps, options) => {
70
- const { keyring, on_audit_event } = deps;
70
+ const { keyring } = deps;
71
71
  const { session_options, bootstrap_status, on_bootstrap, ip_rate_limiter } = options;
72
72
  const { token_path } = bootstrap_status;
73
73
  return [
@@ -123,7 +123,7 @@ export const create_bootstrap_route_specs = (deps, options) => {
123
123
  outcome: 'failure',
124
124
  ip: get_client_ip(c),
125
125
  metadata: { error: result.error },
126
- }, deps.log, on_audit_event);
126
+ }, deps);
127
127
  return c.json({ error: result.error }, result.status);
128
128
  }
129
129
  // Successful bootstrap — update state immediately
@@ -150,7 +150,7 @@ export const create_bootstrap_route_specs = (deps, options) => {
150
150
  actor_id: result.actor.id,
151
151
  account_id: result.account.id,
152
152
  ip: get_client_ip(c),
153
- }, deps.log, on_audit_event);
153
+ }, deps);
154
154
  // CRITICAL: If token file deletion failed, throw to force operator attention.
155
155
  // All success work (session, on_bootstrap, audit) has completed above.
156
156
  // The error response alerts the operator to delete the token file manually.
@@ -20,7 +20,7 @@
20
20
  */
21
21
  import type { Logger } from '@fuzdev/fuz_util/log.js';
22
22
  import type { QueryDeps } from '../db/query_deps.js';
23
- import type { AuditLogEvent } from './audit_log_schema.js';
23
+ import type { AuditLogConfig, AuditLogEvent } from './audit_log_schema.js';
24
24
  /** Dependencies for the cleanup helpers. */
25
25
  export interface AuthCleanupDeps extends QueryDeps {
26
26
  log: Logger;
@@ -30,6 +30,14 @@ export interface AuthCleanupDeps extends QueryDeps {
30
30
  * to skip broadcast — the audit rows still land in the DB.
31
31
  */
32
32
  on_audit_event?: ((event: AuditLogEvent) => void) | null;
33
+ /**
34
+ * Audit-log config. Only the builtin `permit_offer_expire` event type is
35
+ * emitted here, so omitting this is safe — the field exists so consumers
36
+ * threading the same `AppDeps` bundle to scheduled cleanup keep using
37
+ * their registered config (and consumer extensions to the
38
+ * `permit_offer_expire` metadata schema get validated).
39
+ */
40
+ audit_log_config?: AuditLogConfig;
33
41
  }
34
42
  /** Result of `run_auth_cleanup`. */
35
43
  export interface AuthCleanupResult {
@@ -1 +1 @@
1
- {"version":3,"file":"cleanup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAInD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEzD,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;CACzD;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,CA6BzF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,iBAAiB,CAIvF,CAAC"}
1
+ {"version":3,"file":"cleanup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;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;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,CAiCzF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,iBAAiB,CAIvF,CAAC"}
@@ -33,7 +33,7 @@ import { query_audit_log } from './audit_log_queries.js';
33
33
  */
34
34
  export const cleanup_expired_permit_offers = async (deps) => {
35
35
  const expired = await query_permit_offer_sweep_expired(deps);
36
- const { on_audit_event } = deps;
36
+ const { on_audit_event, audit_log_config } = deps;
37
37
  for (const offer of expired) {
38
38
  try {
39
39
  const event = await query_audit_log(deps, {
@@ -46,7 +46,7 @@ export const cleanup_expired_permit_offers = async (deps) => {
46
46
  role: offer.role,
47
47
  scope_id: offer.scope_id,
48
48
  },
49
- });
49
+ }, audit_log_config);
50
50
  if (on_audit_event) {
51
51
  try {
52
52
  on_audit_event(event);
@@ -12,7 +12,7 @@ import type { Keyring } from './keyring.js';
12
12
  import type { PasswordHashDeps } from './password.js';
13
13
  import type { Db } from '../db/db.js';
14
14
  import type { StatResult } from '../runtime/deps.js';
15
- import type { AuditLogEvent } from './audit_log_schema.js';
15
+ import type { AuditLogConfig, AuditLogEvent } from './audit_log_schema.js';
16
16
  /**
17
17
  * Stateless capabilities bundle for fuz_app backends.
18
18
  *
@@ -41,6 +41,18 @@ export interface AppDeps {
41
41
  * Defaults to a noop when not wired to SSE.
42
42
  */
43
43
  on_audit_event: (event: AuditLogEvent) => void;
44
+ /**
45
+ * Audit-log config for `audit_log_fire_and_forget` and `query_audit_log`.
46
+ * Built once at startup via `create_audit_log_config({extra_events})` to
47
+ * register consumer event types. Optional — defaults to
48
+ * `BUILTIN_AUDIT_LOG_CONFIG` when absent.
49
+ *
50
+ * Threaded through `AppDeps` (instead of a per-call positional arg) so
51
+ * consumer handlers cannot silently fall back to the builtin config by
52
+ * forgetting to pass theirs — the deps bundle carries it everywhere
53
+ * fuz_app emits an audit event.
54
+ */
55
+ audit_log_config?: AuditLogConfig;
44
56
  }
45
57
  /**
46
58
  * Capabilities for route spec factories.
@@ -1 +1 @@
1
- {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACvB,+DAA+D;IAC/D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,2BAA2B;IAC3B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yBAAyB;IACzB,EAAE,EAAE,EAAE,CAAC;IACP,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACvB,+DAA+D;IAC/D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,2BAA2B;IAC3B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yBAAyB;IACzB,EAAE,EAAE,EAAE,CAAC;IACP,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC"}
@@ -72,6 +72,20 @@ export interface PermitOfferActionOptions {
72
72
  */
73
73
  authorize?: PermitOfferCreateAuthorize;
74
74
  }
75
+ /**
76
+ * Authorization callback that admits any admin and otherwise falls back to
77
+ * the symmetric default (caller must hold the offered role globally).
78
+ *
79
+ * The `web_grantable` filter in `create_handler` runs **before** the
80
+ * `authorize` callback, so this never sees non-web-grantable roles. Drop
81
+ * into `create_permit_offer_actions({authorize: authorize_admin_or_holder})`
82
+ * (or any factory that forwards `authorize`, e.g. `create_standard_rpc_actions`)
83
+ * for the common "admins offer anything; users offer what they hold"
84
+ * pattern. Scope-aware policies (e.g. classroom_teacher offering
85
+ * classroom_student in their own scope) wrap this and short-circuit `true`
86
+ * before delegating.
87
+ */
88
+ export declare const authorize_admin_or_holder: PermitOfferCreateAuthorize;
75
89
  /**
76
90
  * Dependencies for `create_permit_offer_actions`.
77
91
  *
@@ -80,7 +94,7 @@ export interface PermitOfferActionOptions {
80
94
  * directly (the transport's `send_to_account` signature accepts the broader
81
95
  * `JsonrpcMessageFromServerToClient`, which is contravariantly compatible).
82
96
  */
83
- export interface PermitOfferActionDeps extends Pick<RouteFactoryDeps, 'log' | 'on_audit_event'> {
97
+ export interface PermitOfferActionDeps extends Pick<RouteFactoryDeps, 'log' | 'on_audit_event' | 'audit_log_config'> {
84
98
  /** Optional WS fan-out primitive. `null` or absent → notifications skipped. */
85
99
  notification_sender?: NotificationSender | null;
86
100
  }
@@ -88,7 +102,7 @@ export interface PermitOfferActionDeps extends Pick<RouteFactoryDeps, 'log' | 'o
88
102
  * Create the seven permit-offer RPC actions (six offer-lifecycle methods
89
103
  * plus `permit_revoke`).
90
104
  *
91
- * @param deps - stateless capabilities; needs `log` and `on_audit_event`; optional `notification_sender` for WS fan-out
105
+ * @param deps - `PermitOfferActionDeps` `log`, `on_audit_event`, optional `audit_log_config` (slice of `AppDeps`); optional `notification_sender` for WS fan-out
92
106
  * @param options - role schema, default TTL, authorization override
93
107
  * @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
94
108
  */
@@ -1 +1 @@
1
- {"version":3,"file":"permit_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAa,KAAK,aAAa,EAAE,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxF,OAAO,EAAmC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAsBzF,OAAO,EAAW,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,iCAAiC,CAAC;AAmCzC;;;;;;;;GAQG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACxC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACvC;AAqCD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC;IAC9F,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,qBAAqB,EAC3B,UAAS,wBAA6B,KACpC,KAAK,CAAC,SAAS,CA8djB,CAAC"}
1
+ {"version":3,"file":"permit_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAa,KAAK,aAAa,EAAE,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxF,OAAO,EAAmC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAsBzF,OAAO,EAAW,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,iCAAiC,CAAC;AAmCzC;;;;;;;;GAQG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACxC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACvC;AAyBD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,0BAQvC,CAAC;AAcF;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAClD,gBAAgB,EAChB,KAAK,GAAG,gBAAgB,GAAG,kBAAkB,CAC7C;IACA,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,qBAAqB,EAC3B,UAAS,wBAA6B,KACpC,KAAK,CAAC,SAAS,CAudjB,CAAC"}
@@ -66,6 +66,24 @@ const default_authorize = async (auth, input, _deps, ctx) => {
66
66
  // check — the scope-aware "only in this classroom" policy is consumer-level.
67
67
  return query_permit_has_role(ctx, auth.actor.id, input.role);
68
68
  };
69
+ /**
70
+ * Authorization callback that admits any admin and otherwise falls back to
71
+ * the symmetric default (caller must hold the offered role globally).
72
+ *
73
+ * The `web_grantable` filter in `create_handler` runs **before** the
74
+ * `authorize` callback, so this never sees non-web-grantable roles. Drop
75
+ * into `create_permit_offer_actions({authorize: authorize_admin_or_holder})`
76
+ * (or any factory that forwards `authorize`, e.g. `create_standard_rpc_actions`)
77
+ * for the common "admins offer anything; users offer what they hold"
78
+ * pattern. Scope-aware policies (e.g. classroom_teacher offering
79
+ * classroom_student in their own scope) wrap this and short-circuit `true`
80
+ * before delegating.
81
+ */
82
+ export const authorize_admin_or_holder = async (auth, input, _deps, ctx) => {
83
+ if (has_role(auth, ROLE_ADMIN))
84
+ return true;
85
+ return query_permit_has_role(ctx, auth.actor.id, input.role);
86
+ };
69
87
  /**
70
88
  * Narrow `ctx.auth` to non-null. The RPC dispatcher has already enforced
71
89
  * `auth: 'authenticated'` before the handler runs — this is a type narrow,
@@ -80,7 +98,7 @@ const require_request_auth = (auth) => {
80
98
  * Create the seven permit-offer RPC actions (six offer-lifecycle methods
81
99
  * plus `permit_revoke`).
82
100
  *
83
- * @param deps - stateless capabilities; needs `log` and `on_audit_event`; optional `notification_sender` for WS fan-out
101
+ * @param deps - `PermitOfferActionDeps` `log`, `on_audit_event`, optional `audit_log_config` (slice of `AppDeps`); optional `notification_sender` for WS fan-out
84
102
  * @param options - role schema, default TTL, authorization override
85
103
  * @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
86
104
  */
@@ -104,7 +122,7 @@ export const create_permit_offer_actions = (deps, options = {}) => {
104
122
  scope_id: input.scope_id ?? null,
105
123
  to_account_id: input.to_account_id,
106
124
  },
107
- }, log, on_audit_event);
125
+ }, deps);
108
126
  };
109
127
  // Returns {offer} only — no auto-accept. Recipient must call
110
128
  // permit_offer_accept; admin tests materialize permits via
@@ -162,7 +180,7 @@ export const create_permit_offer_actions = (deps, options = {}) => {
162
180
  scope_id: offer.scope_id,
163
181
  to_account_id: offer.to_account_id,
164
182
  },
165
- }, log, on_audit_event);
183
+ }, deps);
166
184
  const offer_json = to_permit_offer_json(offer);
167
185
  if (notification_sender) {
168
186
  emit_after_commit(ctx, () => {
@@ -258,7 +276,7 @@ export const create_permit_offer_actions = (deps, options = {}) => {
258
276
  scope_id: declined.scope_id,
259
277
  reason: input.reason ?? undefined,
260
278
  },
261
- }, log, on_audit_event);
279
+ }, deps);
262
280
  if (notification_sender) {
263
281
  // Look up the grantor's account (SELECT by PK, same tx) for the
264
282
  // notification target. The decline reason rides along on
@@ -299,7 +317,7 @@ export const create_permit_offer_actions = (deps, options = {}) => {
299
317
  role: retracted.role,
300
318
  scope_id: retracted.scope_id,
301
319
  },
302
- }, log, on_audit_event);
320
+ }, deps);
303
321
  if (notification_sender) {
304
322
  const offer_json = to_permit_offer_json(retracted);
305
323
  emit_after_commit(ctx, () => {
@@ -355,7 +373,7 @@ export const create_permit_offer_actions = (deps, options = {}) => {
355
373
  target_account_id,
356
374
  ip: ctx.client_ip,
357
375
  metadata: { role: permit_row.role, permit_id: input.permit_id },
358
- }, log, on_audit_event);
376
+ }, deps);
359
377
  throw jsonrpc_errors.forbidden('role not web-grantable', {
360
378
  reason: ERROR_ROLE_NOT_WEB_GRANTABLE,
361
379
  });
@@ -378,7 +396,7 @@ export const create_permit_offer_actions = (deps, options = {}) => {
378
396
  scope_id: result.scope_id,
379
397
  reason: input.reason ?? undefined,
380
398
  },
381
- }, log, on_audit_event);
399
+ }, deps);
382
400
  for (const offer of result.superseded_offers) {
383
401
  void audit_log_fire_and_forget(ctx, {
384
402
  event_type: 'permit_offer_supersede',
@@ -392,7 +410,7 @@ export const create_permit_offer_actions = (deps, options = {}) => {
392
410
  reason: 'permit_revoked',
393
411
  cause_id: result.id,
394
412
  },
395
- }, log, on_audit_event);
413
+ }, deps);
396
414
  }
397
415
  if (notification_sender) {
398
416
  const superseded = result.superseded_offers.map((o) => ({
@@ -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
- /** Builtin role configs. Not overridable by consumers. */
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,0DAA0D;AAC1D,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"}
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"}
@@ -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
- /** Builtin role configs. Not overridable by consumers. */
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 }],
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Self-service role grant/revoke RPC actions.
3
+ *
4
+ * Two static `request_response` actions — `self_service_role_grant` and
5
+ * `self_service_role_revoke` — that take `{role}` as input and toggle a
6
+ * permit on the caller for an allowlisted role. Idempotent in both
7
+ * directions: re-granting an already-held role returns `granted: false`;
8
+ * revoking a role the caller doesn't hold returns `revoked: false`.
9
+ *
10
+ * The factory takes an `eligible_roles` allowlist (validated against the
11
+ * supplied `roles.role_options` at factory time so typos surface at startup
12
+ * instead of at first call). Roles outside the allowlist are rejected
13
+ * with `forbidden` + reason `role_not_self_service_eligible`.
14
+ *
15
+ * Audit metadata carries `self_service: true` so admin reviewers can
16
+ * distinguish self-toggled permits from admin grants/offers. The
17
+ * `permit_grant` / `permit_revoke` metadata schemas declare
18
+ * `self_service: z.boolean().optional()` explicitly, so the field is
19
+ * part of the documented schema surface and is round-trip-validated by
20
+ * `query_audit_log`.
21
+ *
22
+ * Static method names — `role` lives in the input, not the method name —
23
+ * so specs are codegen-compatible (`satisfies RequestResponseActionSpec`)
24
+ * and the surface stays constant as consumers add eligible roles. Mirrors
25
+ * the existing `permit_offer_create({role})` precedent rather than
26
+ * generating per-role methods.
27
+ *
28
+ * @module
29
+ */
30
+ import { z } from 'zod';
31
+ import { type RpcAction } from '../actions/action_rpc.js';
32
+ import type { RequestResponseActionSpec } from '../actions/action_spec.js';
33
+ import { type RoleSchemaResult } from './role_schema.js';
34
+ import type { RouteFactoryDeps } from './deps.js';
35
+ /** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
36
+ export declare const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE: "role_not_self_service_eligible";
37
+ /** Input for `self_service_role_grant`. */
38
+ export declare const SelfServiceRoleGrantInput: z.ZodObject<{
39
+ role: z.ZodString;
40
+ }, z.core.$strict>;
41
+ export type SelfServiceRoleGrantInput = z.infer<typeof SelfServiceRoleGrantInput>;
42
+ /**
43
+ * Output for `self_service_role_grant`. `granted` is `false` on idempotent
44
+ * re-grant (caller already held the role globally); `permit_id` is set on
45
+ * new grants only.
46
+ */
47
+ export declare const SelfServiceRoleGrantOutput: z.ZodObject<{
48
+ ok: z.ZodLiteral<true>;
49
+ granted: z.ZodBoolean;
50
+ permit_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
51
+ }, z.core.$strict>;
52
+ export type SelfServiceRoleGrantOutput = z.infer<typeof SelfServiceRoleGrantOutput>;
53
+ /** Input for `self_service_role_revoke`. */
54
+ export declare const SelfServiceRoleRevokeInput: z.ZodObject<{
55
+ role: z.ZodString;
56
+ }, z.core.$strict>;
57
+ export type SelfServiceRoleRevokeInput = z.infer<typeof SelfServiceRoleRevokeInput>;
58
+ /**
59
+ * Output for `self_service_role_revoke`. `revoked` is `false` when the
60
+ * caller held no active global permit for the role (idempotent).
61
+ */
62
+ export declare const SelfServiceRoleRevokeOutput: z.ZodObject<{
63
+ ok: z.ZodLiteral<true>;
64
+ revoked: z.ZodBoolean;
65
+ }, z.core.$strict>;
66
+ export type SelfServiceRoleRevokeOutput = z.infer<typeof SelfServiceRoleRevokeOutput>;
67
+ export declare const self_service_role_grant_action_spec: {
68
+ method: string;
69
+ kind: "request_response";
70
+ initiator: "frontend";
71
+ auth: "authenticated";
72
+ side_effects: true;
73
+ input: z.ZodObject<{
74
+ role: z.ZodString;
75
+ }, z.core.$strict>;
76
+ output: z.ZodObject<{
77
+ ok: z.ZodLiteral<true>;
78
+ granted: z.ZodBoolean;
79
+ permit_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
80
+ }, z.core.$strict>;
81
+ async: true;
82
+ description: string;
83
+ };
84
+ export declare const self_service_role_revoke_action_spec: {
85
+ method: string;
86
+ kind: "request_response";
87
+ initiator: "frontend";
88
+ auth: "authenticated";
89
+ side_effects: true;
90
+ input: z.ZodObject<{
91
+ role: z.ZodString;
92
+ }, z.core.$strict>;
93
+ output: z.ZodObject<{
94
+ ok: z.ZodLiteral<true>;
95
+ revoked: z.ZodBoolean;
96
+ }, z.core.$strict>;
97
+ async: true;
98
+ description: string;
99
+ };
100
+ /**
101
+ * All self-service role action specs — a codegen-ready registry. Method
102
+ * names are static, so consumer typed-client codegen picks them up the
103
+ * same way it picks up `account_*_action_specs`.
104
+ */
105
+ export declare const all_self_service_role_action_specs: Array<RequestResponseActionSpec>;
106
+ /** Options for `create_self_service_role_actions`. */
107
+ export interface SelfServiceRoleActionsOptions {
108
+ /**
109
+ * Allowlist of role strings eligible for self-service. Empty array
110
+ * effectively disables the surface — every call comes back as
111
+ * `forbidden` with reason `role_not_self_service_eligible`.
112
+ */
113
+ eligible_roles: ReadonlyArray<string>;
114
+ /**
115
+ * Optional role schema. When supplied, `eligible_roles` entries are
116
+ * checked against `roles.role_options` at factory time so typos throw
117
+ * at startup instead of at first call.
118
+ */
119
+ roles?: RoleSchemaResult;
120
+ }
121
+ /**
122
+ * Dependencies for `create_self_service_role_actions`. Same shape as the
123
+ * peer factories so consumers thread one deps object through all three.
124
+ * `audit_log_config` flows from `AppDeps` and is consumed by
125
+ * `audit_log_fire_and_forget`.
126
+ */
127
+ export type SelfServiceRoleActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event' | 'audit_log_config'>;
128
+ /**
129
+ * Build the self-service role grant/revoke RPC actions.
130
+ *
131
+ * @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
132
+ * @param options - eligible-role allowlist plus optional role schema for typo-checking
133
+ * @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
134
+ */
135
+ export declare const create_self_service_role_actions: (deps: SelfServiceRoleActionDeps, options: SelfServiceRoleActionsOptions) => Array<RpcAction>;
136
+ //# sourceMappingURL=self_service_role_actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self_service_role_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExF,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAEzE,OAAO,EAAW,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAUhD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,EAAG,gCAAyC,CAAC;AAI9F,2CAA2C;AAC3C,eAAO,MAAM,yBAAyB;;kBAEpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;kBAIrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B;;kBAErC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;kBAGtC,CAAC;AACH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAItF,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;CAWX,CAAC;AAEtC,eAAO,MAAM,oCAAoC;;;;;;;;;;;;;;;CAWZ,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,EAAE,KAAK,CAAC,yBAAyB,CAG/E,CAAC;AAIF,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC7C;;;;OAIG;IACH,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC3C,gBAAgB,EAChB,KAAK,GAAG,gBAAgB,GAAG,kBAAkB,CAC7C,CAAC;AAOF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,yBAAyB,EAC/B,SAAS,6BAA6B,KACpC,KAAK,CAAC,SAAS,CAqHjB,CAAC"}