@fuzdev/fuz_app 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +108 -5
- package/dist/actions/action_event.d.ts +10 -1
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +7 -0
- package/dist/actions/broadcast_api.d.ts +1 -1
- package/dist/actions/broadcast_api.d.ts.map +1 -1
- package/dist/actions/frontend_rpc_client.d.ts +128 -0
- package/dist/actions/frontend_rpc_client.d.ts.map +1 -0
- package/dist/actions/frontend_rpc_client.js +86 -0
- package/dist/actions/rpc_client.d.ts +86 -60
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +102 -80
- package/dist/auth/CLAUDE.md +24 -13
- package/dist/auth/self_service_role_action_specs.d.ts +20 -48
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +22 -44
- package/dist/auth/self_service_role_actions.d.ts +9 -9
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +48 -53
- package/dist/auth/standard_action_specs.d.ts +31 -0
- package/dist/auth/standard_action_specs.d.ts.map +1 -0
- package/dist/auth/standard_action_specs.js +36 -0
- package/dist/testing/ws_round_trip.d.ts +1 -1
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +10 -11
- package/dist/ui/admin_accounts_state.svelte.d.ts +20 -41
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.d.ts +9 -18
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.d.ts +41 -29
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +28 -31
- package/dist/ui/admin_sessions_state.svelte.d.ts +3 -2
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.d.ts +5 -10
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.d.ts +6 -18
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* - **Tier 1** (simple, for tx): transport send/receive, Result return. No `environment`.
|
|
6
6
|
* - **Tier 2** (full, for zzz): ActionEvent lifecycle with `environment`.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Pass the consumer's generated `ActionsApi` interface as `<TApi>` to flow
|
|
9
|
+
* full type safety through without an explicit cast at the call site.
|
|
9
10
|
*
|
|
10
11
|
* @module
|
|
11
12
|
*/
|
|
@@ -20,18 +21,35 @@ import { jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
|
|
|
20
21
|
* - `remote_notification` → send notification, return Result
|
|
21
22
|
* - `local_call` → execute locally (sync or async), return Result or throw
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* Generic `TApi` is the consumer's typed Proxy interface (typically a
|
|
25
|
+
* codegen-derived `ActionsApi`). Required — no default, so forgetting it
|
|
26
|
+
* is a type error rather than a silent slide into `any`. The `as unknown
|
|
27
|
+
* as TApi` coercion lives inside this function so call sites get a typed
|
|
28
|
+
* return without a cast at the seam. `TApi` is a type-layer promise about
|
|
29
|
+
* what the Proxy responds to; the runtime walks `specs` (kept in sync by
|
|
30
|
+
* the consumer, codegen recommended).
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* const api_result = create_rpc_client<MyActionsApi>({peer, environment});
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @param options - client options (peer, environment, optional callbacks)
|
|
37
|
+
* @returns a Proxy typed as `TApi` that responds to any method name found in the environment's specs
|
|
25
38
|
*/
|
|
26
39
|
export const create_rpc_client = (options) => {
|
|
27
|
-
const { peer, environment,
|
|
40
|
+
const { peer, environment, on_action_event, transport_for_method } = options;
|
|
41
|
+
// Internal factories construct broadly-typed `ActionEvent` instances; the
|
|
42
|
+
// public callback narrows `event.spec.method` to `keyof TApi & string`.
|
|
43
|
+
// Cast once here — function parameters are contravariant, so the narrow
|
|
44
|
+
// callback isn't directly assignable to the broad slot the helpers take.
|
|
45
|
+
const broad_on_action_event = on_action_event;
|
|
28
46
|
return new Proxy({}, {
|
|
29
47
|
get(_target, method) {
|
|
30
48
|
const spec = environment.lookup_action_spec(method);
|
|
31
49
|
if (!spec) {
|
|
32
50
|
return undefined;
|
|
33
51
|
}
|
|
34
|
-
return create_action_method(peer, environment, spec,
|
|
52
|
+
return create_action_method(peer, environment, spec, broad_on_action_event, transport_for_method);
|
|
35
53
|
},
|
|
36
54
|
has(_target, method) {
|
|
37
55
|
return environment.lookup_action_spec(method) !== undefined;
|
|
@@ -41,30 +59,26 @@ export const create_rpc_client = (options) => {
|
|
|
41
59
|
/**
|
|
42
60
|
* Creates a method that executes an action through its complete lifecycle.
|
|
43
61
|
*/
|
|
44
|
-
const create_action_method = (peer, environment, spec,
|
|
62
|
+
const create_action_method = (peer, environment, spec, on_action_event, transport_for_method) => {
|
|
45
63
|
switch (spec.kind) {
|
|
46
64
|
case 'local_call':
|
|
47
65
|
return spec.async
|
|
48
|
-
? create_async_local_call_method(environment, spec,
|
|
49
|
-
: create_sync_local_call_method(environment, spec,
|
|
66
|
+
? create_async_local_call_method(environment, spec, on_action_event)
|
|
67
|
+
: create_sync_local_call_method(environment, spec, on_action_event);
|
|
50
68
|
case 'request_response':
|
|
51
|
-
return create_request_response_method(peer, environment, spec,
|
|
69
|
+
return create_request_response_method(peer, environment, spec, on_action_event, transport_for_method);
|
|
52
70
|
case 'remote_notification':
|
|
53
|
-
return create_remote_notification_method(peer, environment, spec,
|
|
71
|
+
return create_remote_notification_method(peer, environment, spec, on_action_event, transport_for_method);
|
|
54
72
|
}
|
|
55
73
|
};
|
|
56
74
|
/**
|
|
57
75
|
* Creates a synchronous local call method.
|
|
58
76
|
* Returns value directly - can throw on error (sync methods cannot return Result).
|
|
59
77
|
*/
|
|
60
|
-
const create_sync_local_call_method = (environment, spec,
|
|
78
|
+
const create_sync_local_call_method = (environment, spec, on_action_event) => {
|
|
61
79
|
return (input) => {
|
|
62
80
|
const event = create_action_event(environment, spec, input);
|
|
63
|
-
|
|
64
|
-
method: spec.method,
|
|
65
|
-
action_event_data: event.toJSON(),
|
|
66
|
-
});
|
|
67
|
-
action?.listen_to_action_event(event);
|
|
81
|
+
on_action_event?.(event);
|
|
68
82
|
event.parse().handle_sync();
|
|
69
83
|
const result = extract_action_result(event);
|
|
70
84
|
if (result.ok) {
|
|
@@ -84,7 +98,7 @@ const create_sync_local_call_method = (environment, spec, actions) => {
|
|
|
84
98
|
* `signal` can only short-circuit before the synchronous handler runs (no
|
|
85
99
|
* cooperative interrupt mid-handler).
|
|
86
100
|
*/
|
|
87
|
-
const create_async_local_call_method = (environment, spec,
|
|
101
|
+
const create_async_local_call_method = (environment, spec, on_action_event) => {
|
|
88
102
|
return async (input, options) => {
|
|
89
103
|
if (options?.signal?.aborted) {
|
|
90
104
|
return {
|
|
@@ -93,11 +107,7 @@ const create_async_local_call_method = (environment, spec, actions) => {
|
|
|
93
107
|
};
|
|
94
108
|
}
|
|
95
109
|
const event = create_action_event(environment, spec, input);
|
|
96
|
-
|
|
97
|
-
method: spec.method,
|
|
98
|
-
action_event_data: event.toJSON(),
|
|
99
|
-
});
|
|
100
|
-
action?.listen_to_action_event(event);
|
|
110
|
+
on_action_event?.(event);
|
|
101
111
|
await event.parse().handle_async();
|
|
102
112
|
return extract_action_result(event);
|
|
103
113
|
};
|
|
@@ -105,14 +115,10 @@ const create_async_local_call_method = (environment, spec, actions) => {
|
|
|
105
115
|
/**
|
|
106
116
|
* Creates a request/response method that communicates over the network.
|
|
107
117
|
*/
|
|
108
|
-
const create_request_response_method = (peer, environment, spec,
|
|
118
|
+
const create_request_response_method = (peer, environment, spec, on_action_event, transport_for_method) => {
|
|
109
119
|
return async (input, options) => {
|
|
110
120
|
const event = create_action_event(environment, spec, input);
|
|
111
|
-
|
|
112
|
-
method: spec.method,
|
|
113
|
-
action_event_data: event.toJSON(),
|
|
114
|
-
});
|
|
115
|
-
action?.listen_to_action_event(event);
|
|
121
|
+
on_action_event?.(event);
|
|
116
122
|
await event.parse().handle_async();
|
|
117
123
|
// Check if we're in send_error phase before type narrowing
|
|
118
124
|
if (event.data.kind === 'request_response' && event.data.phase === 'send_error') {
|
|
@@ -141,14 +147,10 @@ const create_request_response_method = (peer, environment, spec, actions, transp
|
|
|
141
147
|
* Creates a remote notification method (fire and forget).
|
|
142
148
|
* Returns Result<{value: void}> for consistency.
|
|
143
149
|
*/
|
|
144
|
-
const create_remote_notification_method = (peer, environment, spec,
|
|
150
|
+
const create_remote_notification_method = (peer, environment, spec, on_action_event, transport_for_method) => {
|
|
145
151
|
return async (input, options) => {
|
|
146
152
|
const event = create_action_event(environment, spec, input);
|
|
147
|
-
|
|
148
|
-
method: spec.method,
|
|
149
|
-
action_event_data: event.toJSON(),
|
|
150
|
-
});
|
|
151
|
-
action?.listen_to_action_event(event);
|
|
153
|
+
on_action_event?.(event);
|
|
152
154
|
await event.parse().handle_async();
|
|
153
155
|
if (!is_notification_send(event.data))
|
|
154
156
|
throw Error(); // TODO @many maybe make this an assertion helper?
|
|
@@ -169,56 +171,76 @@ const create_remote_notification_method = (peer, environment, spec, actions, tra
|
|
|
169
171
|
};
|
|
170
172
|
};
|
|
171
173
|
/**
|
|
172
|
-
* Wrap a typed RPC client so every call
|
|
174
|
+
* Wrap a typed RPC client so every call resolves to its unwrapped value or
|
|
175
|
+
* throws an `Error` carrying the JSON-RPC `{code, message, data?}` shape.
|
|
176
|
+
*
|
|
177
|
+
* Implementation is a Proxy because the underlying `create_rpc_client`
|
|
178
|
+
* return is itself a Proxy with no concrete keys — a key-by-key wrap would
|
|
179
|
+
* need to enumerate the typed surface, which only the consumer's generated
|
|
180
|
+
* `ActionsApi` interface knows.
|
|
181
|
+
*
|
|
182
|
+
* Pass-through on non-Result returns is deliberate: sync `local_call`
|
|
183
|
+
* Proxy methods return values directly (see `create_sync_local_call_method`
|
|
184
|
+
* above). The Proxy can't distinguish those at get-time, so the wrapper
|
|
185
|
+
* inspects `result` shape at call-time and only unwraps when it sees a
|
|
186
|
+
* Result. Non-object returns pass through unchanged.
|
|
173
187
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* all work. On unknown method, throws a clear "rpc method not found"
|
|
178
|
-
* error instead of the cryptic `undefined is not a function` that
|
|
179
|
-
* would otherwise surface.
|
|
188
|
+
* Only `{code, data}` cross onto the thrown Error — `name` / `stack` are
|
|
189
|
+
* left as the Error's own properties so attacker-shaped `result.error`
|
|
190
|
+
* payloads cannot overwrite them.
|
|
180
191
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
192
|
+
* Recommended consumer convention: `create_frontend_rpc_client` ships
|
|
193
|
+
* both shapes by default — `api` (throwing) for hot-path call sites and
|
|
194
|
+
* `api_result` (Result) for sites that inspect `error.data.reason`
|
|
195
|
+
* without try/catch. Result is the protocol primitive; this wrapper is
|
|
196
|
+
* the ergonomic layer over it. Picking is per call site — both Proxies
|
|
197
|
+
* share the same underlying transport.
|
|
187
198
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* left as the Error's own so attacker-shaped `result.error` payloads
|
|
191
|
-
* cannot overwrite them.
|
|
199
|
+
* Catch blocks read `err.data?.reason` — optional chaining required
|
|
200
|
+
* because JSON-RPC `data` is spec-level optional.
|
|
192
201
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* `
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* otherwise force consumers to `as unknown as Record<string, …>` their
|
|
199
|
-
* generated client. The `| void` arm tolerates `remote_notification`
|
|
200
|
-
* methods, whose `ActionsApi` signature is `(input) => void` even though
|
|
201
|
-
* `create_remote_notification_method` returns a Promise at runtime — the
|
|
202
|
-
* throwing wrapper is intended for `request_response` calls but must
|
|
203
|
-
* accept mixed `ActionsApi` shapes without forcing a cast at the seam.
|
|
202
|
+
* On unknown string-keyed methods, the get trap returns a function that
|
|
203
|
+
* throws `"rpc method not found: <prop>"` on invocation — clearer than
|
|
204
|
+
* the JS default `"api.foo is not a function"`. Symbol props and `then`
|
|
205
|
+
* stay undefined so the Proxy isn't accidentally treated as a thenable
|
|
206
|
+
* (`await api` would otherwise probe `then` and trip the thrower).
|
|
204
207
|
*
|
|
205
|
-
* @param
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
+
* @param api_result - typed Result-returning RPC client from
|
|
209
|
+
* `create_rpc_client<ActionsApi>(...)`. The "_result" suffix names
|
|
210
|
+
* what the underlying calls return (`Result<{value}, {error}>`).
|
|
208
211
|
*/
|
|
209
|
-
export const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
212
|
+
export const create_throwing_api = (api_result) => {
|
|
213
|
+
return new Proxy(api_result, {
|
|
214
|
+
get(target, prop) {
|
|
215
|
+
const fn = target[prop];
|
|
216
|
+
if (typeof fn === 'function') {
|
|
217
|
+
return async (...args) => {
|
|
218
|
+
const result = await fn.apply(target, args);
|
|
219
|
+
if (result === null || typeof result !== 'object')
|
|
220
|
+
return result;
|
|
221
|
+
const r = result;
|
|
222
|
+
if (r.ok === true)
|
|
223
|
+
return r.value;
|
|
224
|
+
if (r.ok === false && r.error && typeof r.error === 'object') {
|
|
225
|
+
const e = r.error;
|
|
226
|
+
throw Object.assign(new Error(e.message), {
|
|
227
|
+
code: e.code,
|
|
228
|
+
data: e.data,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (fn !== undefined)
|
|
235
|
+
return fn;
|
|
236
|
+
// Underlying api has no member by this name. Symbol props and
|
|
237
|
+
// `then` must stay undefined — `await tapi` reads `then` and
|
|
238
|
+
// would otherwise trip the thrower.
|
|
239
|
+
if (typeof prop !== 'string' || prop === 'then')
|
|
240
|
+
return undefined;
|
|
241
|
+
return () => {
|
|
242
|
+
throw new Error(`rpc method not found: ${prop}`);
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
});
|
|
224
246
|
};
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -1039,6 +1039,14 @@ the admin integration suite exercises `account_token_create` /
|
|
|
1039
1039
|
consumer wiring the admin surface without account actions will hit
|
|
1040
1040
|
`method not found` on first admin-suite run.
|
|
1041
1041
|
|
|
1042
|
+
Frontend mirror: `all_standard_action_specs` (in
|
|
1043
|
+
`./standard_action_specs.ts`) bundles `all_admin_action_specs +
|
|
1044
|
+
all_permit_offer_action_specs + all_account_action_specs` into one
|
|
1045
|
+
`ReadonlyArray<RequestResponseActionSpec>` for typed-client codegen
|
|
1046
|
+
and `create_frontend_rpc_client({specs})` wiring. Self-service role
|
|
1047
|
+
specs are not included (opt-in, app-specific `eligible_roles`) —
|
|
1048
|
+
spread `all_self_service_role_action_specs` separately when needed.
|
|
1049
|
+
|
|
1042
1050
|
### `account_action_specs.ts` + `account_actions.ts` — seven self-service RPC actions
|
|
1043
1051
|
|
|
1044
1052
|
Counterpart to `account_routes.ts`. Cookie-lifecycle flows (`login`,
|
|
@@ -1084,15 +1092,17 @@ registry of all seven specs.
|
|
|
1084
1092
|
### `self_service_role_action_specs.ts` + `self_service_role_actions.ts` — opt-in self-service role toggle
|
|
1085
1093
|
|
|
1086
1094
|
Same split as the other registries: `*_action_specs.ts` holds the input/output
|
|
1087
|
-
Zod schemas, the
|
|
1095
|
+
Zod schemas, the `satisfies RequestResponseActionSpec` literal, the
|
|
1088
1096
|
`ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE` reason constant, and the
|
|
1089
1097
|
`all_self_service_role_action_specs` registry — all client-safe. The
|
|
1090
|
-
`*_actions.ts` factory imports the
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1098
|
+
`*_actions.ts` factory imports the spec and pairs it with the handler.
|
|
1099
|
+
|
|
1100
|
+
One static `request_response` action — `self_service_role_set` — that
|
|
1101
|
+
takes `{role, enabled: boolean}` and toggles a global permit on the
|
|
1102
|
+
caller. Idempotent in both directions: `changed: false` when the
|
|
1103
|
+
post-call state already matched the request (already-held when
|
|
1104
|
+
enabling; not-held when disabling). Output is `{ok, enabled, changed}` —
|
|
1105
|
+
`enabled` echoes the post-call state for self-describing responses.
|
|
1096
1106
|
Audit metadata carries `self_service: true` so admin reviewers can
|
|
1097
1107
|
distinguish self-toggled permits from admin grants/offers. The
|
|
1098
1108
|
`permit_grant` / `permit_revoke` metadata schemas declare
|
|
@@ -1100,7 +1110,7 @@ distinguish self-toggled permits from admin grants/offers. The
|
|
|
1100
1110
|
part of the documented surface rather than riding on `z.looseObject`
|
|
1101
1111
|
permissiveness.
|
|
1102
1112
|
|
|
1103
|
-
Method
|
|
1113
|
+
Method name is static — `role` lives in the input, not the method
|
|
1104
1114
|
name. Mirrors the `permit_offer_create({role})` precedent. Per-role
|
|
1105
1115
|
parameterized methods would break the `satisfies RequestResponseActionSpec`
|
|
1106
1116
|
codegen invariant and grow the surface linearly per role.
|
|
@@ -1110,14 +1120,15 @@ codegen invariant and grow the surface linearly per role.
|
|
|
1110
1120
|
- `eligible_roles: ReadonlyArray<string>` — required allowlist. Roles
|
|
1111
1121
|
outside the list are rejected with `forbidden` + reason
|
|
1112
1122
|
`role_not_self_service_eligible` (exported as
|
|
1113
|
-
`ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE`).
|
|
1123
|
+
`ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE`). The eligibility check fires
|
|
1124
|
+
before the `enabled` branch — same rejection regardless of direction.
|
|
1114
1125
|
- `roles?: RoleSchemaResult` — optional. When supplied, every entry in
|
|
1115
1126
|
`eligible_roles` is checked against `roles.role_options` at factory
|
|
1116
1127
|
time so typos throw at startup instead of at first call.
|
|
1117
1128
|
|
|
1118
|
-
Grant
|
|
1129
|
+
Grant branch uses `query_permit_has_role` for a benign-TOCTOU pre-check
|
|
1119
1130
|
(distinguishes new grant from idempotent re-grant), then
|
|
1120
|
-
`query_grant_permit` for the actual insert. Revoke
|
|
1131
|
+
`query_grant_permit` for the actual insert. Revoke branch filters
|
|
1121
1132
|
`query_permit_find_active_for_actor` in JS for the matching
|
|
1122
1133
|
`(actor, role, scope_id IS NULL)` row before calling
|
|
1123
1134
|
`query_revoke_permit`. Bundle is **not** included in
|
|
@@ -1126,8 +1137,8 @@ spread alongside the standard bundle when needed.
|
|
|
1126
1137
|
|
|
1127
1138
|
Deps: `SelfServiceRoleActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event' | 'audit_log_config'>`.
|
|
1128
1139
|
|
|
1129
|
-
`all_self_service_role_action_specs:
|
|
1130
|
-
codegen-ready registry of
|
|
1140
|
+
`all_self_service_role_action_specs: ReadonlyArray<RequestResponseActionSpec>` —
|
|
1141
|
+
codegen-ready registry of the single unified spec.
|
|
1131
1142
|
|
|
1132
1143
|
## Cleanup
|
|
1133
1144
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Unified self-service role toggle action spec — schemas, error reasons,
|
|
3
3
|
* and the codegen-ready registry.
|
|
4
4
|
*
|
|
5
5
|
* Client-safe: no query-layer or audit-write imports. Handler factory
|
|
@@ -11,54 +11,24 @@ import { z } from 'zod';
|
|
|
11
11
|
import type { RequestResponseActionSpec } from '../actions/action_spec.js';
|
|
12
12
|
/** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
|
|
13
13
|
export declare const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE: "role_not_self_service_eligible";
|
|
14
|
-
/** Input for `
|
|
15
|
-
export declare const
|
|
14
|
+
/** Input for `self_service_role_set`. */
|
|
15
|
+
export declare const SelfServiceRoleSetInput: z.ZodObject<{
|
|
16
16
|
role: z.ZodString;
|
|
17
|
+
enabled: z.ZodBoolean;
|
|
17
18
|
}, z.core.$strict>;
|
|
18
|
-
export type
|
|
19
|
+
export type SelfServiceRoleSetInput = z.infer<typeof SelfServiceRoleSetInput>;
|
|
19
20
|
/**
|
|
20
|
-
* Output for `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* Output for `self_service_role_set`. `enabled` echoes the post-call state
|
|
22
|
+
* (always equals the input `enabled` on success). `changed` is `true` only
|
|
23
|
+
* when the call mutated — re-grants / re-revokes return `false`.
|
|
23
24
|
*/
|
|
24
|
-
export declare const
|
|
25
|
+
export declare const SelfServiceRoleSetOutput: z.ZodObject<{
|
|
25
26
|
ok: z.ZodLiteral<true>;
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
enabled: z.ZodBoolean;
|
|
28
|
+
changed: z.ZodBoolean;
|
|
28
29
|
}, z.core.$strict>;
|
|
29
|
-
export type
|
|
30
|
-
|
|
31
|
-
export declare const SelfServiceRoleRevokeInput: z.ZodObject<{
|
|
32
|
-
role: z.ZodString;
|
|
33
|
-
}, z.core.$strict>;
|
|
34
|
-
export type SelfServiceRoleRevokeInput = z.infer<typeof SelfServiceRoleRevokeInput>;
|
|
35
|
-
/**
|
|
36
|
-
* Output for `self_service_role_revoke`. `revoked` is `false` when the
|
|
37
|
-
* caller held no active global permit for the role (idempotent).
|
|
38
|
-
*/
|
|
39
|
-
export declare const SelfServiceRoleRevokeOutput: z.ZodObject<{
|
|
40
|
-
ok: z.ZodLiteral<true>;
|
|
41
|
-
revoked: z.ZodBoolean;
|
|
42
|
-
}, z.core.$strict>;
|
|
43
|
-
export type SelfServiceRoleRevokeOutput = z.infer<typeof SelfServiceRoleRevokeOutput>;
|
|
44
|
-
export declare const self_service_role_grant_action_spec: {
|
|
45
|
-
method: string;
|
|
46
|
-
kind: "request_response";
|
|
47
|
-
initiator: "frontend";
|
|
48
|
-
auth: "authenticated";
|
|
49
|
-
side_effects: true;
|
|
50
|
-
input: z.ZodObject<{
|
|
51
|
-
role: z.ZodString;
|
|
52
|
-
}, z.core.$strict>;
|
|
53
|
-
output: z.ZodObject<{
|
|
54
|
-
ok: z.ZodLiteral<true>;
|
|
55
|
-
granted: z.ZodBoolean;
|
|
56
|
-
permit_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
57
|
-
}, z.core.$strict>;
|
|
58
|
-
async: true;
|
|
59
|
-
description: string;
|
|
60
|
-
};
|
|
61
|
-
export declare const self_service_role_revoke_action_spec: {
|
|
30
|
+
export type SelfServiceRoleSetOutput = z.infer<typeof SelfServiceRoleSetOutput>;
|
|
31
|
+
export declare const self_service_role_set_action_spec: {
|
|
62
32
|
method: string;
|
|
63
33
|
kind: "request_response";
|
|
64
34
|
initiator: "frontend";
|
|
@@ -66,18 +36,20 @@ export declare const self_service_role_revoke_action_spec: {
|
|
|
66
36
|
side_effects: true;
|
|
67
37
|
input: z.ZodObject<{
|
|
68
38
|
role: z.ZodString;
|
|
39
|
+
enabled: z.ZodBoolean;
|
|
69
40
|
}, z.core.$strict>;
|
|
70
41
|
output: z.ZodObject<{
|
|
71
42
|
ok: z.ZodLiteral<true>;
|
|
72
|
-
|
|
43
|
+
enabled: z.ZodBoolean;
|
|
44
|
+
changed: z.ZodBoolean;
|
|
73
45
|
}, z.core.$strict>;
|
|
74
46
|
async: true;
|
|
75
47
|
description: string;
|
|
76
48
|
};
|
|
77
49
|
/**
|
|
78
|
-
* All self-service role action specs — a codegen-ready registry.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
50
|
+
* All self-service role action specs — a codegen-ready registry. Single-element
|
|
51
|
+
* post-unification, kept for symmetry with the other `all_*_action_specs`
|
|
52
|
+
* exports so codegen and frontend bundles import the same shape.
|
|
81
53
|
*/
|
|
82
|
-
export declare const all_self_service_role_action_specs:
|
|
54
|
+
export declare const all_self_service_role_action_specs: ReadonlyArray<RequestResponseActionSpec>;
|
|
83
55
|
//# sourceMappingURL=self_service_role_action_specs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self_service_role_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"self_service_role_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAGzE,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,EAAG,gCAAyC,CAAC;AAE9F,yCAAyC;AACzC,eAAO,MAAM,uBAAuB;;;kBAMlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;;kBAInC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;CAWT,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,EAAE,aAAa,CAAC,yBAAyB,CAEvF,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Unified self-service role toggle action spec — schemas, error reasons,
|
|
3
3
|
* and the codegen-ready registry.
|
|
4
4
|
*
|
|
5
5
|
* Client-safe: no query-layer or audit-write imports. Handler factory
|
|
@@ -8,64 +8,42 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
12
11
|
import { RoleName } from './role_schema.js';
|
|
13
12
|
/** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
|
|
14
13
|
export const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE = 'role_not_self_service_eligible';
|
|
15
|
-
/** Input for `
|
|
16
|
-
export const
|
|
17
|
-
role: RoleName.meta({ description: 'Role to
|
|
14
|
+
/** Input for `self_service_role_set`. */
|
|
15
|
+
export const SelfServiceRoleSetInput = z.strictObject({
|
|
16
|
+
role: RoleName.meta({ description: 'Role to toggle. Must be in the configured allowlist.' }),
|
|
17
|
+
enabled: z.boolean().meta({
|
|
18
|
+
description: 'Desired post-call state. `true` grants if not held; `false` revokes if held. Idempotent in both directions.',
|
|
19
|
+
}),
|
|
18
20
|
});
|
|
19
21
|
/**
|
|
20
|
-
* Output for `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
22
|
+
* Output for `self_service_role_set`. `enabled` echoes the post-call state
|
|
23
|
+
* (always equals the input `enabled` on success). `changed` is `true` only
|
|
24
|
+
* when the call mutated — re-grants / re-revokes return `false`.
|
|
23
25
|
*/
|
|
24
|
-
export const
|
|
26
|
+
export const SelfServiceRoleSetOutput = z.strictObject({
|
|
25
27
|
ok: z.literal(true),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
enabled: z.boolean(),
|
|
29
|
+
changed: z.boolean(),
|
|
28
30
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
role: RoleName.meta({ description: 'Role to self-revoke. Must be in the configured allowlist.' }),
|
|
32
|
-
});
|
|
33
|
-
/**
|
|
34
|
-
* Output for `self_service_role_revoke`. `revoked` is `false` when the
|
|
35
|
-
* caller held no active global permit for the role (idempotent).
|
|
36
|
-
*/
|
|
37
|
-
export const SelfServiceRoleRevokeOutput = z.strictObject({
|
|
38
|
-
ok: z.literal(true),
|
|
39
|
-
revoked: z.boolean(),
|
|
40
|
-
});
|
|
41
|
-
export const self_service_role_grant_action_spec = {
|
|
42
|
-
method: 'self_service_role_grant',
|
|
43
|
-
kind: 'request_response',
|
|
44
|
-
initiator: 'frontend',
|
|
45
|
-
auth: 'authenticated',
|
|
46
|
-
side_effects: true,
|
|
47
|
-
input: SelfServiceRoleGrantInput,
|
|
48
|
-
output: SelfServiceRoleGrantOutput,
|
|
49
|
-
async: true,
|
|
50
|
-
description: 'Self-grant an active permit for an allowlisted role. Idempotent — already-granted callers receive `granted: false`.',
|
|
51
|
-
};
|
|
52
|
-
export const self_service_role_revoke_action_spec = {
|
|
53
|
-
method: 'self_service_role_revoke',
|
|
31
|
+
export const self_service_role_set_action_spec = {
|
|
32
|
+
method: 'self_service_role_set',
|
|
54
33
|
kind: 'request_response',
|
|
55
34
|
initiator: 'frontend',
|
|
56
35
|
auth: 'authenticated',
|
|
57
36
|
side_effects: true,
|
|
58
|
-
input:
|
|
59
|
-
output:
|
|
37
|
+
input: SelfServiceRoleSetInput,
|
|
38
|
+
output: SelfServiceRoleSetOutput,
|
|
60
39
|
async: true,
|
|
61
|
-
description: '
|
|
40
|
+
description: 'Toggle a self-service role. Idempotent in both directions — `changed: false` when post-call state already matched the request.',
|
|
62
41
|
};
|
|
63
42
|
/**
|
|
64
|
-
* All self-service role action specs — a codegen-ready registry.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
43
|
+
* All self-service role action specs — a codegen-ready registry. Single-element
|
|
44
|
+
* post-unification, kept for symmetry with the other `all_*_action_specs`
|
|
45
|
+
* exports so codegen and frontend bundles import the same shape.
|
|
67
46
|
*/
|
|
68
47
|
export const all_self_service_role_action_specs = [
|
|
69
|
-
|
|
70
|
-
self_service_role_revoke_action_spec,
|
|
48
|
+
self_service_role_set_action_spec,
|
|
71
49
|
];
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Unified self-service role toggle RPC action.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* One static `request_response` action — `self_service_role_set` — that
|
|
5
|
+
* takes `{role, enabled}` and toggles a global permit on the caller for an
|
|
6
|
+
* allowlisted role. Idempotent in both directions: re-enabling an
|
|
7
|
+
* already-held role returns `changed: false`; disabling a role the caller
|
|
8
|
+
* doesn't hold returns `changed: false`.
|
|
9
9
|
*
|
|
10
10
|
* The factory takes an `eligible_roles` allowlist (validated against the
|
|
11
11
|
* supplied `roles.role_options` at factory time so typos surface at startup
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* part of the documented schema surface and is round-trip-validated by
|
|
20
20
|
* `query_audit_log`.
|
|
21
21
|
*
|
|
22
|
-
* Static method
|
|
23
|
-
* so
|
|
22
|
+
* Static method name — `role` lives in the input, not the method name —
|
|
23
|
+
* so the spec is codegen-compatible (`satisfies RequestResponseActionSpec`)
|
|
24
24
|
* and the surface stays constant as consumers add eligible roles. Mirrors
|
|
25
25
|
* the existing `permit_offer_create({role})` precedent rather than
|
|
26
26
|
* generating per-role methods.
|
|
@@ -57,7 +57,7 @@ export interface SelfServiceRoleActionsOptions {
|
|
|
57
57
|
*/
|
|
58
58
|
export type SelfServiceRoleActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event' | 'audit_log_config'>;
|
|
59
59
|
/**
|
|
60
|
-
* Build the self-service role
|
|
60
|
+
* Build the unified self-service role toggle RPC action.
|
|
61
61
|
*
|
|
62
62
|
* @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
|
|
63
63
|
* @param options - eligible-role allowlist plus optional role schema for typo-checking
|
|
@@ -1 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAgBhD,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,CA4GjB,CAAC"}
|