@fuzdev/fuz_app 0.39.0 → 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 +89 -17
- 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 +16 -6
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +7 -8
- package/dist/auth/audit_log_schema.d.ts +24 -74
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +17 -2
- 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/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/server/app_backend.d.ts +9 -1
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +12 -1
- 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
|
@@ -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) => ({
|
|
@@ -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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
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 { rpc_action } from '../actions/action_rpc.js';
|
|
32
|
+
import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
33
|
+
import { Uuid } from '../uuid.js';
|
|
34
|
+
import { RoleName } from './role_schema.js';
|
|
35
|
+
import { query_grant_permit, query_permit_find_active_for_actor, query_permit_has_role, query_revoke_permit, } from './permit_queries.js';
|
|
36
|
+
import { audit_log_fire_and_forget } from './audit_log_queries.js';
|
|
37
|
+
/** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
|
|
38
|
+
export const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE = 'role_not_self_service_eligible';
|
|
39
|
+
// -- Input/output schemas ---------------------------------------------------
|
|
40
|
+
/** Input for `self_service_role_grant`. */
|
|
41
|
+
export const SelfServiceRoleGrantInput = z.strictObject({
|
|
42
|
+
role: RoleName.meta({ description: 'Role to self-grant. Must be in the configured allowlist.' }),
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Output for `self_service_role_grant`. `granted` is `false` on idempotent
|
|
46
|
+
* re-grant (caller already held the role globally); `permit_id` is set on
|
|
47
|
+
* new grants only.
|
|
48
|
+
*/
|
|
49
|
+
export const SelfServiceRoleGrantOutput = z.strictObject({
|
|
50
|
+
ok: z.literal(true),
|
|
51
|
+
granted: z.boolean(),
|
|
52
|
+
permit_id: Uuid.optional(),
|
|
53
|
+
});
|
|
54
|
+
/** Input for `self_service_role_revoke`. */
|
|
55
|
+
export const SelfServiceRoleRevokeInput = z.strictObject({
|
|
56
|
+
role: RoleName.meta({ description: 'Role to self-revoke. Must be in the configured allowlist.' }),
|
|
57
|
+
});
|
|
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 const SelfServiceRoleRevokeOutput = z.strictObject({
|
|
63
|
+
ok: z.literal(true),
|
|
64
|
+
revoked: z.boolean(),
|
|
65
|
+
});
|
|
66
|
+
// -- Action specs -----------------------------------------------------------
|
|
67
|
+
export const self_service_role_grant_action_spec = {
|
|
68
|
+
method: 'self_service_role_grant',
|
|
69
|
+
kind: 'request_response',
|
|
70
|
+
initiator: 'frontend',
|
|
71
|
+
auth: 'authenticated',
|
|
72
|
+
side_effects: true,
|
|
73
|
+
input: SelfServiceRoleGrantInput,
|
|
74
|
+
output: SelfServiceRoleGrantOutput,
|
|
75
|
+
async: true,
|
|
76
|
+
description: 'Self-grant an active permit for an allowlisted role. Idempotent — already-granted callers receive `granted: false`.',
|
|
77
|
+
};
|
|
78
|
+
export const self_service_role_revoke_action_spec = {
|
|
79
|
+
method: 'self_service_role_revoke',
|
|
80
|
+
kind: 'request_response',
|
|
81
|
+
initiator: 'frontend',
|
|
82
|
+
auth: 'authenticated',
|
|
83
|
+
side_effects: true,
|
|
84
|
+
input: SelfServiceRoleRevokeInput,
|
|
85
|
+
output: SelfServiceRoleRevokeOutput,
|
|
86
|
+
async: true,
|
|
87
|
+
description: 'Self-revoke an active global permit for an allowlisted role. Idempotent — callers without an active permit receive `revoked: false`.',
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* All self-service role action specs — a codegen-ready registry. Method
|
|
91
|
+
* names are static, so consumer typed-client codegen picks them up the
|
|
92
|
+
* same way it picks up `account_*_action_specs`.
|
|
93
|
+
*/
|
|
94
|
+
export const all_self_service_role_action_specs = [
|
|
95
|
+
self_service_role_grant_action_spec,
|
|
96
|
+
self_service_role_revoke_action_spec,
|
|
97
|
+
];
|
|
98
|
+
const require_request_auth = (auth) => {
|
|
99
|
+
if (!auth)
|
|
100
|
+
throw new Error('unreachable: action auth guard did not enforce authentication');
|
|
101
|
+
return auth;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Build the self-service role grant/revoke RPC actions.
|
|
105
|
+
*
|
|
106
|
+
* @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
|
|
107
|
+
* @param options - eligible-role allowlist plus optional role schema for typo-checking
|
|
108
|
+
* @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
|
|
109
|
+
*/
|
|
110
|
+
export const create_self_service_role_actions = (deps, options) => {
|
|
111
|
+
const eligible = new Set(options.eligible_roles);
|
|
112
|
+
if (options.roles) {
|
|
113
|
+
const role_options = options.roles.role_options;
|
|
114
|
+
for (const r of eligible) {
|
|
115
|
+
if (!role_options.has(r)) {
|
|
116
|
+
throw new Error(`create_self_service_role_actions: eligible_roles entry "${r}" is not registered in roles.role_options — typo or missing call to create_role_schema`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const reject_if_ineligible = (role) => {
|
|
121
|
+
if (!eligible.has(role)) {
|
|
122
|
+
throw jsonrpc_errors.forbidden('role not eligible for self-service', {
|
|
123
|
+
reason: ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const grant_handler = async (input, ctx) => {
|
|
128
|
+
const auth = require_request_auth(ctx.auth);
|
|
129
|
+
reject_if_ineligible(input.role);
|
|
130
|
+
// Pre-check for idempotent re-grant. `query_grant_permit` is itself
|
|
131
|
+
// idempotent (returns the existing permit instead of inserting), but
|
|
132
|
+
// it doesn't signal "already existed" vs "newly inserted" — so we
|
|
133
|
+
// peek first. The TOCTOU window is benign for self-service: two
|
|
134
|
+
// concurrent grants both observe "no permit", both call
|
|
135
|
+
// `query_grant_permit`, and one collapses onto the other inside the
|
|
136
|
+
// query's `ON CONFLICT DO NOTHING`. Worst case both responses report
|
|
137
|
+
// `granted: true`; the DB still ends up with exactly one permit.
|
|
138
|
+
const already = await query_permit_has_role(ctx, auth.actor.id, input.role);
|
|
139
|
+
if (already) {
|
|
140
|
+
return { ok: true, granted: false };
|
|
141
|
+
}
|
|
142
|
+
const permit = await query_grant_permit(ctx, {
|
|
143
|
+
actor_id: auth.actor.id,
|
|
144
|
+
role: input.role,
|
|
145
|
+
scope_id: null,
|
|
146
|
+
expires_at: null,
|
|
147
|
+
granted_by: auth.actor.id,
|
|
148
|
+
});
|
|
149
|
+
void audit_log_fire_and_forget(ctx, {
|
|
150
|
+
event_type: 'permit_grant',
|
|
151
|
+
actor_id: auth.actor.id,
|
|
152
|
+
account_id: auth.account.id,
|
|
153
|
+
ip: ctx.client_ip,
|
|
154
|
+
metadata: {
|
|
155
|
+
role: permit.role,
|
|
156
|
+
permit_id: permit.id,
|
|
157
|
+
scope_id: permit.scope_id,
|
|
158
|
+
self_service: true,
|
|
159
|
+
},
|
|
160
|
+
}, deps);
|
|
161
|
+
return { ok: true, granted: true, permit_id: permit.id };
|
|
162
|
+
};
|
|
163
|
+
const revoke_handler = async (input, ctx) => {
|
|
164
|
+
const auth = require_request_auth(ctx.auth);
|
|
165
|
+
reject_if_ineligible(input.role);
|
|
166
|
+
// Find an active global permit for this (actor, role). No dedicated
|
|
167
|
+
// query exists, but `query_permit_find_active_for_actor` returns the
|
|
168
|
+
// short list of every active permit and we filter in JS — fewer
|
|
169
|
+
// round-trips than a new helper for a one-call-per-revoke path.
|
|
170
|
+
const active = await query_permit_find_active_for_actor(ctx, auth.actor.id);
|
|
171
|
+
const target = active.find((p) => p.role === input.role && p.scope_id === null);
|
|
172
|
+
if (!target) {
|
|
173
|
+
return { ok: true, revoked: false };
|
|
174
|
+
}
|
|
175
|
+
const result = await query_revoke_permit(ctx, target.id, auth.actor.id, auth.actor.id);
|
|
176
|
+
if (!result) {
|
|
177
|
+
// Raced with another revoker — treat as already revoked.
|
|
178
|
+
return { ok: true, revoked: false };
|
|
179
|
+
}
|
|
180
|
+
void audit_log_fire_and_forget(ctx, {
|
|
181
|
+
event_type: 'permit_revoke',
|
|
182
|
+
actor_id: auth.actor.id,
|
|
183
|
+
account_id: auth.account.id,
|
|
184
|
+
ip: ctx.client_ip,
|
|
185
|
+
metadata: {
|
|
186
|
+
role: result.role,
|
|
187
|
+
permit_id: result.id,
|
|
188
|
+
scope_id: result.scope_id,
|
|
189
|
+
self_service: true,
|
|
190
|
+
},
|
|
191
|
+
}, deps);
|
|
192
|
+
return { ok: true, revoked: true };
|
|
193
|
+
};
|
|
194
|
+
return [
|
|
195
|
+
rpc_action(self_service_role_grant_action_spec, grant_handler),
|
|
196
|
+
rpc_action(self_service_role_revoke_action_spec, revoke_handler),
|
|
197
|
+
];
|
|
198
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signup_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAStB,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;AAQhD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAClE,6FAA6F;IAC7F,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,yFAAyF;IACzF,YAAY,EAAE,WAAW,CAAC;CAC1B;AAID,0FAA0F;AAC1F,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,YAAY;;kBAEvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,gBAAgB,EACtB,SAAS,kBAAkB,KACzB,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"signup_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAStB,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;AAQhD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAClE,6FAA6F;IAC7F,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,yFAAyF;IACzF,YAAY,EAAE,WAAW,CAAC;CAC1B;AAID,0FAA0F;AAC1F,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,YAAY;;kBAEvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,gBAAgB,EACtB,SAAS,kBAAkB,KACzB,KAAK,CAAC,SAAS,CA2HjB,CAAC"}
|
|
@@ -38,7 +38,7 @@ export const SignupOutput = z.strictObject({
|
|
|
38
38
|
* @returns route specs (not yet applied to Hono)
|
|
39
39
|
*/
|
|
40
40
|
export const create_signup_route_specs = (deps, options) => {
|
|
41
|
-
const { keyring, password
|
|
41
|
+
const { keyring, password } = deps;
|
|
42
42
|
const { session_options, ip_rate_limiter, signup_account_rate_limiter, app_settings } = options;
|
|
43
43
|
return [
|
|
44
44
|
{
|
|
@@ -144,7 +144,7 @@ export const create_signup_route_specs = (deps, options) => {
|
|
|
144
144
|
account_id: result.id,
|
|
145
145
|
ip: get_client_ip(c),
|
|
146
146
|
metadata: invite ? { invite_id: invite.id, username } : { open_signup: true, username },
|
|
147
|
-
}, deps
|
|
147
|
+
}, deps);
|
|
148
148
|
return c.json({ ok: true });
|
|
149
149
|
},
|
|
150
150
|
},
|
|
@@ -49,7 +49,7 @@ export type StandardRpcActionsDeps = PermitOfferActionDeps;
|
|
|
49
49
|
* and `create_account_actions(deps, {max_tokens})`. The shared `roles`
|
|
50
50
|
* option flows to admin + permit-offer.
|
|
51
51
|
*
|
|
52
|
-
* @param deps -
|
|
52
|
+
* @param deps - `StandardRpcActionsDeps` (`log`, `on_audit_event`, optional `audit_log_config` from `AppDeps`; optional `notification_sender` for WS fan-out)
|
|
53
53
|
* @param options - role schema, optional app-settings ref, permit-offer config, account config
|
|
54
54
|
* @returns RPC actions to pass as `rpc_endpoints` or spread into `create_rpc_endpoint`
|
|
55
55
|
*/
|
|
@@ -28,7 +28,7 @@ import { create_account_actions } from './account_actions.js';
|
|
|
28
28
|
* and `create_account_actions(deps, {max_tokens})`. The shared `roles`
|
|
29
29
|
* option flows to admin + permit-offer.
|
|
30
30
|
*
|
|
31
|
-
* @param deps -
|
|
31
|
+
* @param deps - `StandardRpcActionsDeps` (`log`, `on_audit_event`, optional `audit_log_config` from `AppDeps`; optional `notification_sender` for WS fan-out)
|
|
32
32
|
* @param options - role schema, optional app-settings ref, permit-offer config, account config
|
|
33
33
|
* @returns RPC actions to pass as `rpc_endpoints` or spread into `create_rpc_endpoint`
|
|
34
34
|
*/
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { Logger } from '@fuzdev/fuz_util/log.js';
|
|
14
14
|
import type { AppDeps } from '../auth/deps.js';
|
|
15
|
-
import type { AuditLogEvent } from '../auth/audit_log_schema.js';
|
|
15
|
+
import type { AuditLogConfig, AuditLogEvent } from '../auth/audit_log_schema.js';
|
|
16
16
|
import type { DbType } from '../db/db.js';
|
|
17
17
|
import type { Keyring } from '../auth/keyring.js';
|
|
18
18
|
import type { PasswordHashDeps } from '../auth/password.js';
|
|
@@ -60,6 +60,14 @@ export interface CreateAppBackendOptions {
|
|
|
60
60
|
* to all route factories automatically. Defaults to a noop.
|
|
61
61
|
*/
|
|
62
62
|
on_audit_event?: (event: AuditLogEvent) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Audit-log config for consumer event-type extensions. Built once at
|
|
65
|
+
* startup via `create_audit_log_config({extra_events})` and threaded
|
|
66
|
+
* through `AppDeps.audit_log_config` to every fuz_app emit site so
|
|
67
|
+
* consumer handlers cannot silently fall back to the builtin config.
|
|
68
|
+
* Omit to use `BUILTIN_AUDIT_LOG_CONFIG` (no extra events).
|
|
69
|
+
*/
|
|
70
|
+
audit_log_config?: AuditLogConfig;
|
|
63
71
|
/**
|
|
64
72
|
* Additional migration namespaces to run after the builtin auth namespace.
|
|
65
73
|
* Each namespace's own `schema_version` row tracks progress; order is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAE/C,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/
|
|
1
|
+
{"version":3,"file":"app_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAE/C,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/E,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAiB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAI/F;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,oIAAoI;IACpI,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC3D,iEAAiE;IACjE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACvC,+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,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC;;;;;;;;OAQG;IACH,oBAAoB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAAU,SAAS,uBAAuB,KAAG,OAAO,CAAC,UAAU,CAoC7F,CAAC"}
|
|
@@ -28,6 +28,7 @@ export const create_app_backend = async (options) => {
|
|
|
28
28
|
const { database_url, keyring, password, stat, read_text_file, delete_file } = options;
|
|
29
29
|
const log = options.log ?? new Logger('server');
|
|
30
30
|
const on_audit_event = options.on_audit_event ?? (() => { }); // eslint-disable-line @typescript-eslint/no-empty-function
|
|
31
|
+
const { audit_log_config } = options;
|
|
31
32
|
const { db, close, db_type, db_name } = await create_db(database_url);
|
|
32
33
|
if (options.migration_namespaces?.length) {
|
|
33
34
|
for (const ns of options.migration_namespaces) {
|
|
@@ -45,6 +46,16 @@ export const create_app_backend = async (options) => {
|
|
|
45
46
|
db_name,
|
|
46
47
|
migration_results,
|
|
47
48
|
close,
|
|
48
|
-
deps: {
|
|
49
|
+
deps: {
|
|
50
|
+
keyring,
|
|
51
|
+
password,
|
|
52
|
+
db,
|
|
53
|
+
stat,
|
|
54
|
+
read_text_file,
|
|
55
|
+
delete_file,
|
|
56
|
+
log,
|
|
57
|
+
on_audit_event,
|
|
58
|
+
audit_log_config,
|
|
59
|
+
},
|
|
49
60
|
};
|
|
50
61
|
};
|
package/dist/ui/ui_format.d.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import type { AuditEventType } from '../auth/audit_log_schema.js';
|
|
10
9
|
/**
|
|
11
10
|
* Format a timestamp as a relative time string.
|
|
12
11
|
*
|
|
@@ -55,9 +54,9 @@ export declare const format_datetime_local: (timestamp: string | number | Date)
|
|
|
55
54
|
/**
|
|
56
55
|
* Format audit event metadata for display based on event type.
|
|
57
56
|
*
|
|
58
|
-
* @param event_type - the audit event type
|
|
57
|
+
* @param event_type - the audit event type (builtin or consumer-registered)
|
|
59
58
|
* @param metadata - the metadata object (may be null)
|
|
60
59
|
* @returns human-readable summary string
|
|
61
60
|
*/
|
|
62
|
-
export declare const format_audit_metadata: (event_type:
|
|
61
|
+
export declare const format_audit_metadata: (event_type: string, metadata: Record<string, unknown> | null) => string;
|
|
63
62
|
//# sourceMappingURL=ui_format.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui_format.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/ui_format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH
|
|
1
|
+
{"version":3,"file":"ui_format.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/ui_format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,MAAM,GAAG,MAAM,GAAG,IAAI,EACjC,MAAK,MAAmB,KACtB,MA2BF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,KAAG,MAY1C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,EAAE,YAAY,MAAM,EAAE,kBAAe,KAAG,MAOlF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAAmC,CAAC;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAS7C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,MAAM,GAAG,IAAI,KAAG,MAOzE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,YAAY,MAAM,EAClB,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,KACtC,MAuDF,CAAC"}
|
package/dist/ui/ui_format.js
CHANGED
|
@@ -133,7 +133,7 @@ export const format_datetime_local = (timestamp) => {
|
|
|
133
133
|
/**
|
|
134
134
|
* Format audit event metadata for display based on event type.
|
|
135
135
|
*
|
|
136
|
-
* @param event_type - the audit event type
|
|
136
|
+
* @param event_type - the audit event type (builtin or consumer-registered)
|
|
137
137
|
* @param metadata - the metadata object (may be null)
|
|
138
138
|
* @returns human-readable summary string
|
|
139
139
|
*/
|