@fuzdev/fuz_app 0.131.0 → 0.132.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 (65) hide show
  1. package/dist/actions/action_dispatcher.js +0 -1
  2. package/dist/actions/action_event_data.d.ts +2 -2
  3. package/dist/actions/action_event_types.d.ts +2 -2
  4. package/dist/actions/action_rpc.js +0 -1
  5. package/dist/actions/action_spec.d.ts +19 -19
  6. package/dist/actions/perform_action.js +0 -1
  7. package/dist/actions/register_action_ws.js +0 -1
  8. package/dist/actions/transports_http.d.ts +7 -0
  9. package/dist/actions/transports_http.d.ts.map +1 -1
  10. package/dist/actions/transports_http.js +18 -1
  11. package/dist/auth/CLAUDE.md +1 -1
  12. package/dist/auth/account_routes.js +1 -1
  13. package/dist/auth/audit_log_schema.d.ts +5 -5
  14. package/dist/auth/cell_action_specs.d.ts +3 -3
  15. package/dist/auth/credential_type_schema.d.ts +1 -1
  16. package/dist/auth/middleware.d.ts +13 -10
  17. package/dist/auth/middleware.d.ts.map +1 -1
  18. package/dist/auth/middleware.js +14 -10
  19. package/dist/cli/daemon.js +0 -1
  20. package/dist/cli/logger.js +0 -1
  21. package/dist/env/load.js +0 -1
  22. package/dist/hono_context.d.ts +1 -1
  23. package/dist/http/CLAUDE.md +2 -1
  24. package/dist/http/auth_shape.d.ts +6 -6
  25. package/dist/http/error_schemas.d.ts +12 -2
  26. package/dist/http/error_schemas.d.ts.map +1 -1
  27. package/dist/http/error_schemas.js +10 -1
  28. package/dist/http/origin.d.ts +10 -6
  29. package/dist/http/origin.d.ts.map +1 -1
  30. package/dist/http/origin.js +10 -6
  31. package/dist/http/route_spec.d.ts +19 -12
  32. package/dist/http/route_spec.d.ts.map +1 -1
  33. package/dist/http/route_spec.js +33 -6
  34. package/dist/http/schema_helpers.d.ts +3 -1
  35. package/dist/http/schema_helpers.d.ts.map +1 -1
  36. package/dist/http/schema_helpers.js +3 -2
  37. package/dist/http/simple_request.d.ts +63 -0
  38. package/dist/http/simple_request.d.ts.map +1 -0
  39. package/dist/http/simple_request.js +84 -0
  40. package/dist/http/surface.js +0 -1
  41. package/dist/realtime/sse.js +0 -1
  42. package/dist/server/app_server.d.ts +2 -2
  43. package/dist/server/app_server.d.ts.map +1 -1
  44. package/dist/server/app_server.js +5 -2
  45. package/dist/testing/CLAUDE.md +16 -0
  46. package/dist/testing/admin_integration.d.ts.map +1 -1
  47. package/dist/testing/admin_integration.js +2 -1
  48. package/dist/testing/attack_surface.d.ts.map +1 -1
  49. package/dist/testing/attack_surface.js +14 -15
  50. package/dist/testing/audit_completeness.js +1 -1
  51. package/dist/testing/cross_backend/action_manifest.d.ts +10 -10
  52. package/dist/testing/cross_backend/simple_request.d.ts +11 -0
  53. package/dist/testing/cross_backend/simple_request.d.ts.map +1 -0
  54. package/dist/testing/cross_backend/simple_request.js +142 -0
  55. package/dist/testing/cross_backend/testing_reset_actions.d.ts +5 -5
  56. package/dist/testing/integration.d.ts.map +1 -1
  57. package/dist/testing/integration.js +40 -11
  58. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  59. package/dist/testing/rpc_helpers.js +19 -7
  60. package/dist/testing/stubs.d.ts.map +1 -1
  61. package/dist/testing/stubs.js +3 -0
  62. package/dist/testing/transports/ws_client.js +0 -1
  63. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  64. package/dist/ui/auth_state.svelte.js +5 -1
  65. package/package.json +2 -2
@@ -58,6 +58,12 @@ export const ERROR_PAYLOAD_TOO_LARGE = 'payload_too_large';
58
58
  // --- Origin & bearer token verification ---
59
59
  /** Request origin not in allowlist. */
60
60
  export const ERROR_FORBIDDEN_ORIGIN = 'forbidden_origin';
61
+ /**
62
+ * A `POST` whose `Content-Type` the route refuses: anything but
63
+ * `application/json` on a JSON route, a CORS-safelisted type or none on a
64
+ * `raw_body` one. See `http/simple_request.ts`.
65
+ */
66
+ export const ERROR_UNSUPPORTED_CONTENT_TYPE = 'unsupported_content_type';
61
67
  /** Bearer token failed validation (missing, malformed, or revoked). */
62
68
  export const ERROR_INVALID_TOKEN = 'invalid_token';
63
69
  /** Token references a deleted account. */
@@ -279,7 +285,7 @@ export const AccountVanishedError = z.looseObject({
279
285
  * - `'both'` — both keys.
280
286
  */
281
287
  export const RateLimitKey = z.enum(['ip', 'account', 'both']);
282
- export const derive_error_schemas = ({ auth, has_input = false, has_params = false, has_query = false, rate_limit }) => {
288
+ export const derive_error_schemas = ({ auth, has_input = false, has_params = false, has_query = false, rate_limit, checks_content_type = false }) => {
283
289
  const errors = {};
284
290
  const has_validation = has_input || has_params || has_query;
285
291
  if (needs_actor(auth)) {
@@ -320,5 +326,8 @@ export const derive_error_schemas = ({ auth, has_input = false, has_params = fal
320
326
  if (rate_limit) {
321
327
  errors[429] = RateLimitError;
322
328
  }
329
+ if (checks_content_type) {
330
+ errors[415] = ApiError;
331
+ }
323
332
  return errors;
324
333
  };
@@ -2,10 +2,13 @@
2
2
  * Request source verification middleware for API protection.
3
3
  *
4
4
  * Verifies requests are coming from expected origins/referers.
5
- * CSRF protection is provided by `SameSite: strict` on session cookies
6
- * (see `auth/session_middleware.ts`). This module provides origin allowlisting
7
- * for locally-running services — preventing untrusted websites from
8
- * making requests as the user browses the web.
5
+ * CSRF protection is `SameSite: strict` on session cookies
6
+ * (see `auth/session_middleware.ts`) plus the CORS simple-request refusal
7
+ * (`http/simple_request.ts`) — `SameSite` alone admits same-site pages, which
8
+ * include every other port on the same host. This module provides origin
9
+ * allowlisting for locally-running services — preventing untrusted websites
10
+ * from making requests as the user browses the web — and is the only gate on
11
+ * the WebSocket upgrade, which is never preflighted.
9
12
  *
10
13
  * @module
11
14
  */
@@ -30,7 +33,7 @@ export declare const is_browser_context: (c: Context) => boolean;
30
33
  /**
31
34
  * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
32
35
  * Origin allowlisting for locally-running services — not the CSRF layer
33
- * (that's `SameSite: strict` on session cookies).
36
+ * (that's `SameSite: strict` on session cookies plus `http/simple_request.ts`).
34
37
  *
35
38
  * Accepts comma-separated patterns with limited wildcards:
36
39
  * - Exact origins: `https://api.fuz.dev`
@@ -56,7 +59,8 @@ export declare const should_allow_origin: (origin: string, allowed_patterns: Rea
56
59
  /**
57
60
  * Middleware that verifies the request source against an allowlist.
58
61
  *
59
- * Origin allowlisting (not the CSRF layer — that's `SameSite: strict` cookies):
62
+ * Origin allowlisting (not the CSRF layer — that's `SameSite: strict` cookies
63
+ * plus `http/simple_request.ts`):
60
64
  * - Checks the `Origin` header (if present) against the allowlist
61
65
  * - Allows requests without an `Origin` header (direct access, curl, etc.)
62
66
  *
@@ -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,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAI7C;;;;;;;;;;;;;;;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"}
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAI7C;;;;;;;;;;;;;;;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;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,aAAa,CAAC,MAAM,CAAC,KAAG,OAgB1C,CAAC"}
@@ -2,10 +2,13 @@
2
2
  * Request source verification middleware for API protection.
3
3
  *
4
4
  * Verifies requests are coming from expected origins/referers.
5
- * CSRF protection is provided by `SameSite: strict` on session cookies
6
- * (see `auth/session_middleware.ts`). This module provides origin allowlisting
7
- * for locally-running services — preventing untrusted websites from
8
- * making requests as the user browses the web.
5
+ * CSRF protection is `SameSite: strict` on session cookies
6
+ * (see `auth/session_middleware.ts`) plus the CORS simple-request refusal
7
+ * (`http/simple_request.ts`) — `SameSite` alone admits same-site pages, which
8
+ * include every other port on the same host. This module provides origin
9
+ * allowlisting for locally-running services — preventing untrusted websites
10
+ * from making requests as the user browses the web — and is the only gate on
11
+ * the WebSocket upgrade, which is never preflighted.
9
12
  *
10
13
  * @module
11
14
  */
@@ -31,7 +34,7 @@ export const is_browser_context = (c) => c.req.header('Origin') !== undefined ||
31
34
  /**
32
35
  * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
33
36
  * Origin allowlisting for locally-running services — not the CSRF layer
34
- * (that's `SameSite: strict` on session cookies).
37
+ * (that's `SameSite: strict` on session cookies plus `http/simple_request.ts`).
35
38
  *
36
39
  * Accepts comma-separated patterns with limited wildcards:
37
40
  * - Exact origins: `https://api.fuz.dev`
@@ -63,7 +66,8 @@ export const should_allow_origin = (origin, allowed_patterns) => allowed_pattern
63
66
  /**
64
67
  * Middleware that verifies the request source against an allowlist.
65
68
  *
66
- * Origin allowlisting (not the CSRF layer — that's `SameSite: strict` cookies):
69
+ * Origin allowlisting (not the CSRF layer — that's `SameSite: strict` cookies
70
+ * plus `http/simple_request.ts`):
67
71
  * - Checks the `Origin` header (if present) against the allowlist
68
72
  * - Allows requests without an `Origin` header (direct access, curl, etc.)
69
73
  *
@@ -51,8 +51,9 @@ export type AuthGuardResolver = (auth: RouteAuth) => AuthGuards;
51
51
  * and before input validation; resolves the acting actor (when `auth.actor !==
52
52
  * 'none'`) from the `acting` selector — `c.var.validated_query.acting` on GETs,
53
53
  * read off the raw body on mutations — and sets the request context on the Hono
54
- * context. Per-route order in `apply_route_specs`: params → query →
55
- * pre-authorization auth (401 + rule-3 scope) → authorization phase →
54
+ * context. Per-route order in `apply_route_specs`: content type (415) →
55
+ * params → query → pre-authorization auth (401 + rule-3 scope) →
56
+ * authorization phase →
56
57
  * post-authorization auth (403) → input validation (400) → handler.
57
58
  *
58
59
  * Returns a `Response` to short-circuit (resolution failure → 400 / 500),
@@ -138,12 +139,15 @@ export interface RouteSpec {
138
139
  * overloaded `input: z.null()`, which otherwise can't distinguish "no
139
140
  * body" (`GET /health`) from "raw bytes" (a binary upload).
140
141
  *
141
- * Purely descriptive metadata — the dispatcher doesn't read it. Its one
142
- * consumer is the schema-driven round-trip test suite, which auto-skips
143
- * `raw_body` routes (it can neither synthesize a meaningful body nor
144
- * assert a JSON output shape), so consumers no longer hand-maintain a
145
- * `skip_routes` entry per binary route. Also surfaces in `AppSurfaceRoute`
146
- * so generated docs render "raw" instead of a misleading `null` body.
142
+ * A `raw_body` `POST` takes any `Content-Type` a browser can't send
143
+ * cross-origin without a preflight; every other `POST` route requires
144
+ * `application/json`, bodyless or not (415 `unsupported_content_type`
145
+ * otherwise — see `http/simple_request.ts`). The schema-driven round-trip
146
+ * test suite auto-skips `raw_body` routes (it can neither synthesize a
147
+ * meaningful body nor assert a JSON output shape), so consumers don't
148
+ * hand-maintain a `skip_routes` entry per binary route. Also surfaces in
149
+ * `AppSurfaceRoute` so generated docs render "raw" instead of a
150
+ * misleading `null` body.
147
151
  */
148
152
  raw_body?: boolean;
149
153
  /**
@@ -228,10 +232,13 @@ export declare const apply_middleware_specs: (app: Hono, specs: Array<Middleware
228
232
  * validation, wraps with error catch layer (catches `ThrownJsonrpcError`
229
233
  * and generic errors), and registers the route.
230
234
  *
231
- * Per-route middleware order: params → query → pre-authorization auth
232
- * guards (401 + rule-3 scope) → authorization phase →
233
- * post-authorization auth guards (403) → input validation (400) →
234
- * handler. Body validation runs behind every authority gate, so a caller
235
+ * Per-route middleware order: content type (415, `POST` only) → params →
236
+ * query → pre-authorization auth guards (401 + rule-3 scope) →
237
+ * authorization phase → post-authorization auth guards (403) → input
238
+ * validation (400) → handler. The content type goes first because it says
239
+ * almost nothing about the route, and a forged request should be refused
240
+ * before any credential is read. Body
241
+ * validation runs behind every authority gate, so a caller
235
242
  * any of them refuse never sees route-shape information from a parse
236
243
  * failure — a 400 there would confirm the route exists and describe how
237
244
  * to call it. Params and query still validate first (they address the
@@ -1 +1 @@
1
- {"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAW,IAAI,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACtE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACN,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAKjB,MAAM,oBAAoB,CAAC;AAS5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAA0C,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEzF;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,UAAU;IAC1B,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC5C,kBAAkB,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,UAAU,CAAC;AAEhE;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;AAE7F,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC5B;;;OAGG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;;OAKG;IACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;;;;;OAOG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACvD;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE7F;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mEAAmE;IACnE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,oCAAoC;IACpC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACxF,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;AAK5D;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzF,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;AAK7D;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACxF,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;AA6J5D;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,KAAK,IAAI,EAAE,OAAO,KAAK,CAAC,cAAc,CAAC,KAAG,IAIhF,CAAC;AAwFF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,iBAAiB,GAC7B,KAAK,IAAI,EACT,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,qBAAqB,iBAAiB,EACtC,KAAK,MAAM,EACX,IAAI,EAAE,EACN,YAAY,oBAAoB,KAC9B,IAoEF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,EAAE,OAAO,KAAK,CAAC,SAAS,CAAC,KAAG,KAAK,CAAC,SAAS,CAK3F,CAAC"}
1
+ {"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,8 @@
13
13
  * @module
14
14
  */
15
15
  import { DEV } from 'esm-env';
16
- import { ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_ROUTE_PARAMS, ERROR_INVALID_QUERY_PARAMS } from './error_schemas.js';
16
+ import { ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_ROUTE_PARAMS, ERROR_INVALID_QUERY_PARAMS, ERROR_UNSUPPORTED_CONTENT_TYPE } from './error_schemas.js';
17
+ import { is_post_content_type_refused } from './simple_request.js';
17
18
  import { ThrownJsonrpcError, jsonrpc_error_code_to_http_status, jsonrpc_error_code_to_name, dev_only } from './jsonrpc_errors.js';
18
19
  import { is_null_schema, merge_error_schemas } from './schema_helpers.js';
19
20
  import { dispatch_with_post_commit_rollback } from './pending_effects.js';
@@ -27,6 +28,28 @@ export function get_route_params(c, _schema) {
27
28
  export function get_route_query(c, _schema) {
28
29
  return c.get('validated_query');
29
30
  }
31
+ /**
32
+ * Create the content-type check for a route spec: a `POST` whose
33
+ * `Content-Type` the route refuses (`is_post_content_type_refused` — anything
34
+ * but JSON, or for a `raw_body` route anything a browser sends cross-origin
35
+ * without a preflight) gets 415 before anything else on the route runs. The
36
+ * Rust spine's JSON handlers check the same, first thing after the origin.
37
+ *
38
+ * Returns an empty array for other methods, which are never CORS-simple and
39
+ * so always preflighted.
40
+ */
41
+ const create_content_type_check = (spec) => {
42
+ if (spec.method !== 'POST')
43
+ return [];
44
+ const raw_body = spec.raw_body ?? false;
45
+ const check = async (c, next) => {
46
+ if (is_post_content_type_refused(c.req.header('content-type'), raw_body)) {
47
+ return c.json({ error: ERROR_UNSUPPORTED_CONTENT_TYPE }, 415);
48
+ }
49
+ await next();
50
+ };
51
+ return [check];
52
+ };
30
53
  /**
31
54
  * Create input validation middleware for a route spec.
32
55
  *
@@ -266,10 +289,13 @@ const build_rest_error_body = (err) => {
266
289
  * validation, wraps with error catch layer (catches `ThrownJsonrpcError`
267
290
  * and generic errors), and registers the route.
268
291
  *
269
- * Per-route middleware order: params → query → pre-authorization auth
270
- * guards (401 + rule-3 scope) → authorization phase →
271
- * post-authorization auth guards (403) → input validation (400) →
272
- * handler. Body validation runs behind every authority gate, so a caller
292
+ * Per-route middleware order: content type (415, `POST` only) → params →
293
+ * query → pre-authorization auth guards (401 + rule-3 scope) →
294
+ * authorization phase → post-authorization auth guards (403) → input
295
+ * validation (400) → handler. The content type goes first because it says
296
+ * almost nothing about the route, and a forged request should be refused
297
+ * before any credential is read. Body
298
+ * validation runs behind every authority gate, so a caller
273
299
  * any of them refuse never sees route-shape information from a parse
274
300
  * failure — a 400 there would confirm the route exists and describe how
275
301
  * to call it. Params and query still validate first (they address the
@@ -304,6 +330,7 @@ export const apply_route_specs = (app, specs, resolve_auth_guards, log, db, auth
304
330
  registered.add(route_key);
305
331
  assert_route_auth_acting_biconditional(spec.auth, { input: spec.input, query: spec.query }, `Route "${route_key}"`);
306
332
  const { pre_authorization: pre_authorization_guards, post_authorization: post_authorization_guards } = resolve_auth_guards(spec.auth);
333
+ const content_type_check = create_content_type_check(spec);
307
334
  const params_validation = create_params_validation(spec.params);
308
335
  const query_validation = create_query_validation(spec.query);
309
336
  const input_validation = create_input_validation(spec.input, spec.method);
@@ -340,7 +367,7 @@ export const apply_route_specs = (app, specs, resolve_auth_guards, log, db, auth
340
367
  handler = wrap_output_validation(handler, spec.output, merged_errors, log);
341
368
  // Step 3: error catch layer
342
369
  handler = wrap_error_catch(handler, log);
343
- app.on(spec.method, [spec.path], ...params_validation, ...query_validation, ...pre_authorization_guards, ...authorization, ...post_authorization_guards, ...input_validation, handler);
370
+ app.on(spec.method, [spec.path], ...content_type_check, ...params_validation, ...query_validation, ...pre_authorization_guards, ...authorization, ...post_authorization_guards, ...input_validation, handler);
344
371
  }
345
372
  };
346
373
  /**
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { z } from 'zod';
11
11
  import type { RouteAuth } from './auth_shape.js';
12
+ import type { RouteMethod } from './route_spec.js';
12
13
  import { type RateLimitKey, type RouteErrorSchemas } from './error_schemas.js';
13
14
  /**
14
15
  * Check if a schema is exactly `z.null()`.
@@ -111,11 +112,12 @@ export declare const middleware_applies: (mw_path: string, route_path: string) =
111
112
  * Merge order: derived -> middleware -> explicit route errors.
112
113
  * Later layers override earlier ones for the same status code.
113
114
  *
114
- * @param spec - the route spec (needs `auth`, `input`, `params`, `rate_limit`, `errors`)
115
+ * @param spec - the route spec (reads `method`, `auth`, `input`, `params`, `query`, `rate_limit`, `errors`)
115
116
  * @param middleware_errors - errors contributed by middleware whose path matches the route
116
117
  * @returns merged error schemas, or `null` if empty
117
118
  */
118
119
  export declare const merge_error_schemas: (spec: {
120
+ method: RouteMethod;
119
121
  auth: RouteAuth;
120
122
  input: z.ZodType;
121
123
  params?: z.ZodObject;
@@ -1 +1 @@
1
- {"version":3,"file":"schema_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/schema_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAAI,YAAY,OAAO,KAAG,OAUpD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,CAAC,CAAC,OAAO,EAAE,YAAY,OAAO,KAAG,OACA,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gCAAgC,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EACnE,QAAQ,CAAC,EACT,OAAO,OAAO,KACZ,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAclC,CAAC;AAWF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OACe,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAQrD,CAAC;AAoBF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,KAAG,OAQxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM;IACL,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B,EACD,oBAAoB,iBAAiB,GAAG,IAAI,KAC1C,iBAAiB,GAAG,IAUtB,CAAC"}
1
+ {"version":3,"file":"schema_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/schema_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAAI,YAAY,OAAO,KAAG,OAUpD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,CAAC,CAAC,OAAO,EAAE,YAAY,OAAO,KAAG,OACA,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gCAAgC,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EACnE,QAAQ,CAAC,EACT,OAAO,OAAO,KACZ,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAclC,CAAC;AAWF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OACe,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAQrD,CAAC;AAoBF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,KAAG,OAQxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM;IACL,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B,EACD,oBAAoB,iBAAiB,GAAG,IAAI,KAC1C,iBAAiB,GAAG,IAWtB,CAAC"}
@@ -185,7 +185,7 @@ export const middleware_applies = (mw_path, route_path) => {
185
185
  * Merge order: derived -> middleware -> explicit route errors.
186
186
  * Later layers override earlier ones for the same status code.
187
187
  *
188
- * @param spec - the route spec (needs `auth`, `input`, `params`, `rate_limit`, `errors`)
188
+ * @param spec - the route spec (reads `method`, `auth`, `input`, `params`, `query`, `rate_limit`, `errors`)
189
189
  * @param middleware_errors - errors contributed by middleware whose path matches the route
190
190
  * @returns merged error schemas, or `null` if empty
191
191
  */
@@ -195,7 +195,8 @@ export const merge_error_schemas = (spec, middleware_errors) => {
195
195
  has_input: !is_null_schema(spec.input),
196
196
  has_params: !!spec.params,
197
197
  has_query: !!spec.query,
198
- rate_limit: spec.rate_limit
198
+ rate_limit: spec.rate_limit,
199
+ checks_content_type: spec.method === 'POST'
199
200
  });
200
201
  const merged = { ...derived, ...middleware_errors, ...spec.errors };
201
202
  return Object.keys(merged).length > 0 ? merged : null;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Refusing CORS simple requests — the cross-origin POSTs a browser sends
3
+ * without asking first.
4
+ *
5
+ * A cross-origin `fetch` or form `POST` skips the CORS preflight when its
6
+ * `Content-Type` is absent or one of the three CORS-safelisted types
7
+ * (`text/plain`, `application/x-www-form-urlencoded`, `multipart/form-data`).
8
+ * The browser sends such a request as-is, with whatever cookies apply — only
9
+ * the response is withheld from the page. Any other content type makes the
10
+ * browser send an `OPTIONS` preflight first, and since fuz_app answers no
11
+ * preflight with CORS headers, the real request is never sent.
12
+ *
13
+ * The session cookie's `SameSite=Strict` doesn't close this on its own:
14
+ * same-site ignores the port, so a page on `http://localhost:8000` is
15
+ * same-site with a server on `http://localhost:4460` and its requests carry
16
+ * the cookie. The origin allowlist (`http/origin.ts`) refuses such a page
17
+ * unless a wildcard (`http://localhost:*`) admits it; this gate refuses it
18
+ * whatever the allowlist says.
19
+ *
20
+ * So every `POST` route carries a content-type check, run first by
21
+ * `apply_route_specs` (`is_post_content_type_refused`, 415
22
+ * `unsupported_content_type`): a JSON route takes only `application/json`,
23
+ * bodyless or not, and a `raw_body` route takes any type that isn't
24
+ * safelisted. Only `POST` needs it: `PUT`, `PATCH`, and `DELETE` are never
25
+ * simple methods, so the browser always preflights them.
26
+ *
27
+ * The check rests on the server answering no preflight. A consumer that adds
28
+ * CORS middleware granting cross-origin requests reopens what this closes.
29
+ *
30
+ * Twin of the Rust spine's `fuz_http::simple_request`: the same predicates,
31
+ * `is_json_content_type` checked in each spine JSON handler and
32
+ * `is_cors_simple_content_type` by the `simple_request_layer` consumers mount
33
+ * over routes with other bodies.
34
+ *
35
+ * @module
36
+ */
37
+ /**
38
+ * True when a browser could have sent a `POST` with this `Content-Type`
39
+ * cross-origin without a preflight: none, or a CORS-safelisted one.
40
+ *
41
+ * Matches more than browsers do (a value they'd refuse to call safelisted —
42
+ * one with odd bytes, say — counts as safelisted here), so it only ever
43
+ * refuses too much, never too little.
44
+ *
45
+ * @param content_type - the request's `Content-Type` header, if any
46
+ */
47
+ export declare const is_cors_simple_content_type: (content_type: string | undefined) => boolean;
48
+ /**
49
+ * True when `Content-Type` is `application/json` (any parameters, such as
50
+ * `charset`, allowed).
51
+ *
52
+ * @param content_type - the request's `Content-Type` header, if any
53
+ */
54
+ export declare const is_json_content_type: (content_type: string | undefined) => boolean;
55
+ /**
56
+ * True when a `POST` route refuses this `Content-Type`: a JSON route anything
57
+ * but `application/json`, a `raw_body` route a CORS-safelisted type or none.
58
+ *
59
+ * @param content_type - the request's `Content-Type` header, if any
60
+ * @param raw_body - the route's `RouteSpec.raw_body`
61
+ */
62
+ export declare const is_post_content_type_refused: (content_type: string | undefined, raw_body: boolean) => boolean;
63
+ //# sourceMappingURL=simple_request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simple_request.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/simple_request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAqBH;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GAAI,cAAc,MAAM,GAAG,SAAS,KAAG,OAI9E,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,cAAc,MAAM,GAAG,SAAS,KAAG,OACmB,CAAC;AAE5F;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,cAAc,MAAM,GAAG,SAAS,EAChC,UAAU,OAAO,KACf,OACwF,CAAC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Refusing CORS simple requests — the cross-origin POSTs a browser sends
3
+ * without asking first.
4
+ *
5
+ * A cross-origin `fetch` or form `POST` skips the CORS preflight when its
6
+ * `Content-Type` is absent or one of the three CORS-safelisted types
7
+ * (`text/plain`, `application/x-www-form-urlencoded`, `multipart/form-data`).
8
+ * The browser sends such a request as-is, with whatever cookies apply — only
9
+ * the response is withheld from the page. Any other content type makes the
10
+ * browser send an `OPTIONS` preflight first, and since fuz_app answers no
11
+ * preflight with CORS headers, the real request is never sent.
12
+ *
13
+ * The session cookie's `SameSite=Strict` doesn't close this on its own:
14
+ * same-site ignores the port, so a page on `http://localhost:8000` is
15
+ * same-site with a server on `http://localhost:4460` and its requests carry
16
+ * the cookie. The origin allowlist (`http/origin.ts`) refuses such a page
17
+ * unless a wildcard (`http://localhost:*`) admits it; this gate refuses it
18
+ * whatever the allowlist says.
19
+ *
20
+ * So every `POST` route carries a content-type check, run first by
21
+ * `apply_route_specs` (`is_post_content_type_refused`, 415
22
+ * `unsupported_content_type`): a JSON route takes only `application/json`,
23
+ * bodyless or not, and a `raw_body` route takes any type that isn't
24
+ * safelisted. Only `POST` needs it: `PUT`, `PATCH`, and `DELETE` are never
25
+ * simple methods, so the browser always preflights them.
26
+ *
27
+ * The check rests on the server answering no preflight. A consumer that adds
28
+ * CORS middleware granting cross-origin requests reopens what this closes.
29
+ *
30
+ * Twin of the Rust spine's `fuz_http::simple_request`: the same predicates,
31
+ * `is_json_content_type` checked in each spine JSON handler and
32
+ * `is_cors_simple_content_type` by the `simple_request_layer` consumers mount
33
+ * over routes with other bodies.
34
+ *
35
+ * @module
36
+ */
37
+ /** The content types a browser sends cross-origin without a preflight. */
38
+ const CORS_SAFELISTED_CONTENT_TYPES = new Set([
39
+ 'text/plain',
40
+ 'application/x-www-form-urlencoded',
41
+ 'multipart/form-data'
42
+ ]);
43
+ /** Any character outside visible ASCII and tab — what Rust's `HeaderValue::to_str` refuses. */
44
+ const NON_VISIBLE_ASCII = /[^\t\x20-\x7e]/;
45
+ /**
46
+ * The MIME essence (`type/subtype`, lowercased, parameters dropped) of a
47
+ * `Content-Type` value, or `null` when it holds anything but visible ASCII.
48
+ */
49
+ const to_content_type_essence = (content_type) => {
50
+ if (NON_VISIBLE_ASCII.test(content_type))
51
+ return null;
52
+ return (content_type.split(';')[0] ?? '').trim().toLowerCase();
53
+ };
54
+ /**
55
+ * True when a browser could have sent a `POST` with this `Content-Type`
56
+ * cross-origin without a preflight: none, or a CORS-safelisted one.
57
+ *
58
+ * Matches more than browsers do (a value they'd refuse to call safelisted —
59
+ * one with odd bytes, say — counts as safelisted here), so it only ever
60
+ * refuses too much, never too little.
61
+ *
62
+ * @param content_type - the request's `Content-Type` header, if any
63
+ */
64
+ export const is_cors_simple_content_type = (content_type) => {
65
+ if (content_type === undefined)
66
+ return true;
67
+ const essence = to_content_type_essence(content_type);
68
+ return essence === null || CORS_SAFELISTED_CONTENT_TYPES.has(essence);
69
+ };
70
+ /**
71
+ * True when `Content-Type` is `application/json` (any parameters, such as
72
+ * `charset`, allowed).
73
+ *
74
+ * @param content_type - the request's `Content-Type` header, if any
75
+ */
76
+ export const is_json_content_type = (content_type) => content_type !== undefined && to_content_type_essence(content_type) === 'application/json';
77
+ /**
78
+ * True when a `POST` route refuses this `Content-Type`: a JSON route anything
79
+ * but `application/json`, a `raw_body` route a CORS-safelisted type or none.
80
+ *
81
+ * @param content_type - the request's `Content-Type` header, if any
82
+ * @param raw_body - the route's `RouteSpec.raw_body`
83
+ */
84
+ export const is_post_content_type_refused = (content_type, raw_body) => raw_body ? is_cors_simple_content_type(content_type) : !is_json_content_type(content_type);
@@ -6,7 +6,6 @@
6
6
  *
7
7
  * @module
8
8
  */
9
- import { z } from 'zod';
10
9
  import { schema_to_surface, middleware_applies, merge_error_schemas, is_null_schema, is_strict_object_schema } from './schema_helpers.js';
11
10
  // --- Surface generation ---
12
11
  /**
@@ -7,7 +7,6 @@
7
7
  * @module
8
8
  */
9
9
  import { streamSSE } from 'hono/streaming';
10
- import { z } from 'zod';
11
10
  import { DEV } from 'esm-env';
12
11
  import { SSE_CONNECTED_COMMENT } from './sse_constants.js';
13
12
  const is_node_connection_stream = (value) => {
@@ -63,13 +63,13 @@ export interface BootstrapDisabledOptions {
63
63
  }
64
64
  export interface BootstrapSurfaceOnlyOptions {
65
65
  mode: 'surface_only';
66
- /** Route prefix for surface generation. Default `'/api/account'`. */
66
+ /** Route prefix for surface generation. Default `'/api/account'`; must sit under `/api/`. */
67
67
  route_prefix?: string;
68
68
  }
69
69
  export interface BootstrapLiveOptions {
70
70
  mode: 'live';
71
71
  token_path: string;
72
- /** Route prefix for bootstrap routes. Default `'/api/account'`. */
72
+ /** Route prefix for bootstrap routes. Default `'/api/account'`; must sit under `/api/`. */
73
73
  route_prefix?: string;
74
74
  /**
75
75
  * Called after successful bootstrap (account + session created).
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,6 @@
10
10
  import { Hono } from 'hono';
11
11
  import { logger } from 'hono/logger';
12
12
  import { bodyLimit } from 'hono/body-limit';
13
- import { z } from 'zod';
14
13
  import { session_cookie_options } from '../auth/session_cookie.js';
15
14
  import { create_audit_log_sse, create_sse_connection_closer, audit_log_event_specs } from '../realtime/sse_auth_guard.js';
16
15
  import { BaseServerEnv } from './env.js';
@@ -333,7 +332,11 @@ const assemble_app_server = async (options, releases) => {
333
332
  bootstrap_ip_rate_limiter
334
333
  });
335
334
  const prefix = options.bootstrap.route_prefix ?? '/api/account';
336
- factory_routes.push(...prefix_route_specs(prefix, bootstrap_routes));
335
+ const prefixed = prefix_route_specs(prefix, bootstrap_routes);
336
+ for (const route of prefixed) {
337
+ assert_endpoint_in_auth_scope('bootstrap.route_prefix', route.path);
338
+ }
339
+ factory_routes.push(...prefixed);
337
340
  }
338
341
  // RPC endpoint auto-mount — resolve specs then append their routes so
339
342
  // surface generation and live dispatch share one source of truth.
@@ -1564,6 +1564,22 @@ in-process `auth/origin_parity.db.test.ts` + the cross-process
1564
1564
  Rust spine returned a plain-text body — now converged to the canonical TS
1565
1565
  `{error: "forbidden_origin"}` via `fuz_http::forbidden_origin_response()`.
1566
1566
 
1567
+ ### CORS simple-request parity — `cross_backend/simple_request.ts`
1568
+
1569
+ `describe_simple_request_cross_tests({setup_test, rpc_path?, login_path?, logout_path?})`
1570
+ — a JSON route refuses any `POST` that isn't `application/json` with a flat
1571
+ 415 `{error: 'unsupported_content_type'}` before reading a credential or the
1572
+ body: the CORS-safelisted types in any spelling (`text/plain`, `Text/Plain`,
1573
+ form-encoded, multipart) on RPC with a session, untyped RPC (a byte body, so
1574
+ `fetch` adds no type), `application/octet-stream` / `+json` / empty-typed RPC
1575
+ and login, form-encoded login, a forged `text/plain` logout with a session
1576
+ (which must leave the session valid), and an untyped logout with no session
1577
+ (415, not 401); a bodyless JSON logout reaches the auth check (401), and
1578
+ `application/json` with a charset is dispatched. The TS spine checks per route
1579
+ (`apply_route_specs`), the Rust spine in each JSON handler. Runs both legs:
1580
+ the in-process `auth/simple_request_parity.db.test.ts` and the cross-process
1581
+ `simple_request.cross.test.ts`.
1582
+
1567
1583
  ### Query-string shape parity — `cross_backend/query_shape.ts`
1568
1584
 
1569
1585
  `describe_query_shape_cross_tests({setup_test, capabilities, rpc_path?})` —
@@ -1 +1 @@
1
- {"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW3E,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAmB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAYvE;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAiBD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IAk5BF,CAAC"}
1
+ {"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW3E,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAmB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAYvE;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAiBD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IAm5BF,CAAC"}
@@ -595,7 +595,8 @@ export const describe_standard_admin_integration_tests = (options) => {
595
595
  headers: {
596
596
  host: 'localhost',
597
597
  origin: 'http://localhost:5173',
598
- cookie: `${cookie_name}=${user_two_cookie}`
598
+ cookie: `${cookie_name}=${user_two_cookie}`,
599
+ 'content-type': 'application/json'
599
600
  }
600
601
  });
601
602
  }
@@ -1 +1 @@
1
- {"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAejC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsDpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA2J3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,iIAAiI;IACjI,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAC5D;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA4EF,CAAC"}
1
+ {"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAejC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsDpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA6J3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,iIAAiI;IACjI,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAC5D;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA4EF,CAAC"}