@fuzdev/fuz_app 0.53.0 → 0.55.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.
Files changed (144) hide show
  1. package/dist/actions/CLAUDE.md +68 -13
  2. package/dist/actions/action_codegen.d.ts +13 -0
  3. package/dist/actions/action_codegen.d.ts.map +1 -1
  4. package/dist/actions/action_codegen.js +15 -1
  5. package/dist/actions/action_rpc.d.ts +60 -7
  6. package/dist/actions/action_rpc.d.ts.map +1 -1
  7. package/dist/actions/action_rpc.js +158 -44
  8. package/dist/actions/register_action_ws.d.ts +4 -4
  9. package/dist/actions/register_action_ws.js +6 -6
  10. package/dist/actions/register_ws_endpoint.d.ts +20 -7
  11. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  12. package/dist/actions/register_ws_endpoint.js +30 -5
  13. package/dist/actions/transports.d.ts.map +1 -1
  14. package/dist/actions/transports.js +0 -4
  15. package/dist/auth/CLAUDE.md +230 -63
  16. package/dist/auth/account_actions.d.ts +6 -6
  17. package/dist/auth/account_actions.d.ts.map +1 -1
  18. package/dist/auth/account_actions.js +8 -11
  19. package/dist/auth/account_queries.d.ts +6 -3
  20. package/dist/auth/account_queries.d.ts.map +1 -1
  21. package/dist/auth/account_queries.js +14 -5
  22. package/dist/auth/account_routes.d.ts +7 -10
  23. package/dist/auth/account_routes.d.ts.map +1 -1
  24. package/dist/auth/account_routes.js +70 -23
  25. package/dist/auth/account_schema.d.ts +19 -0
  26. package/dist/auth/account_schema.d.ts.map +1 -1
  27. package/dist/auth/account_schema.js +20 -0
  28. package/dist/auth/admin_action_specs.d.ts +45 -11
  29. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  30. package/dist/auth/admin_action_specs.js +23 -8
  31. package/dist/auth/admin_actions.d.ts +8 -7
  32. package/dist/auth/admin_actions.d.ts.map +1 -1
  33. package/dist/auth/admin_actions.js +11 -18
  34. package/dist/auth/audit_log_queries.d.ts +53 -14
  35. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  36. package/dist/auth/audit_log_queries.js +45 -2
  37. package/dist/auth/audit_log_schema.d.ts +55 -1
  38. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  39. package/dist/auth/audit_log_schema.js +19 -3
  40. package/dist/auth/bearer_auth.d.ts +9 -7
  41. package/dist/auth/bearer_auth.d.ts.map +1 -1
  42. package/dist/auth/bearer_auth.js +13 -21
  43. package/dist/auth/cleanup.d.ts.map +1 -1
  44. package/dist/auth/cleanup.js +5 -0
  45. package/dist/auth/daemon_token_middleware.d.ts +23 -11
  46. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  47. package/dist/auth/daemon_token_middleware.js +26 -20
  48. package/dist/auth/deps.d.ts +14 -0
  49. package/dist/auth/deps.d.ts.map +1 -1
  50. package/dist/auth/middleware.d.ts.map +1 -1
  51. package/dist/auth/middleware.js +4 -2
  52. package/dist/auth/migrations.d.ts +15 -7
  53. package/dist/auth/migrations.d.ts.map +1 -1
  54. package/dist/auth/migrations.js +15 -7
  55. package/dist/auth/permit_offer_action_specs.d.ts +45 -6
  56. package/dist/auth/permit_offer_action_specs.d.ts.map +1 -1
  57. package/dist/auth/permit_offer_action_specs.js +38 -7
  58. package/dist/auth/permit_offer_actions.d.ts +2 -2
  59. package/dist/auth/permit_offer_actions.d.ts.map +1 -1
  60. package/dist/auth/permit_offer_actions.js +106 -95
  61. package/dist/auth/permit_offer_notifications.d.ts +10 -0
  62. package/dist/auth/permit_offer_notifications.d.ts.map +1 -1
  63. package/dist/auth/permit_offer_queries.d.ts +68 -9
  64. package/dist/auth/permit_offer_queries.d.ts.map +1 -1
  65. package/dist/auth/permit_offer_queries.js +147 -35
  66. package/dist/auth/permit_offer_schema.d.ts +23 -1
  67. package/dist/auth/permit_offer_schema.d.ts.map +1 -1
  68. package/dist/auth/permit_offer_schema.js +5 -0
  69. package/dist/auth/permit_queries.d.ts +17 -5
  70. package/dist/auth/permit_queries.d.ts.map +1 -1
  71. package/dist/auth/permit_queries.js +19 -8
  72. package/dist/auth/request_context.d.ts +360 -32
  73. package/dist/auth/request_context.d.ts.map +1 -1
  74. package/dist/auth/request_context.js +442 -60
  75. package/dist/auth/route_guards.d.ts +10 -4
  76. package/dist/auth/route_guards.d.ts.map +1 -1
  77. package/dist/auth/route_guards.js +14 -8
  78. package/dist/auth/self_service_role_action_specs.d.ts +2 -0
  79. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  80. package/dist/auth/self_service_role_action_specs.js +2 -0
  81. package/dist/auth/self_service_role_actions.d.ts +6 -5
  82. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  83. package/dist/auth/self_service_role_actions.js +32 -19
  84. package/dist/db/migrate.d.ts +11 -7
  85. package/dist/db/migrate.d.ts.map +1 -1
  86. package/dist/db/migrate.js +9 -6
  87. package/dist/dev/setup.d.ts.map +1 -1
  88. package/dist/dev/setup.js +5 -3
  89. package/dist/hono_context.d.ts +77 -0
  90. package/dist/hono_context.d.ts.map +1 -1
  91. package/dist/hono_context.js +50 -0
  92. package/dist/http/CLAUDE.md +80 -17
  93. package/dist/http/error_schemas.d.ts +92 -1
  94. package/dist/http/error_schemas.d.ts.map +1 -1
  95. package/dist/http/error_schemas.js +73 -16
  96. package/dist/http/jsonrpc_errors.d.ts +27 -2
  97. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  98. package/dist/http/jsonrpc_errors.js +26 -2
  99. package/dist/http/route_spec.d.ts +62 -4
  100. package/dist/http/route_spec.d.ts.map +1 -1
  101. package/dist/http/route_spec.js +117 -21
  102. package/dist/http/schema_helpers.d.ts +13 -1
  103. package/dist/http/schema_helpers.d.ts.map +1 -1
  104. package/dist/http/schema_helpers.js +21 -2
  105. package/dist/http/surface.d.ts +10 -1
  106. package/dist/http/surface.d.ts.map +1 -1
  107. package/dist/http/surface.js +2 -2
  108. package/dist/server/app_server.d.ts.map +1 -1
  109. package/dist/server/app_server.js +11 -1
  110. package/dist/testing/CLAUDE.md +23 -17
  111. package/dist/testing/admin_integration.d.ts.map +1 -1
  112. package/dist/testing/admin_integration.js +15 -13
  113. package/dist/testing/adversarial_headers.js +1 -1
  114. package/dist/testing/app_server.js +2 -2
  115. package/dist/testing/audit_completeness.d.ts.map +1 -1
  116. package/dist/testing/audit_completeness.js +21 -7
  117. package/dist/testing/auth_apps.d.ts.map +1 -1
  118. package/dist/testing/auth_apps.js +6 -3
  119. package/dist/testing/entities.d.ts +2 -1
  120. package/dist/testing/entities.d.ts.map +1 -1
  121. package/dist/testing/entities.js +1 -0
  122. package/dist/testing/integration_helpers.d.ts +4 -2
  123. package/dist/testing/integration_helpers.d.ts.map +1 -1
  124. package/dist/testing/integration_helpers.js +9 -5
  125. package/dist/testing/middleware.d.ts +12 -8
  126. package/dist/testing/middleware.d.ts.map +1 -1
  127. package/dist/testing/middleware.js +67 -25
  128. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  129. package/dist/testing/rpc_helpers.js +3 -1
  130. package/dist/testing/schema_generators.d.ts.map +1 -1
  131. package/dist/testing/schema_generators.js +12 -0
  132. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  133. package/dist/testing/ws_round_trip.js +5 -1
  134. package/dist/ui/CLAUDE.md +16 -10
  135. package/dist/ui/PermitOfferForm.svelte +14 -0
  136. package/dist/ui/PermitOfferForm.svelte.d.ts +6 -0
  137. package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -1
  138. package/dist/ui/admin_accounts_state.svelte.d.ts +8 -1
  139. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  140. package/dist/ui/admin_accounts_state.svelte.js +14 -3
  141. package/dist/ui/permit_offers_state.svelte.d.ts +9 -1
  142. package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -1
  143. package/dist/ui/permit_offers_state.svelte.js +7 -1
  144. package/package.json +1 -1
@@ -49,24 +49,34 @@ export const query_grant_permit = async (deps, input) => {
49
49
  return assert_row(existing, 'idempotent permit grant');
50
50
  };
51
51
  /**
52
- * Look up the role of an active permit, constrained to a specific actor.
52
+ * Look up the role of an active permit (constrained to a specific
53
+ * actor) plus the actor's `account_id`.
53
54
  *
54
55
  * Used by admin routes to inspect the permit's role before acting
55
56
  * (e.g., enforcing `web_grantable` on revoke). The actor constraint
56
57
  * mirrors `query_revoke_permit` so IDOR protection is consistent:
57
58
  * a caller can only see permits belonging to the target actor.
58
59
  *
60
+ * The JOIN to `actor` collapses what used to be a second
61
+ * `query_actor_by_id` round-trip in the revoke handler into one read,
62
+ * which closes the small TOCTOU window where the actor row could be
63
+ * deleted between the IDOR check and the actor lookup. The `account_id`
64
+ * is needed by the audit envelope's `target_account_id` field and the
65
+ * SSE/WS socket-close fan-out targeting.
66
+ *
59
67
  * Returns `null` if the permit is not found, already revoked, or
60
68
  * belongs to a different actor.
61
69
  *
62
70
  * @param deps - query dependencies
63
71
  * @param permit_id - the permit id to look up
64
72
  * @param actor_id - the actor that must own the permit
65
- * @returns `{role}` on a match, or `null`
73
+ * @returns `{role, account_id}` on a match, or `null`
66
74
  */
67
75
  export const query_permit_find_active_role_for_actor = async (deps, permit_id, actor_id) => {
68
- const row = await deps.db.query_one(`SELECT role FROM permit
69
- WHERE id = $1 AND actor_id = $2 AND revoked_at IS NULL`, [permit_id, actor_id]);
76
+ const row = await deps.db.query_one(`SELECT permit.role, actor.account_id
77
+ FROM permit
78
+ JOIN actor ON actor.id = permit.actor_id
79
+ WHERE permit.id = $1 AND permit.actor_id = $2 AND permit.revoked_at IS NULL`, [permit_id, actor_id]);
70
80
  return row ?? null;
71
81
  };
72
82
  /**
@@ -206,16 +216,17 @@ export const query_permit_find_account_id_for_role = async (deps, role) => {
206
216
  * @mutates `permit_offer` table - stamps `superseded_at` on every pending row at `scope_id`
207
217
  */
208
218
  export const query_permit_revoke_for_scope = async (deps, scope_id, revoked_by, reason) => {
209
- // Revoke every active permit at the scope. CTE pulls `account_id` via a
210
- // join on `actor` so callers fan out `permit_revoke` notifications without
211
- // an extra round-trip.
219
+ // Revoke every active permit at the scope. CTE returns `actor_id` directly
220
+ // from the permit row (drives `target_actor_id` audit envelopes); a join
221
+ // against `actor` resolves `account_id` for `target_account_id`
222
+ // + WS/SSE socket-close fan-out, all in one round-trip.
212
223
  const revoked = await deps.db.query(`WITH updated AS (
213
224
  UPDATE permit
214
225
  SET revoked_at = NOW(), revoked_by = $2, revoked_reason = $3
215
226
  WHERE scope_id = $1 AND revoked_at IS NULL
216
227
  RETURNING id, role, scope_id, actor_id
217
228
  )
218
- SELECT u.id AS permit_id, u.role, u.scope_id, a.account_id
229
+ SELECT u.id AS permit_id, u.role, u.scope_id, u.actor_id, a.account_id
219
230
  FROM updated u
220
231
  JOIN actor a ON a.id = u.actor_id`, [scope_id, revoked_by ?? null, reason ?? null]);
221
232
  // Supersede every pending offer at the scope — tuple-matched or orphan,
@@ -1,24 +1,64 @@
1
1
  /**
2
2
  * Request context middleware and permit checking helpers.
3
3
  *
4
- * Builds `{ account, actor, permits }` from a session cookie
5
- * for every authenticated request. Downstream handlers check
6
- * permits, never flags.
4
+ * Two-phase identity resolution:
7
5
  *
8
- * `build_request_context` is the shared helper used by session,
9
- * bearer, and daemon token middleware to resolve account → actor → permits.
10
- * `refresh_permits` reloads permits on an existing context.
6
+ * 1. **Authentication (middleware)** `create_request_context_middleware`,
7
+ * `bearer_auth`, and `daemon_token_middleware` validate the credential
8
+ * (session cookie, bearer token, daemon token) and set `c.var.account_id`
9
+ * + `c.var.credential_type` on the Hono context. They do not resolve
10
+ * an acting actor or load permits; `REQUEST_CONTEXT_KEY` stays null at
11
+ * this stage, so account-grain identity is the only thing known.
12
+ * 2. **Authorization (route-spec wrapper / RPC dispatcher)** — after input
13
+ * validation, the per-route layer inspects the route. If the input
14
+ * schema declared `acting?: ActingActor` (reference equality with the
15
+ * canonical `ActingActor` schema) or the auth requires permits
16
+ * (`role` / `keeper`), `apply_authorization_phase` resolves the actor
17
+ * against `c.var.account_id` plus the validated `acting` value via
18
+ * `resolve_acting_actor`, builds the `{account, actor, permits}`
19
+ * context via `build_request_context`, and sets it on
20
+ * `REQUEST_CONTEXT_KEY` before auth guards fire. Authenticated routes
21
+ * that don't need an actor still get an account-only context via
22
+ * `build_account_context` so handler signatures stay uniform.
23
+ *
24
+ * Account-grain operations (logout, password_change, account_verify,
25
+ * etc.) declare neither `acting` nor permit-requiring auth, so no actor
26
+ * is resolved and their handlers see a `RequestContext` with
27
+ * `actor: null` + empty `permits`. They never trigger `actor_required`,
28
+ * which is what makes multi-actor logout work without first picking a
29
+ * persona.
30
+ *
31
+ * `build_request_context` loads `account → actor → permits` and verifies
32
+ * the `actor.account_id === account.id` binding. `refresh_permits`
33
+ * reloads permits on an existing context.
11
34
  *
12
35
  * @module
13
36
  */
14
37
  import type { Context, MiddlewareHandler } from 'hono';
38
+ import { z } from 'zod';
15
39
  import type { Logger } from '@fuzdev/fuz_util/log.js';
16
40
  import { type Account, type Actor, type Permit } from './account_schema.js';
17
41
  import type { QueryDeps } from '../db/query_deps.js';
18
- /** The resolved identity context for an authenticated request. */
42
+ import type { ActionAuth } from '../actions/action_spec.js';
43
+ import type { RouteAuth, RouteSpec } from '../http/route_spec.js';
44
+ import { ERROR_ACTOR_REQUIRED, ERROR_ACTOR_NOT_ON_ACCOUNT, ERROR_NO_ACTORS_ON_ACCOUNT, ERROR_ACCOUNT_VANISHED } from '../http/error_schemas.js';
45
+ /**
46
+ * The resolved identity context for an authenticated request.
47
+ *
48
+ * `actor` is null on account-grain routes (no `acting` field on input,
49
+ * no `role` / `keeper` auth) — those handlers don't trigger actor
50
+ * resolution. `permits` is empty in that case. Permit checks
51
+ * (`has_role`, `has_scoped_role`, `has_any_scoped_role`) are
52
+ * null-tolerant on `RequestContext | null`; they additionally treat
53
+ * `actor: null` as "no permits" so callers don't have to narrow.
54
+ *
55
+ * Multi-actor invariant: when populated, `actor.account_id === account.id`.
56
+ * `build_request_context` enforces this; the dispatcher's authorization
57
+ * phase rejects with `actor_not_on_account` before reaching the handler.
58
+ */
19
59
  export interface RequestContext {
20
60
  account: Account;
21
- actor: Actor;
61
+ actor: Actor | null;
22
62
  permits: Array<Permit>;
23
63
  }
24
64
  /** Hono context variable name for the request context. */
@@ -43,53 +83,184 @@ export declare const get_request_context: (c: Context) => RequestContext | null;
43
83
  /**
44
84
  * Get the request context, throwing if unauthenticated.
45
85
  *
46
- * Use in route handlers where auth middleware guarantees a context exists
47
- * (i.e., routes with `auth: {type: 'authenticated'}` or stricter).
48
- * Prefer this over `get_request_context(c)!` for explicit error handling.
86
+ * Use in route handlers where the dispatcher's authorization phase guarantees
87
+ * a context exists (i.e., routes with `auth: {type: 'authenticated'}` or
88
+ * stricter). Prefer this over `get_request_context(c)!` for explicit error
89
+ * handling.
49
90
  *
50
91
  * @param c - the Hono context
51
92
  * @returns the request context (never null)
52
- * @throws Error if no request context is set (middleware misconfiguration)
93
+ * @throws Error if no request context is set (dispatcher misconfiguration)
53
94
  */
54
95
  export declare const require_request_context: (c: Context) => RequestContext;
96
+ /**
97
+ * Request context narrowed to a resolved acting actor.
98
+ *
99
+ * Returned by `require_request_actor` for handlers whose route resolves
100
+ * an actor — actions with `auth: 'keeper' | {role}` or with input that
101
+ * declares `acting?: ActingActor`. Lets handlers drop the `auth.actor!`
102
+ * non-null assertion that was masking the dispatcher invariant.
103
+ */
104
+ export interface RequestActorContext extends RequestContext {
105
+ actor: Actor;
106
+ }
107
+ /**
108
+ * Narrow `RequestContext | null` to a non-null context (auth invariant).
109
+ *
110
+ * Use in RPC action handlers whose spec is non-public — the dispatcher's
111
+ * pre-validation auth gate has already short-circuited unauthenticated
112
+ * callers, so `ctx.auth` is non-null by the time the handler runs.
113
+ *
114
+ * @throws Error when called from a public-auth handler (programmer error)
115
+ */
116
+ export declare const require_request_auth: (auth: RequestContext | null) => RequestContext;
117
+ /**
118
+ * Narrow `RequestContext | null` to `RequestActorContext` (actor invariant).
119
+ *
120
+ * Use in RPC action handlers whose spec declares `auth: 'keeper' | {role}`
121
+ * or whose input declares `acting?: ActingActor` — the dispatcher's
122
+ * authorization phase resolves an actor before the handler runs. Replaces
123
+ * the `ctx.auth!.actor!.id` chain that the type system can't otherwise see.
124
+ *
125
+ * @throws Error when the handler runs without actor resolution (programmer error)
126
+ */
127
+ export declare const require_request_actor: (auth: RequestContext | null) => RequestActorContext;
55
128
  /**
56
129
  * Check if a request context has an active permit for a given role.
57
130
  *
58
131
  * Checks the permits already loaded in the context (no DB query).
132
+ * Null-tolerant — `null` ctx (unauthenticated) returns `false`. Symmetric
133
+ * with `has_scoped_role` / `has_any_scoped_role` so the three helpers
134
+ * compose freely in the same predicate (e.g.
135
+ * `has_role(auth, ADMIN) || has_scoped_role(auth, role, scope)`).
59
136
  *
60
- * @param ctx - the request context
137
+ * @param ctx - the request context, or `null` for unauthenticated callers
61
138
  * @param role - the role to check
62
139
  * @param now - current time (defaults to `new Date()`, pass for testability and hot-path efficiency)
63
140
  * @returns `true` if the actor has an active permit for the role
64
141
  */
65
- export declare const has_role: (ctx: RequestContext, role: string, now?: Date) => boolean;
142
+ export declare const has_role: (ctx: RequestContext | null, role: string, now?: Date) => boolean;
143
+ /**
144
+ * Whether the request context holds an active permit for `role` at `scope_id`.
145
+ *
146
+ * Walks the in-memory `ctx.permits` snapshot loaded once per request by
147
+ * the route-spec / RPC dispatcher's authorization phase (when the route
148
+ * declares `acting?: ActingActor` or has permit-requiring auth); zero DB
149
+ * roundtrip per check. The "freshness" framing of a SQL re-query is
150
+ * illusory because the race window is between predicate and the actual
151
+ * mutation, not predicate and authorization load. Closing that race needs
152
+ * a transactional re-check inside the UPDATE/INSERT, which neither style
153
+ * provides.
154
+ *
155
+ * Null-tolerant — `null` ctx (unauthenticated) and account-grain
156
+ * contexts (`actor: null`, empty `permits`) both return `false`. Same
157
+ * convention as `has_role`; lets the helper drop into `auth: 'public'`
158
+ * or account-grain handlers without a manual narrow. See `cell_authorize`
159
+ * for the resource-side analog.
160
+ *
161
+ * `scope_id` semantics: in-memory `permit.scope_id` is `string | null`, so
162
+ * JS `===` matches the SQL `IS NOT DISTINCT FROM` semantics exactly:
163
+ *
164
+ * - `scope_id === null` matches global permits (`scope_id IS NULL`).
165
+ * - `scope_id === '<uuid>'` matches permits bound to that exact scope.
166
+ *
167
+ * @param ctx - the request context, or `null` for unauthenticated callers
168
+ * @param role - the role to check
169
+ * @param scope_id - the scope to check (`null` for global)
170
+ * @param now - current time (defaults to `new Date()`, pass for testability and hot-path efficiency)
171
+ * @returns `true` iff the actor holds an active permit for the role at the requested scope
172
+ */
173
+ export declare const has_scoped_role: (ctx: RequestContext | null, role: string, scope_id: string | null, now?: Date) => boolean;
174
+ /**
175
+ * Whether the request context holds an active permit for any role in `roles`
176
+ * at `scope_id`. Empty `roles` short-circuits to `false` — documents intent
177
+ * at the call site ("zero roles trivially admit no-one"). Same scope and
178
+ * null-tolerance semantics as `has_scoped_role`.
179
+ *
180
+ * @param ctx - the request context, or `null` for unauthenticated callers
181
+ * @param roles - the roles that would admit the caller (any-of)
182
+ * @param scope_id - the scope to check (`null` for global)
183
+ * @param now - current time (defaults to `new Date()`, pass for testability)
184
+ * @returns `true` iff the actor holds an active permit for any role in `roles` at the requested scope
185
+ */
186
+ export declare const has_any_scoped_role: (ctx: RequestContext | null, roles: ReadonlyArray<string>, scope_id: string | null, now?: Date) => boolean;
187
+ /**
188
+ * Result of `resolve_acting_actor` — either an actor id or a structured
189
+ * error the caller maps to an HTTP response.
190
+ */
191
+ export type ResolveActingActorResult = {
192
+ ok: true;
193
+ actor_id: string;
194
+ } | {
195
+ ok: false;
196
+ reason: 'no_actors';
197
+ } | {
198
+ ok: false;
199
+ reason: 'actor_required';
200
+ available: Array<{
201
+ id: string;
202
+ name: string;
203
+ }>;
204
+ } | {
205
+ ok: false;
206
+ reason: 'actor_not_on_account';
207
+ };
208
+ /**
209
+ * Resolve the acting actor for an authenticated request.
210
+ *
211
+ * Called from the route-spec / RPC dispatcher's authorization phase
212
+ * with the authenticated account id and the validated `acting` value
213
+ * (from the request payload). Applies the uniform resolution rules:
214
+ *
215
+ * - `acting_actor_id` omitted + 1 actor → use it.
216
+ * - `acting_actor_id` omitted + 0 actors → `no_actors` (defensive —
217
+ * signup / bootstrap always create an actor in the same tx, so this
218
+ * is a server error).
219
+ * - `acting_actor_id` omitted + multiple actors → `actor_required` with
220
+ * the available list so the client can prompt; never pick silently.
221
+ * - `acting_actor_id` present + matches an actor on the account → use it.
222
+ * - `acting_actor_id` present + does not match → `actor_not_on_account`.
223
+ * The available list is intentionally not echoed in this branch (treat
224
+ * as opaque rejection).
225
+ *
226
+ * @param deps - query dependencies
227
+ * @param account_id - the authenticated account
228
+ * @param acting_actor_id - the requested acting actor id, or `undefined`
229
+ */
230
+ export declare const resolve_acting_actor: (deps: QueryDeps, account_id: string, acting_actor_id: string | undefined) => Promise<ResolveActingActorResult>;
66
231
  /**
67
- * Create middleware that builds the request context from a session cookie.
232
+ * Create middleware that authenticates the account from a session cookie.
68
233
  *
69
- * Reads the session identity (set by session middleware), looks up
70
- * the `auth_session`, loads account + actor + active permits, and
71
- * sets the `RequestContext` on the Hono context.
234
+ * Reads the session identity (set by session middleware), looks up the
235
+ * `auth_session`, and on a valid session sets `c.var.auth_account_id`,
236
+ * `CREDENTIAL_TYPE_KEY = 'session'`, and `AUTH_SESSION_TOKEN_HASH_KEY`.
237
+ * Touches the session (fire-and-forget). Does not load actor or permits;
238
+ * `REQUEST_CONTEXT_KEY` is left null — the route-spec / RPC dispatcher
239
+ * authorization phase resolves the acting actor and builds the full
240
+ * `RequestContext` when the route needs one.
72
241
  *
73
- * If the session is invalid or the account is not found, the context
74
- * is set to `null` (unauthenticated). No 401 is returned — use
75
- * `require_role` or `require_auth` for enforcement.
242
+ * Invalid / missing session leaves all keys null and calls `next()`
243
+ * `require_auth` / `require_role` enforce.
76
244
  *
77
245
  * @param deps - query dependencies (pool-level db for middleware)
78
246
  * @param log - the logger instance
79
247
  * @param session_context_key - the Hono context key where session middleware stored the session token
80
- * @mutates Hono context - sets `REQUEST_CONTEXT_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, and `AUTH_API_TOKEN_ID_KEY`
248
+ * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, and `AUTH_API_TOKEN_ID_KEY`
81
249
  */
82
250
  export declare const create_request_context_middleware: (deps: QueryDeps, log: Logger, session_context_key?: string) => MiddlewareHandler;
83
251
  /**
84
252
  * Middleware that requires authentication.
85
253
  *
86
- * Returns 401 if no request context is set.
254
+ * Returns 401 if the auth middleware did not set `c.var.auth_account_id`.
87
255
  */
88
256
  export declare const require_auth: MiddlewareHandler;
89
257
  /**
90
258
  * Create middleware that requires a specific role.
91
259
  *
92
- * Returns 401 if unauthenticated, 403 if the role is missing.
260
+ * Returns 401 if unauthenticated, 403 if the role is missing. Reads
261
+ * `REQUEST_CONTEXT_KEY` because role-gated routes always run the
262
+ * dispatcher's authorization phase before this guard (the phase sets the
263
+ * actor-bound `RequestContext`).
93
264
  *
94
265
  * @param role - the required role
95
266
  */
@@ -102,24 +273,181 @@ export declare const require_role: (role: string) => MiddlewareHandler;
102
273
  * or after receiving a revocation signal.
103
274
  *
104
275
  * Returns a new `RequestContext` with updated permits — the original
105
- * context is not mutated, making concurrent calls safe.
276
+ * context is not mutated, making concurrent calls safe. Throws when
277
+ * `ctx.actor` is null; account-grain contexts have no permits to refresh.
106
278
  *
107
279
  * @param ctx - the request context to refresh
108
280
  * @param deps - query dependencies
109
281
  * @returns a new `RequestContext` with fresh permits
282
+ * @throws Error when called on an account-grain context (`actor: null`)
110
283
  */
111
284
  export declare const refresh_permits: (ctx: RequestContext, deps: QueryDeps) => Promise<RequestContext>;
112
285
  /**
113
- * Build a full `RequestContext` from an account id.
286
+ * Build a full `RequestContext` from an account id and an explicit
287
+ * actor id (already resolved via `resolve_acting_actor`).
288
+ *
289
+ * Loads `account` + the named `actor` + the actor's active permits.
290
+ * Verifies the `actor.account_id === account.id` binding so downstream
291
+ * handlers can trust `ctx.actor.account_id === ctx.account.id`. Returns
292
+ * `null` when the account is missing, the actor is missing, or the
293
+ * actor doesn't belong to the supplied account.
294
+ *
295
+ * Called by the route-spec / RPC dispatcher's authorization phase for
296
+ * routes that need an acting actor; account-grain routes use
297
+ * `build_account_context` instead.
298
+ *
299
+ * @param deps - query dependencies
300
+ * @param account_id - the account to build context for
301
+ * @param actor_id - the actor this request acts as
302
+ * @returns a request context, or `null` if account/actor not found or mismatched
303
+ */
304
+ export declare const build_request_context: (deps: QueryDeps, account_id: string, actor_id: string) => Promise<RequestActorContext | null>;
305
+ /**
306
+ * Build an account-only `RequestContext` (no actor, no permits) from
307
+ * an account id.
308
+ *
309
+ * Used by the dispatcher's authorization phase for authenticated routes
310
+ * that don't need an acting actor — account-grain operations (logout,
311
+ * password change, account self-service). Lets handlers read
312
+ * `auth.account.id` / `auth.account.username` uniformly with permit-bound
313
+ * routes; the cost is one extra `query_account_by_id` per request.
114
314
  *
115
- * Shared helper used by session, bearer, and daemon token middleware,
116
- * as well as WebSocket upgrade handlers. Does the account actor → permits
117
- * lookup pipeline and returns the composed context, or `null` if
118
- * the account or actor is not found.
315
+ * Returns `null` when the account row is missing (e.g. deleted between
316
+ * the auth middleware's session lookup and the dispatcher) caller
317
+ * surfaces that as a 500 since it represents a torn read.
119
318
  *
120
319
  * @param deps - query dependencies
121
320
  * @param account_id - the account to build context for
122
- * @returns a request context, or `null` if account/actor not found
321
+ * @returns an account-only request context, or `null` if the account is missing
322
+ */
323
+ export declare const build_account_context: (deps: QueryDeps, account_id: string) => Promise<RequestContext | null>;
324
+ /**
325
+ * Whether the supplied auth descriptor implies an acting actor must be
326
+ * resolved (i.e., permit-requiring auth: `'role'` or `'keeper'`).
327
+ *
328
+ * The dispatcher's authorization phase uses this to decide whether to
329
+ * walk the actor list when the input schema doesn't already declare
330
+ * `acting?: ActingActor`. Accepts either auth shape — the route-spec
331
+ * `RouteAuth` (`{type: 'role' | 'keeper' | ...}`) or the action-spec
332
+ * `ActionAuth` (`'keeper' | {role}`) — so HTTP and RPC dispatchers share
333
+ * one source of truth for the "permit-bound" rule.
334
+ */
335
+ export declare const is_actor_implying_auth: (auth: RouteAuth | ActionAuth) => boolean;
336
+ /**
337
+ * Whether an input schema declares the canonical `acting?: ActingActor`
338
+ * field. Reference-equality on the exported `ActingActor` schema —
339
+ * consumer schemas with unrelated `acting` fields don't trip this check.
340
+ *
341
+ * Peels through Zod wrappers (`optional`, `nullable`, `default`,
342
+ * `transform`, `pipe`, `prefault`) via `zod_unwrap_to_object` so a spec
343
+ * authored as `z.optional(z.strictObject({acting: ActingActor}))` or
344
+ * `z.strictObject({acting: ActingActor}).default({})` still trips the
345
+ * predicate. The wrapper-tolerant lookup is defense-in-depth — the
346
+ * canonical shape is the un-wrapped `z.strictObject({acting: ActingActor})`,
347
+ * but variant B in `~/dev/grimoire/lore/fuz_app/TODO_PUBLIC_AUTH_PHASE.md`
348
+ * makes this predicate authorization-correctness load-bearing for
349
+ * `auth: 'public'` actions, so missing a wrapper-bound declaration
350
+ * would silently skip actor resolution. The reference-equality check
351
+ * on `ActingActor` keeps consumer schemas with unrelated `acting`
352
+ * fields from tripping the predicate even after the wrapper peel.
353
+ *
354
+ * The dispatcher's authorization phase uses this to decide whether to
355
+ * pull the actor id from validated input (so multi-actor users can pick
356
+ * a persona on actor-needing routes).
357
+ */
358
+ export declare const input_schema_declares_acting: (schema: z.ZodType) => boolean;
359
+ /**
360
+ * Resolution-failure shape returned by `apply_authorization_phase`. Each
361
+ * transport binds this to the appropriate wire shape — REST emits the body
362
+ * directly via `c.json(body, status)`; the RPC dispatcher folds it into a
363
+ * JSON-RPC error envelope `{jsonrpc, id, error: {code, message, data}}`.
364
+ *
365
+ * The auth phase deliberately stops short of constructing a `Response` so
366
+ * the same failure flows through every transport without the auth-domain
367
+ * code knowing about JSON-RPC. See `fuz_app/CLAUDE.md` § Cleanest
368
+ * architecture takes priority for the rationale.
369
+ */
370
+ export type AuthorizationFailureBody = {
371
+ error: typeof ERROR_ACTOR_REQUIRED;
372
+ available: Array<{
373
+ id: string;
374
+ name: string;
375
+ }>;
376
+ } | {
377
+ error: typeof ERROR_ACTOR_NOT_ON_ACCOUNT;
378
+ } | {
379
+ error: typeof ERROR_NO_ACTORS_ON_ACCOUNT;
380
+ } | {
381
+ error: typeof ERROR_ACCOUNT_VANISHED;
382
+ };
383
+ /**
384
+ * A `(status, body)` pair the caller binds to a transport-shaped response.
385
+ * `status` is narrowed to the two values the auth phase emits — Hono's
386
+ * `c.json` status overload accepts the literals directly, and downstream
387
+ * binders avoid casts they would otherwise need against a `number`.
388
+ */
389
+ export interface AuthorizationFailure {
390
+ status: 400 | 500;
391
+ body: AuthorizationFailureBody;
392
+ }
393
+ /**
394
+ * Apply the dispatcher's authorization phase. Shared by the route-spec
395
+ * wrapper and the RPC dispatcher.
396
+ *
397
+ * - When `c.var.auth_account_id` is `null`, returns `void` so the
398
+ * downstream auth guard can fire 401 (less-helpful than `actor_required`
399
+ * for the unauthenticated case).
400
+ * - When `needs_actor` is true, resolves the actor against the account
401
+ * plus the supplied `acting` value, then builds the full
402
+ * `{account, actor, permits}` context.
403
+ * - When `needs_actor` is false, builds an account-only context so
404
+ * handler signatures stay uniform across the surface.
405
+ *
406
+ * On resolution failure returns an `AuthorizationFailure` (`{status, body}`)
407
+ * the caller wraps in a transport-appropriate response. Three 500 branches
408
+ * are kept distinct so the wire shape names what actually went wrong:
409
+ *
410
+ * - 500 `ERROR_NO_ACTORS_ON_ACCOUNT` — `resolve_acting_actor` returned
411
+ * `no_actors`. The actor enumeration succeeded and came back empty;
412
+ * signup / bootstrap should have created one in the same transaction,
413
+ * so this is a real corruption signal.
414
+ * - 500 `ERROR_ACCOUNT_VANISHED` — `build_request_context` /
415
+ * `build_account_context` returned null after a successful
416
+ * `resolve_acting_actor`. The account or actor row was deleted between
417
+ * the credential check and authorization (torn read race), or — in
418
+ * the `build_request_context` actor↔account mismatch sub-branch — the
419
+ * binding flipped under us. Reachability of the mismatch sub-branch in
420
+ * production is essentially zero (`resolve_acting_actor` already
421
+ * verified the actor was on this account, and `actor.account_id` only
422
+ * changes via row-level edits no production path makes), so collapsing
423
+ * that case into the torn-read shape costs nothing.
424
+ *
425
+ * Other failure paths: 400 `ERROR_ACTOR_REQUIRED` / `ERROR_ACTOR_NOT_ON_ACCOUNT`.
426
+ * Returns `undefined` on success.
427
+ *
428
+ * @mutates Hono context - sets `REQUEST_CONTEXT_KEY` on success
429
+ */
430
+ export declare const apply_authorization_phase: (deps: QueryDeps, c: Context, needs_actor: boolean, acting_value: string | undefined) => Promise<AuthorizationFailure | void>;
431
+ /**
432
+ * Create the route-spec authorization handler used by `apply_route_specs`.
433
+ *
434
+ * Decides whether the route needs actor resolution from `spec.auth` plus
435
+ * `spec.input` introspection, extracts the raw `acting` value (string
436
+ * typeguard, no schema validation), and delegates to
437
+ * `apply_authorization_phase`. Public routes (`auth.type === 'none'`) skip
438
+ * the phase entirely; their handlers see no `RequestContext`.
439
+ *
440
+ * Authorization runs before input validation (matches the RPC dispatcher's
441
+ * order). For GET routes `acting` comes from the URL query string; for
442
+ * mutating methods it comes from a pre-parse of the JSON body. The pre-
443
+ * parse result lands on `c.var.cached_request_body` so the subsequent
444
+ * `create_input_validation` step reads the parsed value from there
445
+ * without re-running `JSON.parse` — explicit cache, independent of
446
+ * Hono's internal `bodyCache` behavior. A malformed body fails the
447
+ * pre-parse silently (`acting` treated as undefined, cache flagged
448
+ * `{ok: false}`) and is then rejected with `ERROR_INVALID_JSON_BODY`
449
+ * by the input-validation step that reads the failure flag — producing
450
+ * the same final response as if the validation step had parsed first.
123
451
  */
124
- export declare const build_request_context: (deps: QueryDeps, account_id: string) => Promise<RequestContext | null>;
452
+ export declare const create_fuz_authorization_handler: (deps: QueryDeps) => ((c: Context, spec: RouteSpec) => Promise<Response | void>);
125
453
  //# sourceMappingURL=request_context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAC;AACrD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,OAAO,EAAE,KAAK,KAAK,EAAoB,KAAK,MAAM,EAAC,MAAM,qBAAqB,CAAC;AAQ5F,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAOnD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,GAAG,OAAO,KAAG,cAAc,GAAG,IAEjE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,OAAO,KAAG,cAMpD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,cAAc,EAAE,MAAM,MAAM,EAAE,MAAK,IAAiB,KAAG,OAChB,CAAC;AAEtE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,KAAK,MAAM,EACX,4BAAuC,KACrC,iBA6CF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,iBAM1B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,iBAW3C,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAC3B,KAAK,cAAc,EACnB,MAAM,SAAS,KACb,OAAO,CAAC,cAAc,CAGxB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAS/B,CAAC"}
1
+ {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}