@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.
- package/dist/auth/CLAUDE.md +124 -36
- package/dist/auth/account_actions.d.ts +5 -3
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +5 -6
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +7 -7
- package/dist/auth/admin_action_specs.d.ts +6 -138
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +4 -2
- package/dist/auth/admin_actions.d.ts +4 -3
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +8 -9
- package/dist/auth/audit_log_queries.d.ts +32 -20
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +52 -40
- package/dist/auth/audit_log_schema.d.ts +105 -84
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +84 -12
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +3 -3
- package/dist/auth/cleanup.d.ts +9 -1
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +2 -2
- package/dist/auth/deps.d.ts +13 -1
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/permit_offer_actions.d.ts +16 -2
- package/dist/auth/permit_offer_actions.d.ts.map +1 -1
- package/dist/auth/permit_offer_actions.js +26 -8
- 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/auth/self_service_role_actions.d.ts +136 -0
- package/dist/auth/self_service_role_actions.d.ts.map +1 -0
- package/dist/auth/self_service_role_actions.js +198 -0
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +2 -2
- package/dist/auth/standard_rpc_actions.d.ts +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -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 +26 -7
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +29 -7
- 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/dist/ui/ui_format.d.ts +2 -3
- package/dist/ui/ui_format.d.ts.map +1 -1
- package/dist/ui/ui_format.js +1 -1
- 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
|
-
/**
|
|
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(),
|
|
@@ -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
|
-
/**
|
|
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:
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/dist/auth/cleanup.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/auth/cleanup.js
CHANGED
|
@@ -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);
|
package/dist/auth/deps.d.ts
CHANGED
|
@@ -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.
|
package/dist/auth/deps.d.ts.map
CHANGED
|
@@ -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;
|
|
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 -
|
|
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;
|
|
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 -
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
/**
|
|
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 }],
|
|
@@ -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"}
|