@fuzdev/fuz_app 0.63.0 → 0.65.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 +525 -827
- package/dist/actions/broadcast_api.d.ts +1 -1
- package/dist/actions/broadcast_api.js +1 -1
- package/dist/actions/cancel.d.ts +2 -2
- package/dist/actions/cancel.js +3 -3
- package/dist/actions/connection_closer.d.ts +65 -0
- package/dist/actions/connection_closer.d.ts.map +1 -0
- package/dist/actions/connection_closer.js +38 -0
- package/dist/actions/register_action_ws.d.ts +2 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/register_ws_endpoint.d.ts +12 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -5
- package/dist/actions/transports_ws_auth_guard.d.ts +25 -10
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +24 -9
- package/dist/actions/ws_endpoint_spec.d.ts +119 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
- package/dist/actions/ws_endpoint_spec.js +13 -0
- package/dist/auth/CLAUDE.md +592 -1808
- package/dist/auth/account_action_specs.d.ts +1 -1
- package/dist/auth/account_actions.d.ts +13 -0
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +31 -1
- package/dist/auth/account_routes.d.ts +12 -2
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +55 -8
- package/dist/auth/account_schema.d.ts +4 -4
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.d.ts +8 -8
- package/dist/auth/admin_actions.d.ts +11 -0
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +25 -0
- package/dist/auth/api_token_queries.js +1 -1
- package/dist/auth/audit_emitter.d.ts +56 -12
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +38 -12
- package/dist/auth/audit_log_ddl.d.ts +1 -1
- package/dist/auth/audit_log_ddl.d.ts.map +1 -1
- package/dist/auth/audit_log_ddl.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +5 -3
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +5 -3
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +1 -5
- package/dist/auth/bootstrap_routes.d.ts +8 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +15 -11
- package/dist/auth/invite_schema.d.ts +2 -2
- package/dist/auth/keyring.d.ts +6 -6
- package/dist/auth/keyring.js +8 -8
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +4 -2
- package/dist/auth/signup_routes.d.ts +1 -1
- package/dist/auth/standard_rpc_actions.d.ts +1 -0
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -0
- package/dist/db/create_db.d.ts.map +1 -1
- package/dist/db/create_db.js +13 -0
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.js +3 -3
- package/dist/http/CLAUDE.md +225 -483
- package/dist/http/error_schemas.d.ts +0 -4
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +0 -4
- package/dist/http/ip_canonical.d.ts +100 -0
- package/dist/http/ip_canonical.d.ts.map +1 -0
- package/dist/http/ip_canonical.js +195 -0
- package/dist/http/origin.d.ts +14 -6
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +14 -32
- package/dist/http/pending_effects.d.ts +1 -1
- package/dist/http/pending_effects.js +1 -1
- package/dist/http/proxy.d.ts +13 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +15 -23
- package/dist/http/surface.d.ts +50 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +27 -1
- package/dist/primitive_schemas.d.ts +20 -4
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +25 -4
- package/dist/realtime/sse_auth_guard.d.ts +16 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +15 -3
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +66 -19
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +57 -34
- package/dist/server/app_server.d.ts +101 -10
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +105 -6
- package/dist/server/env.d.ts +7 -7
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +14 -14
- package/dist/server/startup.d.ts.map +1 -1
- package/dist/server/startup.js +12 -0
- package/dist/server/static.d.ts +4 -4
- package/dist/server/static.js +7 -7
- package/dist/testing/CLAUDE.md +269 -59
- package/dist/testing/admin_integration.d.ts +18 -23
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +159 -202
- package/dist/testing/adversarial_headers.d.ts +6 -0
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -5
- package/dist/testing/app_server.d.ts +148 -60
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +143 -54
- package/dist/testing/attack_surface.d.ts +8 -7
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +12 -8
- package/dist/testing/audit_completeness.d.ts +23 -22
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +199 -158
- package/dist/testing/audit_drift_guard.d.ts +116 -0
- package/dist/testing/audit_drift_guard.d.ts.map +1 -0
- package/dist/testing/audit_drift_guard.js +134 -0
- package/dist/testing/bootstrap_success.d.ts +28 -0
- package/dist/testing/bootstrap_success.d.ts.map +1 -0
- package/dist/testing/bootstrap_success.js +144 -0
- package/dist/testing/connection_closer_helpers.d.ts +44 -0
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
- package/dist/testing/connection_closer_helpers.js +48 -0
- package/dist/testing/cross_backend/capabilities.d.ts +64 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
- package/dist/testing/cross_backend/capabilities.js +47 -0
- package/dist/testing/cross_backend/setup.d.ts +215 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/setup.js +101 -0
- package/dist/testing/data_exposure.d.ts +14 -15
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +127 -146
- package/dist/testing/db_entities.d.ts +11 -1
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +13 -1
- package/dist/testing/integration.d.ts +35 -21
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +231 -293
- package/dist/testing/integration_helpers.d.ts +16 -6
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +7 -7
- package/dist/testing/mock_fs.d.ts.map +1 -1
- package/dist/testing/mock_fs.js +0 -2
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +13 -4
- package/dist/testing/role_grant_helpers.d.ts +31 -0
- package/dist/testing/role_grant_helpers.d.ts.map +1 -0
- package/dist/testing/role_grant_helpers.js +46 -0
- package/dist/testing/round_trip.d.ts +21 -16
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +65 -86
- package/dist/testing/rpc_helpers.d.ts +2 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.d.ts +24 -21
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +91 -106
- package/dist/testing/schema_introspect.d.ts +106 -0
- package/dist/testing/schema_introspect.d.ts.map +1 -0
- package/dist/testing/schema_introspect.js +123 -0
- package/dist/testing/schema_parity.d.ts +144 -0
- package/dist/testing/schema_parity.d.ts.map +1 -0
- package/dist/testing/schema_parity.js +233 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +12 -6
- package/dist/testing/standard.d.ts +57 -25
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +62 -5
- package/dist/testing/stubs.d.ts +22 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +28 -21
- package/dist/testing/surface_invariants.d.ts +66 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +103 -1
- package/dist/testing/transports/surface_source.d.ts +51 -0
- package/dist/testing/transports/surface_source.d.ts.map +1 -0
- package/dist/testing/transports/surface_source.js +19 -0
- package/dist/ui/SurfaceExplorer.svelte +161 -2
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/actions/cancel.d.ts
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* Wire format is snake_case `cancel` with `{request_id}`, not MCP's
|
|
24
24
|
* `$/cancelRequest` with `{requestId}` — fuz_app's WS transport isn't MCP,
|
|
25
25
|
* and adopting MCP's convention would leak protocol-specific framing into
|
|
26
|
-
* the base transport.
|
|
27
|
-
* layer at the
|
|
26
|
+
* the base transport. If an MCP adapter is ever built, the translation
|
|
27
|
+
* layer at the adapter is the right seam.
|
|
28
28
|
*
|
|
29
29
|
* @module
|
|
30
30
|
*/
|
package/dist/actions/cancel.js
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* Wire format is snake_case `cancel` with `{request_id}`, not MCP's
|
|
24
24
|
* `$/cancelRequest` with `{requestId}` — fuz_app's WS transport isn't MCP,
|
|
25
25
|
* and adopting MCP's convention would leak protocol-specific framing into
|
|
26
|
-
* the base transport.
|
|
27
|
-
* layer at the
|
|
26
|
+
* the base transport. If an MCP adapter is ever built, the translation
|
|
27
|
+
* layer at the adapter is the right seam.
|
|
28
28
|
*
|
|
29
29
|
* @module
|
|
30
30
|
*/
|
|
@@ -63,7 +63,7 @@ export const cancel_action_spec = {
|
|
|
63
63
|
* tuple shape; the dispatcher short-circuits cancel notifications before any
|
|
64
64
|
* handler lookup happens.
|
|
65
65
|
*/
|
|
66
|
-
export const cancel_handler = () => { };
|
|
66
|
+
export const cancel_handler = () => { };
|
|
67
67
|
/**
|
|
68
68
|
* Protocol-action tuple — spread into the server's `actions` array (or via
|
|
69
69
|
* `protocol_actions` from `actions/protocol.ts`) so the dispatcher registers the
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow structural capability for closing live WebSocket connections
|
|
3
|
+
* tied to a session token hash, API token id, or account id.
|
|
4
|
+
*
|
|
5
|
+
* **Why this exists.** Per-message authorization phase on WebSocket
|
|
6
|
+
* (`actions/perform_action.ts`) reloads role_grants from the DB on every
|
|
7
|
+
* message but does NOT re-query session / token validity — that
|
|
8
|
+
* trade-off keeps chatty connections fast. The cost: revocation
|
|
9
|
+
* doesn't actually disconnect open sockets unless something closes
|
|
10
|
+
* them. `transports_ws_auth_guard.ts` is the listener-based seam
|
|
11
|
+
* (audit-event → close), but it only fires after the audit INSERT
|
|
12
|
+
* succeeds — if the INSERT fails (DB error, pool exhausted, handler
|
|
13
|
+
* dies mid-flight) the listener never runs and the live socket keeps
|
|
14
|
+
* working with a stale `RequestContext` until disconnect.
|
|
15
|
+
*
|
|
16
|
+
* Used by self-service revocation handlers (`account_session_revoke` /
|
|
17
|
+
* `_revoke_all`, `account_token_revoke`, `logout`, `password`) and the
|
|
18
|
+
* admin revoke-all handlers (`admin_session_revoke_all`,
|
|
19
|
+
* `admin_token_revoke_all`) to eagerly drop affected sockets BEFORE
|
|
20
|
+
* emitting the corresponding audit event. The audit listener stays as
|
|
21
|
+
* a fail-safe for out-of-band emit sites (admin tools, scheduled
|
|
22
|
+
* jobs, SSE-driven flows). `close_sockets_for_*` is idempotent so the
|
|
23
|
+
* second pass is a no-op.
|
|
24
|
+
*
|
|
25
|
+
* Mirrors `zzz_server`'s `close_sockets_for_*` calls in
|
|
26
|
+
* `account.rs::logout_inner` / `_password_inner` /
|
|
27
|
+
* `handlers/account.rs::handle_account_session_revoke[_all]` /
|
|
28
|
+
* `_token_revoke` (landed 2026-05-16).
|
|
29
|
+
*
|
|
30
|
+
* `BackendWebsocketTransport` satisfies this interface structurally,
|
|
31
|
+
* so consumers pass their transport instance directly (same shape as
|
|
32
|
+
* `NotificationSender`). The interface stays local so handlers don't
|
|
33
|
+
* couple to the concrete transport, and tests can inject a capturing
|
|
34
|
+
* stub with no WS machinery.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Narrow capability — three idempotent socket-close methods, each
|
|
40
|
+
* returning the number of sockets actually closed (zero when none
|
|
41
|
+
* matched). Callers typically ignore the return value (used by
|
|
42
|
+
* telemetry / tests).
|
|
43
|
+
*/
|
|
44
|
+
export interface ConnectionCloser {
|
|
45
|
+
/**
|
|
46
|
+
* Close every connection authenticated with a session whose blake3
|
|
47
|
+
* hash matches `session_token_hash`. Idempotent — calling on an
|
|
48
|
+
* already-closed session is a no-op.
|
|
49
|
+
*/
|
|
50
|
+
close_sockets_for_session: (session_token_hash: string) => number;
|
|
51
|
+
/**
|
|
52
|
+
* Close every connection authenticated with the given API token id.
|
|
53
|
+
* Idempotent — calling on an already-revoked token is a no-op.
|
|
54
|
+
*/
|
|
55
|
+
close_sockets_for_token: (api_token_id: string) => number;
|
|
56
|
+
/**
|
|
57
|
+
* Close every connection bound to `account_id`, regardless of
|
|
58
|
+
* credential type (session / api_token / daemon_token). Coarse
|
|
59
|
+
* closure used when every credential on an account is invalidated
|
|
60
|
+
* — password change, session-revoke-all, token-revoke-all, logout.
|
|
61
|
+
* Idempotent.
|
|
62
|
+
*/
|
|
63
|
+
close_sockets_for_account: (account_id: string) => number;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=connection_closer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE;;;OAGG;IACH,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;;;OAMG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow structural capability for closing live WebSocket connections
|
|
3
|
+
* tied to a session token hash, API token id, or account id.
|
|
4
|
+
*
|
|
5
|
+
* **Why this exists.** Per-message authorization phase on WebSocket
|
|
6
|
+
* (`actions/perform_action.ts`) reloads role_grants from the DB on every
|
|
7
|
+
* message but does NOT re-query session / token validity — that
|
|
8
|
+
* trade-off keeps chatty connections fast. The cost: revocation
|
|
9
|
+
* doesn't actually disconnect open sockets unless something closes
|
|
10
|
+
* them. `transports_ws_auth_guard.ts` is the listener-based seam
|
|
11
|
+
* (audit-event → close), but it only fires after the audit INSERT
|
|
12
|
+
* succeeds — if the INSERT fails (DB error, pool exhausted, handler
|
|
13
|
+
* dies mid-flight) the listener never runs and the live socket keeps
|
|
14
|
+
* working with a stale `RequestContext` until disconnect.
|
|
15
|
+
*
|
|
16
|
+
* Used by self-service revocation handlers (`account_session_revoke` /
|
|
17
|
+
* `_revoke_all`, `account_token_revoke`, `logout`, `password`) and the
|
|
18
|
+
* admin revoke-all handlers (`admin_session_revoke_all`,
|
|
19
|
+
* `admin_token_revoke_all`) to eagerly drop affected sockets BEFORE
|
|
20
|
+
* emitting the corresponding audit event. The audit listener stays as
|
|
21
|
+
* a fail-safe for out-of-band emit sites (admin tools, scheduled
|
|
22
|
+
* jobs, SSE-driven flows). `close_sockets_for_*` is idempotent so the
|
|
23
|
+
* second pass is a no-op.
|
|
24
|
+
*
|
|
25
|
+
* Mirrors `zzz_server`'s `close_sockets_for_*` calls in
|
|
26
|
+
* `account.rs::logout_inner` / `_password_inner` /
|
|
27
|
+
* `handlers/account.rs::handle_account_session_revoke[_all]` /
|
|
28
|
+
* `_token_revoke` (landed 2026-05-16).
|
|
29
|
+
*
|
|
30
|
+
* `BackendWebsocketTransport` satisfies this interface structurally,
|
|
31
|
+
* so consumers pass their transport instance directly (same shape as
|
|
32
|
+
* `NotificationSender`). The interface stays local so handlers don't
|
|
33
|
+
* couple to the concrete transport, and tests can inject a capturing
|
|
34
|
+
* stub with no WS machinery.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
export {};
|
|
@@ -42,8 +42,8 @@ export declare const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60000;
|
|
|
42
42
|
*
|
|
43
43
|
* Fires after the transport has registered the new connection (so
|
|
44
44
|
* `connection_id` is valid) but before any client message can dispatch.
|
|
45
|
-
* Consumers use this to bootstrap per-socket domain state — e.g.
|
|
46
|
-
*
|
|
45
|
+
* Consumers use this to bootstrap per-socket domain state — e.g.
|
|
46
|
+
* spawning a per-account unit and pushing an initial state snapshot.
|
|
47
47
|
*/
|
|
48
48
|
export interface SocketOpenContext {
|
|
49
49
|
/** The raw WebSocket context — exposed for edge cases; prefer `notify` for sends. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAUjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAgBpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAG9F,YAAY,EAAC,MAAM,EAAC,CAAC;AAErB,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB;IACvC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACjD;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,uBAAuB,KAAG,
|
|
1
|
+
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAUjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAgBpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAG9F,YAAY,EAAC,MAAM,EAAC,CAAC;AAErB,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB;IACvC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACjD;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,uBAAuB,KAAG,sBA4VrE,CAAC"}
|
|
@@ -90,6 +90,14 @@ export const register_action_ws = (options) => {
|
|
|
90
90
|
// `credential_type` from this closure; the live request_context is
|
|
91
91
|
// only used by the test-preset escape hatch (perform_action runs
|
|
92
92
|
// the authorization phase fresh on every message in production).
|
|
93
|
+
//
|
|
94
|
+
// Per-message dispatch reloads role_grants via the authorization
|
|
95
|
+
// phase but does NOT re-query session / token validity — those
|
|
96
|
+
// are checked once at upgrade. Revocation enforcement therefore
|
|
97
|
+
// lives outside this dispatcher, in the audit-driven WS auth
|
|
98
|
+
// guard (`transports_ws_auth_guard.ts`). Without that guard wired
|
|
99
|
+
// into the audit chain, `session_revoke` / `token_revoke` are
|
|
100
|
+
// no-ops for existing WS connections.
|
|
93
101
|
const upgrade_context = require_request_context(c);
|
|
94
102
|
const account_id = upgrade_context.account.id;
|
|
95
103
|
const client_ip = get_client_ip(c);
|
|
@@ -263,8 +271,21 @@ export const register_action_ws = (options) => {
|
|
|
263
271
|
// eager fire-and-forget pool writes (audit emits, etc.);
|
|
264
272
|
// `post_commit_effects` collects deferred thunks pushed
|
|
265
273
|
// via `emit_after_commit` (WS notifications). Both flush
|
|
266
|
-
// in the
|
|
267
|
-
//
|
|
274
|
+
// in the `finally` so the next message sees a clean slate.
|
|
275
|
+
//
|
|
276
|
+
// Ordering invariant — reply-before-flush is load-bearing.
|
|
277
|
+
// Handlers that revoke their own credential
|
|
278
|
+
// (`session_revoke_all`, `token_revoke` of the calling
|
|
279
|
+
// bearer) audit-emit events whose listener chain — wired
|
|
280
|
+
// by the WS auth guard in `transports_ws_auth_guard.ts` —
|
|
281
|
+
// closes this socket when the audit row writes. The
|
|
282
|
+
// synchronous `ws.send` on the success path returns
|
|
283
|
+
// before any close can fire (the DB write that triggers
|
|
284
|
+
// the chain is async — even in production with
|
|
285
|
+
// `await_pending_effects: false`, the listener chain only
|
|
286
|
+
// runs after the row lands). Inverting the order —
|
|
287
|
+
// flushing the queues before the send — would silently
|
|
288
|
+
// strand the caller without a reply.
|
|
268
289
|
const pending_effects = [];
|
|
269
290
|
const post_commit_effects = [];
|
|
270
291
|
const notify = notify_socket(ws);
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* and build the `RequestContext` that per-message dispatch reads.
|
|
13
13
|
* Multi-actor accounts must supply `?acting` to pick a persona;
|
|
14
14
|
* single-actor accounts work without it.
|
|
15
|
-
* 4. Optional `require_role(
|
|
16
|
-
*
|
|
15
|
+
* 4. Optional `require_role(required_roles)` — for endpoints gated to a
|
|
16
|
+
* non-empty any-of set of roles.
|
|
17
17
|
*
|
|
18
18
|
* Then delegates to `register_action_ws` for per-message JSON-RPC
|
|
19
19
|
* dispatch.
|
|
@@ -25,19 +25,21 @@ import { type RegisterActionWsOptions, type RegisterActionWsResult } from './reg
|
|
|
25
25
|
/** Options for `register_ws_endpoint`. */
|
|
26
26
|
export interface RegisterWsEndpointOptions extends RegisterActionWsOptions {
|
|
27
27
|
/**
|
|
28
|
-
* Origin allowlist regexes — typically parsed from the `
|
|
28
|
+
* Origin allowlist regexes — typically parsed from the `FUZ_ALLOWED_ORIGINS`
|
|
29
29
|
* env var via `parse_allowed_origins`. Passed straight to
|
|
30
30
|
* `verify_request_source`.
|
|
31
31
|
*/
|
|
32
|
-
allowed_origins:
|
|
32
|
+
allowed_origins: ReadonlyArray<RegExp>;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
34
|
+
* Roles permitted to upgrade — any-of disjunction (matches the
|
|
35
|
+
* underlying `require_role` semantics). Omit (or pass `[]`) for any
|
|
36
|
+
* authenticated account (`require_auth` + actor resolution alone);
|
|
37
|
+
* set to e.g. `[ROLE_ADMIN]` to gate the endpoint behind a single role
|
|
38
|
+
* or `[ROLE_ADMIN, ROLE_KEEPER]` to permit either. The per-action
|
|
39
|
+
* `auth` in each spec still applies at dispatch time — this is a coarse
|
|
40
|
+
* upgrade-time gate.
|
|
39
41
|
*/
|
|
40
|
-
|
|
42
|
+
required_roles?: ReadonlyArray<RoleName>;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* Mount a WebSocket endpoint with the standard upgrade stack (origin check
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAGrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAGrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAmBF,CAAC"}
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* and build the `RequestContext` that per-message dispatch reads.
|
|
13
13
|
* Multi-actor accounts must supply `?acting` to pick a persona;
|
|
14
14
|
* single-actor accounts work without it.
|
|
15
|
-
* 4. Optional `require_role(
|
|
16
|
-
*
|
|
15
|
+
* 4. Optional `require_role(required_roles)` — for endpoints gated to a
|
|
16
|
+
* non-empty any-of set of roles.
|
|
17
17
|
*
|
|
18
18
|
* Then delegates to `register_action_ws` for per-message JSON-RPC
|
|
19
19
|
* dispatch.
|
|
@@ -77,12 +77,12 @@ const create_ws_authorization_middleware = (db) => {
|
|
|
77
77
|
* then registers the `GET path` route via the inner `register_action_ws`
|
|
78
78
|
*/
|
|
79
79
|
export const register_ws_endpoint = (options) => {
|
|
80
|
-
const { app, path, allowed_origins, db,
|
|
80
|
+
const { app, path, allowed_origins, db, required_roles, log = new Logger('[ws]'), ...rest } = options;
|
|
81
81
|
app.use(path, verify_request_source(allowed_origins));
|
|
82
82
|
app.use(path, require_auth);
|
|
83
83
|
app.use(path, create_ws_authorization_middleware(db));
|
|
84
|
-
if (
|
|
85
|
-
app.use(path, require_role(
|
|
84
|
+
if (required_roles?.length) {
|
|
85
|
+
app.use(path, require_role(required_roles));
|
|
86
86
|
}
|
|
87
87
|
return register_action_ws({ app, path, db, log, ...rest });
|
|
88
88
|
};
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebSocket auth guard — bridges audit events to `BackendWebsocketTransport`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* **Why this exists.** `register_action_ws` captures `account_id` and
|
|
5
|
+
* `credential_type` at upgrade time and reuses them for every message.
|
|
6
|
+
* `perform_action`'s per-message authorization phase reloads role_grants
|
|
7
|
+
* from the DB, but session and token VALIDITY are not re-queried — that
|
|
8
|
+
* trade-off keeps chatty WS connections fast. The cost: nothing in the
|
|
9
|
+
* dispatch path notices when a session is revoked or a token is rotated.
|
|
10
|
+
* This guard is the enforcement mechanism — it listens on the audit
|
|
11
|
+
* chain and closes affected sockets when revocation events fire, so
|
|
12
|
+
* revocation actually takes effect on existing connections. Without it,
|
|
13
|
+
* `session_revoke` / `token_revoke` are no-ops for open WS connections.
|
|
7
14
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
15
|
+
* Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket
|
|
16
|
+
* transport. Dispatches audit events to the right `close_sockets_for_*`
|
|
17
|
+
* method so consumers do not re-implement the switch themselves.
|
|
18
|
+
*
|
|
19
|
+
* For standard WS endpoints mounted via `AppServerOptions.ws_endpoints`,
|
|
20
|
+
* `create_app_server` composes the guard automatically per
|
|
21
|
+
* `WsEndpointSpec.auth_guard`. For custom wiring, append the handler
|
|
22
|
+
* inside the consumer's `audit_factory` body (or via
|
|
23
|
+
* `audit.on_event_chain.push(...)` post-assembly).
|
|
10
24
|
*
|
|
11
25
|
* @module
|
|
12
26
|
*/
|
|
@@ -14,7 +28,7 @@ import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
|
14
28
|
import type { AuditLogEvent } from '../auth/audit_log_schema.js';
|
|
15
29
|
import type { BackendWebsocketTransport } from './transports_ws_backend.js';
|
|
16
30
|
/**
|
|
17
|
-
* Audit-event callback shape — the function `
|
|
31
|
+
* Audit-event callback shape — the function `CreateAuditEmitterOptions.on_audit_event`
|
|
18
32
|
* accepts and that the helpers in this module return.
|
|
19
33
|
*
|
|
20
34
|
* Exported so consumers composing multiple handlers (typically
|
|
@@ -46,8 +60,10 @@ export declare const ws_disconnect_event_types: ReadonlySet<string>;
|
|
|
46
60
|
* user close another user's socket by guessing a session hash or token id.
|
|
47
61
|
*
|
|
48
62
|
* @param log - logger for disconnect events (info level on non-zero closures)
|
|
49
|
-
* @returns an `on_audit_event` callback suitable for `
|
|
50
|
-
*
|
|
63
|
+
* @returns an `on_audit_event` callback suitable for `create_audit_emitter`'s
|
|
64
|
+
* `on_audit_event` slot, or for appending onto
|
|
65
|
+
* `audit.on_event_chain` post-assembly. The returned callback mutates
|
|
66
|
+
* `transport` (closing matching sockets via
|
|
51
67
|
* `close_sockets_for_session` / `_token` / `_account`) on every relevant event.
|
|
52
68
|
*/
|
|
53
69
|
export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport, log: Logger) => AuditEventHandler;
|
|
@@ -58,8 +74,7 @@ export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport
|
|
|
58
74
|
* Sibling helper to `create_ws_auth_guard` — kept separate because
|
|
59
75
|
* `ws_disconnect_event_types` deliberately omits `logout` (admin-initiated
|
|
60
76
|
* revocations use `session_revoke`, while `logout` is the user-initiated
|
|
61
|
-
* case).
|
|
62
|
-
* before extraction.
|
|
77
|
+
* case). Multiple consumers hand-rolled this same branch before extraction.
|
|
63
78
|
*
|
|
64
79
|
* Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
|
|
65
80
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBA6CF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,uBAAuB,GACnC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBAaF,CAAC"}
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebSocket auth guard — bridges audit events to `BackendWebsocketTransport`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* **Why this exists.** `register_action_ws` captures `account_id` and
|
|
5
|
+
* `credential_type` at upgrade time and reuses them for every message.
|
|
6
|
+
* `perform_action`'s per-message authorization phase reloads role_grants
|
|
7
|
+
* from the DB, but session and token VALIDITY are not re-queried — that
|
|
8
|
+
* trade-off keeps chatty WS connections fast. The cost: nothing in the
|
|
9
|
+
* dispatch path notices when a session is revoked or a token is rotated.
|
|
10
|
+
* This guard is the enforcement mechanism — it listens on the audit
|
|
11
|
+
* chain and closes affected sockets when revocation events fire, so
|
|
12
|
+
* revocation actually takes effect on existing connections. Without it,
|
|
13
|
+
* `session_revoke` / `token_revoke` are no-ops for open WS connections.
|
|
7
14
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
15
|
+
* Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket
|
|
16
|
+
* transport. Dispatches audit events to the right `close_sockets_for_*`
|
|
17
|
+
* method so consumers do not re-implement the switch themselves.
|
|
18
|
+
*
|
|
19
|
+
* For standard WS endpoints mounted via `AppServerOptions.ws_endpoints`,
|
|
20
|
+
* `create_app_server` composes the guard automatically per
|
|
21
|
+
* `WsEndpointSpec.auth_guard`. For custom wiring, append the handler
|
|
22
|
+
* inside the consumer's `audit_factory` body (or via
|
|
23
|
+
* `audit.on_event_chain.push(...)` post-assembly).
|
|
10
24
|
*
|
|
11
25
|
* @module
|
|
12
26
|
*/
|
|
@@ -39,8 +53,10 @@ export const ws_disconnect_event_types = new Set([
|
|
|
39
53
|
* user close another user's socket by guessing a session hash or token id.
|
|
40
54
|
*
|
|
41
55
|
* @param log - logger for disconnect events (info level on non-zero closures)
|
|
42
|
-
* @returns an `on_audit_event` callback suitable for `
|
|
43
|
-
*
|
|
56
|
+
* @returns an `on_audit_event` callback suitable for `create_audit_emitter`'s
|
|
57
|
+
* `on_audit_event` slot, or for appending onto
|
|
58
|
+
* `audit.on_event_chain` post-assembly. The returned callback mutates
|
|
59
|
+
* `transport` (closing matching sockets via
|
|
44
60
|
* `close_sockets_for_session` / `_token` / `_account`) on every relevant event.
|
|
45
61
|
*/
|
|
46
62
|
export const create_ws_auth_guard = (transport, log) => {
|
|
@@ -92,8 +108,7 @@ export const create_ws_auth_guard = (transport, log) => {
|
|
|
92
108
|
* Sibling helper to `create_ws_auth_guard` — kept separate because
|
|
93
109
|
* `ws_disconnect_event_types` deliberately omits `logout` (admin-initiated
|
|
94
110
|
* revocations use `session_revoke`, while `logout` is the user-initiated
|
|
95
|
-
* case).
|
|
96
|
-
* before extraction.
|
|
111
|
+
* case). Multiple consumers hand-rolled this same branch before extraction.
|
|
97
112
|
*
|
|
98
113
|
* Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
|
|
99
114
|
*
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `WsEndpointSpec` — the canonical WebSocket endpoint declaration consumed
|
|
3
|
+
* by `create_app_server`'s `ws_endpoints` option (mirror of `RpcEndpointSpec`
|
|
4
|
+
* for HTTP RPC).
|
|
5
|
+
*
|
|
6
|
+
* Lives in its own module so both `server/app_server.ts` (which mounts
|
|
7
|
+
* endpoints from these specs) and `http/surface.ts` (which threads the
|
|
8
|
+
* resolved spec list into surface generation) can import it without a
|
|
9
|
+
* cycle between the two.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { Action } from './action_types.js';
|
|
14
|
+
import type { RoleName } from '../auth/role_schema.js';
|
|
15
|
+
import type { BackendWebsocketTransport } from './transports_ws_backend.js';
|
|
16
|
+
import type { ServerHeartbeatOptions, SocketCloseContext, SocketOpenContext } from './register_action_ws.js';
|
|
17
|
+
import type { AuditEventHandler } from './transports_ws_auth_guard.js';
|
|
18
|
+
/**
|
|
19
|
+
* Declarative description of a WebSocket endpoint to be auto-mounted by
|
|
20
|
+
* `create_app_server`.
|
|
21
|
+
*
|
|
22
|
+
* Single source of truth for mount + surface — the same array drives
|
|
23
|
+
* `register_ws_endpoint`-style upgrade wiring AND the `surface.ws_endpoints`
|
|
24
|
+
* slot emitted into `AppSurface`, so consumers cannot drift their declared
|
|
25
|
+
* actions from what dispatch actually serves.
|
|
26
|
+
*/
|
|
27
|
+
export interface WsEndpointSpec {
|
|
28
|
+
/** Hono mount path (e.g. `/api/ws`). */
|
|
29
|
+
path: string;
|
|
30
|
+
/**
|
|
31
|
+
* Origin allowlist regexes — typically parsed via `parse_allowed_origins`.
|
|
32
|
+
* Passed straight to `verify_request_source` on upgrade.
|
|
33
|
+
*/
|
|
34
|
+
allowed_origins: ReadonlyArray<RegExp>;
|
|
35
|
+
/**
|
|
36
|
+
* The actions registered on this endpoint. Spread `protocol_actions`
|
|
37
|
+
* from `actions/protocol.ts` first to complete the
|
|
38
|
+
* disconnect-detection + per-request cancel pairing with the frontend
|
|
39
|
+
* client.
|
|
40
|
+
*/
|
|
41
|
+
actions: ReadonlyArray<Action>;
|
|
42
|
+
/**
|
|
43
|
+
* Roles permitted to upgrade — any-of disjunction. Omit (or pass `[]`)
|
|
44
|
+
* to skip the upgrade-time role gate; per-action `auth` on each spec
|
|
45
|
+
* still applies at dispatch time via `perform_action`. Pass
|
|
46
|
+
* `[ROLE_ADMIN]` for a zap-style admin-only WS endpoint.
|
|
47
|
+
*/
|
|
48
|
+
required_roles?: ReadonlyArray<RoleName>;
|
|
49
|
+
/**
|
|
50
|
+
* Existing transport to register connections with. Auto-created when
|
|
51
|
+
* omitted. Either way the mounted transport is reachable on
|
|
52
|
+
* `AppServer.ws_endpoints[path]` for broadcast / fan-out.
|
|
53
|
+
*/
|
|
54
|
+
transport?: BackendWebsocketTransport;
|
|
55
|
+
/**
|
|
56
|
+
* Server-side heartbeat policy. Default-on (60s receive-silence
|
|
57
|
+
* timeout). Set `false` only when an upstream stack (TCP keepalive,
|
|
58
|
+
* Cloudflare idle timeout) already owns disconnect detection.
|
|
59
|
+
*/
|
|
60
|
+
heartbeat?: boolean | ServerHeartbeatOptions;
|
|
61
|
+
/** Optional per-message delay for testing loading states. */
|
|
62
|
+
artificial_delay?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Called once per socket after `transport.add_connection` but before
|
|
65
|
+
* the first message dispatches. See
|
|
66
|
+
* `RegisterActionWsOptions.on_socket_open`.
|
|
67
|
+
*/
|
|
68
|
+
on_socket_open?: (ctx: SocketOpenContext) => void | Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Called once per socket on close, before `transport.remove_connection`.
|
|
71
|
+
* See `RegisterActionWsOptions.on_socket_close`.
|
|
72
|
+
*/
|
|
73
|
+
on_socket_close?: (ctx: SocketCloseContext) => void | Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Default `true` — auto-composes `create_ws_auth_guard` +
|
|
76
|
+
* `create_ws_logout_closer` against this endpoint's transport and
|
|
77
|
+
* appends them to `deps.audit.on_event_chain`. Wiring is deduped by
|
|
78
|
+
* transport **reference identity** (`WeakSet<BackendWebsocketTransport>`),
|
|
79
|
+
* so two `WsEndpointSpec`s sharing the exact same instance get a
|
|
80
|
+
* single pair of listeners.
|
|
81
|
+
*
|
|
82
|
+
* **Shared-transport OR-semantics.** When multiple `WsEndpointSpec`s
|
|
83
|
+
* share one transport, the guard is wired iff **any** of those specs
|
|
84
|
+
* has `auth_guard !== false`. To opt out for a shared transport,
|
|
85
|
+
* every sibling spec must pass `auth_guard: false`. The default is
|
|
86
|
+
* "fail safe" — easier to enable than disable, and predictable
|
|
87
|
+
* regardless of spec order.
|
|
88
|
+
*
|
|
89
|
+
* Reference-identity dedupe means **wrapped or proxied transports
|
|
90
|
+
* dedupe as separate entries** — a consumer threading every
|
|
91
|
+
* transport through a tracing / DI / metrics shim will get a fresh
|
|
92
|
+
* pair of listeners per shimmed reference, even when the underlying
|
|
93
|
+
* transport is the same. If you wrap or proxy, set `auth_guard:
|
|
94
|
+
* false` on the duplicate `WsEndpointSpec`s and compose
|
|
95
|
+
* `create_ws_auth_guard` / `create_ws_logout_closer` against the
|
|
96
|
+
* underlying transport once.
|
|
97
|
+
*
|
|
98
|
+
* Set `false` when a consumer needs to compose their own callback
|
|
99
|
+
* from scratch — or to opt out of the auto-wiring entirely.
|
|
100
|
+
*
|
|
101
|
+
* NOTE: does NOT close sockets on `role_grant_revoke` — that omission
|
|
102
|
+
* is deliberate (per-connection role tracking is out of scope). A user
|
|
103
|
+
* whose admin role is revoked keeps their socket open; the next message
|
|
104
|
+
* gets `forbidden` from the per-message authorization phase. Consumers
|
|
105
|
+
* wanting role-revoke disconnection use `extra_audit_handlers`.
|
|
106
|
+
*/
|
|
107
|
+
auth_guard?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Extra audit-event handlers appended to `deps.audit.on_event_chain`
|
|
110
|
+
* AFTER the standard `auth_guard` wiring (when enabled). By the time
|
|
111
|
+
* these run, the standard guards may have already closed sockets. Use
|
|
112
|
+
* for role-revoke disconnection, custom analytics, etc.
|
|
113
|
+
*
|
|
114
|
+
* Never deduped — consumer-owned; pass the same handler twice and it
|
|
115
|
+
* fires twice.
|
|
116
|
+
*/
|
|
117
|
+
extra_audit_handlers?: ReadonlyArray<AuditEventHandler>;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=ws_endpoint_spec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws_endpoint_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/ws_endpoint_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAC1E,OAAO,KAAK,EACX,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC;AAErE;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC9B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;OAKG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACzC;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;OAQG;IACH,oBAAoB,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;CACxD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `WsEndpointSpec` — the canonical WebSocket endpoint declaration consumed
|
|
3
|
+
* by `create_app_server`'s `ws_endpoints` option (mirror of `RpcEndpointSpec`
|
|
4
|
+
* for HTTP RPC).
|
|
5
|
+
*
|
|
6
|
+
* Lives in its own module so both `server/app_server.ts` (which mounts
|
|
7
|
+
* endpoints from these specs) and `http/surface.ts` (which threads the
|
|
8
|
+
* resolved spec list into surface generation) can import it without a
|
|
9
|
+
* cycle between the two.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
export {};
|