@fuzdev/fuz_app 0.106.0 → 0.107.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 +42 -23
- package/dist/actions/action_bridge.d.ts +34 -3
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +32 -1
- package/dist/actions/action_rpc.d.ts +5 -4
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +4 -3
- package/dist/actions/action_spec.d.ts +8 -2
- package/dist/actions/action_spec.d.ts.map +1 -1
- package/dist/actions/action_spec.js +5 -2
- package/dist/actions/compile_action_registry.d.ts +26 -14
- package/dist/actions/compile_action_registry.d.ts.map +1 -1
- package/dist/actions/compile_action_registry.js +29 -14
- package/dist/actions/perform_action.d.ts +26 -16
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +69 -44
- package/dist/actions/register_action_ws.d.ts +4 -4
- package/dist/actions/register_action_ws.js +5 -5
- package/dist/actions/register_ws_endpoint.d.ts +13 -5
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +15 -6
- package/dist/auth/CLAUDE.md +16 -9
- package/dist/auth/audit_log_route_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_route_schema.js +11 -1
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +4 -9
- package/dist/auth/auth_guard_resolver.d.ts +48 -21
- package/dist/auth/auth_guard_resolver.d.ts.map +1 -1
- package/dist/auth/auth_guard_resolver.js +60 -26
- package/dist/auth/bearer_auth.d.ts +1 -1
- package/dist/auth/bearer_auth.js +1 -1
- package/dist/auth/middleware.js +1 -1
- package/dist/auth/request_context.d.ts +54 -24
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +117 -39
- package/dist/auth/token_scope.d.ts +109 -21
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +106 -21
- package/dist/http/CLAUDE.md +23 -11
- package/dist/http/auth_shape.d.ts +58 -3
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +63 -5
- package/dist/http/error_schemas.d.ts +24 -1
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +36 -14
- package/dist/http/route_spec.d.ts +29 -26
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +12 -16
- package/dist/http/surface_query.d.ts +2 -2
- package/dist/http/surface_query.js +2 -2
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +12 -10
- package/dist/testing/CLAUDE.md +39 -3
- package/dist/testing/cross_backend/conformance_case.js +1 -1
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +6 -5
- package/dist/testing/cross_backend/token_scope_surface.d.ts +9 -0
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -0
- package/dist/testing/cross_backend/token_scope_surface.js +170 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +3 -2
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +4 -2
- package/dist/ui/account_sessions_state.svelte.d.ts +3 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -29,14 +29,16 @@ Canonical source of truth. Three concrete kinds discriminate on `kind`:
|
|
|
29
29
|
- `remote_notification` — `auth: null`, `side_effects: true`, `output: z.ZodVoid`, `async: true`.
|
|
30
30
|
- `local_call` — `auth: null`, `side_effects` arbitrary, `output` arbitrary, `async` boolean.
|
|
31
31
|
|
|
32
|
-
`RouteAuth` is the flat record
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
`RouteAuth` is the flat record
|
|
33
|
+
`{account, actor, roles?, credential_types?, required_scope?}` from
|
|
34
|
+
`http/auth_shape.ts` — same shape governs `RouteSpec.auth` so one auth
|
|
35
|
+
surface drives REST and SAES. Cross-axis invariants: roles imply
|
|
36
|
+
`actor: 'required'`; `account: 'none'` implies `actor: 'none'` (no
|
|
37
|
+
accountless actors in v1); the unrestricted leaf
|
|
37
38
|
(`account: 'none', actor: 'none'`) cannot declare roles or credential
|
|
38
39
|
gates. The biconditional `actor !== 'none' ⟺ input declares acting?: ActingActor`
|
|
39
40
|
is enforced at registration time via `assert_route_auth_acting_biconditional`.
|
|
41
|
+
`required_scope` is the one axis actions cannot use — see §Registry compile.
|
|
40
42
|
|
|
41
43
|
Optional fields:
|
|
42
44
|
|
|
@@ -73,14 +75,14 @@ non-`remote_notification` kind.
|
|
|
73
75
|
## Registry compile (`actions/compile_action_registry.ts`)
|
|
74
76
|
|
|
75
77
|
`compile_action_registry` is the shared registration loop called by both
|
|
76
|
-
`create_rpc_endpoint` and `register_action_ws`. Validates
|
|
77
|
-
|
|
78
|
-
dispatchers use:
|
|
78
|
+
`create_rpc_endpoint` and `register_action_ws`. Validates these checks in
|
|
79
|
+
order and returns the `Map<method, RpcAction>` the dispatchers use:
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
4. **
|
|
81
|
+
- **Auth-shape biconditional** — `actor !== 'none' ⟺ input declares acting?: ActingActor` (via `assert_route_auth_acting_biconditional`).
|
|
82
|
+
- **No `auth.required_scope`** — the slot is route-spec-only; nothing mounts a guard from it on an action, so declaring it would be a control that silently does nothing. Narrowed tokens are gated per method by the scope check in `perform_action`, which derives the `rpc:<method>` capability from `spec.method` rather than trusting a hand-written one.
|
|
83
|
+
- **Rate-limit account axis** — `rate_limit: 'account' | 'both'` requires `auth.account === 'required'`.
|
|
84
|
+
- **JSON-RPC §4.2 wire validity** — `request_response` specs with a handler may not use `z.null()` for input (use `z.void()` for nullary).
|
|
85
|
+
- **Unique method names** across the array.
|
|
84
86
|
|
|
85
87
|
Only `request_response` specs with a handler reach the dispatch map;
|
|
86
88
|
`remote_notification` / handler-less specs (e.g. WS `cancel`) stay
|
|
@@ -174,7 +176,25 @@ and `FrontendActionHandlers`.
|
|
|
174
176
|
Derives transport-specific specs from action specs. HTTP-specific concerns
|
|
175
177
|
(path, handler, errors) come from options, not the action spec.
|
|
176
178
|
|
|
177
|
-
- `create_action_route_spec(spec, options)` — one action → one `RouteSpec`. HTTP method defaults by `side_effects` (`true` → POST, `false` → GET; override via `options.http_method`). `route.auth` is `spec.auth`
|
|
179
|
+
- `create_action_route_spec(spec, options)` — one action → one `RouteSpec`. HTTP method defaults by `side_effects` (`true` → POST, `false` → GET; override via `options.http_method`). `route.auth` is `spec.auth` plus a derived `required_scope`. `transaction: spec.side_effects`. Throws if `spec.auth` is null.
|
|
180
|
+
|
|
181
|
+
**The bridge carries the token-scope gate across.** A bridged route runs
|
|
182
|
+
`options.handler` through the REST pipeline and never reaches
|
|
183
|
+
`perform_action`, so the dispatcher's per-method scope check cannot fire on it
|
|
184
|
+
— leaving a bearer-reachable surface a **narrowed** token walks through, which
|
|
185
|
+
is what the RPC-only rule exists to prevent. So the derived spec declares
|
|
186
|
+
`auth.required_scope: 'rpc:<method>'` and `fuz_auth_guard_resolver` mounts the
|
|
187
|
+
refusal ahead of the role gate. Per-method rather than a blanket refusal
|
|
188
|
+
because a bridged route _has_ a method identity, which the non-RPC surfaces
|
|
189
|
+
the RPC-only rule covers do not.
|
|
190
|
+
|
|
191
|
+
Bridge something with no request/response shape — an SSE stream, a file
|
|
192
|
+
download — and the whole-surface rule applies instead: pass `options.auth` with
|
|
193
|
+
`required_scope: 'surface:<name>'`, naming your own surface (the vocabulary is
|
|
194
|
+
open on the identifier). An `options.auth` that omits `required_scope` still
|
|
195
|
+
gets the derived one; public actions get none (the guard would enforce
|
|
196
|
+
nothing). fuz_app mounts no bridged routes itself; this is consumer-facing.
|
|
197
|
+
|
|
178
198
|
- `create_action_event_spec(spec, {channel?})` — one notification action → one `EventSpec` for SSE surface + `create_validated_broadcaster`. Throws on non-`remote_notification` kind.
|
|
179
199
|
- `derive_http_method(side_effects)` — exported for custom bridges.
|
|
180
200
|
|
|
@@ -190,11 +210,10 @@ The HTTP RPC dispatcher is a thin shim around `perform_action`
|
|
|
190
210
|
parsing, GET vs POST split, `c.json` binding); the
|
|
191
211
|
auth/validation/dispatch pipeline is shared with the WebSocket dispatcher.
|
|
192
212
|
|
|
193
|
-
**Phase order: 401 →
|
|
194
|
-
after
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
is published in `library.json` codegen anyway.
|
|
213
|
+
**Phase order: 401 → authz → 403 → 400 → handler.** Authorize first,
|
|
214
|
+
validate after — a caller the authority gates refuse never learns the
|
|
215
|
+
action's input shape, and the coarse authority facts settle before the fine
|
|
216
|
+
ones. Matches the Rust spine, which validates handler-side.
|
|
198
217
|
|
|
199
218
|
Shim responsibilities (per-request):
|
|
200
219
|
|
|
@@ -267,12 +286,12 @@ The transport-agnostic post-parse pipeline. Each transport assembles a
|
|
|
267
286
|
`perform_action(input, deps)`, and binds the discriminated
|
|
268
287
|
`PerformActionResult` to its wire shape.
|
|
269
288
|
|
|
270
|
-
Pipeline (401 →
|
|
289
|
+
Pipeline (401 → authz → 403 → 400 → handler):
|
|
271
290
|
|
|
272
|
-
1. Pre-
|
|
273
|
-
2.
|
|
274
|
-
3.
|
|
275
|
-
4.
|
|
291
|
+
1. Pre-authorization auth (401)
|
|
292
|
+
2. Authorization phase — `apply_authorization_phase` against `account_id` + `read_acting(auth, raw_params)`, which takes the selector off the raw params (malformed reads as omitted) since validation now runs later. Test escape hatch via `preset.request_context`
|
|
293
|
+
3. Post-authorization auth (403) — credential-type gate, then token scope, then role
|
|
294
|
+
4. Validate params (400) — `spec.input.safeParse` with `z.void()` / `?? {}` rules
|
|
276
295
|
5. Rate limit (429) — throttle-requests semantics
|
|
277
296
|
6. Dispatch + DEV output validation + error normalization — `spec.side_effects` picks transaction vs pool. `ThrownJsonrpcError` preserves code + data; generic throws become `internal_error`
|
|
278
297
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { z } from 'zod';
|
|
11
11
|
import type { ActionSpec, ActionSideEffects } from './action_spec.js';
|
|
12
12
|
import type { RouteSpec, RouteMethod, RouteHandler } from '../http/route_spec.js';
|
|
13
|
-
import type
|
|
13
|
+
import { type RouteAuth } from '../http/auth_shape.js';
|
|
14
14
|
import type { EventSpec } from '../realtime/sse.js';
|
|
15
15
|
import type { RouteErrorSchemas } from '../http/error_schemas.js';
|
|
16
16
|
/** Options for deriving a `RouteSpec` from an `ActionSpec`. */
|
|
@@ -25,8 +25,17 @@ export interface ActionRouteOptions {
|
|
|
25
25
|
http_method?: RouteMethod;
|
|
26
26
|
/**
|
|
27
27
|
* Override the route's auth shape — defaults to the action spec's `auth`
|
|
28
|
-
* (the canonical
|
|
29
|
-
*
|
|
28
|
+
* (the canonical shape from `http/auth_shape.ts` is shared verbatim between
|
|
29
|
+
* action specs and route specs, so no mapping is needed).
|
|
30
|
+
*
|
|
31
|
+
* The bridge fills in `required_scope` on whichever shape it ends up with,
|
|
32
|
+
* so an override still gets the token-scope gate; declare `required_scope`
|
|
33
|
+
* here to name a different capability (e.g. `surface:<name>` when the
|
|
34
|
+
* bridged route is a stream rather than a request/response call — see the
|
|
35
|
+
* token-scope note on `create_action_route_spec`).
|
|
36
|
+
*
|
|
37
|
+
* Overriding to *widen* (admitting a credential the action's own gate
|
|
38
|
+
* refused) makes this route the consumer's to audit, not the spine's.
|
|
30
39
|
*/
|
|
31
40
|
auth?: RouteAuth;
|
|
32
41
|
/** Handler-specific error schemas (HTTP status code → Zod schema). Transport-specific — not on ActionSpec. */
|
|
@@ -49,6 +58,28 @@ export declare const derive_http_method: (side_effects: ActionSideEffects) => Ro
|
|
|
49
58
|
* on the options, not the action spec. Action specs define the contract;
|
|
50
59
|
* transport concerns like HTTP error codes are added at the bridge layer.
|
|
51
60
|
*
|
|
61
|
+
* **The bridge carries the token-scope gate across for you.** A bridged route
|
|
62
|
+
* runs `options.handler` through the REST pipeline and never reaches
|
|
63
|
+
* `perform_action`, so the dispatcher's per-method scope check cannot fire on
|
|
64
|
+
* it — which would leave a bearer-reachable route that a **narrowed** api token
|
|
65
|
+
* walks straight through. So the derived spec declares
|
|
66
|
+
* `auth.required_scope: 'rpc:<method>'`, and `fuz_auth_guard_resolver` mounts
|
|
67
|
+
* the same refusal ahead of the role gate. A token that lists the method
|
|
68
|
+
* reaches the bridged route; one that doesn't gets the 403 it would have gotten
|
|
69
|
+
* over RPC.
|
|
70
|
+
*
|
|
71
|
+
* Per-method rather than rule 3's blanket refusal because a bridged route
|
|
72
|
+
* *has* a method identity — which is exactly what the non-RPC surfaces rule 3
|
|
73
|
+
* covers (the db browser, a bare-hash read, a stream) do not, and why that rule
|
|
74
|
+
* is all-or-nothing there. **Bridge something with no request/response shape —
|
|
75
|
+
* an SSE stream, a file download — and rule 3's reasoning applies instead**:
|
|
76
|
+
* pass `options.auth` with `required_scope: 'surface:<name>'`, naming your own
|
|
77
|
+
* surface. See `docs/security.md` §Token scoping.
|
|
78
|
+
*
|
|
79
|
+
* Skipped for a public action (`account: 'none', actor: 'none'`), where the
|
|
80
|
+
* same holder reaches the route by dropping the credential, so the guard would
|
|
81
|
+
* enforce nothing — the shape `fuz_auth_guard_resolver` refuses outright.
|
|
82
|
+
*
|
|
52
83
|
* @param spec - the action spec (must have non-null `auth`)
|
|
53
84
|
* @param options - HTTP-specific options (path, handler, optional overrides)
|
|
54
85
|
* @returns a `RouteSpec` ready for `apply_route_specs`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SA4BF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,SAcF,CAAC"}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
+
import { token_scope_method_capability } from '../auth/token_scope.js';
|
|
11
|
+
import { is_public_auth } from '../http/auth_shape.js';
|
|
10
12
|
/** Derive the default HTTP method from side effects. */
|
|
11
13
|
export const derive_http_method = (side_effects) => {
|
|
12
14
|
return side_effects ? 'POST' : 'GET';
|
|
@@ -22,6 +24,28 @@ export const derive_http_method = (side_effects) => {
|
|
|
22
24
|
* on the options, not the action spec. Action specs define the contract;
|
|
23
25
|
* transport concerns like HTTP error codes are added at the bridge layer.
|
|
24
26
|
*
|
|
27
|
+
* **The bridge carries the token-scope gate across for you.** A bridged route
|
|
28
|
+
* runs `options.handler` through the REST pipeline and never reaches
|
|
29
|
+
* `perform_action`, so the dispatcher's per-method scope check cannot fire on
|
|
30
|
+
* it — which would leave a bearer-reachable route that a **narrowed** api token
|
|
31
|
+
* walks straight through. So the derived spec declares
|
|
32
|
+
* `auth.required_scope: 'rpc:<method>'`, and `fuz_auth_guard_resolver` mounts
|
|
33
|
+
* the same refusal ahead of the role gate. A token that lists the method
|
|
34
|
+
* reaches the bridged route; one that doesn't gets the 403 it would have gotten
|
|
35
|
+
* over RPC.
|
|
36
|
+
*
|
|
37
|
+
* Per-method rather than rule 3's blanket refusal because a bridged route
|
|
38
|
+
* *has* a method identity — which is exactly what the non-RPC surfaces rule 3
|
|
39
|
+
* covers (the db browser, a bare-hash read, a stream) do not, and why that rule
|
|
40
|
+
* is all-or-nothing there. **Bridge something with no request/response shape —
|
|
41
|
+
* an SSE stream, a file download — and rule 3's reasoning applies instead**:
|
|
42
|
+
* pass `options.auth` with `required_scope: 'surface:<name>'`, naming your own
|
|
43
|
+
* surface. See `docs/security.md` §Token scoping.
|
|
44
|
+
*
|
|
45
|
+
* Skipped for a public action (`account: 'none', actor: 'none'`), where the
|
|
46
|
+
* same holder reaches the route by dropping the credential, so the guard would
|
|
47
|
+
* enforce nothing — the shape `fuz_auth_guard_resolver` refuses outright.
|
|
48
|
+
*
|
|
25
49
|
* @param spec - the action spec (must have non-null `auth`)
|
|
26
50
|
* @param options - HTTP-specific options (path, handler, optional overrides)
|
|
27
51
|
* @returns a `RouteSpec` ready for `apply_route_specs`
|
|
@@ -32,10 +56,17 @@ export const create_action_route_spec = (spec, options) => {
|
|
|
32
56
|
if (spec.auth === null) {
|
|
33
57
|
throw new Error(`Cannot derive route spec from action '${spec.method}': auth is null (only request_response actions with non-null auth can become routes)`);
|
|
34
58
|
}
|
|
59
|
+
const auth = options.auth ?? spec.auth;
|
|
60
|
+
// Carry the dispatcher's per-method scope gate across — see the token-scope
|
|
61
|
+
// note above. An explicit capability wins; a public route has no credential
|
|
62
|
+
// to narrow, and the resolver rejects the gate on that shape anyway.
|
|
63
|
+
const derives_scope_gate = auth.required_scope === undefined && !is_public_auth(auth);
|
|
35
64
|
return {
|
|
36
65
|
method: options.http_method ?? derive_http_method(spec.side_effects),
|
|
37
66
|
path: options.path,
|
|
38
|
-
auth:
|
|
67
|
+
auth: derives_scope_gate
|
|
68
|
+
? { ...auth, required_scope: token_scope_method_capability(spec.method) }
|
|
69
|
+
: auth,
|
|
39
70
|
handler: options.handler,
|
|
40
71
|
description: spec.description,
|
|
41
72
|
...(options.params ? { params: options.params } : {}),
|
|
@@ -122,7 +122,7 @@ export type ActionHandler<TInput = any, TOutput = any> = (input: TInput, ctx: Ac
|
|
|
122
122
|
* `ActionContext` narrowed to a non-null `RequestContext`.
|
|
123
123
|
*
|
|
124
124
|
* Used by handlers whose spec declares `auth.account === 'required'`
|
|
125
|
-
* (with `auth.actor === 'none'`) — the dispatcher's pre-
|
|
125
|
+
* (with `auth.actor === 'none'`) — the dispatcher's pre-authorization 401
|
|
126
126
|
* gate guarantees `request_context` is populated before the handler
|
|
127
127
|
* runs, but the actor slot stays null because no `acting` resolution
|
|
128
128
|
* happened. Selected automatically by `rpc_action`'s conditional return
|
|
@@ -263,13 +263,14 @@ export interface CreateRpcEndpointOptions {
|
|
|
263
263
|
* 1. **Parse envelope** — POST: JSON body as `JsonrpcRequest`. GET: `method`
|
|
264
264
|
* and `params` from query string.
|
|
265
265
|
* 2. **Lookup method** — find the `RpcAction` by method name.
|
|
266
|
-
* 3. **Pre-
|
|
267
|
-
* account is on the request, before
|
|
266
|
+
* 3. **Pre-authorization auth** — short-circuit `unauthenticated` when no
|
|
267
|
+
* account is on the request, before any later phase runs.
|
|
268
268
|
* 4. **Authorization phase** — resolve the acting actor (when the action's
|
|
269
269
|
* auth requires role_grants or its input declares `acting?: ActingActor`)
|
|
270
270
|
* and build the request context. Runs before input validation so
|
|
271
271
|
* role-grant-grain auth checks return 403 before 400 invalid_params;
|
|
272
|
-
* `acting` is read from raw params
|
|
272
|
+
* `acting` is read from raw params by `read_acting`, which parses it as
|
|
273
|
+
* a UUID and treats anything else as omitted.
|
|
273
274
|
* 5. **Post-authorization auth** — enforce role / keeper requirements
|
|
274
275
|
* against the request context.
|
|
275
276
|
* 6. **Validate params** — parse input against the action's `input` schema.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE1E,OAAO,EAEN,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAKN,KAAK,cAAc,EACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EAGN,KAAK,gBAAgB,EAErB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,UAAU,EAAE,gBAAgB,CAAC;IAC7B;;;;OAIG;IACH,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B;;;;;OAKG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;;OAKG;IACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;;;OAKG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD;;;;;OAKG;IACH,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CACxD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC;IACrE,IAAI,EAAE,cAAc,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CAC5D,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,iBAAiB,KAClB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC;IACtE,IAAI,EAAE,mBAAmB,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CAC7D,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,kBAAkB,KACnB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,yBAAyB,IAAI;IACrE,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;CACtB,SAAS,CAAC,UAAU,CAAC,GACnB,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GACrE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,GAC9C,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GACpE,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,SAAS,yBAAyB,EACjE,MAAM,KAAK,EACX,SAAS,cAAc,CAAC,KAAK,CAAC,KAC5B,SAGD,CAAC;AAEH,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;;;OAOG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACjD;AAkBD
|
|
1
|
+
{"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"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"}
|
|
@@ -92,13 +92,14 @@ const jsonrpc_error_envelope = (id, error) => ({
|
|
|
92
92
|
* 1. **Parse envelope** — POST: JSON body as `JsonrpcRequest`. GET: `method`
|
|
93
93
|
* and `params` from query string.
|
|
94
94
|
* 2. **Lookup method** — find the `RpcAction` by method name.
|
|
95
|
-
* 3. **Pre-
|
|
96
|
-
* account is on the request, before
|
|
95
|
+
* 3. **Pre-authorization auth** — short-circuit `unauthenticated` when no
|
|
96
|
+
* account is on the request, before any later phase runs.
|
|
97
97
|
* 4. **Authorization phase** — resolve the acting actor (when the action's
|
|
98
98
|
* auth requires role_grants or its input declares `acting?: ActingActor`)
|
|
99
99
|
* and build the request context. Runs before input validation so
|
|
100
100
|
* role-grant-grain auth checks return 403 before 400 invalid_params;
|
|
101
|
-
* `acting` is read from raw params
|
|
101
|
+
* `acting` is read from raw params by `read_acting`, which parses it as
|
|
102
|
+
* a UUID and treats anything else as omitted.
|
|
102
103
|
* 5. **Post-authorization auth** — enforce role / keeper requirements
|
|
103
104
|
* against the request context.
|
|
104
105
|
* 6. **Validate params** — parse input against the action's `input` schema.
|
|
@@ -38,13 +38,16 @@ export declare const ActionSpec: z.ZodObject<{
|
|
|
38
38
|
backend: "backend";
|
|
39
39
|
}>;
|
|
40
40
|
/**
|
|
41
|
-
* The
|
|
41
|
+
* The flat auth shape (canonical schema in `http/auth_shape.ts`).
|
|
42
42
|
* `null` for `remote_notification` and `local_call` — those don't
|
|
43
43
|
* dispatch through the request/response auth gate.
|
|
44
44
|
*
|
|
45
45
|
* See `http/auth_shape.ts` for the design rationale (orthogonal
|
|
46
46
|
* authentication / account-resolution / actor-resolution / role-and-
|
|
47
|
-
* credential authorization axes).
|
|
47
|
+
* credential authorization axes). `required_scope` is the one field an
|
|
48
|
+
* action may not set — `compile_action_registry` rejects it, since
|
|
49
|
+
* nothing on the RPC path mounts a guard from it; the dispatcher derives
|
|
50
|
+
* the same `rpc:<method>` capability from `spec.method` instead.
|
|
48
51
|
*/
|
|
49
52
|
auth: z.ZodNullable<z.ZodObject<{
|
|
50
53
|
account: z.ZodEnum<{
|
|
@@ -59,6 +62,7 @@ export declare const ActionSpec: z.ZodObject<{
|
|
|
59
62
|
}>;
|
|
60
63
|
roles: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
61
64
|
credential_types: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
65
|
+
required_scope: z.ZodOptional<z.ZodString>;
|
|
62
66
|
}, z.core.$strict>>;
|
|
63
67
|
side_effects: z.ZodBoolean;
|
|
64
68
|
input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
@@ -142,6 +146,7 @@ export declare const RequestResponseActionSpec: z.ZodObject<{
|
|
|
142
146
|
}>;
|
|
143
147
|
roles: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
144
148
|
credential_types: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
149
|
+
required_scope: z.ZodOptional<z.ZodString>;
|
|
145
150
|
}, z.core.$strict>;
|
|
146
151
|
async: z.ZodDefault<z.ZodLiteral<true>>;
|
|
147
152
|
}, z.core.$strict>;
|
|
@@ -228,6 +233,7 @@ export declare const ActionSpecUnion: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
228
233
|
}>;
|
|
229
234
|
roles: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
230
235
|
credential_types: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
236
|
+
required_scope: z.ZodOptional<z.ZodString>;
|
|
231
237
|
}, z.core.$strict>;
|
|
232
238
|
async: z.ZodDefault<z.ZodLiteral<true>>;
|
|
233
239
|
}, z.core.$strict>, z.ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,UAAU;;;;EAAoE,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;IAItB
|
|
1
|
+
{"version":3,"file":"action_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,UAAU;;;;EAAoE,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;IAItB;;;;;;;;;;;OAWG;;;;;;;;;;;;;;;;;;;;;IAOH;;;;;;OAMG;;IAEH;;;;;;;;OAQG;;IAEH;;;;;;;;;;;;;;;;;;;OAmBG;;;;;;kBAEF,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;kBAMvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,gHAAgH;AAChH,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAKoB,CAAC;AAE9E,eAAO,MAAM,gBAAgB;;;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
|
@@ -21,13 +21,16 @@ export const ActionSpec = z.strictObject({
|
|
|
21
21
|
kind: ActionKind,
|
|
22
22
|
initiator: ActionInitiator,
|
|
23
23
|
/**
|
|
24
|
-
* The
|
|
24
|
+
* The flat auth shape (canonical schema in `http/auth_shape.ts`).
|
|
25
25
|
* `null` for `remote_notification` and `local_call` — those don't
|
|
26
26
|
* dispatch through the request/response auth gate.
|
|
27
27
|
*
|
|
28
28
|
* See `http/auth_shape.ts` for the design rationale (orthogonal
|
|
29
29
|
* authentication / account-resolution / actor-resolution / role-and-
|
|
30
|
-
* credential authorization axes).
|
|
30
|
+
* credential authorization axes). `required_scope` is the one field an
|
|
31
|
+
* action may not set — `compile_action_registry` rejects it, since
|
|
32
|
+
* nothing on the RPC path mounts a guard from it; the dispatcher derives
|
|
33
|
+
* the same `rpc:<method>` capability from `spec.method` instead.
|
|
31
34
|
*/
|
|
32
35
|
auth: RouteAuth.nullable(),
|
|
33
36
|
side_effects: ActionSideEffects,
|
|
@@ -3,21 +3,33 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `create_rpc_endpoint` (HTTP RPC) and `register_action_ws` (WebSocket)
|
|
5
5
|
* both build a `Map<method, RpcAction>` from a list of action specs and
|
|
6
|
-
* gate the build on the same
|
|
6
|
+
* gate the build on the same checks, in this order (named rather than
|
|
7
|
+
* numbered — "registry-time invariant N" elsewhere in the codebase refers to
|
|
8
|
+
* the `RouteAuth` cross-axis set in `http/auth_shape.ts`, a different list):
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
10
|
+
* - **Auth-shape biconditional** — per `assert_route_auth_acting_biconditional`
|
|
11
|
+
* in `http/auth_shape.ts`: `auth.actor !== 'none' ⟺ input declares
|
|
12
|
+
* acting?: ActingActor` (that set's invariant 2). Fires for every spec with
|
|
13
|
+
* non-null auth.
|
|
14
|
+
* - **No `auth.required_scope` on an action** — `ActionSpec.auth` is the same
|
|
15
|
+
* `RouteAuth` a route spec carries, so the field parses here, but only
|
|
16
|
+
* `apply_route_specs` mounts a guard from it and `perform_action` never
|
|
17
|
+
* reads it. Declaring it on an action would be a security control that
|
|
18
|
+
* silently does nothing, which is the exact shape token scoping was built
|
|
19
|
+
* to refuse. Rejected loudly instead; actions are governed by the
|
|
20
|
+
* per-method scope gate inside `perform_action`, which derives the same
|
|
21
|
+
* `rpc:<method>` capability from `spec.method` rather than trusting a
|
|
22
|
+
* hand-written one.
|
|
23
|
+
* - **Rate-limit / account axis** — `rate_limit: 'account' | 'both'`
|
|
24
|
+
* requires `auth.account === 'required'`; without an account on the
|
|
25
|
+
* request there is no key for the per-account bucket.
|
|
26
|
+
* - **JSON-RPC §4.2 wire validity** — `request_response` specs whose
|
|
27
|
+
* handler will reach the dispatch map must not use `z.null()` for
|
|
28
|
+
* input (the wire format forbids `"params": null`; use `z.void()`
|
|
29
|
+
* for parameterless methods).
|
|
30
|
+
* - **Duplicate method names** — JSON-RPC keys on `method`, so every
|
|
31
|
+
* spec in the array must declare a unique `method` regardless of
|
|
32
|
+
* kind / handler presence.
|
|
21
33
|
*
|
|
22
34
|
* Pre-consolidation each dispatcher inlined these checks; the comment
|
|
23
35
|
* in `actions/register_action_ws.ts` literally said "mirrors the HTTP RPC
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile_action_registry.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/compile_action_registry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"compile_action_registry.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/compile_action_registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjD,oDAAoD;AACpD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,aAAa,CAAC,MAAM,CAAC,EAC9B,WAAW,MAAM,KACf,2BA6CF,CAAC"}
|
|
@@ -3,21 +3,33 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `create_rpc_endpoint` (HTTP RPC) and `register_action_ws` (WebSocket)
|
|
5
5
|
* both build a `Map<method, RpcAction>` from a list of action specs and
|
|
6
|
-
* gate the build on the same
|
|
6
|
+
* gate the build on the same checks, in this order (named rather than
|
|
7
|
+
* numbered — "registry-time invariant N" elsewhere in the codebase refers to
|
|
8
|
+
* the `RouteAuth` cross-axis set in `http/auth_shape.ts`, a different list):
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
10
|
+
* - **Auth-shape biconditional** — per `assert_route_auth_acting_biconditional`
|
|
11
|
+
* in `http/auth_shape.ts`: `auth.actor !== 'none' ⟺ input declares
|
|
12
|
+
* acting?: ActingActor` (that set's invariant 2). Fires for every spec with
|
|
13
|
+
* non-null auth.
|
|
14
|
+
* - **No `auth.required_scope` on an action** — `ActionSpec.auth` is the same
|
|
15
|
+
* `RouteAuth` a route spec carries, so the field parses here, but only
|
|
16
|
+
* `apply_route_specs` mounts a guard from it and `perform_action` never
|
|
17
|
+
* reads it. Declaring it on an action would be a security control that
|
|
18
|
+
* silently does nothing, which is the exact shape token scoping was built
|
|
19
|
+
* to refuse. Rejected loudly instead; actions are governed by the
|
|
20
|
+
* per-method scope gate inside `perform_action`, which derives the same
|
|
21
|
+
* `rpc:<method>` capability from `spec.method` rather than trusting a
|
|
22
|
+
* hand-written one.
|
|
23
|
+
* - **Rate-limit / account axis** — `rate_limit: 'account' | 'both'`
|
|
24
|
+
* requires `auth.account === 'required'`; without an account on the
|
|
25
|
+
* request there is no key for the per-account bucket.
|
|
26
|
+
* - **JSON-RPC §4.2 wire validity** — `request_response` specs whose
|
|
27
|
+
* handler will reach the dispatch map must not use `z.null()` for
|
|
28
|
+
* input (the wire format forbids `"params": null`; use `z.void()`
|
|
29
|
+
* for parameterless methods).
|
|
30
|
+
* - **Duplicate method names** — JSON-RPC keys on `method`, so every
|
|
31
|
+
* spec in the array must declare a unique `method` regardless of
|
|
32
|
+
* kind / handler presence.
|
|
21
33
|
*
|
|
22
34
|
* Pre-consolidation each dispatcher inlined these checks; the comment
|
|
23
35
|
* in `actions/register_action_ws.ts` literally said "mirrors the HTTP RPC
|
|
@@ -50,6 +62,9 @@ export const compile_action_registry = (actions, ctx_label) => {
|
|
|
50
62
|
// per the spec union means `kind === 'request_response'`).
|
|
51
63
|
if (spec.auth !== null) {
|
|
52
64
|
assert_route_auth_acting_biconditional(spec.auth, { input: spec.input }, ctx);
|
|
65
|
+
if (spec.auth.required_scope !== undefined) {
|
|
66
|
+
throw new Error(`${ctx} declares auth.required_scope: '${spec.auth.required_scope}' — that slot is for route specs only. Actions dispatch through the RPC endpoint, where a narrowed token is gated per method by the scope check in perform_action, which derives the capability from the method name. Remove the field.`);
|
|
67
|
+
}
|
|
53
68
|
if ((spec.rate_limit === 'account' || spec.rate_limit === 'both') &&
|
|
54
69
|
spec.auth.account !== 'required') {
|
|
55
70
|
throw new Error(`${ctx} declares rate_limit: '${spec.rate_limit}' but auth.account !== 'required' — no account guaranteed for account-keyed limiting. Use 'ip' or set auth.account: 'required'.`);
|
|
@@ -5,22 +5,32 @@
|
|
|
5
5
|
* `perform_action` runs the post-parse pipeline that every action-spec
|
|
6
6
|
* handler must traverse:
|
|
7
7
|
*
|
|
8
|
-
* 1. **Pre-
|
|
9
|
-
* on `'required'` axes
|
|
10
|
-
*
|
|
11
|
-
* 2. **Validate params (400)** — `spec.input.safeParse(raw_params)` with
|
|
12
|
-
* `z.void()` / `?? {}` rules. The validated input lands inside the
|
|
13
|
-
* function so the authorization phase reads `acting` as a typed Zod
|
|
14
|
-
* field.
|
|
15
|
-
* 3. **Authorization phase** — when `auth.actor !== 'none'` (or
|
|
8
|
+
* 1. **Pre-authorization auth (401)** — short-circuits unauthenticated callers
|
|
9
|
+
* on `'required'` axes.
|
|
10
|
+
* 2. **Authorization phase** — when `auth.actor !== 'none'` (or
|
|
16
11
|
* `auth.account !== 'none' && actor === 'none'`), resolves the actor
|
|
17
12
|
* via `apply_authorization_phase` against the supplied `account_id`
|
|
18
|
-
* plus `
|
|
19
|
-
*
|
|
13
|
+
* plus the request's `acting` selector, read off the raw params by
|
|
14
|
+
* `read_acting`. Failures fold into a JSON-RPC error envelope. The
|
|
15
|
+
* test-harness escape hatch lives in the caller — pass
|
|
20
16
|
* `preset.request_context` to skip the live phase and use a pre-baked
|
|
21
17
|
* context instead.
|
|
22
|
-
*
|
|
23
|
-
* `auth.roles` against the resolved
|
|
18
|
+
* 3. **Post-authorization auth (403)** — gates `auth.credential_types`,
|
|
19
|
+
* the credential's `token_scope`, and `auth.roles` against the resolved
|
|
20
|
+
* context.
|
|
21
|
+
* 4. **Validate params (400)** — `spec.input.safeParse(raw_params)` with
|
|
22
|
+
* `z.void()` / `?? {}` rules. Runs **after** the authority gates: a
|
|
23
|
+
* 400 describing the action's input shape to a caller those gates
|
|
24
|
+
* refuse would confirm the method exists and describe how to call it,
|
|
25
|
+
* to a channel that should have learned nothing. Coarse authority
|
|
26
|
+
* facts before fine ones, and no shape disclosure before authority is
|
|
27
|
+
* settled — the same argument that puts the credential gate ahead of
|
|
28
|
+
* the scope gate. The cost: a malformed request from an *authenticated*
|
|
29
|
+
* caller now pays for the authorization phase's actor + role_grant reads
|
|
30
|
+
* before being rejected, where validation used to turn it away first.
|
|
31
|
+
* Not a new DoS lever — the same caller reaches that work anyway by
|
|
32
|
+
* sending well-formed params — but it does remove a cheap-rejection path
|
|
33
|
+
* that buggy clients used to hit.
|
|
24
34
|
* 5. **Rate limit (429)** — per-action IP / account throttling, throttle-
|
|
25
35
|
* requests semantics (every invocation records, regardless of outcome).
|
|
26
36
|
* 6. **Dispatch + DEV-only output validation + error normalization** —
|
|
@@ -140,10 +150,10 @@ export type PerformActionResult = {
|
|
|
140
150
|
* transport calls into this with pre-parsed inputs and binds the result
|
|
141
151
|
* to its wire shape.
|
|
142
152
|
*
|
|
143
|
-
* Phase order: 401 →
|
|
144
|
-
* dispatcher skips the live authorization phase and uses the supplied
|
|
145
|
-
* pre-baked context for post-authorization checks;
|
|
146
|
-
*
|
|
153
|
+
* Phase order: 401 → authz → 403 → 400 → handler. On the test-preset path
|
|
154
|
+
* the dispatcher skips the live authorization phase and uses the supplied
|
|
155
|
+
* pre-baked context for post-authorization checks; the 401 still fires when
|
|
156
|
+
* the harness omits `account_id`.
|
|
147
157
|
*/
|
|
148
158
|
export declare const perform_action: (input: PerformActionInput, deps: PerformActionDeps) => Promise<PerformActionResult>;
|
|
149
159
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"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"}
|