@fuzdev/fuz_app 0.96.0 → 0.97.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 (31) hide show
  1. package/dist/auth/CLAUDE.md +1 -1
  2. package/dist/auth/bearer_auth.d.ts.map +1 -1
  3. package/dist/auth/bearer_auth.js +6 -7
  4. package/dist/auth/daemon_token_middleware.d.ts +21 -4
  5. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  6. package/dist/auth/daemon_token_middleware.js +61 -10
  7. package/dist/auth/middleware.d.ts.map +1 -1
  8. package/dist/auth/middleware.js +6 -2
  9. package/dist/http/CLAUDE.md +3 -2
  10. package/dist/http/error_schemas.d.ts +0 -6
  11. package/dist/http/error_schemas.d.ts.map +1 -1
  12. package/dist/http/error_schemas.js +0 -6
  13. package/dist/http/origin.d.ts +18 -1
  14. package/dist/http/origin.d.ts.map +1 -1
  15. package/dist/http/origin.js +17 -0
  16. package/dist/testing/CLAUDE.md +23 -3
  17. package/dist/testing/audit_completeness.d.ts.map +1 -1
  18. package/dist/testing/audit_completeness.js +4 -1
  19. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -1
  20. package/dist/testing/cross_backend/bootstrap_backend.js +8 -0
  21. package/dist/testing/cross_backend/conformance_case.d.ts +40 -0
  22. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  23. package/dist/testing/cross_backend/conformance_case.js +34 -0
  24. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  25. package/dist/testing/cross_backend/conformance_table.js +58 -0
  26. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  27. package/dist/testing/cross_backend/fact_serving.js +5 -1
  28. package/dist/testing/cross_backend/setup.d.ts +17 -0
  29. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  30. package/dist/testing/cross_backend/setup.js +29 -4
  31. package/package.json +1 -1
@@ -157,7 +157,7 @@ Everything else listed under §RPC action surfaces.
157
157
  - `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`.
158
158
  - `auth/session_middleware.ts` — `process_session_cookie` integration, `create_session_and_set_cookie` (shared by login / signup / bootstrap).
159
159
  - `auth/bearer_auth.ts` — soft-fail bearer middleware; rejects when `Origin` or `Referer` present (browser context).
160
- - `auth/daemon_token_middleware.ts` — `start_daemon_token_rotation` + `create_daemon_token_middleware` (atomic file write, fail-closed validation, keeper account resolution).
160
+ - `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.
161
161
 
162
162
  See root ../../../CLAUDE.md §Middleware Ordering for canonical assembly
163
163
  order. The auth-specific invariants are described below in §Cross-cutting
@@ -1 +1 @@
1
- {"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAIpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,iBAAiB,WAAW,GAAG,IAAI,EACnC,KAAK,MAAM,KACT,iBAkFF,CAAC"}
1
+ {"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAIpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,iBAAiB,WAAW,GAAG,IAAI,EACnC,KAAK,MAAM,KACT,iBAgFF,CAAC"}
@@ -18,6 +18,7 @@ import { DEV } from 'esm-env';
18
18
  import { AUTH_API_TOKEN_ID_KEY, ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
19
19
  import { query_validate_api_token } from './api_token_queries.js';
20
20
  import { get_client_ip } from '../http/client_ip.js';
21
+ import { is_browser_context } from '../http/origin.js';
21
22
  import { rate_limit_exceeded_response } from '../rate_limiter.js';
22
23
  /**
23
24
  * Create middleware that authenticates via bearer token.
@@ -62,13 +63,11 @@ export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
62
63
  await next();
63
64
  return;
64
65
  }
65
- // Silently discard bearer tokens in browser context — defense-in-depth:
66
- // checks both Origin and Referer (not just Origin) because some browser
67
- // requests send only Referer. Uses `!== undefined` so that empty-string
68
- // headers (e.g. `Origin: ''`) are still treated as browser context.
69
- // Discards rather than returning 403 so that the RPC dispatcher can still
70
- // handle public actions or fall through to cookie auth.
71
- if (c.req.header('Origin') !== undefined || c.req.header('Referer') !== undefined) {
66
+ // Silently discard bearer tokens in browser context (`is_browser_context`
67
+ // — Origin or Referer present). Discards rather than returning 403 so that
68
+ // the RPC dispatcher can still handle public actions or fall through to
69
+ // cookie auth.
70
+ if (is_browser_context(c)) {
72
71
  log.debug('bearer auth rejected: browser context (Origin/Referer present)');
73
72
  // The discard is silent on the wire by design (a stolen-token probe
74
73
  // gets an indistinguishable 401, not a "your token was dropped"
@@ -100,9 +100,24 @@ export declare const start_daemon_token_rotation: (runtime: DaemonTokenWriteDeps
100
100
  *
101
101
  * Checks the `X-Daemon-Token` header. Behavior:
102
102
  * - No header: pass through (don't touch existing context).
103
- * - Header present + Zod-invalid: return 401 (fail-closed).
104
- * - Header present + invalid value: return 401 (fail-closed, no downgrade).
105
- * - Header present + valid + `keeper_account_id` null: return 503.
103
+ * - Header present + `Origin` / `Referer` present: discard the credential
104
+ * (browser context) and pass through — daemon tokens are loopback-only and
105
+ * never carry an `Origin` in production, so a header-bearing request is not
106
+ * a legitimate daemon caller. Mirrors the bearer guard: `next()` rather than
107
+ * 401, so downstream auth enforcement returns `credential_type_required`
108
+ * (not a hard fail). Silent on the wire (anti-enumeration); in `DEV` only,
109
+ * sets `X-Fuz-Auth-Debug: daemon_token_discarded_browser_context`.
110
+ * - Header present + Zod-invalid (malformed): soft-fail discard (pass through,
111
+ * not 401) — mirrors the bearer guard and the Rust spine's `resolve.rs`
112
+ * (`None`). Downstream a daemon-gated action returns `credential_type_required`;
113
+ * a public action proceeds anonymous.
114
+ * - Header present + invalid value (not the current/previous token): soft-fail
115
+ * discard (pass through, not 401) — same downstream behavior.
116
+ * - Header present + valid + `keeper_account_id` null (still pre-bootstrap
117
+ * after the lazy refresh): soft-fail discard (pass through, not 503) —
118
+ * mirrors the Rust spine's `resolve.rs` (`None`), so the request falls
119
+ * through to anonymous and a daemon-gated action returns
120
+ * `credential_type_required` downstream.
106
121
  * - Header present + valid + ok: set `c.var.auth_account_id =
107
122
  * state.keeper_account_id`, `CREDENTIAL_TYPE_KEY = 'daemon_token'`
108
123
  * (overrides any existing session / bearer identity).
@@ -113,7 +128,9 @@ export declare const start_daemon_token_rotation: (runtime: DaemonTokenWriteDeps
113
128
  * an explicit `acting` value.
114
129
  *
115
130
  * @param state - the daemon token runtime state
131
+ * @param deps - query dependencies (pool-level db for keeper-account resolution)
132
+ * @param log - the logger instance
116
133
  * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on a valid token
117
134
  */
118
- export declare const create_daemon_token_middleware: (state: DaemonTokenState, deps: QueryDeps) => MiddlewareHandler;
135
+ export declare const create_daemon_token_middleware: (state: DaemonTokenState, deps: QueryDeps, log: Logger) => MiddlewareHandler;
119
136
  //# sourceMappingURL=daemon_token_middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AASrF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAC1D,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,iBAAiB,GAAG,QAAQ,CAAC,GAAG;IAC3D,6FAA6F;IAC7F,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EACjC,MAAM,MAAM,KACV,MAAM,GAAG,IAGX,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,gBAAgB,CAAC;IACxB,kGAAkG;IAClG,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAqD7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,KACb,iBAsCF,CAAC"}
1
+ {"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAMrF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAC1D,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,iBAAiB,GAAG,QAAQ,CAAC,GAAG;IAC3D,6FAA6F;IAC7F,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EACjC,MAAM,MAAM,KACV,MAAM,GAAG,IAGX,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,gBAAgB,CAAC;IACxB,kGAAkG;IAClG,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAqD7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,EACf,KAAK,MAAM,KACT,iBAuEF,CAAC"}
@@ -9,11 +9,12 @@
9
9
  *
10
10
  * @module
11
11
  */
12
+ import { DEV } from 'esm-env';
12
13
  import {} from '../runtime/deps.js';
13
14
  import { write_file_atomic } from '../runtime/fs.js';
14
15
  import { get_app_dir } from '../cli/config.js';
15
16
  import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
16
- import { ERROR_INVALID_DAEMON_TOKEN, ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED, } from '../http/error_schemas.js';
17
+ import { is_browser_context } from '../http/origin.js';
17
18
  import { query_role_grant_find_account_id_for_role } from './role_grant_queries.js';
18
19
  import { ROLE_KEEPER } from './role_schema.js';
19
20
  import { DaemonToken, DAEMON_TOKEN_HEADER, generate_daemon_token, validate_daemon_token, } from './daemon_token.js';
@@ -137,9 +138,24 @@ export const start_daemon_token_rotation = async (runtime, deps, options, log) =
137
138
  *
138
139
  * Checks the `X-Daemon-Token` header. Behavior:
139
140
  * - No header: pass through (don't touch existing context).
140
- * - Header present + Zod-invalid: return 401 (fail-closed).
141
- * - Header present + invalid value: return 401 (fail-closed, no downgrade).
142
- * - Header present + valid + `keeper_account_id` null: return 503.
141
+ * - Header present + `Origin` / `Referer` present: discard the credential
142
+ * (browser context) and pass through — daemon tokens are loopback-only and
143
+ * never carry an `Origin` in production, so a header-bearing request is not
144
+ * a legitimate daemon caller. Mirrors the bearer guard: `next()` rather than
145
+ * 401, so downstream auth enforcement returns `credential_type_required`
146
+ * (not a hard fail). Silent on the wire (anti-enumeration); in `DEV` only,
147
+ * sets `X-Fuz-Auth-Debug: daemon_token_discarded_browser_context`.
148
+ * - Header present + Zod-invalid (malformed): soft-fail discard (pass through,
149
+ * not 401) — mirrors the bearer guard and the Rust spine's `resolve.rs`
150
+ * (`None`). Downstream a daemon-gated action returns `credential_type_required`;
151
+ * a public action proceeds anonymous.
152
+ * - Header present + invalid value (not the current/previous token): soft-fail
153
+ * discard (pass through, not 401) — same downstream behavior.
154
+ * - Header present + valid + `keeper_account_id` null (still pre-bootstrap
155
+ * after the lazy refresh): soft-fail discard (pass through, not 503) —
156
+ * mirrors the Rust spine's `resolve.rs` (`None`), so the request falls
157
+ * through to anonymous and a daemon-gated action returns
158
+ * `credential_type_required` downstream.
143
159
  * - Header present + valid + ok: set `c.var.auth_account_id =
144
160
  * state.keeper_account_id`, `CREDENTIAL_TYPE_KEY = 'daemon_token'`
145
161
  * (overrides any existing session / bearer identity).
@@ -150,23 +166,50 @@ export const start_daemon_token_rotation = async (runtime, deps, options, log) =
150
166
  * an explicit `acting` value.
151
167
  *
152
168
  * @param state - the daemon token runtime state
169
+ * @param deps - query dependencies (pool-level db for keeper-account resolution)
170
+ * @param log - the logger instance
153
171
  * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on a valid token
154
172
  */
155
- export const create_daemon_token_middleware = (state, deps) => {
173
+ export const create_daemon_token_middleware = (state, deps, log) => {
156
174
  return async (c, next) => {
157
175
  const token_header = c.req.header(DAEMON_TOKEN_HEADER);
158
176
  if (!token_header) {
159
177
  await next();
160
178
  return;
161
179
  }
162
- // Zod-validate the token format at the I/O boundary
180
+ // Silently discard daemon tokens in browser context (`is_browser_context`
181
+ // — Origin or Referer present) — mirrors the bearer guard (and the Rust
182
+ // spine's `resolve.rs`, which returns `None`). Daemon tokens are
183
+ // loopback-only and never carry an `Origin` in production, so a
184
+ // header-bearing request is not a legitimate daemon caller. Discards
185
+ // (next()) rather than 401 so the dispatcher returns
186
+ // `credential_type_required` downstream rather than a hard fail.
187
+ if (is_browser_context(c)) {
188
+ log.debug('daemon token auth rejected: browser context (Origin/Referer present)');
189
+ if (DEV)
190
+ c.header('X-Fuz-Auth-Debug', 'daemon_token_discarded_browser_context');
191
+ await next();
192
+ return;
193
+ }
194
+ // Zod-validate the token format at the I/O boundary. A malformed token is
195
+ // a soft-fail discard (pass through), not a 401 — mirroring the bearer
196
+ // guard and the Rust spine's `resolve.rs`, which return `None` on an
197
+ // unparseable credential. The request falls through: on a daemon-gated
198
+ // action the dispatcher returns `credential_type_required` downstream; on
199
+ // a public action it proceeds anonymous.
163
200
  const parse_result = DaemonToken.safeParse(token_header);
164
201
  if (!parse_result.success) {
165
- return c.json({ error: ERROR_INVALID_DAEMON_TOKEN }, 401);
202
+ log.debug('daemon token auth soft-fail: malformed token');
203
+ await next();
204
+ return;
166
205
  }
167
- // fail-closed: header present but invalid token value
206
+ // Well-formed but not the current/previous token — soft-fail discard, not
207
+ // a 401, for the same reason: no downgrade, falls through to downstream
208
+ // `credential_type_required` (matching the Rust spine's `None`).
168
209
  if (!validate_daemon_token(parse_result.data, state)) {
169
- return c.json({ error: ERROR_INVALID_DAEMON_TOKEN }, 401);
210
+ log.debug('daemon token auth soft-fail: token not found or invalid');
211
+ await next();
212
+ return;
170
213
  }
171
214
  // daemon token valid — resolve keeper account. `start_daemon_token_rotation`
172
215
  // resolves the keeper once at startup, but rotation often starts before the
@@ -176,8 +219,16 @@ export const create_daemon_token_middleware = (state, deps) => {
176
219
  if (!state.keeper_account_id) {
177
220
  state.keeper_account_id = await resolve_keeper_account_id(deps);
178
221
  }
222
+ // Valid token but no keeper configured (still pre-bootstrap after the lazy
223
+ // refresh) — soft-fail discard (pass through), not a 503. Mirrors the Rust
224
+ // spine's `resolve.rs`, which returns `None` when the daemon leg can't
225
+ // resolve a keeper: the request falls through to the next auth leg →
226
+ // anonymous, so a daemon-gated action returns `credential_type_required`
227
+ // downstream. The no-keeper state is a transient pre-bootstrap window.
179
228
  if (!state.keeper_account_id) {
180
- return c.json({ error: ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED }, 503);
229
+ log.debug('daemon token auth soft-fail: keeper account not configured');
230
+ await next();
231
+ return;
181
232
  }
182
233
  c.set(ACCOUNT_ID_KEY, state.keeper_account_id);
183
234
  c.set(CREDENTIAL_TYPE_KEY, 'daemon_token');
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,oFAAoF;IACpF,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAwE/B,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IACtC,oFAAoF;IACpF,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAgF/B,CAAC"}
@@ -61,12 +61,16 @@ export const create_auth_middleware_specs = async (deps, options) => {
61
61
  ];
62
62
  if (daemon_token_state) {
63
63
  const { create_daemon_token_middleware } = await import('./daemon_token_middleware.js');
64
- const daemon_token_middleware = create_daemon_token_middleware(daemon_token_state, query_deps);
64
+ const daemon_token_middleware = create_daemon_token_middleware(daemon_token_state, query_deps, deps.log);
65
65
  specs.push({
66
66
  name: 'daemon_token',
67
67
  path,
68
68
  handler: daemon_token_middleware,
69
- errors: { 401: ApiError, 500: ApiError, 503: ApiError },
69
+ // Soft-fails (discards) on every non-success path — browser context,
70
+ // malformed/invalid token, and no-keeper all `next()` through to the
71
+ // dispatcher's credential gate (matching the Rust spine's `None`). The
72
+ // layer returns no error response of its own.
73
+ errors: {},
70
74
  });
71
75
  }
72
76
  return specs;
@@ -203,8 +203,8 @@ confirmation.
203
203
 
204
204
  - **Validation**: `ERROR_INVALID_REQUEST_BODY`, `ERROR_INVALID_JSON_BODY`, `ERROR_INVALID_ROUTE_PARAMS`, `ERROR_INVALID_QUERY_PARAMS`
205
205
  - **Auth**: `ERROR_AUTHENTICATION_REQUIRED`, `ERROR_INSUFFICIENT_PERMISSIONS`, `ERROR_CREDENTIAL_TYPE_REQUIRED`, `ERROR_RATE_LIMIT_EXCEEDED`, `ERROR_INVALID_CREDENTIALS`, `ERROR_PAYLOAD_TOO_LARGE`
206
- - **Origin + bearer**: `ERROR_FORBIDDEN_ORIGIN`, `ERROR_BEARER_REJECTED_BROWSER`, `ERROR_INVALID_TOKEN`, `ERROR_ACCOUNT_NOT_FOUND`
207
- - **Keeper/daemon**: `ERROR_INVALID_DAEMON_TOKEN`, `ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED`, `ERROR_KEEPER_ACCOUNT_NOT_FOUND`
206
+ - **Origin + bearer**: `ERROR_FORBIDDEN_ORIGIN`, `ERROR_INVALID_TOKEN`, `ERROR_ACCOUNT_NOT_FOUND`
207
+ - **Keeper/daemon**: `ERROR_KEEPER_ACCOUNT_NOT_FOUND`
208
208
  - **Bootstrap**: `ERROR_ALREADY_BOOTSTRAPPED`, `ERROR_TOKEN_FILE_MISSING`
209
209
  - **Signup/invites**: `ERROR_NO_MATCHING_INVITE`, `ERROR_SIGNUP_CONFLICT`, `ERROR_INVITE_NOT_FOUND`, `ERROR_INVITE_DUPLICATE`, `ERROR_INVITE_ACCOUNT_EXISTS_USERNAME`, `ERROR_INVITE_ACCOUNT_EXISTS_EMAIL`
210
210
  - **Admin**: `ERROR_ROLE_NOT_WEB_GRANTABLE`, `ERROR_ROLE_GRANT_NOT_FOUND`, `ERROR_INVALID_EVENT_TYPE`
@@ -320,6 +320,7 @@ CSRF is handled by `SameSite: strict` on session cookies (`auth/session_middlewa
320
320
  - `parse_allowed_origins(env_value)` — comma-separated patterns → `Array<RegExp>`
321
321
  - `should_allow_origin(origin, patterns)` — case-insensitive match
322
322
  - `verify_request_source(allowed_patterns)` — Hono handler: `Origin` present → must match allowlist or 403 `ERROR_FORBIDDEN_ORIGIN`; no `Origin` → allow through (curl, CLI, token auth is primary control)
323
+ - `is_browser_context(c)` — true when `Origin` or `Referer` is present (`!== undefined`, so empty-string counts). The bearer + daemon-token middleware discard their credential when true; twin of the Rust spine's `bearer_auth::is_browser_context`. Distinct from `verify_request_source` (allowlist gate) — this only detects browser context
323
324
 
324
325
  **Origin-only by design.** Fetch spec mandates `Origin` on every unsafe
325
326
  method, so a real browser request on any state-changing surface always
@@ -43,8 +43,6 @@ export declare const ERROR_INVALID_CREDENTIALS: "invalid_credentials";
43
43
  export declare const ERROR_PAYLOAD_TOO_LARGE: "payload_too_large";
44
44
  /** Request origin not in allowlist. */
45
45
  export declare const ERROR_FORBIDDEN_ORIGIN: "forbidden_origin";
46
- /** Bearer token sent with Origin/Referer header (browser context). */
47
- export declare const ERROR_BEARER_REJECTED_BROWSER: "bearer_token_rejected_in_browser_context";
48
46
  /** Bearer token failed validation (missing, malformed, or revoked). */
49
47
  export declare const ERROR_INVALID_TOKEN: "invalid_token";
50
48
  /** Token references a deleted account. */
@@ -82,10 +80,6 @@ export declare const ERROR_NO_ACTORS_ON_ACCOUNT: "no_actors_on_account";
82
80
  * `ERROR_NO_ACTORS_ON_ACCOUNT` (the actor list enumerated empty).
83
81
  */
84
82
  export declare const ERROR_ACCOUNT_VANISHED: "account_vanished";
85
- /** Daemon token header present but malformed or not matching current/previous token. */
86
- export declare const ERROR_INVALID_DAEMON_TOKEN: "invalid_daemon_token";
87
- /** Daemon token valid but keeper account not yet resolved (pre-bootstrap). */
88
- export declare const ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED: "keeper_account_not_configured";
89
83
  /** Keeper account ID set but account row not found. */
90
84
  export declare const ERROR_KEEPER_ACCOUNT_NOT_FOUND: "keeper_account_not_found";
91
85
  /** Bootstrap lock already acquired — system already bootstrapped. */
@@ -1 +1 @@
1
- {"version":3,"file":"error_schemas.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/error_schemas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"error_schemas.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/error_schemas.ts"],"names":[],"mappings":"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"}
@@ -46,8 +46,6 @@ export const ERROR_PAYLOAD_TOO_LARGE = 'payload_too_large';
46
46
  // --- Origin & bearer token verification ---
47
47
  /** Request origin not in allowlist. */
48
48
  export const ERROR_FORBIDDEN_ORIGIN = 'forbidden_origin';
49
- /** Bearer token sent with Origin/Referer header (browser context). */
50
- export const ERROR_BEARER_REJECTED_BROWSER = 'bearer_token_rejected_in_browser_context';
51
49
  /** Bearer token failed validation (missing, malformed, or revoked). */
52
50
  export const ERROR_INVALID_TOKEN = 'invalid_token';
53
51
  /** Token references a deleted account. */
@@ -86,10 +84,6 @@ export const ERROR_NO_ACTORS_ON_ACCOUNT = 'no_actors_on_account';
86
84
  */
87
85
  export const ERROR_ACCOUNT_VANISHED = 'account_vanished';
88
86
  // --- Keeper / daemon token ---
89
- /** Daemon token header present but malformed or not matching current/previous token. */
90
- export const ERROR_INVALID_DAEMON_TOKEN = 'invalid_daemon_token';
91
- /** Daemon token valid but keeper account not yet resolved (pre-bootstrap). */
92
- export const ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED = 'keeper_account_not_configured';
93
87
  /** Keeper account ID set but account row not found. */
94
88
  export const ERROR_KEEPER_ACCOUNT_NOT_FOUND = 'keeper_account_not_found';
95
89
  // --- Bootstrap ---
@@ -9,7 +9,24 @@
9
9
  *
10
10
  * @module
11
11
  */
12
- import type { Handler } from 'hono';
12
+ import type { Context, Handler } from 'hono';
13
+ /**
14
+ * True when the request looks like it originated from a browser context — it
15
+ * carries an `Origin` or `Referer` header. Browsers attach these
16
+ * automatically on the state-changing surface; loopback / CLI clients don't.
17
+ * Uses `!== undefined` so an empty-string header still counts as browser
18
+ * context. Checks `Referer` too (not just `Origin`) because some browser
19
+ * requests send only `Referer`. Twin of the Rust spine's
20
+ * `bearer_auth::is_browser_context`.
21
+ *
22
+ * The bearer (`auth/bearer_auth.ts`) and daemon-token
23
+ * (`auth/daemon_token_middleware.ts`) middleware both silently discard their
24
+ * credential when this is true: a stolen bearer token can't be replayed from a
25
+ * browser, and the loopback daemon token never legitimately carries an
26
+ * `Origin`. Distinct from `verify_request_source` — that gates browser
27
+ * requests against an allowlist; this only detects browser context.
28
+ */
29
+ export declare const is_browser_context: (c: Context) => boolean;
13
30
  /**
14
31
  * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
15
32
  * Origin allowlisting for locally-running services — not the CSRF layer
@@ -1 +1 @@
1
- {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAIlC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,KAAK,CAAC,MAAM,CAO5E,CAAC;AAEP;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC/B,QAAQ,MAAM,EACd,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,OAAuD,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,aAAa,CAAC,MAAM,CAAC,KAAG,OAgB1C,CAAC"}
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAC,OAAO,EAAE,OAAO,EAAC,MAAM,MAAM,CAAC;AAI3C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,OAAO,KAAG,OAC8B,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,KAAK,CAAC,MAAM,CAO5E,CAAC;AAEP;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC/B,QAAQ,MAAM,EACd,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,OAAuD,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,aAAa,CAAC,MAAM,CAAC,KAAG,OAgB1C,CAAC"}
@@ -11,6 +11,23 @@
11
11
  */
12
12
  import { escape_regexp } from '@fuzdev/fuz_util/regexp.ts';
13
13
  import { ERROR_FORBIDDEN_ORIGIN } from './error_schemas.js';
14
+ /**
15
+ * True when the request looks like it originated from a browser context — it
16
+ * carries an `Origin` or `Referer` header. Browsers attach these
17
+ * automatically on the state-changing surface; loopback / CLI clients don't.
18
+ * Uses `!== undefined` so an empty-string header still counts as browser
19
+ * context. Checks `Referer` too (not just `Origin`) because some browser
20
+ * requests send only `Referer`. Twin of the Rust spine's
21
+ * `bearer_auth::is_browser_context`.
22
+ *
23
+ * The bearer (`auth/bearer_auth.ts`) and daemon-token
24
+ * (`auth/daemon_token_middleware.ts`) middleware both silently discard their
25
+ * credential when this is true: a stolen bearer token can't be replayed from a
26
+ * browser, and the loopback daemon token never legitimately carries an
27
+ * `Origin`. Distinct from `verify_request_source` — that gates browser
28
+ * requests against an allowlist; this only detects browser context.
29
+ */
30
+ export const is_browser_context = (c) => c.req.header('Origin') !== undefined || c.req.header('Referer') !== undefined;
14
31
  /**
15
32
  * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
16
33
  * Origin allowlisting for locally-running services — not the CSRF layer
@@ -973,9 +973,29 @@ wire-shape check passes green on even when behavior is wrong.
973
973
  error_reason?, fields?, headers?, equivalence_group?}, note?, xfail?}`. A case is **data** — `method`
974
974
  resolves its `input`/`output` from the live registry (RPC) or `RouteSpec`
975
975
  (the 6 REST auth routes), so the case never carries a schema. `as` is the
976
- closed `ConformancePrincipal` enum (`keeper` / `daemon` / `token` /
977
- `anonymous` / `fresh_non_admin` / `role_holder` / `wrong_role` /
978
- `expired_session`) — fixture accessors, never inline credential minting.
976
+ closed `ConformancePrincipal` enum (`keeper` / `daemon` / `invalid_daemon` /
977
+ `daemon_browser` / `token` / `bearer_browser` / `anonymous` /
978
+ `fresh_non_admin` / `role_holder` / `wrong_role` / `expired_session`) —
979
+ fixture accessors, never inline credential minting.
980
+ `invalid_daemon` sends a malformed `X-Daemon-Token` alongside the keeper
981
+ session cookie over a no-Origin transport: the middleware soft-fail-discards
982
+ the daemon token (matching the Rust spine's `None`), auth falls through to the
983
+ session leg, and a daemon-gated action then refuses the session credential
984
+ with `credential_type_required` — not a hard invalid-token 401 (the session
985
+ base credential is what makes the credential-type gate, not the auth gate, the
986
+ refusing layer).
987
+ `daemon_browser` sends a _valid_ `X-Daemon-Token` in a browser context
988
+ (default `Origin` present) alongside the keeper session cookie: the middleware
989
+ discards the header-bearing token as browser context (mirroring the bearer
990
+ guard + the Rust spine's `is_browser_context`), so auth falls through to the
991
+ session leg → `credential_type_required`. Unlike `daemon` (no Origin → token
992
+ honored → 400 confirm guard), the 403 proves a valid token is _dropped_, not
993
+ honored, when an `Origin` is present.
994
+ `bearer_browser` is the bearer twin: a _valid_ bearer api-token in a browser
995
+ context (default `Origin` present), fresh jar (no session) — the bearer
996
+ middleware discards it, so an authed action 401s, wire-indistinguishable from
997
+ `anonymous` (pinned by the `browser_bearer_replay` equivalence group). The
998
+ `token` principal is the honored counterpart (suppresses `Origin`).
979
999
  `expired_session` is the keeper behind an expired server-side session
980
1000
  (`fixture.mint_expired_session()`: a backdated `auth_session` row behind a
981
1001
  still-valid signed cookie, so the DB-row expiry gate is what refuses it).
@@ -1 +1 @@
1
- {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAU9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,0BAA0B,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AA8FD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAomBzF,CAAC"}
1
+ {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAU9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,0BAA0B,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AA8FD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAumBzF,CAAC"}
@@ -445,7 +445,10 @@ export const describe_audit_completeness_tests = (options) => {
445
445
  path: rpc_path,
446
446
  spec: account_purge_action_spec,
447
447
  params: { account_id: target.account.id, confirm: true },
448
- // Keeper-gated: daemon-token credential, not a session.
448
+ // Keeper-gated: daemon-token credential, not a session. Suppress the
449
+ // default `origin` header — the daemon-token middleware discards the
450
+ // credential in a browser context (Origin/Referer present).
451
+ suppress_default_origin: true,
449
452
  headers: fixture.create_daemon_token_headers(),
450
453
  });
451
454
  assert.ok(res.ok, `account_purge failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/bootstrap_backend.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIvD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,aAAa,KACnB,OAAO,CAAC,yBAAyB,CAgBnC,CAAC"}
1
+ {"version":3,"file":"bootstrap_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/bootstrap_backend.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIvD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,aAAa,KACnB,OAAO,CAAC,yBAAyB,CAwBnC,CAAC"}
@@ -22,6 +22,14 @@ export const bootstrap_backend = async (config) => {
22
22
  return {
23
23
  ...handle,
24
24
  keeper_transport,
25
+ // Origin-free twin carrying the same keeper cookie jar — the
26
+ // daemon-token middleware discards the credential in a browser
27
+ // context, so the `_testing_*` daemon-token calls route through this.
28
+ keeper_daemon_transport: create_fetch_transport({
29
+ base_url: config.base_url,
30
+ initial_cookies: keeper.cookies,
31
+ origin: null,
32
+ }),
25
33
  keeper_account: keeper.account,
26
34
  keeper_actor: keeper.actor,
27
35
  keeper_cookies: keeper.cookies,
@@ -33,8 +33,39 @@ import { z } from 'zod';
33
33
  * - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
34
34
  * `ROLE_ADMIN`), session credential.
35
35
  * - `daemon` — the keeper authenticated via the daemon-token header.
36
+ * - `invalid_daemon` — a *malformed/invalid* `X-Daemon-Token` carried
37
+ * alongside the keeper's session cookie, over a non-browser (no-Origin)
38
+ * transport. The middleware soft-fail-discards the invalid daemon token
39
+ * (matching the Rust spine's `None`), so auth falls through to the session
40
+ * leg: the request authenticates as the keeper-via-session and a
41
+ * daemon-gated action then refuses the session credential with
42
+ * `credential_type_required` — not a hard `invalid_daemon_token` 401. The
43
+ * no-Origin transport keeps the daemon token on the invalid-token path
44
+ * rather than the browser-context discard; the session base credential is
45
+ * what makes the credential-type gate (not the auth gate) the refusing
46
+ * layer (without it the discard would 401 anonymous).
47
+ * - `daemon_browser` — a *valid* `X-Daemon-Token` carried in a browser
48
+ * context (default `Origin` present) alongside the keeper's session cookie.
49
+ * Browsers attach `Origin` automatically; the daemon-token middleware
50
+ * discards a header-bearing daemon token as browser context (mirroring the
51
+ * bearer guard and the Rust spine's `is_browser_context`), so the *valid*
52
+ * token is dropped and auth falls through to the session leg → a daemon-gated
53
+ * action then refuses the session credential with `credential_type_required`.
54
+ * Distinct from `invalid_daemon`: here the token is well-formed and current,
55
+ * so a 403 (not a 400 confirm-guard hit like `daemon`) proves the
56
+ * browser-context discard fired — a valid daemon token does NOT authenticate
57
+ * when an `Origin` is present. `Origin` is deliberately NOT suppressed; its
58
+ * presence is the signal under test.
36
59
  * - `token` — the keeper authenticated via a bearer api-token (non-browser
37
60
  * context; the runner suppresses `Origin` so the token isn't discarded).
61
+ * - `bearer_browser` — a *valid* bearer api-token carried in a browser context
62
+ * (default `Origin` present), fresh jar so NO session rides alongside. The
63
+ * bearer middleware discards the token as browser context (mirroring the
64
+ * daemon guard + the Rust spine's `is_browser_context`), so the request
65
+ * arrives anonymous and an authed action 401s. Proves a stolen bearer cannot
66
+ * be replayed from a browser — wire-indistinguishable from sending no
67
+ * credential (the `token` principal is the honored counterpart: it suppresses
68
+ * `Origin`, so the same token authenticates).
38
69
  * - `anonymous` — no credential, fresh cookie jar.
39
70
  * - `fresh_non_admin` — a freshly minted account with no roles, session
40
71
  * credential (via the production invite → signup → login flow).
@@ -53,6 +84,9 @@ export declare const ConformancePrincipal: z.ZodEnum<{
53
84
  keeper: "keeper";
54
85
  token: "token";
55
86
  daemon: "daemon";
87
+ invalid_daemon: "invalid_daemon";
88
+ daemon_browser: "daemon_browser";
89
+ bearer_browser: "bearer_browser";
56
90
  anonymous: "anonymous";
57
91
  fresh_non_admin: "fresh_non_admin";
58
92
  role_holder: "role_holder";
@@ -68,6 +102,9 @@ export declare const ConformanceCaseRequest: z.ZodObject<{
68
102
  keeper: "keeper";
69
103
  token: "token";
70
104
  daemon: "daemon";
105
+ invalid_daemon: "invalid_daemon";
106
+ daemon_browser: "daemon_browser";
107
+ bearer_browser: "bearer_browser";
71
108
  anonymous: "anonymous";
72
109
  fresh_non_admin: "fresh_non_admin";
73
110
  role_holder: "role_holder";
@@ -120,6 +157,9 @@ export declare const ConformanceCase: z.ZodObject<{
120
157
  keeper: "keeper";
121
158
  token: "token";
122
159
  daemon: "daemon";
160
+ invalid_daemon: "invalid_daemon";
161
+ daemon_browser: "daemon_browser";
162
+ bearer_browser: "bearer_browser";
123
163
  anonymous: "anonymous";
124
164
  fresh_non_admin: "fresh_non_admin";
125
165
  role_holder: "role_holder";
@@ -1 +1 @@
1
- {"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;EAS/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;kBAgBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B;;;;;;kBAiDrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;kBAK/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAY/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;kBAgBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B;;;;;;kBAiDrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;kBAK/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
@@ -33,8 +33,39 @@ import { z } from 'zod';
33
33
  * - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
34
34
  * `ROLE_ADMIN`), session credential.
35
35
  * - `daemon` — the keeper authenticated via the daemon-token header.
36
+ * - `invalid_daemon` — a *malformed/invalid* `X-Daemon-Token` carried
37
+ * alongside the keeper's session cookie, over a non-browser (no-Origin)
38
+ * transport. The middleware soft-fail-discards the invalid daemon token
39
+ * (matching the Rust spine's `None`), so auth falls through to the session
40
+ * leg: the request authenticates as the keeper-via-session and a
41
+ * daemon-gated action then refuses the session credential with
42
+ * `credential_type_required` — not a hard `invalid_daemon_token` 401. The
43
+ * no-Origin transport keeps the daemon token on the invalid-token path
44
+ * rather than the browser-context discard; the session base credential is
45
+ * what makes the credential-type gate (not the auth gate) the refusing
46
+ * layer (without it the discard would 401 anonymous).
47
+ * - `daemon_browser` — a *valid* `X-Daemon-Token` carried in a browser
48
+ * context (default `Origin` present) alongside the keeper's session cookie.
49
+ * Browsers attach `Origin` automatically; the daemon-token middleware
50
+ * discards a header-bearing daemon token as browser context (mirroring the
51
+ * bearer guard and the Rust spine's `is_browser_context`), so the *valid*
52
+ * token is dropped and auth falls through to the session leg → a daemon-gated
53
+ * action then refuses the session credential with `credential_type_required`.
54
+ * Distinct from `invalid_daemon`: here the token is well-formed and current,
55
+ * so a 403 (not a 400 confirm-guard hit like `daemon`) proves the
56
+ * browser-context discard fired — a valid daemon token does NOT authenticate
57
+ * when an `Origin` is present. `Origin` is deliberately NOT suppressed; its
58
+ * presence is the signal under test.
36
59
  * - `token` — the keeper authenticated via a bearer api-token (non-browser
37
60
  * context; the runner suppresses `Origin` so the token isn't discarded).
61
+ * - `bearer_browser` — a *valid* bearer api-token carried in a browser context
62
+ * (default `Origin` present), fresh jar so NO session rides alongside. The
63
+ * bearer middleware discards the token as browser context (mirroring the
64
+ * daemon guard + the Rust spine's `is_browser_context`), so the request
65
+ * arrives anonymous and an authed action 401s. Proves a stolen bearer cannot
66
+ * be replayed from a browser — wire-indistinguishable from sending no
67
+ * credential (the `token` principal is the honored counterpart: it suppresses
68
+ * `Origin`, so the same token authenticates).
38
69
  * - `anonymous` — no credential, fresh cookie jar.
39
70
  * - `fresh_non_admin` — a freshly minted account with no roles, session
40
71
  * credential (via the production invite → signup → login flow).
@@ -52,7 +83,10 @@ import { z } from 'zod';
52
83
  export const ConformancePrincipal = z.enum([
53
84
  'keeper',
54
85
  'daemon',
86
+ 'invalid_daemon',
87
+ 'daemon_browser',
55
88
  'token',
89
+ 'bearer_browser',
56
90
  'anonymous',
57
91
  'fresh_non_admin',
58
92
  'role_holder',
@@ -1 +1 @@
1
- {"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8B9B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,KAAK,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,YAAY,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AA+FD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,MAAM,CAIrD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,MAAM,KACX,IAQF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EACvC,OAAO,MAAM,KACX,IAYF,CAAC;AAgKF;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,uBAAuB,KAAG,IAkEnF,CAAC"}
1
+ {"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8B9B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAOjE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,KAAK,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,YAAY,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAyJD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,MAAM,CAIrD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,MAAM,KACX,IAQF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EACvC,OAAO,MAAM,KACX,IAYF,CAAC;AAgKF;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,uBAAuB,KAAG,IAkEnF,CAAC"}
@@ -25,10 +25,21 @@ import '../assert_dev_env.js';
25
25
  * @module
26
26
  */
27
27
  import { assert, describe, test } from 'vitest';
28
+ import { DAEMON_TOKEN_HEADER } from '../../auth/daemon_token.js';
28
29
  import { find_auth_route, rest_auth_route_suffixes, } from '../integration_helpers.js';
29
30
  import { find_rpc_action, headers_to_record, rpc_call, resolve_rpc_endpoints_for_setup, } from '../rpc_helpers.js';
30
31
  import {} from './conformance_case.js';
31
32
  import { xfail_until } from './xfail.js';
33
+ /**
34
+ * A deliberately malformed `X-Daemon-Token` value for the `invalid_daemon`
35
+ * principal. Fails the `DaemonToken` Zod schema (not 43 base64url chars), so
36
+ * the daemon-token middleware soft-fail-discards it rather than authenticating
37
+ * — the request falls through with no identity, surfacing
38
+ * `credential_type_required` on a daemon-gated action (matching the Rust
39
+ * spine's `None`). Sent over a no-Origin transport so it lands on the
40
+ * invalid-token path, not the browser-context discard.
41
+ */
42
+ const INVALID_DAEMON_TOKEN_VALUE = 'not-a-valid-daemon-token';
32
43
  /**
33
44
  * Map a `ConformancePrincipal` onto the transport + headers it
34
45
  * authenticates with, reading exclusively from the per-test `TestFixture`.
@@ -51,6 +62,39 @@ const resolve_principal = async (fixture, as, principals) => {
51
62
  headers: fixture.create_daemon_token_headers(),
52
63
  suppress_default_origin: true,
53
64
  };
65
+ case 'invalid_daemon':
66
+ // A malformed `X-Daemon-Token` carried ALONGSIDE the keeper's session
67
+ // cookie, over a non-browser (no-Origin) transport. The invalid daemon
68
+ // token is soft-fail-discarded (matching the Rust spine's `None`), so
69
+ // auth falls through to the session leg: the request authenticates as
70
+ // the keeper-via-session (clearing `require_auth`), then the daemon-gated
71
+ // action's credential-type gate refuses the session credential →
72
+ // `403 credential_type_required`. No-Origin keeps the daemon token on
73
+ // the invalid-token path, NOT the browser-context discard. Without the
74
+ // base credential this would 401 (anonymous) — the session is what makes
75
+ // the *credential-type* gate, not the auth gate, the refusing layer.
76
+ return {
77
+ transport: fixture.fresh_transport({ origin: null }),
78
+ headers: fixture.create_session_headers({
79
+ [DAEMON_TOKEN_HEADER]: INVALID_DAEMON_TOKEN_VALUE,
80
+ }),
81
+ suppress_default_origin: true,
82
+ };
83
+ case 'daemon_browser':
84
+ // A VALID `X-Daemon-Token` carried in a browser context (default Origin
85
+ // present) ALONGSIDE the keeper's session cookie. The daemon-token
86
+ // middleware discards a header-bearing daemon token as browser context
87
+ // (mirroring the bearer guard + the Rust spine's `is_browser_context`),
88
+ // so the valid token is dropped and auth falls through to the session
89
+ // leg → keeper-via-session → a daemon-gated action refuses the session
90
+ // credential with `credential_type_required`. Origin is NOT suppressed —
91
+ // its presence is the browser-context signal under test. Unlike the
92
+ // `daemon` principal (no Origin → token honored → reaches the 400 confirm
93
+ // guard), the 403 here proves the valid token was discarded, not honored.
94
+ return {
95
+ transport: fixture.transport,
96
+ headers: fixture.create_session_headers(fixture.create_daemon_token_headers()),
97
+ };
54
98
  case 'token':
55
99
  // Bearer is discarded in a browser context — empty jar + no Origin.
56
100
  return {
@@ -58,6 +102,20 @@ const resolve_principal = async (fixture, as, principals) => {
58
102
  headers: fixture.create_bearer_headers(),
59
103
  suppress_default_origin: true,
60
104
  };
105
+ case 'bearer_browser':
106
+ // A VALID bearer api-token in a browser context (default Origin
107
+ // present), fresh jar so no session rides alongside. The bearer
108
+ // middleware discards a header-bearing token as browser context
109
+ // (mirroring the daemon guard + the Rust spine's `is_browser_context`),
110
+ // so the request arrives anonymous → an authed action 401s. Origin is
111
+ // NOT suppressed (its presence is the browser-context signal) — the
112
+ // inverse of the `token` principal, which suppresses it so the same
113
+ // token is honored. The 401 proves the valid token was discarded, not
114
+ // honored (a non-discarding leg would authenticate → 200).
115
+ return {
116
+ transport: fixture.fresh_transport(),
117
+ headers: fixture.create_bearer_headers(),
118
+ };
61
119
  case 'anonymous':
62
120
  // Fresh jar so the keeper cookie (cross-process) can't leak in.
63
121
  return { transport: fixture.fresh_transport(), headers: {} };
@@ -1 +1 @@
1
- {"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAC,wBAAwB,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB;IAC5E,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AA8BD,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8NxF,CAAC"}
1
+ {"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAC,wBAAwB,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB;IAC5E,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AA8BD,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAiOxF,CAAC"}
@@ -68,7 +68,11 @@ export const describe_fact_serving_cross_tests = (options) => {
68
68
  const { setup_test, setup_test_multi_actor, capabilities } = options;
69
69
  const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
70
70
  /** Seed an embedded fact over the daemon channel; returns its `blake3:` hash. */
71
- const put_fact = async (fixture, content, content_type) => expect_output(await cross_rpc_call(fixture.transport, rpc_path, '_testing_put_fact', content_type === undefined ? { content } : { content, content_type }, fixture.create_daemon_token_headers()), TestingPutFactOutput).hash;
71
+ const put_fact = async (fixture, content, content_type) => expect_output(await cross_rpc_call(
72
+ // Origin-free transport — the daemon-token middleware discards the
73
+ // credential when an `Origin` / `Referer` is present (browser
74
+ // context), and cross-process `fixture.transport` auto-adds `Origin`.
75
+ fixture.fresh_transport({ origin: null }), rpc_path, '_testing_put_fact', content_type === undefined ? { content } : { content, content_type }, fixture.create_daemon_token_headers()), TestingPutFactOutput).hash;
72
76
  /**
73
77
  * Create a cell referencing `hash` (`extract_refs` lifts it into `cell.refs`).
74
78
  * Pass `acting` when the caller's account holds more than one actor —
@@ -253,6 +253,11 @@ export interface RpcPathCrossSuiteOptions extends CrossSuiteOptions {
253
253
  * const bootstrapped: BootstrappedBackendHandle = {
254
254
  * ...handle,
255
255
  * keeper_transport,
256
+ * keeper_daemon_transport: create_fetch_transport({
257
+ * base_url: config.base_url,
258
+ * initial_cookies: keeper.cookies,
259
+ * origin: null,
260
+ * }),
256
261
  * keeper_account: keeper.account,
257
262
  * keeper_actor: keeper.actor,
258
263
  * keeper_cookies: keeper.cookies,
@@ -268,6 +273,18 @@ export interface RpcPathCrossSuiteOptions extends CrossSuiteOptions {
268
273
  export interface BootstrappedBackendHandle extends BackendHandle {
269
274
  /** Transport carrying the keeper session cookie + cookie jar. */
270
275
  readonly keeper_transport: FetchTransport;
276
+ /**
277
+ * Daemon-token transport for the keeper — same cookie jar as
278
+ * `keeper_transport` but with `origin: null` so it sends no `Origin`
279
+ * header. The daemon-token middleware discards the credential in a
280
+ * browser context (any `Origin` / `Referer`), so the `_testing_*`
281
+ * daemon-token calls (`_testing_reset` / `_testing_schema_snapshot` /
282
+ * `_testing_action_manifest` / `_testing_mint_session`) must route
283
+ * through this Origin-free transport. CSRF/cookie-session calls
284
+ * (`invite_create`, `role_grant_offer_create`) stay on `keeper_transport`,
285
+ * which keeps its default `Origin`.
286
+ */
287
+ readonly keeper_daemon_transport: FetchTransport;
271
288
  /** Keeper account JSON captured from `POST /bootstrap`. */
272
289
  readonly keeper_account: {
273
290
  readonly id: Uuid;
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/setup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/setup.ts"],"names":[],"mappings":"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"}
@@ -82,6 +82,15 @@ export const reconstruct_bootstrapped_handle = (serialized) => ({
82
82
  base_url: serialized.config.base_url,
83
83
  initial_cookies: serialized.keeper_cookies,
84
84
  }),
85
+ // Same cookie jar but Origin-free so the daemon-token middleware doesn't
86
+ // discard the credential as browser-context — used by the `_testing_*`
87
+ // daemon-token calls (`_testing_reset` / `_testing_schema_snapshot` /
88
+ // `_testing_action_manifest` / `_testing_mint_session`).
89
+ keeper_daemon_transport: create_fetch_transport({
90
+ base_url: serialized.config.base_url,
91
+ initial_cookies: serialized.keeper_cookies,
92
+ origin: null,
93
+ }),
85
94
  });
86
95
  /**
87
96
  * Structural subset of `SignupOutput` the runner cares about. Looser
@@ -163,7 +172,7 @@ const rpc_via_transport = async (transport, rpc_path, method, params, backend_na
163
172
  * the snapshot shapes match by design.
164
173
  */
165
174
  export const capture_schema_snapshot = async (handle, options = {}) => {
166
- const raw = await rpc_via_transport(handle.keeper_transport, handle.config.rpc_path, '_testing_schema_snapshot', { exclude_tables: [...(options.exclude_tables ?? [])] }, handle.config.name, { [DAEMON_TOKEN_HEADER]: handle.daemon_token });
175
+ const raw = await rpc_via_transport(handle.keeper_daemon_transport, handle.config.rpc_path, '_testing_schema_snapshot', { exclude_tables: [...(options.exclude_tables ?? [])] }, handle.config.name, { [DAEMON_TOKEN_HEADER]: handle.daemon_token });
167
176
  // TODO: cast, not parse — `SchemaSnapshot.parse(raw)` would validate the wire
168
177
  // against the schema (the `capture_action_manifest` twin parses + uses a
169
178
  // strict object); parsing here may surface a latent schema-model gap to
@@ -181,7 +190,7 @@ export const capture_schema_snapshot = async (handle, options = {}) => {
181
190
  * `fuz_testing` mirror); the normalized shapes match by design.
182
191
  */
183
192
  export const capture_action_manifest = async (handle) => {
184
- const raw = await rpc_via_transport(handle.keeper_transport, handle.config.rpc_path, '_testing_action_manifest', {}, handle.config.name, { [DAEMON_TOKEN_HEADER]: handle.daemon_token });
193
+ const raw = await rpc_via_transport(handle.keeper_daemon_transport, handle.config.rpc_path, '_testing_action_manifest', {}, handle.config.name, { [DAEMON_TOKEN_HEADER]: handle.daemon_token });
185
194
  return ActionManifest.parse(raw);
186
195
  };
187
196
  /**
@@ -215,7 +224,7 @@ const TestingResetResponseShape = z.object({
215
224
  * can close over them.
216
225
  */
217
226
  const fire_testing_reset = async (handle, options) => {
218
- const raw = await rpc_via_transport(handle.keeper_transport, handle.config.rpc_path, '_testing_reset', {
227
+ const raw = await rpc_via_transport(handle.keeper_daemon_transport, handle.config.rpc_path, '_testing_reset', {
219
228
  extra_keeper_roles: options.extra_keeper_roles ?? [],
220
229
  extra_accounts: (options.extra_accounts ?? []).map((spec) => ({
221
230
  username: spec.username,
@@ -387,6 +396,17 @@ const create_keeper_transport = (handle, cookie_name, session_cookie) => create_
387
396
  base_url: handle.config.base_url,
388
397
  initial_cookies: [`${cookie_name}=${session_cookie}`],
389
398
  });
399
+ /**
400
+ * Origin-free twin of `create_keeper_transport` — same fresh keeper cookie
401
+ * jar but `origin: null` so the daemon-token middleware doesn't discard the
402
+ * credential as browser-context. Used for the per-test `_testing_*`
403
+ * daemon-token calls (the rebuilt handle's `keeper_daemon_transport`).
404
+ */
405
+ const create_keeper_daemon_transport = (handle, cookie_name, session_cookie) => create_fetch_transport({
406
+ base_url: handle.config.base_url,
407
+ initial_cookies: [`${cookie_name}=${session_cookie}`],
408
+ origin: null,
409
+ });
390
410
  /**
391
411
  * Build a `SetupTest` against a spawned + bootstrapped backend.
392
412
  *
@@ -428,9 +448,14 @@ export const default_cross_process_setup = (handle, options) => {
428
448
  // `session_cookie` for any keeper-acting calls this test makes
429
449
  // (e.g. `fixture.create_account()`'s `invite_create` step).
430
450
  const keeper_transport = create_keeper_transport(handle, cookie_name, keeper.session_cookie);
451
+ // Origin-free twin for the per-test daemon-token calls — the original
452
+ // `handle.keeper_daemon_transport` carries the wiped `globalSetup` keeper
453
+ // cookie, so rebuild it against the freshly re-seeded keeper.
454
+ const keeper_daemon_transport = create_keeper_daemon_transport(handle, cookie_name, keeper.session_cookie);
431
455
  const refreshed_handle = {
432
456
  ...handle,
433
457
  keeper_transport,
458
+ keeper_daemon_transport,
434
459
  keeper_account: keeper.account,
435
460
  keeper_actor: keeper.actor,
436
461
  keeper_cookies: [`${cookie_name}=${keeper.session_cookie}`],
@@ -508,7 +533,7 @@ export const default_cross_process_setup = (handle, options) => {
508
533
  // so `_testing_mint_session` mints the backdated row + signs the
509
534
  // cookie server-side over the keeper's daemon-token channel.
510
535
  mint_expired_session: async () => {
511
- const raw = await rpc_via_transport(refreshed_handle.keeper_transport, handle.config.rpc_path, '_testing_mint_session', { account_id: keeper.account.id, expires_in_seconds: EXPIRED_SESSION_OFFSET_SECONDS }, handle.config.name, { [DAEMON_TOKEN_HEADER]: handle.daemon_token });
536
+ const raw = await rpc_via_transport(refreshed_handle.keeper_daemon_transport, handle.config.rpc_path, '_testing_mint_session', { account_id: keeper.account.id, expires_in_seconds: EXPIRED_SESSION_OFFSET_SECONDS }, handle.config.name, { [DAEMON_TOKEN_HEADER]: handle.daemon_token });
512
537
  const parsed = MintSessionResponseShape.safeParse(raw);
513
538
  if (!parsed.success) {
514
539
  throw new Error(`_testing_mint_session(${handle.config.name}) returned unexpected result: ` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.96.0",
3
+ "version": "0.97.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",