@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
|
@@ -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** —
|
|
@@ -45,50 +55,39 @@ import { dispatch_with_post_commit_rollback } from '../http/pending_effects.js';
|
|
|
45
55
|
import { JSONRPC_VERSION } from '../http/jsonrpc.js';
|
|
46
56
|
import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, http_status_to_jsonrpc_error_code, JSONRPC_ERROR_CODES, dev_only } from '../http/jsonrpc_errors.js';
|
|
47
57
|
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_TOKEN_SCOPE_REQUIRED } from '../http/error_schemas.js';
|
|
48
|
-
import { is_public_auth } from '../http/auth_shape.js';
|
|
49
|
-
import { token_scope_admits_method } from '../auth/token_scope.js';
|
|
58
|
+
import { is_public_auth, needs_actor, parse_acting } from '../http/auth_shape.js';
|
|
59
|
+
import { token_scope_admits_method, token_scope_method_capability } from '../auth/token_scope.js';
|
|
50
60
|
/**
|
|
51
61
|
* The shared dispatch core. Pure data — no Hono context, no socket. Each
|
|
52
62
|
* transport calls into this with pre-parsed inputs and binds the result
|
|
53
63
|
* to its wire shape.
|
|
54
64
|
*
|
|
55
|
-
* Phase order: 401 →
|
|
56
|
-
* dispatcher skips the live authorization phase and uses the supplied
|
|
57
|
-
* pre-baked context for post-authorization checks;
|
|
58
|
-
*
|
|
65
|
+
* Phase order: 401 → authz → 403 → 400 → handler. On the test-preset path
|
|
66
|
+
* the dispatcher skips the live authorization phase and uses the supplied
|
|
67
|
+
* pre-baked context for post-authorization checks; the 401 still fires when
|
|
68
|
+
* the harness omits `account_id`.
|
|
59
69
|
*/
|
|
60
70
|
export const perform_action = async (input, deps) => {
|
|
61
71
|
const { action, raw_params, request_id: id, account_id, credential_type, token_scope, client_ip, signal, notify, connection_id, request_client, preset } = input;
|
|
62
72
|
const { db, pending_effects, post_commit_effects, log, action_ip_rate_limiter, action_account_rate_limiter } = deps;
|
|
63
73
|
const { spec, handler } = action;
|
|
64
74
|
const action_auth = spec.auth;
|
|
65
|
-
// step 1: pre-
|
|
66
|
-
const pre =
|
|
75
|
+
// step 1: pre-authorization auth — 401 short-circuit.
|
|
76
|
+
const pre = check_action_auth_pre_authorization(action_auth, account_id);
|
|
67
77
|
if (pre)
|
|
68
78
|
return error_result(pre);
|
|
69
|
-
// step 2:
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return error_result(jsonrpc_error_messages.invalid_params('invalid params', dev_only({ issues: parse_result.error.issues })));
|
|
76
|
-
}
|
|
77
|
-
const validated_input = parse_result.data;
|
|
78
|
-
// step 3: authorization phase. `acting` reads off the typed Zod field
|
|
79
|
-
// validated in step 2. Per registry-time invariant 2,
|
|
80
|
-
// `auth.actor !== 'none' ⟺ input declares acting?: ActingActor` — so
|
|
81
|
-
// the typed read is safe whenever the dispatcher needs it.
|
|
79
|
+
// step 2: authorization phase. `acting` reads off the raw params rather
|
|
80
|
+
// than a validated field, because validation now runs after the authority
|
|
81
|
+
// gates. Per registry-time invariant 2,
|
|
82
|
+
// `auth.actor !== 'none' ⟺ input declares acting?: ActingActor` — so the
|
|
83
|
+
// selector is present on exactly the specs that need it, and step 4 is
|
|
84
|
+
// what rejects a malformed one.
|
|
82
85
|
let request_context = null;
|
|
83
86
|
if (preset !== undefined) {
|
|
84
87
|
request_context = preset.request_context;
|
|
85
88
|
}
|
|
86
89
|
else if (!is_public_auth(action_auth)) {
|
|
87
|
-
const
|
|
88
|
-
const acting_value = validated_with_acting && typeof validated_with_acting.acting === 'string'
|
|
89
|
-
? validated_with_acting.acting
|
|
90
|
-
: undefined;
|
|
91
|
-
const result = await apply_authorization_phase({ db }, account_id, action_auth, acting_value);
|
|
90
|
+
const result = await apply_authorization_phase({ db }, account_id, action_auth, read_acting(action_auth, raw_params));
|
|
92
91
|
if (!result.ok) {
|
|
93
92
|
const { error: reason, ...rest } = result.body;
|
|
94
93
|
const code = http_status_to_jsonrpc_error_code(result.status);
|
|
@@ -102,10 +101,19 @@ export const perform_action = async (input, deps) => {
|
|
|
102
101
|
// unauthenticated-optional axis; the handler sees null in either case.
|
|
103
102
|
request_context = result.request_context;
|
|
104
103
|
}
|
|
105
|
-
// step
|
|
104
|
+
// step 3: post-authorization auth — credential gate, then scope, then role.
|
|
106
105
|
const post = check_action_auth_post_authorization(action_auth, request_context, credential_type, action.spec.method, token_scope);
|
|
107
106
|
if (post)
|
|
108
107
|
return error_result(post);
|
|
108
|
+
// step 4: validate params. JSON-RPC 2.0 §4.2 forbids `params: null`;
|
|
109
|
+
// registration sites reject `z.null()` inputs. Empty-body convention
|
|
110
|
+
// (`raw_params ?? {}`) lets all-optional-object methods omit `params`.
|
|
111
|
+
const params = is_void_schema(spec.input) ? raw_params : (raw_params ?? {});
|
|
112
|
+
const parse_result = spec.input.safeParse(params);
|
|
113
|
+
if (!parse_result.success) {
|
|
114
|
+
return error_result(jsonrpc_error_messages.invalid_params('invalid params', dev_only({ issues: parse_result.error.issues })));
|
|
115
|
+
}
|
|
116
|
+
const validated_input = parse_result.data;
|
|
109
117
|
// step 5: rate limit — throttle-requests semantics (record on every
|
|
110
118
|
// invocation, no success-reset). Same limiters shared with the WS
|
|
111
119
|
// dispatcher so an attacker can't switch transports to bypass the budget.
|
|
@@ -203,16 +211,33 @@ const rate_limited_result = (retry_after) => {
|
|
|
203
211
|
};
|
|
204
212
|
};
|
|
205
213
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
214
|
+
* Read the `acting` actor selector off the raw params for the authorization
|
|
215
|
+
* phase.
|
|
216
|
+
*
|
|
217
|
+
* Consulted only when the spec declares an actor axis; `parse_acting` owns
|
|
218
|
+
* what counts as a selector and why a malformed one reads as omitted.
|
|
219
|
+
*
|
|
220
|
+
* @param auth - the spec's auth shape; no actor axis means no selector
|
|
221
|
+
* @param raw_params - the request's unvalidated params
|
|
222
|
+
* @returns the actor id, or `undefined` when absent, malformed, or unwanted
|
|
223
|
+
*/
|
|
224
|
+
const read_acting = (auth, raw_params) => {
|
|
225
|
+
if (!needs_actor(auth))
|
|
226
|
+
return undefined;
|
|
227
|
+
if (typeof raw_params !== 'object' || raw_params === null)
|
|
228
|
+
return undefined;
|
|
229
|
+
return parse_acting(raw_params.acting);
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Pre-authorization auth gate — fires before the authorization phase so
|
|
233
|
+
* missing credentials short-circuit with `unauthenticated`.
|
|
209
234
|
*
|
|
210
235
|
* 401 fires when `auth.account === 'required'` (or `auth.actor === 'required'`,
|
|
211
236
|
* since registry-time invariant 3 forbids accountless actors in v1) and
|
|
212
237
|
* no account is on the request. `'optional'` axes pass through — the
|
|
213
238
|
* authorization phase decides based on whatever the credential supports.
|
|
214
239
|
*/
|
|
215
|
-
const
|
|
240
|
+
const check_action_auth_pre_authorization = (auth, account_id) => {
|
|
216
241
|
if (auth.account === 'required' || auth.actor === 'required') {
|
|
217
242
|
if (account_id == null) {
|
|
218
243
|
// Carry the reason on `error.data.reason` (symmetric with the 403
|
|
@@ -264,7 +289,7 @@ const check_action_auth_post_authorization = (auth, request_context, credential_
|
|
|
264
289
|
if (token_scope && !token_scope_admits_method(token_scope, method)) {
|
|
265
290
|
return jsonrpc_error_messages.forbidden('forbidden', {
|
|
266
291
|
reason: ERROR_TOKEN_SCOPE_REQUIRED,
|
|
267
|
-
required_scope:
|
|
292
|
+
required_scope: token_scope_method_capability(method)
|
|
268
293
|
});
|
|
269
294
|
}
|
|
270
295
|
if (auth.roles?.length) {
|
|
@@ -174,10 +174,10 @@ export interface RegisterActionWsResult {
|
|
|
174
174
|
* - Notifications (method + no id) are silently dropped per JSON-RPC spec.
|
|
175
175
|
* Exception: `cancel` notifications abort the matching pending request's
|
|
176
176
|
* `ctx.signal` before bubbling out.
|
|
177
|
-
* - Per-message dispatch goes through `perform_action`: pre-
|
|
178
|
-
* auth (401) →
|
|
179
|
-
*
|
|
180
|
-
*
|
|
177
|
+
* - Per-message dispatch goes through `perform_action`: pre-authorization
|
|
178
|
+
* auth (401) → authorization phase → post-authorization auth (403) →
|
|
179
|
+
* input validation (400) → rate limit (429) → handler (with transaction
|
|
180
|
+
* wrap iff `spec.side_effects: true`) → DEV output validation.
|
|
181
181
|
* - Authorization phase runs **per message** — role_grant changes during a
|
|
182
182
|
* connection lifetime are picked up on the next message without any
|
|
183
183
|
* in-place refresh. Authentication invalidation closes the socket via
|
|
@@ -53,10 +53,10 @@ export const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60_000;
|
|
|
53
53
|
* - Notifications (method + no id) are silently dropped per JSON-RPC spec.
|
|
54
54
|
* Exception: `cancel` notifications abort the matching pending request's
|
|
55
55
|
* `ctx.signal` before bubbling out.
|
|
56
|
-
* - Per-message dispatch goes through `perform_action`: pre-
|
|
57
|
-
* auth (401) →
|
|
58
|
-
*
|
|
59
|
-
*
|
|
56
|
+
* - Per-message dispatch goes through `perform_action`: pre-authorization
|
|
57
|
+
* auth (401) → authorization phase → post-authorization auth (403) →
|
|
58
|
+
* input validation (400) → rate limit (429) → handler (with transaction
|
|
59
|
+
* wrap iff `spec.side_effects: true`) → DEV output validation.
|
|
60
60
|
* - Authorization phase runs **per message** — role_grant changes during a
|
|
61
61
|
* connection lifetime are picked up on the next message without any
|
|
62
62
|
* in-place refresh. Authentication invalidation closes the socket via
|
|
@@ -98,7 +98,7 @@ export const register_action_ws = (options) => {
|
|
|
98
98
|
// scoped token that could upgrade would observe far more than it was
|
|
99
99
|
// granted.
|
|
100
100
|
async (c, next) => {
|
|
101
|
-
const denied = token_scope_surface_denial(c, 'ws_upgrade');
|
|
101
|
+
const denied = token_scope_surface_denial(c, 'surface:ws_upgrade');
|
|
102
102
|
if (denied)
|
|
103
103
|
return denied;
|
|
104
104
|
return next();
|
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
* 1. `verify_request_source(allowed_origins)` — reject disallowed origins
|
|
8
8
|
* before the upgrade handshake runs.
|
|
9
9
|
* 2. `require_auth` — reject unauthenticated upgrades.
|
|
10
|
-
* 3.
|
|
10
|
+
* 3. `require_token_scope('surface:ws_upgrade')` — rule 3: a narrowed token
|
|
11
|
+
* cannot hold a socket. Mounted here, ahead of the role guard, so a
|
|
12
|
+
* narrowed token is told about its scope rather than about a role it
|
|
13
|
+
* also lacks. `register_action_ws` repeats the check on its own route
|
|
14
|
+
* chain, which is what covers consumers mounting it directly; the
|
|
15
|
+
* earlier answer wins when both are wired.
|
|
16
|
+
* 4. **Authorization phase** — resolve the acting actor against the
|
|
11
17
|
* authenticated account plus an optional `?acting=<uuid>` query string,
|
|
12
18
|
* and build the `RequestContext` that per-message dispatch reads.
|
|
13
19
|
* Multi-actor accounts must supply `?acting` to pick a persona;
|
|
14
20
|
* single-actor accounts work without it.
|
|
15
|
-
*
|
|
21
|
+
* 5. Optional `require_role(required_roles)` — for endpoints gated to a
|
|
16
22
|
* non-empty any-of set of roles.
|
|
17
23
|
*
|
|
18
24
|
* Then delegates to `register_action_ws` for per-message JSON-RPC
|
|
@@ -43,14 +49,16 @@ export interface RegisterWsEndpointOptions extends RegisterActionWsOptions {
|
|
|
43
49
|
}
|
|
44
50
|
/**
|
|
45
51
|
* Mount a WebSocket endpoint with the standard upgrade stack (origin check
|
|
46
|
-
* + auth + actor resolution + optional role) and JSON-RPC
|
|
52
|
+
* + auth + token scope + actor resolution + optional role) and JSON-RPC
|
|
53
|
+
* dispatch.
|
|
47
54
|
*
|
|
48
55
|
* Returns the `BackendWebsocketTransport` (supplied or freshly
|
|
49
56
|
* created), same as `register_action_ws` — retain it to wire
|
|
50
57
|
* `create_ws_auth_guard` on `on_audit_event` or to broadcast.
|
|
51
58
|
*
|
|
52
|
-
* @mutates options.app - applies origin/auth/authorization/role middleware
|
|
53
|
-
* then registers the `GET path` route via the inner
|
|
59
|
+
* @mutates options.app - applies origin/auth/scope/authorization/role middleware
|
|
60
|
+
* via `app.use`, then registers the `GET path` route via the inner
|
|
61
|
+
* `register_action_ws`
|
|
54
62
|
*/
|
|
55
63
|
export declare const register_ws_endpoint: (options: RegisterWsEndpointOptions) => RegisterActionWsResult;
|
|
56
64
|
//# sourceMappingURL=register_ws_endpoint.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAaH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAoBF,CAAC"}
|
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
* 1. `verify_request_source(allowed_origins)` — reject disallowed origins
|
|
8
8
|
* before the upgrade handshake runs.
|
|
9
9
|
* 2. `require_auth` — reject unauthenticated upgrades.
|
|
10
|
-
* 3.
|
|
10
|
+
* 3. `require_token_scope('surface:ws_upgrade')` — rule 3: a narrowed token
|
|
11
|
+
* cannot hold a socket. Mounted here, ahead of the role guard, so a
|
|
12
|
+
* narrowed token is told about its scope rather than about a role it
|
|
13
|
+
* also lacks. `register_action_ws` repeats the check on its own route
|
|
14
|
+
* chain, which is what covers consumers mounting it directly; the
|
|
15
|
+
* earlier answer wins when both are wired.
|
|
16
|
+
* 4. **Authorization phase** — resolve the acting actor against the
|
|
11
17
|
* authenticated account plus an optional `?acting=<uuid>` query string,
|
|
12
18
|
* and build the `RequestContext` that per-message dispatch reads.
|
|
13
19
|
* Multi-actor accounts must supply `?acting` to pick a persona;
|
|
14
20
|
* single-actor accounts work without it.
|
|
15
|
-
*
|
|
21
|
+
* 5. Optional `require_role(required_roles)` — for endpoints gated to a
|
|
16
22
|
* non-empty any-of set of roles.
|
|
17
23
|
*
|
|
18
24
|
* Then delegates to `register_action_ws` for per-message JSON-RPC
|
|
@@ -21,7 +27,7 @@
|
|
|
21
27
|
* @module
|
|
22
28
|
*/
|
|
23
29
|
import { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
24
|
-
import { apply_authorization_phase, REQUEST_CONTEXT_KEY, require_auth, require_role } from '../auth/request_context.js';
|
|
30
|
+
import { apply_authorization_phase, REQUEST_CONTEXT_KEY, require_auth, require_role, require_token_scope } from '../auth/request_context.js';
|
|
25
31
|
import { verify_request_source } from '../http/origin.js';
|
|
26
32
|
import { ACCOUNT_ID_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
27
33
|
import { register_action_ws } from './register_action_ws.js';
|
|
@@ -67,19 +73,22 @@ const create_ws_authorization_middleware = (db) => {
|
|
|
67
73
|
};
|
|
68
74
|
/**
|
|
69
75
|
* Mount a WebSocket endpoint with the standard upgrade stack (origin check
|
|
70
|
-
* + auth + actor resolution + optional role) and JSON-RPC
|
|
76
|
+
* + auth + token scope + actor resolution + optional role) and JSON-RPC
|
|
77
|
+
* dispatch.
|
|
71
78
|
*
|
|
72
79
|
* Returns the `BackendWebsocketTransport` (supplied or freshly
|
|
73
80
|
* created), same as `register_action_ws` — retain it to wire
|
|
74
81
|
* `create_ws_auth_guard` on `on_audit_event` or to broadcast.
|
|
75
82
|
*
|
|
76
|
-
* @mutates options.app - applies origin/auth/authorization/role middleware
|
|
77
|
-
* then registers the `GET path` route via the inner
|
|
83
|
+
* @mutates options.app - applies origin/auth/scope/authorization/role middleware
|
|
84
|
+
* via `app.use`, then registers the `GET path` route via the inner
|
|
85
|
+
* `register_action_ws`
|
|
78
86
|
*/
|
|
79
87
|
export const register_ws_endpoint = (options) => {
|
|
80
88
|
const { app, path, allowed_origins, db, required_roles, log = new Logger('[ws]'), ...rest } = options;
|
|
81
89
|
app.use(path, verify_request_source(allowed_origins));
|
|
82
90
|
app.use(path, require_auth);
|
|
91
|
+
app.use(path, require_token_scope('surface:ws_upgrade'));
|
|
83
92
|
app.use(path, create_ws_authorization_middleware(db));
|
|
84
93
|
if (required_roles?.length) {
|
|
85
94
|
app.use(path, require_role(required_roles));
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -61,6 +61,7 @@ Convention — `*_schema.ts` is Zod-only; `*_ddl.ts` holds DDL strings.
|
|
|
61
61
|
- `auth/scope_kind_schema.ts` — `ScopeKindName`, `create_scope_kind_schema` (open registry, no builtins).
|
|
62
62
|
- `auth/credential_type_schema.ts` — `CredentialTypeName`, `CREDENTIAL_TYPE_SESSION` / `_API_TOKEN` / `_DAEMON_TOKEN`, `create_credential_type_schema`.
|
|
63
63
|
- `auth/grant_path_schema.ts` — `GrantPathName`, `GRANT_PATH_ADMIN` / `_SELF_SERVICE` / `_SYSTEM` / `_BOOTSTRAP`, `create_grant_path_schema`.
|
|
64
|
+
- `auth/token_scope.ts` — per-credential authority narrowing stored on `api_token.scope`: `TokenScope` / `TokenScopeInput` + constructors, `parse_stored_token_scope` (fail-closed), `token_scope_admits_method` / `_non_rpc`, and the `<section>:<id>` capability vocabulary `RouteAuth.required_scope` is written in (`parse_token_scope_capability`, `token_scope_admits_capability`, `token_scope_denied_body`, `TOKEN_SURFACES`). Not an open registry — see ../../../docs/security.md §Token scoping for why the identifier half is deliberately unregistered.
|
|
64
65
|
- `auth/auth_ddl.ts` — `CREATE TABLE` / index / seed strings for the core identity tables.
|
|
65
66
|
- `auth/audit_log_schema.ts` — `AUDIT_EVENT_TYPES` (27 builtins), `AuditEventType` / `AuditEventTypeName`, `audit_metadata_schemas`, `AuditLogEvent`, `AuditLogInput`, `AuditLogConfig`, `create_audit_log_config`.
|
|
66
67
|
- `auth/audit_log_ddl.ts` — `audit_log` table DDL with `seq BIGSERIAL` for cursor-based gap fill (BIGSERIAL converges with the Rust spine; `create_db` registers a `pg.types` int8 parser so `seq` still reads as a JS number).
|
|
@@ -123,7 +124,7 @@ they track the same config. Sample via `get_*`; `reset_*` are test-only.
|
|
|
123
124
|
- `auth/bootstrap_routes.ts` — `POST /bootstrap` + `check_bootstrap_status`; `BootstrapStatus` runtime ref.
|
|
124
125
|
- `auth/signup_routes.ts` — `POST /signup` (open or invite-gated).
|
|
125
126
|
- `auth/audit_log_routes.ts` — optional `GET /audit/stream` (SSE); list/history are on the RPC surface.
|
|
126
|
-
- `auth/auth_guard_resolver.ts` — `fuz_auth_guard_resolver` injected into `apply_route_specs` so the framework stays auth-agnostic.
|
|
127
|
+
- `auth/auth_guard_resolver.ts` — `fuz_auth_guard_resolver` injected into `apply_route_specs` so the framework stays auth-agnostic. Maps each `RouteAuth` axis to a guard and is the single site every route spec traverses, so it also carries the two invariants the schema can't (a `required_scope` that isn't a well-formed capability, and one on an unrestricted route).
|
|
127
128
|
|
|
128
129
|
**Hono-free route shapes.** Each cookie/SSE-coupled route module has a sibling
|
|
129
130
|
`*_route_schema.ts` (`account_route_schema.ts`, `signup_route_schema.ts`,
|
|
@@ -158,7 +159,7 @@ Everything else listed under §RPC action surfaces.
|
|
|
158
159
|
### Middleware
|
|
159
160
|
|
|
160
161
|
- `auth/middleware.ts` — `create_auth_middleware_specs(deps, options)` assembles `[origin, session, request_context, bearer_auth]` + optional `daemon_token`.
|
|
161
|
-
- `auth/request_context.ts` — `RequestContext`, `resolve_acting_actor`, `build_request_context`, predicates (`has_role`, `has_scoped_role`, `has_any_scoped_role`), guards (`require_auth`, `require_role`, `require_credential_types`), `refresh_role_grants`.
|
|
162
|
+
- `auth/request_context.ts` — `RequestContext`, `resolve_acting_actor`, `build_request_context`, predicates (`has_role`, `has_scoped_role`, `has_any_scoped_role`), guards (`require_auth`, `require_role`, `require_credential_types`, `require_token_scope`), `token_scope_surface_denial` (the direct-call form, for surfaces that aren't route specs), `refresh_role_grants`.
|
|
162
163
|
- `auth/session_middleware.ts` — `process_session_cookie` integration, `create_session_and_set_cookie` (shared by login / signup / bootstrap).
|
|
163
164
|
- `auth/bearer_auth.ts` — soft-fail bearer middleware; rejects when `Origin` or `Referer` present (browser context).
|
|
164
165
|
- `auth/daemon_token_middleware.ts` — `start_daemon_token_rotation` + `create_daemon_token_middleware(state, deps, log)` (atomic file write, soft-fail validation, keeper account resolution). Soft-fails — discards the credential (pass-through, no own 401/503) on **every** non-success path: browser context (`Origin`/`Referer` present), malformed/invalid token, and valid-token-but-no-keeper all `next()` through to the dispatcher's `credential_type_required` (403) gate, mirroring the bearer guard and the Rust spine's `resolve.rs`. Daemon tokens are loopback-only, so browser context never arises in practice — the discard is defense-in-depth.
|
|
@@ -274,12 +275,15 @@ Sets `c.var.account_id` + `CREDENTIAL_TYPE_KEY` on a valid credential.
|
|
|
274
275
|
Account-only — never loads actor or role_grants, never populates
|
|
275
276
|
`REQUEST_CONTEXT_KEY`.
|
|
276
277
|
|
|
277
|
-
**Authorization runs
|
|
278
|
-
401 →
|
|
279
|
-
When the route's input declares `acting?: ActingActor` or its auth
|
|
280
|
-
role_grants, the authorization phase calls `resolve_acting_actor` over
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
**Authorization runs before input validation**, matching the dispatcher's
|
|
279
|
+
401 → authz → 403 → 400 phase order (see `http/CLAUDE.md` §Validation
|
|
280
|
+
pipeline). When the route's input declares `acting?: ActingActor` or its auth
|
|
281
|
+
requires role_grants, the authorization phase calls `resolve_acting_actor` over
|
|
282
|
+
the `acting` selector — the validated query value on GETs, read off the raw
|
|
283
|
+
body on mutations — and builds an actor-bound `RequestContext`. A malformed
|
|
284
|
+
selector reads as omitted (it only ever picks among actors the account already
|
|
285
|
+
owns, and input validation rejects it a step later). Account-grain routes run
|
|
286
|
+
with `RequestContext.actor: null`.
|
|
283
287
|
|
|
284
288
|
`apply_authorization_phase` is pure data — returns `AuthorizationResult`
|
|
285
289
|
(`{ok: true, request_context: RequestContext | null} | {ok: false, status, body}`)
|
|
@@ -399,7 +403,10 @@ Keeper is not a dedicated guard — it's a composable `RouteAuth` shape:
|
|
|
399
403
|
`{account: 'required', actor: 'required', roles: ['keeper'], credential_types: ['daemon_token']}`.
|
|
400
404
|
The two-part check is `require_credential_types(['daemon_token'])` (403
|
|
401
405
|
`ERROR_CREDENTIAL_TYPE_REQUIRED`) followed by `require_role(['keeper'])`
|
|
402
|
-
(403 `ERROR_INSUFFICIENT_PERMISSIONS`)
|
|
406
|
+
(403 `ERROR_INSUFFICIENT_PERMISSIONS`) — the channel gate lands
|
|
407
|
+
pre-authorization and the role gate post-authorization, so a non-daemon
|
|
408
|
+
credential is refused before the route resolves an actor for it. Same
|
|
409
|
+
scope-aware semantics mirrored
|
|
403
410
|
in the HTTP RPC dispatcher (`actions/action_rpc.ts`), the WS dispatcher
|
|
404
411
|
(`actions/register_action_ws.ts`), and the admin bypasses inside
|
|
405
412
|
`auth/role_grant_offer_actions.ts`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB;;kBAA0C,CAAC;AACxE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,+DAA+D;AAC/D,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GACxC,gBAAe,MAAkC,KAC/C,IAAI,CAAC,SAAS,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"audit_log_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB;;kBAA0C,CAAC;AACxE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,+DAA+D;AAC/D,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GACxC,gBAAe,MAAkC,KAC/C,IAAI,CAAC,SAAS,EAAE,SAAS,CAkB1B,CAAC"}
|
|
@@ -28,7 +28,17 @@ export const DEFAULT_AUDIT_STREAM_ROLE = 'admin';
|
|
|
28
28
|
export const create_audit_log_route_shape = (required_role = DEFAULT_AUDIT_STREAM_ROLE) => ({
|
|
29
29
|
method: 'GET',
|
|
30
30
|
path: '/audit/stream',
|
|
31
|
-
auth: {
|
|
31
|
+
auth: {
|
|
32
|
+
account: 'required',
|
|
33
|
+
actor: 'required',
|
|
34
|
+
roles: [required_role],
|
|
35
|
+
// Rule 3 — a narrowed token holds no audit feed. Same reasoning as the
|
|
36
|
+
// WS upgrade: this is a long-lived server→client stream whose contents
|
|
37
|
+
// are decided by the account's role, not by any per-message scope
|
|
38
|
+
// check, so there is no point after open at which a narrowing could
|
|
39
|
+
// apply.
|
|
40
|
+
required_scope: 'surface:audit_stream'
|
|
41
|
+
},
|
|
32
42
|
description: 'Subscribe to realtime audit log events',
|
|
33
43
|
query: AuditStreamQuery,
|
|
34
44
|
input: z.null(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAuB,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"audit_log_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAuB,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAI3E,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACpC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QACR,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;QAC1F,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GAAI,UAAU,oBAAoB,KAAG,KAAK,CAAC,SAAS,CA4B5F,CAAC"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { create_audit_log_route_shape } from './audit_log_route_schema.js';
|
|
15
15
|
import { create_sse_response } from '../realtime/sse.js';
|
|
16
|
-
import { AUTH_SESSION_TOKEN_HASH_KEY, require_request_context
|
|
16
|
+
import { AUTH_SESSION_TOKEN_HASH_KEY, require_request_context } from './request_context.js';
|
|
17
17
|
import { AUDIT_LOG_CHANNEL } from '../realtime/sse_auth_guard.js';
|
|
18
18
|
/**
|
|
19
19
|
* Create the optional audit-log SSE route spec.
|
|
@@ -32,14 +32,9 @@ export const create_audit_log_route_specs = (options) => {
|
|
|
32
32
|
{
|
|
33
33
|
...create_audit_log_route_shape(options.required_role),
|
|
34
34
|
handler: (c) => {
|
|
35
|
-
// Rule 3
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// per-message scope check, so there is no point after open at
|
|
39
|
-
// which a narrowing could apply.
|
|
40
|
-
const denied = token_scope_surface_denial(c, 'audit_stream');
|
|
41
|
-
if (denied)
|
|
42
|
-
return denied;
|
|
35
|
+
// Rule 3 is declared on the route shape's `auth.required_scope`,
|
|
36
|
+
// so a narrowed token is refused ahead of the role gate rather
|
|
37
|
+
// than here.
|
|
43
38
|
const ctx = require_request_context(c);
|
|
44
39
|
// scope = session hash (capped → tabs-per-session limit and
|
|
45
40
|
// session-specific `session_revoke` close). groups = [account_id]
|
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth guard resolver for the route spec system.
|
|
3
3
|
*
|
|
4
|
-
* Maps the
|
|
5
|
-
* `credential_types`) to two-phase middleware sets that
|
|
4
|
+
* Maps the five-axis `RouteAuth` (`account` / `actor` / `roles` /
|
|
5
|
+
* `credential_types` / `required_scope`) to two-phase middleware sets that
|
|
6
6
|
* `apply_route_specs` weaves into the per-route pipeline:
|
|
7
7
|
*
|
|
8
|
-
* - `
|
|
9
|
-
*
|
|
10
|
-
* 'required'`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
8
|
+
* - `pre_authorization` runs before the authorization phase, and holds every
|
|
9
|
+
* gate that reads only what the auth middleware already set: `require_auth`
|
|
10
|
+
* whenever `auth.account === 'required'` or `auth.actor === 'required'`
|
|
11
|
+
* (per registry-time invariant 3, `actor: 'required'` today implies a
|
|
12
|
+
* credential — accountless actors are out of scope for v1),
|
|
13
|
+
* `require_credential_types(types)` whenever `auth.credential_types?.length`,
|
|
14
|
+
* and the token-scope guard whenever `auth.required_scope` names a capability.
|
|
15
|
+
* All three fire before any body parsing, so a caller they refuse never sees
|
|
16
|
+
* route-shape information — and before the authorization phase, so a wrong
|
|
17
|
+
* channel costs no actor resolution.
|
|
18
|
+
* - `post_authorization` runs after the dispatcher's authorization phase has
|
|
19
|
+
* populated `RequestContext`, and holds the one gate that needs it:
|
|
20
|
+
* `require_role(roles)`, whenever `auth.roles?.length`, reads
|
|
21
|
+
* `c.var.request_context.role_grants`.
|
|
22
|
+
*
|
|
23
|
+
* The resulting order — credential presence, then credential type, then scope,
|
|
24
|
+
* then role — is the spine's canonical coarse-to-fine ordering, the same one
|
|
25
|
+
* `check_action_auth_post_authorization` runs for actions. A route declaring
|
|
26
|
+
* both a credential gate and a surface gate therefore answers with the
|
|
27
|
+
* coarser fact ("this channel may never call me") rather than the finer one
|
|
28
|
+
* ("your token is scoped"), on both dispatch surfaces alike.
|
|
19
29
|
*
|
|
20
30
|
* Public routes (`auth.account === 'none' && auth.actor === 'none'`)
|
|
21
31
|
* yield empty guard arrays. `'optional'` axes contribute no
|
|
22
|
-
* pre-
|
|
32
|
+
* pre-authorization 401; the authorization phase sets `RequestContext`
|
|
23
33
|
* to whatever the credential supports and the post-authorization
|
|
24
34
|
* gates decide whether the actor's role_grants / credential type match.
|
|
25
35
|
*
|
|
@@ -29,16 +39,33 @@ import type { AuthGuardResolver } from '../http/route_spec.js';
|
|
|
29
39
|
/**
|
|
30
40
|
* Standard auth guard resolver for fuz_app.
|
|
31
41
|
*
|
|
32
|
-
* Reads each axis of the
|
|
33
|
-
*
|
|
42
|
+
* Reads each axis of the `RouteAuth` shape and emits the corresponding
|
|
43
|
+
* middleware:
|
|
34
44
|
*
|
|
35
|
-
* - `account === 'required'` or `actor === 'required'` → pre-
|
|
45
|
+
* - `account === 'required'` or `actor === 'required'` → pre-authorization `require_auth`
|
|
46
|
+
* - `credential_types?.length` → pre-authorization `require_credential_types(types)`
|
|
47
|
+
* - `required_scope` → pre-authorization `require_token_scope(capability)`
|
|
36
48
|
* - `roles?.length` → post-authorization `require_role(roles)` (multi-role any-of)
|
|
37
|
-
* - `credential_types?.length` → post-authorization `require_credential_types(types)`
|
|
38
49
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
50
|
+
* Guards run in declaration order, which is the coarse-to-fine order above:
|
|
51
|
+
* presence first (an anonymous caller holds neither a credential type nor a
|
|
52
|
+
* scope to check), then the channel, then the scope, then the role — the last
|
|
53
|
+
* being the only one that needs the resolved `RequestContext`.
|
|
54
|
+
*
|
|
55
|
+
* @throws Error if `auth.required_scope` is not a well-formed capability string
|
|
56
|
+
* (via `require_token_scope`) — `RouteAuth` types it as a plain string to keep
|
|
57
|
+
* `http/` free of an `auth/` import, so this is where the value is checked.
|
|
58
|
+
* The check is on *shape*, not membership: the vocabulary is deliberately open
|
|
59
|
+
* so a consumer can name its own surface, and a `surface:` name decides
|
|
60
|
+
* nothing (rule 3 is all-or-nothing), so the closed set would only have been
|
|
61
|
+
* a spelling check on a diagnostic string. fuz_app's own declarations are
|
|
62
|
+
* pinned to the surfaces it actually mounts by the surface census.
|
|
63
|
+
* @throws Error if `auth.required_scope` sits on an unrestricted route
|
|
64
|
+
* (`account: 'none' && actor: 'none'`). The same holder reaches that route
|
|
65
|
+
* by dropping the credential, so the guard reads as a control and enforces
|
|
66
|
+
* nothing — the shape `compile_action_registry` refuses on an `ActionSpec`
|
|
67
|
+
* for the same reason. Extends `RouteAuth`'s leaf invariant, which the
|
|
68
|
+
* route pipeline never parses.
|
|
42
69
|
*/
|
|
43
70
|
export declare const fuz_auth_guard_resolver: AuthGuardResolver;
|
|
44
71
|
//# sourceMappingURL=auth_guard_resolver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth_guard_resolver.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/auth_guard_resolver.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth_guard_resolver.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/auth_guard_resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AASH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,uBAAuB,EAAE,iBAuBrC,CAAC"}
|