@fuzdev/fuz_app 0.131.0 → 0.132.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/transports_http.d.ts +7 -0
- package/dist/actions/transports_http.d.ts.map +1 -1
- package/dist/actions/transports_http.js +18 -1
- package/dist/auth/CLAUDE.md +1 -1
- package/dist/auth/middleware.d.ts +13 -10
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +14 -10
- package/dist/http/CLAUDE.md +3 -2
- package/dist/http/error_schemas.d.ts +16 -1
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +14 -1
- package/dist/http/origin.d.ts +10 -6
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +10 -6
- package/dist/http/route_spec.d.ts +19 -12
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +33 -6
- package/dist/http/schema_helpers.d.ts +3 -1
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +3 -2
- package/dist/http/simple_request.d.ts +63 -0
- package/dist/http/simple_request.d.ts.map +1 -0
- package/dist/http/simple_request.js +84 -0
- package/dist/server/app_server.d.ts +2 -2
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +5 -1
- package/dist/testing/CLAUDE.md +17 -0
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +4 -2
- package/dist/testing/adversarial_404.d.ts.map +1 -1
- package/dist/testing/adversarial_404.js +5 -3
- package/dist/testing/adversarial_input.d.ts.map +1 -1
- package/dist/testing/adversarial_input.js +9 -3
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +15 -16
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/cross_backend/simple_request.d.ts +11 -0
- package/dist/testing/cross_backend/simple_request.d.ts.map +1 -0
- package/dist/testing/cross_backend/simple_request.js +142 -0
- package/dist/testing/cross_backend/standard.d.ts +6 -0
- package/dist/testing/cross_backend/standard.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.js +2 -1
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +11 -8
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +40 -11
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +3 -9
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +19 -6
- package/dist/testing/schema_generators.d.ts +12 -0
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +10 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +3 -0
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +5 -1
- package/package.json +3 -3
|
@@ -22,6 +22,13 @@ import type { Transport, TransportSendOptions } from './transports.js';
|
|
|
22
22
|
export declare class FrontendHttpTransport implements Transport {
|
|
23
23
|
#private;
|
|
24
24
|
readonly transport_name: "frontend_http_rpc";
|
|
25
|
+
/**
|
|
26
|
+
* @param url - the JSON-RPC endpoint
|
|
27
|
+
* @param headers - extra headers, merged over the JSON `content-type` and
|
|
28
|
+
* `accept` defaults case-insensitively — a POST without
|
|
29
|
+
* `application/json` is refused (415)
|
|
30
|
+
* @param has_side_effects - when it returns `false` for a method, the call is a GET
|
|
31
|
+
*/
|
|
25
32
|
constructor(url: string, headers?: Record<string, string>, has_side_effects?: (method: string) => boolean);
|
|
26
33
|
send(message: JsonrpcRequest, options?: TransportSendOptions): Promise<JsonrpcResponseOrError>;
|
|
27
34
|
send(message: JsonrpcNotification, options?: TransportSendOptions): Promise<JsonrpcErrorResponse | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports_http.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,OAAO,EACN,oBAAoB,EAGpB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAEnB,KAAK,sBAAsB,EAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"transports_http.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,OAAO,EACN,oBAAoB,EAGpB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAEnB,KAAK,sBAAsB,EAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAQvE;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAsB,YAAW,SAAS;;IACtD,QAAQ,CAAC,cAAc,EAAG,mBAAmB,CAAU;IAMvD;;;;;;OAMG;gBAEF,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO;IAYzC,IAAI,CACT,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,sBAAsB,CAAC;IAC5B,IAAI,CACT,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA+FvC,QAAQ,IAAI,OAAO;CAGnB"}
|
|
@@ -7,6 +7,11 @@ import { DEV } from 'esm-env';
|
|
|
7
7
|
import { ThrownJsonrpcError, jsonrpc_error_messages, http_status_to_jsonrpc_error_code, jsonrpc_error_code_to_http_status, UNKNOWN_ERROR_MESSAGE } from '../http/jsonrpc_errors.js';
|
|
8
8
|
import { create_jsonrpc_error_response, to_jsonrpc_message_id, is_jsonrpc_error_response, is_jsonrpc_response } from '../http/jsonrpc_helpers.js';
|
|
9
9
|
import { JsonrpcErrorResponse } from '../http/jsonrpc.js';
|
|
10
|
+
/** Headers every request carries unless the caller's replace them. */
|
|
11
|
+
const DEFAULT_HEADERS = {
|
|
12
|
+
'content-type': 'application/json',
|
|
13
|
+
accept: 'application/json'
|
|
14
|
+
};
|
|
10
15
|
/**
|
|
11
16
|
* Thin `fetch` adapter for the JSON-RPC endpoint. POST by default; GET when
|
|
12
17
|
* the optional `has_side_effects(method)` callback returns `false` for the
|
|
@@ -26,9 +31,21 @@ export class FrontendHttpTransport {
|
|
|
26
31
|
#url;
|
|
27
32
|
#headers;
|
|
28
33
|
#has_side_effects;
|
|
34
|
+
/**
|
|
35
|
+
* @param url - the JSON-RPC endpoint
|
|
36
|
+
* @param headers - extra headers, merged over the JSON `content-type` and
|
|
37
|
+
* `accept` defaults case-insensitively — a POST without
|
|
38
|
+
* `application/json` is refused (415)
|
|
39
|
+
* @param has_side_effects - when it returns `false` for a method, the call is a GET
|
|
40
|
+
*/
|
|
29
41
|
constructor(url, headers, has_side_effects) {
|
|
30
42
|
this.#url = url;
|
|
31
|
-
this.#headers =
|
|
43
|
+
this.#headers = { ...DEFAULT_HEADERS };
|
|
44
|
+
if (headers) {
|
|
45
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
46
|
+
this.#headers[name.toLowerCase()] = value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
32
49
|
this.#has_side_effects = has_side_effects;
|
|
33
50
|
}
|
|
34
51
|
async send(message, options) {
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -215,7 +215,7 @@ Everything else listed under §RPC action surfaces.
|
|
|
215
215
|
|
|
216
216
|
### Middleware
|
|
217
217
|
|
|
218
|
-
- `auth/middleware.ts` — `create_auth_middleware_specs(deps, options)` assembles `[origin, session, request_context, bearer_auth]` + optional `daemon_token
|
|
218
|
+
- `auth/middleware.ts` — `create_auth_middleware_specs(deps, options)` assembles `[origin, session, request_context, bearer_auth]` + optional `daemon_token` (the content-type refusal isn't a middleware: `apply_route_specs` checks every `POST` route, see `http/simple_request.ts`). `AUTH_MIDDLEWARE_PATH` (`'/api/*'`) is where `create_app_server` mounts the stack; two assembly asserts lean on it: `assert_endpoint_in_auth_scope` (an `rpc_endpoints` / `ws_endpoints` path, or the bootstrap route under `bootstrap.route_prefix`, must sit inside it) and `assert_middleware_stack_mounted(stack_specs, middleware_specs)`, run on a `transform_middleware` result against the pre-transform stack (`[proxy, ...auth]`) — each stack spec, matched by its `handler` closure (not its spoofable `name`), mounted exactly once at its original path (`'*'` for the trusted proxy, `AUTH_MIDDLEWARE_PATH` for auth), in its original order; other specs may sit anywhere, including before the proxy and between the auth layers. The proxy is in the stack because dropping it turns every `get_client_ip` into `'unknown'`, one shared bucket for every IP-keyed limiter. The transform receives shallow copies in a fresh array, so an in-place mutation can't rewrite the originals the check reads. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access. Both run in `create_test_app_surface_spec` too.
|
|
219
219
|
- `auth/request_context.ts` — `RequestContext`, `resolve_acting_actor`, `build_request_context`, predicates (`has_role`, `has_scoped_role`, `has_any_scoped_role` — the dispatcher's role gate and every builtin-role check read the **global** grant via `has_scoped_role(_, role, null)` / `has_any_scoped_role(_, roles, null)`; scope-blind `has_role` is never the gate for a builtin role), guards (`require_auth`, `require_role`, `require_credential_types`, `require_token_scope`), `token_scope_surface_denial` (the direct-call form, for surfaces that aren't route specs), `refresh_role_grants`.
|
|
220
220
|
- `auth/session_middleware.ts` — `process_session_cookie` integration, `create_session_and_set_cookie` (shared by login / signup / bootstrap; returns the token hashes of the sessions the cap evicted, which the caller closes after its commit — only login passes a cap).
|
|
221
221
|
- `auth/resolved_auth.ts` — `ResolvedAuth` (the resolved credential as one value: `account_id`, `credential_type`, `token_hash`, `api_token_id`), `get_resolved_auth(c)` (gathers it from the context keys the auth middleware set), and `revalidate_resolved_auth(deps, resolved)`, the no-touch re-read a long-lived connection runs once it is registered: session row / token row (by id) still live and still on the account, account not soft-deleted; a daemon token re-reads its account only. Reuses the middleware's own queries and predicates. Called by the WS upgrade (`actions/register_action_ws.ts`) and the audit SSE route between registering a connection pending and admitting it. Twin of the Rust `revalidate_resolved_auth`.
|
|
@@ -17,27 +17,30 @@ import type { MiddlewareSpec } from '../http/middleware_spec.js';
|
|
|
17
17
|
*
|
|
18
18
|
* `create_app_server` mounts the stack here, and refuses at assembly (via
|
|
19
19
|
* `assert_endpoint_in_auth_scope`, which `create_test_app_surface_spec` also
|
|
20
|
-
* runs) an `rpc_endpoints` or
|
|
21
|
-
* with `middleware_applies`, which follows Hono: `'/api/*'`
|
|
22
|
-
* and everything under `/api/`): an endpoint outside it would
|
|
23
|
-
* session parsing, so every call or upgrade would be
|
|
20
|
+
* runs) an `rpc_endpoints`, `ws_endpoints`, or bootstrap path the pattern does
|
|
21
|
+
* not match (tested with `middleware_applies`, which follows Hono: `'/api/*'`
|
|
22
|
+
* matches `/api` and everything under `/api/`): an endpoint outside it would
|
|
23
|
+
* get no origin check or session parsing, so every call or upgrade would be
|
|
24
|
+
* unauthenticated and any page could send it.
|
|
24
25
|
*/
|
|
25
26
|
export declare const AUTH_MIDDLEWARE_PATH = "/api/*";
|
|
26
27
|
/**
|
|
27
28
|
* Throw when an endpoint path sits outside the auth middleware's scope
|
|
28
|
-
* (`AUTH_MIDDLEWARE_PATH`) — there it would get no
|
|
29
|
-
* parsing, so every call or upgrade would be
|
|
29
|
+
* (`AUTH_MIDDLEWARE_PATH`) — there it would get no origin check and no
|
|
30
|
+
* session or bearer parsing, so every call or upgrade would be
|
|
31
|
+
* unauthenticated and any page could send it.
|
|
30
32
|
* `middleware_applies` matches the way Hono matches the middleware's pattern.
|
|
31
33
|
*
|
|
32
34
|
* `create_app_server` calls it for every `rpc_endpoints` and `ws_endpoints`
|
|
33
|
-
* path at assembly, and `create_test_app_surface_spec`
|
|
34
|
-
* surface snapshot never records an endpoint the real
|
|
35
|
+
* path and the bootstrap route at assembly, and `create_test_app_surface_spec`
|
|
36
|
+
* does the same, so a surface snapshot never records an endpoint the real
|
|
37
|
+
* server refuses.
|
|
35
38
|
*
|
|
36
39
|
* @param option - the option the path came from, named in the error
|
|
37
40
|
* @param path - the endpoint path to check
|
|
38
41
|
* @throws Error when `path` is outside `AUTH_MIDDLEWARE_PATH`
|
|
39
42
|
*/
|
|
40
|
-
export declare const assert_endpoint_in_auth_scope: (option: "rpc_endpoints" | "ws_endpoints", path: string) => void;
|
|
43
|
+
export declare const assert_endpoint_in_auth_scope: (option: "rpc_endpoints" | "ws_endpoints" | "bootstrap.route_prefix", path: string) => void;
|
|
41
44
|
/**
|
|
42
45
|
* Throw unless a middleware list still mounts every spec of the stack it was
|
|
43
46
|
* built from — the trusted-proxy spec and the auth middleware — exactly once,
|
|
@@ -84,7 +87,7 @@ export interface AuthMiddlewareOptions {
|
|
|
84
87
|
*
|
|
85
88
|
* Returns `[origin, session, request_context, bearer_auth]` middleware specs
|
|
86
89
|
* for the given path pattern. When `daemon_token_state` is provided, appends
|
|
87
|
-
* a
|
|
90
|
+
* a `daemon_token` layer. Apps can append extra entries for non-standard
|
|
88
91
|
* paths (e.g., tx's `/tx` binary endpoint).
|
|
89
92
|
*
|
|
90
93
|
* @param deps - stateless capabilities (keyring, db)
|
|
@@ -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,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAIjE
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAIjE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,6BAA6B,GACzC,QAAQ,eAAe,GAAG,cAAc,GAAG,wBAAwB,EACnE,MAAM,MAAM,KACV,IAQF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,+BAA+B,GAC3C,aAAa,aAAa,CAAC,cAAc,CAAC,EAC1C,kBAAkB,aAAa,CAAC,cAAc,CAAC,KAC7C,IA+BF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CA4E/B,CAAC"}
|
package/dist/auth/middleware.js
CHANGED
|
@@ -15,21 +15,24 @@ import { middleware_applies } from '../http/schema_helpers.js';
|
|
|
15
15
|
*
|
|
16
16
|
* `create_app_server` mounts the stack here, and refuses at assembly (via
|
|
17
17
|
* `assert_endpoint_in_auth_scope`, which `create_test_app_surface_spec` also
|
|
18
|
-
* runs) an `rpc_endpoints` or
|
|
19
|
-
* with `middleware_applies`, which follows Hono: `'/api/*'`
|
|
20
|
-
* and everything under `/api/`): an endpoint outside it would
|
|
21
|
-
* session parsing, so every call or upgrade would be
|
|
18
|
+
* runs) an `rpc_endpoints`, `ws_endpoints`, or bootstrap path the pattern does
|
|
19
|
+
* not match (tested with `middleware_applies`, which follows Hono: `'/api/*'`
|
|
20
|
+
* matches `/api` and everything under `/api/`): an endpoint outside it would
|
|
21
|
+
* get no origin check or session parsing, so every call or upgrade would be
|
|
22
|
+
* unauthenticated and any page could send it.
|
|
22
23
|
*/
|
|
23
24
|
export const AUTH_MIDDLEWARE_PATH = '/api/*';
|
|
24
25
|
/**
|
|
25
26
|
* Throw when an endpoint path sits outside the auth middleware's scope
|
|
26
|
-
* (`AUTH_MIDDLEWARE_PATH`) — there it would get no
|
|
27
|
-
* parsing, so every call or upgrade would be
|
|
27
|
+
* (`AUTH_MIDDLEWARE_PATH`) — there it would get no origin check and no
|
|
28
|
+
* session or bearer parsing, so every call or upgrade would be
|
|
29
|
+
* unauthenticated and any page could send it.
|
|
28
30
|
* `middleware_applies` matches the way Hono matches the middleware's pattern.
|
|
29
31
|
*
|
|
30
32
|
* `create_app_server` calls it for every `rpc_endpoints` and `ws_endpoints`
|
|
31
|
-
* path at assembly, and `create_test_app_surface_spec`
|
|
32
|
-
* surface snapshot never records an endpoint the real
|
|
33
|
+
* path and the bootstrap route at assembly, and `create_test_app_surface_spec`
|
|
34
|
+
* does the same, so a surface snapshot never records an endpoint the real
|
|
35
|
+
* server refuses.
|
|
33
36
|
*
|
|
34
37
|
* @param option - the option the path came from, named in the error
|
|
35
38
|
* @param path - the endpoint path to check
|
|
@@ -38,7 +41,8 @@ export const AUTH_MIDDLEWARE_PATH = '/api/*';
|
|
|
38
41
|
export const assert_endpoint_in_auth_scope = (option, path) => {
|
|
39
42
|
if (!middleware_applies(AUTH_MIDDLEWARE_PATH, path)) {
|
|
40
43
|
throw new Error(`${option} path ${path} is outside the auth middleware's scope ` +
|
|
41
|
-
`(${AUTH_MIDDLEWARE_PATH}) — it would
|
|
44
|
+
`(${AUTH_MIDDLEWARE_PATH}) — it would skip the origin check and see every caller ` +
|
|
45
|
+
`as unauthenticated — mount it under /api/`);
|
|
42
46
|
}
|
|
43
47
|
};
|
|
44
48
|
/**
|
|
@@ -105,7 +109,7 @@ export const assert_middleware_stack_mounted = (stack_specs, middleware_specs) =
|
|
|
105
109
|
*
|
|
106
110
|
* Returns `[origin, session, request_context, bearer_auth]` middleware specs
|
|
107
111
|
* for the given path pattern. When `daemon_token_state` is provided, appends
|
|
108
|
-
* a
|
|
112
|
+
* a `daemon_token` layer. Apps can append extra entries for non-standard
|
|
109
113
|
* paths (e.g., tx's `/tx` binary endpoint).
|
|
110
114
|
*
|
|
111
115
|
* @param deps - stateless capabilities (keyring, db)
|
package/dist/http/CLAUDE.md
CHANGED
|
@@ -151,7 +151,7 @@ WS); see ../../../docs/architecture.md §DEV-only Output Validation.
|
|
|
151
151
|
`http/error_schemas.ts` is the **declarative** error surface:
|
|
152
152
|
|
|
153
153
|
- `ERROR_*` `snake_case` string constants — single source of truth; use `.literal(ERROR_*)` in Zod schemas and inline checks in handlers
|
|
154
|
-
- `ApiError`, `ValidationError`, `PermissionError`, `CredentialTypeRequiredError`, `RateLimitError`, `PayloadTooLargeError`, `ForeignKeyError` — standard shapes
|
|
154
|
+
- `ApiError`, `ValidationError`, `PermissionError`, `CredentialTypeRequiredError`, `RateLimitError`, `PayloadTooLargeError`, `UnsupportedContentTypeError`, `ForeignKeyError` — standard shapes
|
|
155
155
|
- `RouteErrorSchemas = Partial<Record<number, z.ZodType>>`
|
|
156
156
|
- `RateLimitKey = 'ip' | 'account' | 'both'`
|
|
157
157
|
|
|
@@ -167,12 +167,13 @@ Pair every schema with the `z.infer` type export.
|
|
|
167
167
|
`merge_error_schemas(spec, middleware_errors?)` (in `http/schema_helpers.ts`)
|
|
168
168
|
merges three layers, later overrides earlier at the same status code:
|
|
169
169
|
|
|
170
|
-
1. **Derived** — from `derive_error_schemas({auth, has_input?, has_params?, has_query?, rate_limit?})`:
|
|
170
|
+
1. **Derived** — from `derive_error_schemas({auth, has_input?, has_params?, has_query?, rate_limit?, checks_content_type?})`:
|
|
171
171
|
- `has_input || has_params || has_query` → 400 `ValidationError`
|
|
172
172
|
- `auth.account === 'required'` or `auth.actor === 'required'` → 401 `ApiError`
|
|
173
173
|
- `auth.roles?.length` → 403 `PermissionError` (carries `required_roles`)
|
|
174
174
|
- `auth.credential_types?.length` → 403 `CredentialTypeRequiredError` (carries `required_credential_types`; both gates set yields `z.union([PermissionError, CredentialTypeRequiredError])`)
|
|
175
175
|
- `rate_limit` → 429 `RateLimitError`
|
|
176
|
+
- `checks_content_type` (every `POST`) → 415 `UnsupportedContentTypeError`
|
|
176
177
|
- `auth.actor !== 'none'` → widens 400 to a union with `ActorRequiredError` / `ActorNotOnAccountError` and adds 500 union of `NoActorsOnAccountError` / `AccountVanishedError`. Mirrors what the dispatcher's authorization phase actually emits on routes whose input declares `acting?: ActingActor` (per registry-time invariant 2) — so DEV-mode error-schema validation doesn't reject the auth phase's body.
|
|
177
178
|
2. **Middleware** — from `MiddlewareSpec.errors` that apply to the route's path (via `middleware_applies`)
|
|
178
179
|
3. **Explicit** — `RouteSpec.errors` — always wins
|
|
@@ -55,6 +55,12 @@ export declare const ERROR_INVALID_CREDENTIALS: "invalid_credentials";
|
|
|
55
55
|
export declare const ERROR_PAYLOAD_TOO_LARGE: "payload_too_large";
|
|
56
56
|
/** Request origin not in allowlist. */
|
|
57
57
|
export declare const ERROR_FORBIDDEN_ORIGIN: "forbidden_origin";
|
|
58
|
+
/**
|
|
59
|
+
* A `POST` whose `Content-Type` the route refuses: anything but
|
|
60
|
+
* `application/json` on a JSON route, a CORS-safelisted type or none on a
|
|
61
|
+
* `raw_body` one. See `http/simple_request.ts`.
|
|
62
|
+
*/
|
|
63
|
+
export declare const ERROR_UNSUPPORTED_CONTENT_TYPE: "unsupported_content_type";
|
|
58
64
|
/** Bearer token failed validation (missing, malformed, or revoked). */
|
|
59
65
|
export declare const ERROR_INVALID_TOKEN: "invalid_token";
|
|
60
66
|
/** Token references a deleted account. */
|
|
@@ -230,6 +236,11 @@ export declare const PayloadTooLargeError: z.ZodObject<{
|
|
|
230
236
|
error: z.ZodLiteral<"payload_too_large">;
|
|
231
237
|
}, z.core.$loose>;
|
|
232
238
|
export type PayloadTooLargeError = z.infer<typeof PayloadTooLargeError>;
|
|
239
|
+
/** Unsupported content type error — returned when a `POST` route refuses the request's `Content-Type`. */
|
|
240
|
+
export declare const UnsupportedContentTypeError: z.ZodObject<{
|
|
241
|
+
error: z.ZodLiteral<"unsupported_content_type">;
|
|
242
|
+
}, z.core.$loose>;
|
|
243
|
+
export type UnsupportedContentTypeError = z.infer<typeof UnsupportedContentTypeError>;
|
|
233
244
|
/** Foreign key violation error — returned when a delete is blocked by references. */
|
|
234
245
|
export declare const ForeignKeyError: z.ZodObject<{
|
|
235
246
|
error: z.ZodLiteral<"foreign_key_violation">;
|
|
@@ -320,6 +331,9 @@ export type RateLimitKey = z.infer<typeof RateLimitKey>;
|
|
|
320
331
|
* of `NoActorsOnAccountError` / `AccountVanishedError`. The dispatcher's
|
|
321
332
|
* authorization phase emits these whenever it tries to resolve an actor.
|
|
322
333
|
* - **rate_limit**: 429 (`RateLimitError` with `retry_after`).
|
|
334
|
+
* - **checks_content_type**: 415 (`UnsupportedContentTypeError`) —
|
|
335
|
+
* every `POST` route (see `is_post_content_type_refused` in
|
|
336
|
+
* `http/simple_request.ts`).
|
|
323
337
|
*/
|
|
324
338
|
export interface DeriveErrorSchemasOptions {
|
|
325
339
|
auth: RouteAuth;
|
|
@@ -327,6 +341,7 @@ export interface DeriveErrorSchemasOptions {
|
|
|
327
341
|
has_params?: boolean;
|
|
328
342
|
has_query?: boolean;
|
|
329
343
|
rate_limit?: RateLimitKey;
|
|
344
|
+
checks_content_type?: boolean;
|
|
330
345
|
}
|
|
331
|
-
export declare const derive_error_schemas: ({ auth, has_input, has_params, has_query, rate_limit }: DeriveErrorSchemasOptions) => RouteErrorSchemas;
|
|
346
|
+
export declare const derive_error_schemas: ({ auth, has_input, has_params, has_query, rate_limit, checks_content_type }: DeriveErrorSchemasOptions) => RouteErrorSchemas;
|
|
332
347
|
//# sourceMappingURL=error_schemas.d.ts.map
|
|
@@ -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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"}
|
|
@@ -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. */
|
|
@@ -233,6 +239,10 @@ export const RateLimitError = z.looseObject({
|
|
|
233
239
|
export const PayloadTooLargeError = z.looseObject({
|
|
234
240
|
error: z.literal(ERROR_PAYLOAD_TOO_LARGE)
|
|
235
241
|
});
|
|
242
|
+
/** Unsupported content type error — returned when a `POST` route refuses the request's `Content-Type`. */
|
|
243
|
+
export const UnsupportedContentTypeError = z.looseObject({
|
|
244
|
+
error: z.literal(ERROR_UNSUPPORTED_CONTENT_TYPE)
|
|
245
|
+
});
|
|
236
246
|
/** Foreign key violation error — returned when a delete is blocked by references. */
|
|
237
247
|
export const ForeignKeyError = z.looseObject({
|
|
238
248
|
error: z.literal(ERROR_FOREIGN_KEY_VIOLATION)
|
|
@@ -279,7 +289,7 @@ export const AccountVanishedError = z.looseObject({
|
|
|
279
289
|
* - `'both'` — both keys.
|
|
280
290
|
*/
|
|
281
291
|
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 }) => {
|
|
292
|
+
export const derive_error_schemas = ({ auth, has_input = false, has_params = false, has_query = false, rate_limit, checks_content_type = false }) => {
|
|
283
293
|
const errors = {};
|
|
284
294
|
const has_validation = has_input || has_params || has_query;
|
|
285
295
|
if (needs_actor(auth)) {
|
|
@@ -320,5 +330,8 @@ export const derive_error_schemas = ({ auth, has_input = false, has_params = fal
|
|
|
320
330
|
if (rate_limit) {
|
|
321
331
|
errors[429] = RateLimitError;
|
|
322
332
|
}
|
|
333
|
+
if (checks_content_type) {
|
|
334
|
+
errors[415] = UnsupportedContentTypeError;
|
|
335
|
+
}
|
|
323
336
|
return errors;
|
|
324
337
|
};
|
package/dist/http/origin.d.ts
CHANGED
|
@@ -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
|
|
6
|
-
* (see `auth/session_middleware.ts`)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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"}
|
package/dist/http/origin.js
CHANGED
|
@@ -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
|
|
6
|
-
* (see `auth/session_middleware.ts`)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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`:
|
|
55
|
-
* pre-authorization auth (401 + rule-3 scope) →
|
|
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
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* `
|
|
144
|
-
*
|
|
145
|
-
* `
|
|
146
|
-
*
|
|
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:
|
|
232
|
-
* guards (401 + rule-3 scope) →
|
|
233
|
-
* post-authorization auth guards (403) → input
|
|
234
|
-
* handler.
|
|
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,
|
|
1
|
+
{"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"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"}
|
package/dist/http/route_spec.js
CHANGED
|
@@ -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:
|
|
270
|
-
* guards (401 + rule-3 scope) →
|
|
271
|
-
* post-authorization auth guards (403) → input
|
|
272
|
-
* handler.
|
|
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 (
|
|
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,
|
|
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 (
|
|
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;
|