@fuzdev/fuz_app 0.127.0 → 0.129.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +35 -18
- package/dist/actions/connection_closer.d.ts +16 -4
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +20 -4
- package/dist/actions/register_action_ws.d.ts +10 -3
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +6 -3
- package/dist/actions/register_ws_endpoint.d.ts +3 -1
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -1
- package/dist/auth/CLAUDE.md +14 -10
- package/dist/auth/actor_lookup_action_specs.d.ts +2 -2
- package/dist/auth/actor_lookup_action_specs.js +2 -2
- package/dist/auth/audit_emitter.d.ts +73 -19
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +57 -21
- package/dist/auth/bearer_auth.d.ts +3 -2
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +3 -2
- package/dist/auth/middleware.d.ts +30 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +59 -0
- package/dist/auth/request_context.d.ts +1 -1
- package/dist/auth/request_context.js +1 -1
- package/dist/auth/token_scope.d.ts +4 -2
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +10 -2
- package/dist/http/CLAUDE.md +3 -4
- package/dist/http/auth_shape.d.ts +3 -2
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +3 -2
- package/dist/http/common_routes.d.ts +1 -14
- package/dist/http/common_routes.d.ts.map +1 -1
- package/dist/http/common_routes.js +1 -19
- package/dist/http/ip_canonical.d.ts +1 -1
- package/dist/http/ip_canonical.js +1 -1
- package/dist/realtime/sse_auth_guard.d.ts +6 -2
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +3 -1
- package/dist/server/app_server.d.ts +157 -19
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +226 -128
- package/dist/server/surface_route.d.ts +32 -0
- package/dist/server/surface_route.d.ts.map +1 -0
- package/dist/server/surface_route.js +46 -0
- package/dist/testing/CLAUDE.md +50 -32
- package/dist/testing/app_server.d.ts +40 -5
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +84 -68
- package/dist/testing/attack_surface.d.ts +1 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/audit_drift_guard.d.ts +5 -3
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +14 -4
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +1 -2
- package/dist/testing/cross_backend/capabilities.d.ts +3 -1
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.d.ts +2 -2
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +5 -2
- package/dist/testing/cross_backend/identity_parity.js +1 -1
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/in_process_setup.js +4 -1
- package/dist/testing/cross_backend/method_coverage.d.ts +1 -1
- package/dist/testing/cross_backend/method_coverage.d.ts.map +1 -1
- package/dist/testing/cross_backend/method_coverage.js +2 -2
- package/dist/testing/cross_backend/peer_ping_ws.d.ts +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.d.ts +4 -4
- package/dist/testing/cross_backend/setup.js +1 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts +1 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +51 -32
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +70 -51
- package/dist/testing/cross_backend/testing_server_bun.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_bun.js +80 -64
- package/dist/testing/cross_backend/testing_server_core.d.ts +10 -4
- package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_deno.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_deno.js +1 -1
- package/dist/testing/cross_backend/testing_server_node.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_node.js +2 -2
- package/dist/testing/cross_backend/ws_round_trip.d.ts +3 -2
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +51 -13
- package/dist/testing/stubs.d.ts +65 -10
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +95 -41
- package/dist/testing/ws_round_trip.d.ts +1 -3
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/ui/AdminSurface.svelte +3 -1
- package/dist/ui/AdminSurface.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +3 -1
- package/dist/ui/async_slot.svelte.d.ts +4 -4
- package/dist/ui/async_slot.svelte.js +4 -4
- package/dist/ui/keyed_async_slot.svelte.d.ts +4 -4
- package/dist/ui/keyed_async_slot.svelte.js +3 -3
- package/package.json +1 -1
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
* the query layer, or the offer-expiry rows `auth/cleanup.ts` writes in
|
|
30
30
|
* its sweep transaction). Runs every registered listener; per-listener
|
|
31
31
|
* throws are isolated.
|
|
32
|
+
* - `drain_inflight()` — test-binary barrier: await every in-flight `emit`
|
|
33
|
+
* write, on an emitter built with `track_inflight: true`.
|
|
32
34
|
*
|
|
33
35
|
* ## When listeners hear about a row
|
|
34
36
|
*
|
|
@@ -56,9 +58,10 @@
|
|
|
56
58
|
* registers additional listeners via `add_listener` after the backend is
|
|
57
59
|
* built (the factory-managed audit-log SSE, per-endpoint WS auth guards, any
|
|
58
60
|
* `extra_audit_handlers` on a `WsEndpointSpec`) before the first request
|
|
59
|
-
* runs
|
|
60
|
-
*
|
|
61
|
-
* `
|
|
61
|
+
* runs, and removes each through the remover `add_listener` returns when its
|
|
62
|
+
* assembly fails or the server closes. Consumers can also register
|
|
63
|
+
* listeners directly on the emitter they return from `audit_factory` for
|
|
64
|
+
* setups that don't pass through `create_app_server`.
|
|
62
65
|
*
|
|
63
66
|
* @module
|
|
64
67
|
*/
|
|
@@ -75,18 +78,21 @@ import { builtin_audit_log_config } from './audit_log_schema.js';
|
|
|
75
78
|
* @returns the bound emitter; closes over the pool + config + listener chain
|
|
76
79
|
*/
|
|
77
80
|
export const create_audit_emitter = (options) => {
|
|
78
|
-
const { db, log, audit_log_config = builtin_audit_log_config, emit_decorator } = options;
|
|
81
|
+
const { db, log, audit_log_config = builtin_audit_log_config, emit_decorator, track_inflight = false } = options;
|
|
79
82
|
// Closure-private listener list — no mutable array is exposed on the
|
|
80
83
|
// returned (frozen) emitter; registration goes through `add_listener`.
|
|
84
|
+
// One entry object per registration, so a remover takes out its own
|
|
85
|
+
// registration even when the same function is registered twice.
|
|
81
86
|
const listeners = [];
|
|
82
87
|
if (options.on_audit_event)
|
|
83
|
-
listeners.push(options.on_audit_event);
|
|
88
|
+
listeners.push({ listener: options.on_audit_event });
|
|
84
89
|
const notify = (event) => {
|
|
85
90
|
// Snapshot-at-notify: iterate a copy so a listener that registers
|
|
86
91
|
// another listener mid-fan-out doesn't have the newcomer fire for the
|
|
87
|
-
// in-flight event (it fires on the next `notify`)
|
|
88
|
-
//
|
|
89
|
-
|
|
92
|
+
// in-flight event (it fires on the next `notify`), and one removed
|
|
93
|
+
// mid-fan-out still hears the in-flight event. Converges with the Rust
|
|
94
|
+
// twin, which clones the listener vec before iterating.
|
|
95
|
+
for (const { listener } of [...listeners]) {
|
|
90
96
|
try {
|
|
91
97
|
listener(event);
|
|
92
98
|
}
|
|
@@ -111,16 +117,41 @@ export const create_audit_emitter = (options) => {
|
|
|
111
117
|
if (event)
|
|
112
118
|
notify(event);
|
|
113
119
|
};
|
|
120
|
+
// In-flight `emit` writes, when tracked — each removed as it settles, so
|
|
121
|
+
// the set is empty exactly when nothing is outstanding. Neither tracked
|
|
122
|
+
// promise rejects (`write` and `notify` log their failures).
|
|
123
|
+
const inflight = track_inflight ? new Set() : null;
|
|
124
|
+
const track = (promise) => {
|
|
125
|
+
if (inflight) {
|
|
126
|
+
inflight.add(promise);
|
|
127
|
+
const remove = () => {
|
|
128
|
+
inflight.delete(promise);
|
|
129
|
+
};
|
|
130
|
+
void promise.then(remove, remove);
|
|
131
|
+
}
|
|
132
|
+
return promise;
|
|
133
|
+
};
|
|
134
|
+
const drain_inflight = async () => {
|
|
135
|
+
if (!inflight)
|
|
136
|
+
return;
|
|
137
|
+
// Wait for zero, not for a snapshot: a write started while waiting —
|
|
138
|
+
// an emit from a post-commit thunk — joins the set and is awaited on
|
|
139
|
+
// the next pass. The removal reactions were registered before these,
|
|
140
|
+
// so a settled write is gone from the set by the time a pass resumes.
|
|
141
|
+
while (inflight.size > 0) {
|
|
142
|
+
await Promise.allSettled(inflight);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
114
145
|
const base_emit = (ctx, input) => {
|
|
115
146
|
if (input.outcome === 'failure') {
|
|
116
147
|
// an attempt happened whether or not the transaction commits
|
|
117
|
-
ctx.pending_effects.push(emit_pool(input));
|
|
148
|
+
ctx.pending_effects.push(track(emit_pool(input)));
|
|
118
149
|
return;
|
|
119
150
|
}
|
|
120
151
|
// A success row is written now and announced after the commit — and
|
|
121
152
|
// not at all on rollback, when the queue is discarded. See the module
|
|
122
153
|
// doc, "When listeners hear about a row".
|
|
123
|
-
const written = write(input);
|
|
154
|
+
const written = track(write(input));
|
|
124
155
|
ctx.pending_effects.push(written.then(() => undefined));
|
|
125
156
|
emit_after_commit(ctx, async () => {
|
|
126
157
|
const event = await written;
|
|
@@ -146,26 +177,31 @@ export const create_audit_emitter = (options) => {
|
|
|
146
177
|
});
|
|
147
178
|
};
|
|
148
179
|
const add_listener = (listener) => {
|
|
149
|
-
|
|
180
|
+
const entry = { listener };
|
|
181
|
+
listeners.push(entry);
|
|
182
|
+
return () => {
|
|
183
|
+
const index = listeners.indexOf(entry);
|
|
184
|
+
if (index !== -1)
|
|
185
|
+
listeners.splice(index, 1);
|
|
186
|
+
};
|
|
150
187
|
};
|
|
151
188
|
const listener_count = () => listeners.length;
|
|
152
189
|
// Freeze the slot layout so consumers cannot hot-patch `emit` /
|
|
153
|
-
// `emit_role_grant_target` / `emit_pool` / `notify` after construction
|
|
154
|
-
//
|
|
155
|
-
// and only happened to work because the four slots were writable —
|
|
190
|
+
// `emit_role_grant_target` / `emit_pool` / `notify` after construction —
|
|
191
|
+
// a patched `emit` would miss role-grant-shape emits anyway, since
|
|
156
192
|
// `emit_role_grant_target` calls the closed-over inner `emit`, not
|
|
157
|
-
// `this.emit
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
// by design).
|
|
193
|
+
// `this.emit`. Tests that need instrumentation pass `emit_decorator` so the
|
|
194
|
+
// wrap is captured by the closure before the freeze. The listener list stays
|
|
195
|
+
// closure-private; it changes only through `add_listener` and the remover
|
|
196
|
+
// it returns (`create_app_server` registers its SSE + WS listeners after
|
|
197
|
+
// the emitter is built, by design, and removes them on close).
|
|
163
198
|
return Object.freeze({
|
|
164
199
|
emit,
|
|
165
200
|
emit_role_grant_target,
|
|
166
201
|
emit_pool,
|
|
167
202
|
notify,
|
|
168
203
|
add_listener,
|
|
169
|
-
listener_count
|
|
204
|
+
listener_count,
|
|
205
|
+
drain_inflight
|
|
170
206
|
});
|
|
171
207
|
};
|
|
@@ -28,8 +28,9 @@ import type { QueryDeps } from '../db/query_deps.js';
|
|
|
28
28
|
* (`invalid_token`, `account_not_found`) that could aid enumeration attacks,
|
|
29
29
|
* and ensures public actions are not blocked by bad credentials.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* Discards bearer tokens when an `Origin` or `Referer` header is present —
|
|
32
|
+
* `next()` with no identity, as for a bad token — so browsers must use cookie
|
|
33
|
+
* auth, reducing attack surface.
|
|
33
34
|
* Auth scheme matching is case-insensitive per RFC 7235.
|
|
34
35
|
* On success, sets `c.var.auth_account_id`, `CREDENTIAL_TYPE_KEY = 'api_token'`,
|
|
35
36
|
* and `AUTH_API_TOKEN_ID_KEY`. Skips when an account is already authenticated
|
|
@@ -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,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAUtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIrD
|
|
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,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAUtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,SAAS,EAAE,KAAK,MAAM,KAAG,iBAuE5E,CAAC"}
|
package/dist/auth/bearer_auth.js
CHANGED
|
@@ -31,8 +31,9 @@ import { is_browser_context } from '../http/origin.js';
|
|
|
31
31
|
* (`invalid_token`, `account_not_found`) that could aid enumeration attacks,
|
|
32
32
|
* and ensures public actions are not blocked by bad credentials.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* Discards bearer tokens when an `Origin` or `Referer` header is present —
|
|
35
|
+
* `next()` with no identity, as for a bad token — so browsers must use cookie
|
|
36
|
+
* auth, reducing attack surface.
|
|
36
37
|
* Auth scheme matching is case-insensitive per RFC 7235.
|
|
37
38
|
* On success, sets `c.var.auth_account_id`, `CREDENTIAL_TYPE_KEY = 'api_token'`,
|
|
38
39
|
* and `AUTH_API_TOKEN_ID_KEY`. Skips when an account is already authenticated
|
|
@@ -38,6 +38,36 @@ export declare const AUTH_MIDDLEWARE_PATH = "/api/*";
|
|
|
38
38
|
* @throws Error when `path` is outside `AUTH_MIDDLEWARE_PATH`
|
|
39
39
|
*/
|
|
40
40
|
export declare const assert_endpoint_in_auth_scope: (option: "rpc_endpoints" | "ws_endpoints", path: string) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Throw unless a middleware list still mounts every spec of the stack it was
|
|
43
|
+
* built from — the trusted-proxy spec and the auth middleware — exactly once,
|
|
44
|
+
* at its original path, in its original order.
|
|
45
|
+
*
|
|
46
|
+
* `create_app_server` and `create_test_app_surface_spec` run it on the list
|
|
47
|
+
* a `transform_middleware` option returns, against the stack they handed it
|
|
48
|
+
* (`[proxy, ...auth]`). The endpoint auth-scope check
|
|
49
|
+
* (`assert_endpoint_in_auth_scope`) assumes the auth stack is mounted at
|
|
50
|
+
* `AUTH_MIDDLEWARE_PATH`, and every IP-keyed rate limiter assumes the proxy
|
|
51
|
+
* spec ran first — without it `get_client_ip` answers `'unknown'` for every
|
|
52
|
+
* caller, collapsing each limiter into one shared bucket. So a transform that
|
|
53
|
+
* moves, drops, duplicates, reorders, wraps, or replaces a stack layer is
|
|
54
|
+
* refused. Other specs may sit anywhere, including before the proxy spec and
|
|
55
|
+
* between the auth layers. The check pins where the stack's layers sit, not
|
|
56
|
+
* what added layers do — a layer you add runs with full context access.
|
|
57
|
+
*
|
|
58
|
+
* Each stack spec is matched by its `handler` — the closure its factory built
|
|
59
|
+
* — not by `name`, which any spec can carry, so a same-named replacement or a
|
|
60
|
+
* wrapped handler counts as missing. A spread copy (`{...spec}`) keeps the
|
|
61
|
+
* handler and still matches. Its path is compared with the original's, so
|
|
62
|
+
* pass specs the transform could not reach: the assemblies hand the transform
|
|
63
|
+
* shallow copies in a fresh array and keep the originals here.
|
|
64
|
+
*
|
|
65
|
+
* @param stack_specs - the stack the transform was built from, in mount order
|
|
66
|
+
* @param middleware_specs - the middleware list the transform returned
|
|
67
|
+
* @throws Error when a stack spec is missing, mounted more than once, mounted
|
|
68
|
+
* at another path, or out of order
|
|
69
|
+
*/
|
|
70
|
+
export declare const assert_middleware_stack_mounted: (stack_specs: ReadonlyArray<MiddlewareSpec>, middleware_specs: ReadonlyArray<MiddlewareSpec>) => void;
|
|
41
71
|
/**
|
|
42
72
|
* Per-factory configuration for the standard auth middleware stack.
|
|
43
73
|
*/
|
|
@@ -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;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,6BAA6B,GACzC,QAAQ,eAAe,GAAG,cAAc,EACxC,MAAM,MAAM,KACV,IAOF,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"}
|
|
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;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,6BAA6B,GACzC,QAAQ,eAAe,GAAG,cAAc,EACxC,MAAM,MAAM,KACV,IAOF,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
|
@@ -41,6 +41,65 @@ export const assert_endpoint_in_auth_scope = (option, path) => {
|
|
|
41
41
|
`(${AUTH_MIDDLEWARE_PATH}) — it would see every caller as unauthenticated — mount it under /api/`);
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Throw unless a middleware list still mounts every spec of the stack it was
|
|
46
|
+
* built from — the trusted-proxy spec and the auth middleware — exactly once,
|
|
47
|
+
* at its original path, in its original order.
|
|
48
|
+
*
|
|
49
|
+
* `create_app_server` and `create_test_app_surface_spec` run it on the list
|
|
50
|
+
* a `transform_middleware` option returns, against the stack they handed it
|
|
51
|
+
* (`[proxy, ...auth]`). The endpoint auth-scope check
|
|
52
|
+
* (`assert_endpoint_in_auth_scope`) assumes the auth stack is mounted at
|
|
53
|
+
* `AUTH_MIDDLEWARE_PATH`, and every IP-keyed rate limiter assumes the proxy
|
|
54
|
+
* spec ran first — without it `get_client_ip` answers `'unknown'` for every
|
|
55
|
+
* caller, collapsing each limiter into one shared bucket. So a transform that
|
|
56
|
+
* moves, drops, duplicates, reorders, wraps, or replaces a stack layer is
|
|
57
|
+
* refused. Other specs may sit anywhere, including before the proxy spec and
|
|
58
|
+
* between the auth layers. The check pins where the stack's layers sit, not
|
|
59
|
+
* what added layers do — a layer you add runs with full context access.
|
|
60
|
+
*
|
|
61
|
+
* Each stack spec is matched by its `handler` — the closure its factory built
|
|
62
|
+
* — not by `name`, which any spec can carry, so a same-named replacement or a
|
|
63
|
+
* wrapped handler counts as missing. A spread copy (`{...spec}`) keeps the
|
|
64
|
+
* handler and still matches. Its path is compared with the original's, so
|
|
65
|
+
* pass specs the transform could not reach: the assemblies hand the transform
|
|
66
|
+
* shallow copies in a fresh array and keep the originals here.
|
|
67
|
+
*
|
|
68
|
+
* @param stack_specs - the stack the transform was built from, in mount order
|
|
69
|
+
* @param middleware_specs - the middleware list the transform returned
|
|
70
|
+
* @throws Error when a stack spec is missing, mounted more than once, mounted
|
|
71
|
+
* at another path, or out of order
|
|
72
|
+
*/
|
|
73
|
+
export const assert_middleware_stack_mounted = (stack_specs, middleware_specs) => {
|
|
74
|
+
let previous_index = -1;
|
|
75
|
+
for (const stack_spec of stack_specs) {
|
|
76
|
+
let index = -1;
|
|
77
|
+
let count = 0;
|
|
78
|
+
for (let i = 0; i < middleware_specs.length; i++) {
|
|
79
|
+
if (middleware_specs[i].handler === stack_spec.handler) {
|
|
80
|
+
if (count === 0)
|
|
81
|
+
index = i;
|
|
82
|
+
count++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const found = count === 0
|
|
86
|
+
? 'missing'
|
|
87
|
+
: count > 1
|
|
88
|
+
? `mounted ${count} times`
|
|
89
|
+
: middleware_specs[index].path !== stack_spec.path
|
|
90
|
+
? `mounted at ${middleware_specs[index].path}`
|
|
91
|
+
: index < previous_index
|
|
92
|
+
? 'out of order'
|
|
93
|
+
: null;
|
|
94
|
+
if (found) {
|
|
95
|
+
throw new Error(`transform_middleware must keep the middleware '${stack_spec.name}' mounted once ` +
|
|
96
|
+
`at ${stack_spec.path} in its original order (${found}) — endpoint auth-scope checks ` +
|
|
97
|
+
'and client-IP rate limiting assume it; add layers around the stack, ' +
|
|
98
|
+
"don't move, drop, wrap, or replace it");
|
|
99
|
+
}
|
|
100
|
+
previous_index = index;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
44
103
|
/**
|
|
45
104
|
* Create the auth middleware stack.
|
|
46
105
|
*
|
|
@@ -251,7 +251,7 @@ export declare const create_request_context_middleware: (deps: QueryDeps, sessio
|
|
|
251
251
|
* across route-spec declarations and direct calls alike, and the twin of the
|
|
252
252
|
* Rust signature. It needs no parse: the surface arm's identifier is pure label,
|
|
253
253
|
* since rule 3 never asks *which* surface. And it is deliberately not narrowed
|
|
254
|
-
* to the spine's
|
|
254
|
+
* to the spine's own — the name decides nothing, so there is nothing for a
|
|
255
255
|
* closed set to protect, while a consumer surface would have nothing to pass.
|
|
256
256
|
* fuz_app's own uses are pinned two ways by the surface census: the exact call
|
|
257
257
|
* site, and the scan asserting every `'surface:<name>'` literal in `src/lib`
|
|
@@ -281,7 +281,7 @@ const token_scope_denial = (c, capability) => {
|
|
|
281
281
|
* across route-spec declarations and direct calls alike, and the twin of the
|
|
282
282
|
* Rust signature. It needs no parse: the surface arm's identifier is pure label,
|
|
283
283
|
* since rule 3 never asks *which* surface. And it is deliberately not narrowed
|
|
284
|
-
* to the spine's
|
|
284
|
+
* to the spine's own — the name decides nothing, so there is nothing for a
|
|
285
285
|
* closed set to protect, while a consumer surface would have nothing to pass.
|
|
286
286
|
* fuz_app's own uses are pinned two ways by the surface census: the exact call
|
|
287
287
|
* site, and the scan asserting every `'surface:<name>'` literal in `src/lib`
|
|
@@ -106,7 +106,9 @@ export declare const token_scope_admits_method: (scope: TokenScope, method: stri
|
|
|
106
106
|
export declare const token_scope_admits_non_rpc: (scope: TokenScope) => boolean;
|
|
107
107
|
/**
|
|
108
108
|
* The non-RPC surfaces the **spine itself** mounts (rule 3). Twin of the Rust
|
|
109
|
-
* `TokenSurface` enum, name for name
|
|
109
|
+
* `TokenSurface` enum, name for name, except `app_surface` — the opt-in
|
|
110
|
+
* `GET /api/surface` explorer (`server/surface_route.ts`), which only the TS
|
|
111
|
+
* spine serves.
|
|
110
112
|
*
|
|
111
113
|
* Not a registry a consumer registers into. `RouteAuth.required_scope` accepts
|
|
112
114
|
* any well-formed `surface:<name>` (see `parse_token_scope_capability`), so a
|
|
@@ -120,7 +122,7 @@ export declare const token_scope_admits_non_rpc: (scope: TokenScope) => boolean;
|
|
|
120
122
|
* refused, in the `required_scope` a denial reports. That is the same field
|
|
121
123
|
* whose `rpc:<method>` arm has always carried arbitrary consumer method names.
|
|
122
124
|
*/
|
|
123
|
-
export declare const TOKEN_SURFACES: readonly ["db_admin", "fact_bare", "audit_stream", "ws_upgrade"];
|
|
125
|
+
export declare const TOKEN_SURFACES: readonly ["db_admin", "fact_bare", "audit_stream", "ws_upgrade", "app_surface"];
|
|
124
126
|
export type TokenSurface = (typeof TOKEN_SURFACES)[number];
|
|
125
127
|
/**
|
|
126
128
|
* Is `value` one of the surfaces the spine mounts?
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/token_scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAW,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU;;;;;;;;;4BAYrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;;;4BAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,QAAO,UAGlC,CAAC;AAEH,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,SAAS,KAAK,CAAC,MAAM,CAAC,KAAG,UAI3D,CAAC;AAEH,qDAAqD;AACrD,eAAO,MAAM,sBAAsB,GAAI,OAAO,eAAe,KAAG,UACgB,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,UAAU,EAAE,QAAQ,MAAM,KAAG,OACtB,CAAC;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,UAAU,KAAG,OAAgC,CAAC;AAEhG
|
|
1
|
+
{"version":3,"file":"token_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/token_scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAW,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU;;;;;;;;;4BAYrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;;;4BAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,QAAO,UAGlC,CAAC;AAEH,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,SAAS,KAAK,CAAC,MAAM,CAAC,KAAG,UAI3D,CAAC;AAEH,qDAAqD;AACrD,eAAO,MAAM,sBAAsB,GAAI,OAAO,eAAe,KAAG,UACgB,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,UAAU,EAAE,QAAQ,MAAM,KAAG,OACtB,CAAC;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,UAAU,KAAG,OAAgC,CAAC;AAEhG;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,iFAMjB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,KAAK,IAAI,YACA,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,QAAgC,CAAC;AAQtE;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,MAAM,KAAG,MAC3B,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAC/B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/F;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GAAI,OAAO,MAAM,KAAG,oBAAoB,GAAG,IAUnF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GACzC,OAAO,UAAU,EACjB,YAAY,oBAAoB,KAC9B,OAGkC,CAAC;AAEtC;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GACnC,YAAY,MAAM,KAChB;IAAE,KAAK,EAAE,sBAAsB,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAGxD,CAAC;AAEH,4CAA4C;AAC5C,eAAO,MAAM,iBAAiB,GAAI,OAAO,UAAU,KAAG,MAGrD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,UAAU,GAAG,IAU5D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,UAAU,KAAG,MAQzD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,UAAU,GAAG,IAQtE,CAAC"}
|
package/dist/auth/token_scope.js
CHANGED
|
@@ -112,7 +112,9 @@ export const token_scope_admits_method = (scope, method) => scope.kind === 'full
|
|
|
112
112
|
export const token_scope_admits_non_rpc = (scope) => scope.kind === 'full';
|
|
113
113
|
/**
|
|
114
114
|
* The non-RPC surfaces the **spine itself** mounts (rule 3). Twin of the Rust
|
|
115
|
-
* `TokenSurface` enum, name for name
|
|
115
|
+
* `TokenSurface` enum, name for name, except `app_surface` — the opt-in
|
|
116
|
+
* `GET /api/surface` explorer (`server/surface_route.ts`), which only the TS
|
|
117
|
+
* spine serves.
|
|
116
118
|
*
|
|
117
119
|
* Not a registry a consumer registers into. `RouteAuth.required_scope` accepts
|
|
118
120
|
* any well-formed `surface:<name>` (see `parse_token_scope_capability`), so a
|
|
@@ -126,7 +128,13 @@ export const token_scope_admits_non_rpc = (scope) => scope.kind === 'full';
|
|
|
126
128
|
* refused, in the `required_scope` a denial reports. That is the same field
|
|
127
129
|
* whose `rpc:<method>` arm has always carried arbitrary consumer method names.
|
|
128
130
|
*/
|
|
129
|
-
export const TOKEN_SURFACES = [
|
|
131
|
+
export const TOKEN_SURFACES = [
|
|
132
|
+
'db_admin',
|
|
133
|
+
'fact_bare',
|
|
134
|
+
'audit_stream',
|
|
135
|
+
'ws_upgrade',
|
|
136
|
+
'app_surface'
|
|
137
|
+
];
|
|
130
138
|
/**
|
|
131
139
|
* Is `value` one of the surfaces the spine mounts?
|
|
132
140
|
*
|
package/dist/http/CLAUDE.md
CHANGED
|
@@ -28,7 +28,7 @@ effects, see ../../../docs/architecture.md.
|
|
|
28
28
|
- `http/jsonrpc.ts` — JSON-RPC 2.0 envelope schemas (MCP superset), `JsonrpcErrorCode`, `_meta`.
|
|
29
29
|
- `http/jsonrpc_errors.ts` — `ThrownJsonrpcError`, `jsonrpc_errors` throwers, HTTP-status mappings.
|
|
30
30
|
- `http/jsonrpc_helpers.ts` — message builders, type guards, input/result normalizers.
|
|
31
|
-
- `http/common_routes.ts` — health check + readiness probe (`/ready` schema-drift deploy gate) + authenticated server-status
|
|
31
|
+
- `http/common_routes.ts` — health check + readiness probe (`/ready` schema-drift deploy gate) + authenticated server-status route specs (the surface explorer route lives in `server/surface_route.ts`).
|
|
32
32
|
- `http/db_routes.ts` — allowlist-gated keeper-only table browser route specs (`public` schema, minus the `NON_BROWSABLE_TABLES` credential floor).
|
|
33
33
|
- `http/pending_effects.ts` — `emit_after_commit` + `dispatch_with_post_commit_rollback` (the shared rollback-discard wrapper both dispatch sites use) + `flush_pending_effects` + `flush_post_commit_effects` + `EmitAfterCommitContext`.
|
|
34
34
|
|
|
@@ -484,13 +484,12 @@ at send time.
|
|
|
484
484
|
|
|
485
485
|
## Common Routes
|
|
486
486
|
|
|
487
|
-
`http/common_routes.ts` exposes
|
|
487
|
+
`http/common_routes.ts` exposes three generic route-spec factories with no
|
|
488
488
|
auth-domain dependencies:
|
|
489
489
|
|
|
490
490
|
- `create_health_route_spec()` — `GET /health`, public, returns `{status: 'ok'}`. Infrastructure endpoint for uptime monitors
|
|
491
|
-
- `create_ready_route_spec({expected, log})` — `GET /ready`, public, the deploy gate. Introspects the live DB's columns (`db/schema_ready.ts`) and compares against the committed `expected` column map: `200 {ready: true}` on match, else `503 {error: 'schema_drift' | 'db_unreachable'}`. Detailed drift logs server-side only (minimal body — no schema leak, mirrors why `/api/surface` is
|
|
491
|
+
- `create_ready_route_spec({expected, log})` — `GET /ready`, public, the deploy gate. Introspects the live DB's columns (`db/schema_ready.ts`) and compares against the committed `expected` column map: `200 {ready: true}` on match, else `503 {error: 'schema_drift' | 'db_unreachable'}`. Detailed drift logs server-side only (minimal body — no schema leak, mirrors why `/api/surface` is admin-only). Throws at assembly on an empty `expected` (fail-loud). Opt-in like `/health`; pair with `load_expected_schema(url)` — loads + URL-caches a consumer's committed `expected_schema.json` and throws on an empty map
|
|
492
492
|
- `create_server_status_route_spec({version, get_uptime_ms})` — `GET /api/server/status`, authenticated, returns `{version, uptime_ms}`
|
|
493
|
-
- `create_surface_route_spec({surface})` — `GET /api/surface`, authenticated, serves the `AppSurface` JSON. Authenticated because surface data reveals API structure (schemas, auth, routes)
|
|
494
493
|
|
|
495
494
|
Auth-aware variants (account status, bootstrap status) live in `auth/` —
|
|
496
495
|
`http/common_routes.ts` stays generic.
|
|
@@ -141,8 +141,9 @@ export declare const RouteAuth: z.ZodObject<{
|
|
|
141
141
|
*
|
|
142
142
|
* - `surface:<name>` — a non-RPC surface, refused to every narrowed token
|
|
143
143
|
* whatever its method list says (token scoping's rule that a narrowed
|
|
144
|
-
* token is RPC-only). `surface:audit_stream
|
|
145
|
-
* the spine's; a consumer names
|
|
144
|
+
* token is RPC-only). `surface:audit_stream`, `surface:fact_bare`, and
|
|
145
|
+
* `surface:app_surface` are the spine's route-spec ones; a consumer names
|
|
146
|
+
* its own without registering it.
|
|
146
147
|
* - `rpc:<method>` — one action method, refused unless the token lists it.
|
|
147
148
|
* What a route bridged from an `ActionSpec` declares: it carries the
|
|
148
149
|
* method's identity but never reaches the dispatcher's per-method gate.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/auth_shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,6DAGtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAGtD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;EAA2C,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;IAMpB
|
|
1
|
+
{"version":3,"file":"auth_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/auth_shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,6DAGtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAGtD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;EAA2C,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;IAMpB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;;kBAyCF,CAAC;AACJ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAUlD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACA,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAG,OAAgC,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,KAAG,OAAkC,CAAC;AAEnF,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,KAAG,OAA+B,CAAC;AAE/E,8FAA8F;AAC9F,eAAO,MAAM,wBAAwB,GAAI,MAAM,SAAS,KAAG,OAC3B,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACQ,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,SAAS,KAAG,OACwB,CAAC;AAYvF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAIhE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,MAAM,SAAS,EACf,OAAO,WAAW,EAClB,SAAS,MAAM,KACb,IAmBF,CAAC"}
|
package/dist/http/auth_shape.js
CHANGED
|
@@ -135,8 +135,9 @@ export const RouteAuth = z
|
|
|
135
135
|
*
|
|
136
136
|
* - `surface:<name>` — a non-RPC surface, refused to every narrowed token
|
|
137
137
|
* whatever its method list says (token scoping's rule that a narrowed
|
|
138
|
-
* token is RPC-only). `surface:audit_stream
|
|
139
|
-
* the spine's; a consumer names
|
|
138
|
+
* token is RPC-only). `surface:audit_stream`, `surface:fact_bare`, and
|
|
139
|
+
* `surface:app_surface` are the spine's route-spec ones; a consumer names
|
|
140
|
+
* its own without registering it.
|
|
140
141
|
* - `rpc:<method>` — one action method, refused unless the token lists it.
|
|
141
142
|
* What a route bridged from an `ActionSpec` declares: it carries the
|
|
142
143
|
* method's identity but never reaches the dispatcher's per-method gate.
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
10
10
|
import type { RouteSpec } from './route_spec.js';
|
|
11
|
-
import type { AppSurface } from './surface.js';
|
|
12
11
|
import { type ExpectedSchema } from '../db/schema_ready.js';
|
|
13
12
|
/**
|
|
14
13
|
* Create a public health check route spec.
|
|
@@ -57,7 +56,7 @@ export interface ReadyRouteOptions {
|
|
|
57
56
|
* else `503 {error}` (`schema_drift` when columns are missing, `db_unreachable`
|
|
58
57
|
* when the introspection query throws). The detailed drift goes to the server
|
|
59
58
|
* log only — the public body stays a minimal code so the endpoint doesn't leak
|
|
60
|
-
* schema structure (mirrors why `/api/surface` is
|
|
59
|
+
* schema structure (mirrors why `/api/surface` is admin-only). A deploy poll
|
|
61
60
|
* treats `503` as a failed release and rolls back, turning a silent
|
|
62
61
|
* schema-drift auth outage into a loud blocked deploy. See `db/schema_ready.ts`
|
|
63
62
|
* for the column-presence rationale and `auth/migrations.ts` for the
|
|
@@ -78,16 +77,4 @@ export interface ServerStatusOptions {
|
|
|
78
77
|
* this requires authentication.
|
|
79
78
|
*/
|
|
80
79
|
export declare const create_server_status_route_spec: (options: ServerStatusOptions) => RouteSpec;
|
|
81
|
-
/** Options for the surface explorer route. */
|
|
82
|
-
export interface SurfaceRouteOptions {
|
|
83
|
-
/** The generated app surface to serve. */
|
|
84
|
-
surface: AppSurface;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Create an authenticated route spec that serves the `AppSurface` as JSON.
|
|
88
|
-
*
|
|
89
|
-
* Surface data reveals API structure (routes, auth, schemas), so this
|
|
90
|
-
* requires authentication like the server status route.
|
|
91
|
-
*/
|
|
92
|
-
export declare const create_surface_route_spec: (options: SurfaceRouteOptions) => RouteSpec;
|
|
93
80
|
//# sourceMappingURL=common_routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/common_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,
|
|
1
|
+
{"version":3,"file":"common_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/common_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAIN,KAAK,cAAc,EACnB,MAAM,uBAAuB,CAAC;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,QAAO,SAQ1C,CAAC;AAKH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,GAAG,GAAG,MAAM,KAAG,cAkBxD,CAAC;AAEF,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IACjC;;;;OAIG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,gFAAgF;IAChF,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,iBAAiB,KAAG,SAoCpE,CAAC;AAEF,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IACnC,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,mBAAmB,KAAG,SAQ7E,CAAC"}
|
|
@@ -71,7 +71,7 @@ export const load_expected_schema = (url) => {
|
|
|
71
71
|
* else `503 {error}` (`schema_drift` when columns are missing, `db_unreachable`
|
|
72
72
|
* when the introspection query throws). The detailed drift goes to the server
|
|
73
73
|
* log only — the public body stays a minimal code so the endpoint doesn't leak
|
|
74
|
-
* schema structure (mirrors why `/api/surface` is
|
|
74
|
+
* schema structure (mirrors why `/api/surface` is admin-only). A deploy poll
|
|
75
75
|
* treats `503` as a failed release and rolls back, turning a silent
|
|
76
76
|
* schema-drift auth outage into a loud blocked deploy. See `db/schema_ready.ts`
|
|
77
77
|
* for the column-presence rationale and `auth/migrations.ts` for the
|
|
@@ -129,21 +129,3 @@ export const create_server_status_route_spec = (options) => ({
|
|
|
129
129
|
input: z.null(),
|
|
130
130
|
output: z.looseObject({ version: z.string(), uptime_ms: z.number() })
|
|
131
131
|
});
|
|
132
|
-
/**
|
|
133
|
-
* Create an authenticated route spec that serves the `AppSurface` as JSON.
|
|
134
|
-
*
|
|
135
|
-
* Surface data reveals API structure (routes, auth, schemas), so this
|
|
136
|
-
* requires authentication like the server status route.
|
|
137
|
-
*/
|
|
138
|
-
export const create_surface_route_spec = (options) => ({
|
|
139
|
-
method: 'GET',
|
|
140
|
-
path: '/api/surface',
|
|
141
|
-
auth: { account: 'required', actor: 'none' },
|
|
142
|
-
handler: (c) => c.json(options.surface),
|
|
143
|
-
description: 'Application surface (routes, middleware, schemas)',
|
|
144
|
-
input: z.null(),
|
|
145
|
-
output: z.looseObject({
|
|
146
|
-
routes: z.array(z.looseObject({})),
|
|
147
|
-
middleware: z.array(z.looseObject({}))
|
|
148
|
-
})
|
|
149
|
-
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* IPv6 forms (`::ffff:127.0.0.1` vs `0:0:0:0:0:ffff:7f00:1` vs the
|
|
13
13
|
* bare `127.0.0.1`) — three keys for one address.
|
|
14
14
|
*
|
|
15
|
-
* Canonicalization runs through
|
|
15
|
+
* Canonicalization runs through `canonicalize_ip` which:
|
|
16
16
|
*
|
|
17
17
|
* 1. Lowercases and char-set filters (`IP_LITERAL_CHARS`) — non-IP
|
|
18
18
|
* strings (`'unknown'`, `'attacker:controlled'`, `'::1\n'`) pass
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* IPv6 forms (`::ffff:127.0.0.1` vs `0:0:0:0:0:ffff:7f00:1` vs the
|
|
13
13
|
* bare `127.0.0.1`) — three keys for one address.
|
|
14
14
|
*
|
|
15
|
-
* Canonicalization runs through
|
|
15
|
+
* Canonicalization runs through `canonicalize_ip` which:
|
|
16
16
|
*
|
|
17
17
|
* 1. Lowercases and char-set filters (`IP_LITERAL_CHARS`) — non-IP
|
|
18
18
|
* strings (`'unknown'`, `'attacker:controlled'`, `'::1\n'`) pass
|
|
@@ -118,7 +118,9 @@ export declare const AUDIT_LOG_SSE_MAX_PER_SCOPE = 10;
|
|
|
118
118
|
* connection closer.
|
|
119
119
|
*
|
|
120
120
|
* Combines `SubscriberRegistry`, `create_sse_auth_guard`, and the broadcast
|
|
121
|
-
* call into a single object, and adds the registry to `connection_closer
|
|
121
|
+
* call into a single object, and adds the registry to `connection_closer` for
|
|
122
|
+
* good — the remover `add` returns is dropped, so a caller that must take the
|
|
123
|
+
* registry out again passes `null` and adds it itself.
|
|
122
124
|
* The result satisfies `AuditLogRouteOptions['stream']`
|
|
123
125
|
* and provides the `on_audit_event` listener for the audit emitter.
|
|
124
126
|
*
|
|
@@ -164,7 +166,9 @@ export declare const create_audit_log_sse: (options: {
|
|
|
164
166
|
* Required so it can't be forgotten: without it a revoked credential's
|
|
165
167
|
* streams close only through the audit listener, which a failed audit write
|
|
166
168
|
* or a cap eviction never reaches. Pass `null` only for a registry no
|
|
167
|
-
* revocation has to reach (tests of the registry itself)
|
|
169
|
+
* revocation has to reach (tests of the registry itself), or when the
|
|
170
|
+
* caller adds `create_sse_connection_closer(registry)` itself — as
|
|
171
|
+
* `create_app_server` does, to hold the remover `add` returns.
|
|
168
172
|
*/
|
|
169
173
|
connection_closer: RealtimeCloser | null;
|
|
170
174
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/realtime/sse_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAIN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE3D,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GAAI,CAAC,EAC7C,UAAU,kBAAkB,CAAC,CAAC,CAAC,KAC7B,gBAKD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,EACtC,UAAU,kBAAkB,CAAC,CAAC,CAAC,EAC/B,eAAe,MAAM,GAAG,IAAI,EAC5B,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA4DjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B;;;;;OAKG;IACH,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;IAC9C,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,yJAAyJ;IACzJ,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,SAAS,CAOlD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C
|
|
1
|
+
{"version":3,"file":"sse_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/realtime/sse_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAIN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE3D,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GAAI,CAAC,EAC7C,UAAU,kBAAkB,CAAC,CAAC,CAAC,KAC7B,gBAKD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,EACtC,UAAU,kBAAkB,CAAC,CAAC,CAAC,EAC/B,eAAe,MAAM,GAAG,IAAI,EAC5B,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA4DjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B;;;;;OAKG;IACH,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;IAC9C,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,yJAAyJ;IACzJ,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,SAAS,CAOlD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS;IAC7C,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;;OASG;IACH,iBAAiB,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,KAAG,WAgBH,CAAC"}
|
|
@@ -164,7 +164,9 @@ export const AUDIT_LOG_SSE_MAX_PER_SCOPE = 10;
|
|
|
164
164
|
* connection closer.
|
|
165
165
|
*
|
|
166
166
|
* Combines `SubscriberRegistry`, `create_sse_auth_guard`, and the broadcast
|
|
167
|
-
* call into a single object, and adds the registry to `connection_closer
|
|
167
|
+
* call into a single object, and adds the registry to `connection_closer` for
|
|
168
|
+
* good — the remover `add` returns is dropped, so a caller that must take the
|
|
169
|
+
* registry out again passes `null` and adds it itself.
|
|
168
170
|
* The result satisfies `AuditLogRouteOptions['stream']`
|
|
169
171
|
* and provides the `on_audit_event` listener for the audit emitter.
|
|
170
172
|
*
|