@fuzdev/fuz_app 0.149.0 → 0.150.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 +9 -7
- package/dist/actions/action_bridge.d.ts +7 -0
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +10 -0
- package/dist/actions/action_event_types.d.ts +1 -1
- package/dist/actions/action_event_types.d.ts.map +1 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +8 -5
- package/dist/actions/register_action_ws.d.ts +8 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +24 -4
- package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +9 -6
- package/dist/actions/ws_endpoint_spec.d.ts +1 -1
- package/dist/auth/CLAUDE.md +69 -31
- package/dist/auth/actor_lookup_queries.d.ts +3 -2
- package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
- package/dist/auth/actor_lookup_queries.js +6 -3
- package/dist/auth/actor_search_queries.d.ts +5 -2
- package/dist/auth/actor_search_queries.d.ts.map +1 -1
- package/dist/auth/actor_search_queries.js +11 -4
- package/dist/auth/admin_action_specs.d.ts +4 -4
- package/dist/auth/admin_action_specs.js +4 -4
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +35 -6
- package/dist/auth/all_cell_actions.d.ts +11 -2
- package/dist/auth/all_cell_actions.d.ts.map +1 -1
- package/dist/auth/all_cell_actions.js +2 -1
- package/dist/auth/audit_emitter.d.ts +9 -3
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +9 -3
- package/dist/auth/audit_log_schema.d.ts +1 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +9 -5
- package/dist/auth/bootstrap_account.d.ts +25 -1
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +37 -4
- package/dist/auth/bootstrap_routes.d.ts +3 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +5 -4
- package/dist/auth/cell_action_specs.d.ts +215 -15
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +103 -16
- package/dist/auth/cell_actions.d.ts +40 -18
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +126 -19
- package/dist/auth/cell_audit_events.d.ts +1 -1
- package/dist/auth/cell_audit_events.d.ts.map +1 -1
- package/dist/auth/cell_audit_events.js +3 -2
- package/dist/auth/cell_audit_metadata.d.ts +11 -0
- package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
- package/dist/auth/cell_audit_metadata.js +10 -0
- package/dist/auth/cell_grant_action_specs.d.ts +7 -0
- package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_grant_action_specs.js +7 -0
- package/dist/auth/cell_grant_actions.d.ts +36 -4
- package/dist/auth/cell_grant_actions.d.ts.map +1 -1
- package/dist/auth/cell_grant_actions.js +20 -3
- package/dist/auth/credential_type_schema.d.ts +3 -2
- package/dist/auth/credential_type_schema.d.ts.map +1 -1
- package/dist/auth/credential_type_schema.js +3 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +25 -3
- package/dist/auth/role_grant_offer_actions.d.ts +9 -4
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +58 -7
- package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_notifications.js +3 -2
- package/dist/auth/role_grant_offer_queries.d.ts +20 -0
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +35 -0
- package/dist/auth/role_grant_queries.d.ts +27 -9
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +40 -13
- package/dist/auth/role_schema.d.ts +14 -8
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +6 -3
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +20 -0
- package/dist/db/CLAUDE.md +9 -2
- package/dist/db/cell_ddl.d.ts +9 -1
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +11 -1
- package/dist/db/cell_queries.d.ts +53 -7
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +61 -7
- package/dist/http/body_limit.d.ts +3 -1
- package/dist/http/body_limit.d.ts.map +1 -1
- package/dist/http/body_limit.js +6 -1
- package/dist/http/jsonrpc.d.ts +9 -0
- package/dist/http/jsonrpc.d.ts.map +1 -1
- package/dist/http/jsonrpc.js +17 -0
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +29 -20
- package/dist/http/proxy.d.ts +4 -2
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +7 -2
- package/dist/http/surface.d.ts +3 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +7 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +3 -2
- package/dist/schema_meta.d.ts +32 -1
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +58 -5
- package/dist/server/app_server.d.ts +13 -5
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +32 -8
- package/dist/testing/CLAUDE.md +34 -21
- package/dist/testing/app_server.d.ts +3 -1
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.js +27 -0
- package/dist/testing/cross_backend/capabilities.d.ts +2 -1
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +109 -13
- package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
- package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_moderate.js +68 -4
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
- package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +12 -10
- package/dist/testing/cross_backend/differential.d.ts +7 -0
- package/dist/testing/cross_backend/differential.d.ts.map +1 -1
- package/dist/testing/cross_backend/differential.js +17 -0
- package/dist/testing/cross_backend/expected_schema.json +1 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +5 -4
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
- package/dist/testing/cross_backend/transport_composition.js +3 -3
- package/package.json +1 -1
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -70,7 +70,8 @@ dropping per-spec `*_METHOD` constants (readers dereference `.method`). See
|
|
|
70
70
|
- `local_call` — none (no REST, no RPC, no WS, no SSE).
|
|
71
71
|
|
|
72
72
|
`create_action_route_spec` throws if `spec.auth` is null (notifications and
|
|
73
|
-
local calls cannot become routes)
|
|
73
|
+
local calls cannot become routes) or `spec.rate_limit` is set (a bridged route
|
|
74
|
+
never reaches the `perform_action` limiters). `create_action_event_spec` throws on any
|
|
74
75
|
non-`remote_notification` kind.
|
|
75
76
|
|
|
76
77
|
## Registry compile (`actions/compile_action_registry.ts`)
|
|
@@ -177,7 +178,7 @@ and `FrontendActionHandlers`.
|
|
|
177
178
|
Derives transport-specific specs from action specs. HTTP-specific concerns
|
|
178
179
|
(path, handler, errors) come from options, not the action spec.
|
|
179
180
|
|
|
180
|
-
- `create_action_route_spec(spec, options)` — one action → one `RouteSpec`. HTTP method defaults by `side_effects` (`true` → POST, `false` → GET; override via `options.http_method`). `route.auth` is `spec.auth` plus a derived `required_scope`. `transaction: spec.side_effects`. Throws if `spec.auth` is null.
|
|
181
|
+
- `create_action_route_spec(spec, options)` — one action → one `RouteSpec`. HTTP method defaults by `side_effects` (`true` → POST, `false` → GET; override via `options.http_method`). `route.auth` is `spec.auth` plus a derived `required_scope`. `transaction: spec.side_effects`. Throws if `spec.auth` is null, or if `spec.rate_limit` is set (the action limiters run in `perform_action`, which a bridged route never reaches).
|
|
181
182
|
|
|
182
183
|
**The bridge carries the token-scope gate across.** A bridged route runs
|
|
183
184
|
`options.handler` through the REST pipeline and never reaches
|
|
@@ -235,9 +236,10 @@ Shim responsibilities (per-request):
|
|
|
235
236
|
6. Bind result via `perform_action_result_to_envelope(id, result)`; `c.json(envelope, result.status)`.
|
|
236
237
|
|
|
237
238
|
Error paths: `ThrownJsonrpcError` (duck-typed via `err instanceof Error &&
|
|
238
|
-
|
|
239
|
+
is_jsonrpc_error_code(err.code)` to handle cross-copy `instanceof` misses,
|
|
239
240
|
e.g. when consumers like zzz throw their own `ThrownJsonrpcError`)
|
|
240
|
-
preserves code + data verbatim
|
|
241
|
+
preserves code + data verbatim; an error whose numeric `code` is not a
|
|
242
|
+
JSON-RPC one (a `DOMException`) takes the generic path. Generic throws become `internal_error` 500;
|
|
241
243
|
message is the raw error under `DEV`, "internal server error" otherwise.
|
|
242
244
|
|
|
243
245
|
A result `c.json` cannot serialize (a `bigint`, a cycle) answers the same
|
|
@@ -360,7 +362,7 @@ and `allow_fallback: boolean` (default `true`). Explicit
|
|
|
360
362
|
|
|
361
363
|
### WS close codes (`actions/transports.ts`)
|
|
362
364
|
|
|
363
|
-
- `WS_CLOSE_SESSION_REVOKED = 4001` — server revoked auth (reason `WS_CLOSE_SESSION_REVOKED_REASON`, `Session revoked`); client enters permanent `revoked` state, no reconnect. Also the close for an upgrade refused at admission because its credential was revoked mid-upgrade — indistinguishable on the wire by design — and for a socket whose credential expired while open, with reason `WS_CLOSE_CREDENTIAL_EXPIRED_REASON` (`Credential expired`), which the client treats as the same `revoked` state.
|
|
365
|
+
- `WS_CLOSE_SESSION_REVOKED = 4001` — server revoked auth (reason `WS_CLOSE_SESSION_REVOKED_REASON`, `Session revoked`); client enters permanent `revoked` state, no reconnect. Also the close for an upgrade refused at admission because its credential was revoked, or its acting actor soft-deleted, mid-upgrade — indistinguishable on the wire by design — and for a socket whose credential expired while open, with reason `WS_CLOSE_CREDENTIAL_EXPIRED_REASON` (`Credential expired`), which the client treats as the same `revoked` state.
|
|
364
366
|
- `WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT = 4002` — client observed receive-silence past its receive timeout (`resolve_heartbeat_receive_timeout`).
|
|
365
367
|
- `WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT = 4003` — server observed receive-silence past `DEFAULT_SERVER_HEARTBEAT_TIMEOUT` (60s).
|
|
366
368
|
- `WS_CLOSE_CONNECTION_LIMIT = 4004` — server closed this socket to admit a newer one on the same account, past the per-account connection cap (reason `connection limit`). Not a revocation: the client stays `closed` with `superseded` set and no reconnect, until `connect()` is called. Twin of the Rust spine's constant.
|
|
@@ -736,8 +738,8 @@ guard reads too — one declaration, so the two can't drift:
|
|
|
736
738
|
- `none` → nothing — every other event, and any event type fuz_app doesn't
|
|
737
739
|
define
|
|
738
740
|
|
|
739
|
-
`outcome === '
|
|
740
|
-
identifiers. Reacting to them would let an authenticated caller close
|
|
741
|
+
Only `outcome === 'success'` events act — a failure carries attacker-controlled
|
|
742
|
+
identifiers, and an outcome outside the enum is no revocation. Reacting to them would let an authenticated caller close
|
|
741
743
|
another user's socket by guessing a session hash or token id.
|
|
742
744
|
|
|
743
745
|
## WebSocket dispatch — three layered entry points
|
|
@@ -80,11 +80,18 @@ export declare const derive_http_method: (side_effects: ActionSideEffects) => Ro
|
|
|
80
80
|
* same holder reaches the route by dropping the credential, so the guard would
|
|
81
81
|
* enforce nothing — the shape `fuz_auth_guard_resolver` refuses outright.
|
|
82
82
|
*
|
|
83
|
+
* **A rate-limited action is refused.** The action limiters run in
|
|
84
|
+
* `perform_action`, which a bridged route never reaches, and a route spec has
|
|
85
|
+
* no limiter of its own — so bridging a `rate_limit` action would serve it
|
|
86
|
+
* unthrottled beside the throttled RPC and WS paths, a way around its budget.
|
|
87
|
+
* Serve it over RPC or WS instead.
|
|
88
|
+
*
|
|
83
89
|
* @param spec - the action spec (must have non-null `auth`)
|
|
84
90
|
* @param options - HTTP-specific options (path, handler, optional overrides)
|
|
85
91
|
* @returns a `RouteSpec` ready for `apply_route_specs`
|
|
86
92
|
* @throws Error if `spec.auth` is null (only `request_response` actions can
|
|
87
93
|
* become routes; notifications and local calls cannot)
|
|
94
|
+
* @throws Error if `spec.rate_limit` is set (the bridged route would be unthrottled)
|
|
88
95
|
*/
|
|
89
96
|
export declare const create_action_route_spec: (spec: ActionSpec, options: ActionRouteOptions) => RouteSpec;
|
|
90
97
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SAmCF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,SAcF,CAAC"}
|
|
@@ -46,16 +46,26 @@ export const derive_http_method = (side_effects) => {
|
|
|
46
46
|
* same holder reaches the route by dropping the credential, so the guard would
|
|
47
47
|
* enforce nothing — the shape `fuz_auth_guard_resolver` refuses outright.
|
|
48
48
|
*
|
|
49
|
+
* **A rate-limited action is refused.** The action limiters run in
|
|
50
|
+
* `perform_action`, which a bridged route never reaches, and a route spec has
|
|
51
|
+
* no limiter of its own — so bridging a `rate_limit` action would serve it
|
|
52
|
+
* unthrottled beside the throttled RPC and WS paths, a way around its budget.
|
|
53
|
+
* Serve it over RPC or WS instead.
|
|
54
|
+
*
|
|
49
55
|
* @param spec - the action spec (must have non-null `auth`)
|
|
50
56
|
* @param options - HTTP-specific options (path, handler, optional overrides)
|
|
51
57
|
* @returns a `RouteSpec` ready for `apply_route_specs`
|
|
52
58
|
* @throws Error if `spec.auth` is null (only `request_response` actions can
|
|
53
59
|
* become routes; notifications and local calls cannot)
|
|
60
|
+
* @throws Error if `spec.rate_limit` is set (the bridged route would be unthrottled)
|
|
54
61
|
*/
|
|
55
62
|
export const create_action_route_spec = (spec, options) => {
|
|
56
63
|
if (spec.auth === null) {
|
|
57
64
|
throw new Error(`Cannot derive route spec from action '${spec.method}': auth is null (only request_response actions with non-null auth can become routes)`);
|
|
58
65
|
}
|
|
66
|
+
if (spec.rate_limit) {
|
|
67
|
+
throw new Error(`Cannot derive route spec from action '${spec.method}': it declares rate_limit '${spec.rate_limit}', which a bridged route would not enforce (serve it over RPC or WS instead)`);
|
|
68
|
+
}
|
|
59
69
|
const auth = options.auth ?? spec.auth;
|
|
60
70
|
// Carry the dispatcher's per-method scope gate across — see the token-scope
|
|
61
71
|
// note above. An explicit capability wins; a public route has no credential
|
|
@@ -31,7 +31,7 @@ export interface ActionEventEnvironment {
|
|
|
31
31
|
* notifications and requests. Unset falls back to `console_action_log`;
|
|
32
32
|
* `null` silences them.
|
|
33
33
|
*/
|
|
34
|
-
readonly log?:
|
|
34
|
+
readonly log?: ActionLog | null;
|
|
35
35
|
}
|
|
36
36
|
/** The logging surface the action dispatch path uses. */
|
|
37
37
|
export type ActionLog = Pick<Logger, 'error' | 'warn' | 'debug'>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_event_types.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event_types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtF,eAAO,MAAM,cAAc;;;EAAkC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,eAAO,MAAM,eAAe;;;;;;EAAiE,CAAC;AAC9F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAS9D,eAAO,MAAM,6BAA6B,EAAE,MAAM,CACjD,eAAe,EACf,aAAa,CAAC,eAAe,CAAC,CAO9B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAW1F,CAAC;AAEF,eAAO,MAAM,8BAA8B,EAAE,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAU5F,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,qBAAqB,EAAE,CACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,KACnB,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;IACvC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,eAAe,GAAG,SAAS,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"action_event_types.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event_types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtF,eAAO,MAAM,cAAc;;;EAAkC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,eAAO,MAAM,eAAe;;;;;;EAAiE,CAAC;AAC9F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAS9D,eAAO,MAAM,6BAA6B,EAAE,MAAM,CACjD,eAAe,EACf,aAAa,CAAC,eAAe,CAAC,CAO9B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAW1F,CAAC;AAEF,eAAO,MAAM,8BAA8B,EAAE,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAU5F,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,qBAAqB,EAAE,CACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,KACnB,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;IACvC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,eAAe,GAAG,SAAS,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CAChC;AAED,yDAAyD;AACzD,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAIhC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,aAAa,sBAAsB,KAAG,SAAS,GAAG,IAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAKtC,OAAO,EAEN,KAAK,gBAAgB,EAErB,KAAK,kBAAkB,EACvB,MAAM,oBAAoB,CAAC;AAc5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,KAAK,EAAgC,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAGN,KAAK,UAAU,EACf,MAAM,wBAAwB,CAAC;AAEhC;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,kEAAkE;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,yDAAyD;IACzD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uEAAuE;IACvE,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC;;;;;OAKG;IACH,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB,sFAAsF;IACtF,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,uDAAuD;IACvD,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAE,eAAe,EAAE,cAAc,GAAG,IAAI,CAAA;KAAE,CAAC;CACpD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,gGAAgG;IAChG,EAAE,EAAE,EAAE,CAAC;IACP;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;OAGG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,uEAAuE;IACvE,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC9B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhG;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,OAAO,kBAAkB,EACzB,MAAM,iBAAiB,KACrB,OAAO,CAAC,mBAAmB,
|
|
1
|
+
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"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"}
|
|
@@ -64,7 +64,7 @@ import { resolve_issues_invalid_reason } from '../schema_meta.js';
|
|
|
64
64
|
import { to_input_params } from '../http/schema_helpers.js';
|
|
65
65
|
import { rate_limit_ip_key } from '../http/ip_canonical.js';
|
|
66
66
|
import { dispatch_with_post_commit_rollback } from '../http/pending_effects.js';
|
|
67
|
-
import { JSONRPC_VERSION } from '../http/jsonrpc.js';
|
|
67
|
+
import { JSONRPC_VERSION, is_jsonrpc_error_code } from '../http/jsonrpc.js';
|
|
68
68
|
import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, http_status_to_jsonrpc_error_code, JSONRPC_ERROR_CODES, dev_only } from '../http/jsonrpc_errors.js';
|
|
69
69
|
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_TOKEN_SCOPE_REQUIRED } from '../http/error_schemas.js';
|
|
70
70
|
import { is_public_auth, needs_actor, parse_acting } from '../http/auth_shape.js';
|
|
@@ -197,11 +197,14 @@ export const perform_action = async (input, deps) => {
|
|
|
197
197
|
return await dispatch_with_post_commit_rollback(post_commit_effects, () => use_transaction ? db.transaction((tx) => execute(tx)) : execute(db));
|
|
198
198
|
}
|
|
199
199
|
catch (err) {
|
|
200
|
-
// Duck-type check: Error
|
|
201
|
-
// Avoids cross-realm `instanceof` misses when consumers
|
|
202
|
-
// `ThrownJsonrpcError` (structurally identical, different
|
|
200
|
+
// Duck-type check: an Error whose `code` is a JSON-RPC error code signals
|
|
201
|
+
// a JSON-RPC error. Avoids cross-realm `instanceof` misses when consumers
|
|
202
|
+
// throw their own `ThrownJsonrpcError` (structurally identical, different
|
|
203
|
+
// class identity). The code must be a valid JSON-RPC one, not just a
|
|
204
|
+
// number — a `DOMException` (`code` 20, 23, ...) is an internal error,
|
|
205
|
+
// logged and masked like any other.
|
|
203
206
|
const error_like = err;
|
|
204
|
-
if (err instanceof Error &&
|
|
207
|
+
if (err instanceof Error && is_jsonrpc_error_code(error_like.code)) {
|
|
205
208
|
const code = error_like.code;
|
|
206
209
|
const status = jsonrpc_error_code_to_http_status(code);
|
|
207
210
|
const error = { code, message: err.message };
|
|
@@ -54,6 +54,12 @@
|
|
|
54
54
|
* admits a revoked credential; `docs/security.md` §Connection Admission says
|
|
55
55
|
* what that rests on.
|
|
56
56
|
*
|
|
57
|
+
* The acting actor the upgrade resolved is re-read beside the credential on
|
|
58
|
+
* every endpoint, gated or not: an actor soft-deleted under its live account
|
|
59
|
+
* keeps the account's sessions and tokens and its own grant rows, so neither
|
|
60
|
+
* the credential nor the role re-read would refuse it. A tombstoned acting
|
|
61
|
+
* actor is refused like a dead credential.
|
|
62
|
+
*
|
|
57
63
|
* An endpoint gated on `required_roles` re-reads them beside the credential:
|
|
58
64
|
* its upgrade passed the role gate before the socket registered, so a role
|
|
59
65
|
* revocation landing in between would close nothing (`close_sockets_for_role`
|
|
@@ -62,9 +68,9 @@
|
|
|
62
68
|
* admits on a global grant of any listed role, as the gate did.
|
|
63
69
|
*
|
|
64
70
|
* A refused upgrade is closed with `WS_CLOSE_SESSION_REVOKED` (4001, the
|
|
65
|
-
* reason a revoked socket gets) for a dead credential,
|
|
71
|
+
* reason a revoked socket gets) for a dead credential or a deleted acting actor,
|
|
66
72
|
* `WS_CLOSE_PERMISSION_REVOKED` (4006) for a lost role, or
|
|
67
|
-
* `WS_CLOSE_INTERNAL_ERROR` (1011) when
|
|
73
|
+
* `WS_CLOSE_INTERNAL_ERROR` (1011) when a re-read itself failed — fail
|
|
68
74
|
* closed, never admitted unchecked. The handshake has already answered `101`,
|
|
69
75
|
* so a refusal can only be a close frame.
|
|
70
76
|
*
|
|
@@ -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
|
|
1
|
+
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,6 +54,12 @@
|
|
|
54
54
|
* admits a revoked credential; `docs/security.md` §Connection Admission says
|
|
55
55
|
* what that rests on.
|
|
56
56
|
*
|
|
57
|
+
* The acting actor the upgrade resolved is re-read beside the credential on
|
|
58
|
+
* every endpoint, gated or not: an actor soft-deleted under its live account
|
|
59
|
+
* keeps the account's sessions and tokens and its own grant rows, so neither
|
|
60
|
+
* the credential nor the role re-read would refuse it. A tombstoned acting
|
|
61
|
+
* actor is refused like a dead credential.
|
|
62
|
+
*
|
|
57
63
|
* An endpoint gated on `required_roles` re-reads them beside the credential:
|
|
58
64
|
* its upgrade passed the role gate before the socket registered, so a role
|
|
59
65
|
* revocation landing in between would close nothing (`close_sockets_for_role`
|
|
@@ -62,9 +68,9 @@
|
|
|
62
68
|
* admits on a global grant of any listed role, as the gate did.
|
|
63
69
|
*
|
|
64
70
|
* A refused upgrade is closed with `WS_CLOSE_SESSION_REVOKED` (4001, the
|
|
65
|
-
* reason a revoked socket gets) for a dead credential,
|
|
71
|
+
* reason a revoked socket gets) for a dead credential or a deleted acting actor,
|
|
66
72
|
* `WS_CLOSE_PERMISSION_REVOKED` (4006) for a lost role, or
|
|
67
|
-
* `WS_CLOSE_INTERNAL_ERROR` (1011) when
|
|
73
|
+
* `WS_CLOSE_INTERNAL_ERROR` (1011) when a re-read itself failed — fail
|
|
68
74
|
* closed, never admitted unchecked. The handshake has already answered `101`,
|
|
69
75
|
* so a refusal can only be a close frame.
|
|
70
76
|
*
|
|
@@ -116,6 +122,7 @@ import { wait } from '@fuzdev/fuz_util/async.ts';
|
|
|
116
122
|
import { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
117
123
|
import { get_request_context, has_any_scoped_role, refresh_role_grants, require_request_context, token_scope_surface_denial } from '../auth/request_context.js';
|
|
118
124
|
import { get_resolved_auth, revalidate_resolved_auth } from '../auth/resolved_auth.js';
|
|
125
|
+
import { query_active_actors_by_account } from '../auth/account_queries.js';
|
|
119
126
|
import { schedule_credential_deadline } from '../auth/credential_deadline.js';
|
|
120
127
|
import { get_client_ip } from '../http/client_ip.js';
|
|
121
128
|
import { flush_pending_effects, flush_post_commit_effects } from '../http/pending_effects.js';
|
|
@@ -562,14 +569,22 @@ export const register_action_ws = (options) => {
|
|
|
562
569
|
}
|
|
563
570
|
if (!upgrade_preset) {
|
|
564
571
|
let credential_is_live;
|
|
572
|
+
let actor_is_active = true;
|
|
565
573
|
let role_is_held = true;
|
|
566
574
|
try {
|
|
567
575
|
// pool-level `db`: the statements must see every revocation
|
|
568
576
|
// committed before they start
|
|
569
577
|
credential_is_live = await revalidate_resolved_auth({ db }, resolved);
|
|
578
|
+
// The acting actor the upgrade resolved, on every endpoint: a
|
|
579
|
+
// soft-deleted actor keeps its account's credential and its
|
|
580
|
+
// grant rows, so neither re-read above or below refuses it.
|
|
581
|
+
const acting_actor = upgrade_context.actor;
|
|
582
|
+
if (credential_is_live && acting_actor) {
|
|
583
|
+
actor_is_active = (await query_active_actors_by_account({ db }, account_id)).some((a) => a.id === acting_actor.id);
|
|
584
|
+
}
|
|
570
585
|
// Global grants only, like the gate in front: a scoped grant
|
|
571
586
|
// never admitted this socket.
|
|
572
|
-
if (credential_is_live && required_roles.length > 0) {
|
|
587
|
+
if (credential_is_live && actor_is_active && required_roles.length > 0) {
|
|
573
588
|
role_is_held = has_any_scoped_role(await refresh_role_grants(upgrade_context, { db }), required_roles, null);
|
|
574
589
|
}
|
|
575
590
|
}
|
|
@@ -581,7 +596,7 @@ export const register_action_ws = (options) => {
|
|
|
581
596
|
log.info('ws upgrade admission: re-check ended by the shutdown', error);
|
|
582
597
|
}
|
|
583
598
|
else {
|
|
584
|
-
log.error('ws upgrade admission: credential or role re-check failed', error);
|
|
599
|
+
log.error('ws upgrade admission: credential, actor, or role re-check failed', error);
|
|
585
600
|
}
|
|
586
601
|
refuse_upgrade(ws, WS_CLOSE_INTERNAL_ERROR, 'internal error');
|
|
587
602
|
return null;
|
|
@@ -591,6 +606,11 @@ export const register_action_ws = (options) => {
|
|
|
591
606
|
refuse_upgrade(ws, WS_CLOSE_SESSION_REVOKED, WS_CLOSE_SESSION_REVOKED_REASON);
|
|
592
607
|
return null;
|
|
593
608
|
}
|
|
609
|
+
if (!actor_is_active) {
|
|
610
|
+
log.info('ws upgrade admission: acting actor deleted during the upgrade', connection_id, account_id);
|
|
611
|
+
refuse_upgrade(ws, WS_CLOSE_SESSION_REVOKED, WS_CLOSE_SESSION_REVOKED_REASON);
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
594
614
|
if (!role_is_held) {
|
|
595
615
|
log.info('ws upgrade admission: role revoked during the upgrade', connection_id, account_id);
|
|
596
616
|
refuse_upgrade(ws, WS_CLOSE_PERMISSION_REVOKED, WS_CLOSE_PERMISSION_REVOKED_REASON);
|
|
@@ -77,10 +77,12 @@ export type AuditEventHandler = (event: AuditLogEvent) => void;
|
|
|
77
77
|
* - `none` → nothing — every other event, and any event type fuz_app does not
|
|
78
78
|
* define
|
|
79
79
|
*
|
|
80
|
-
*
|
|
81
|
-
* identifiers (e.g. a `session_revoke` that the DB
|
|
82
|
-
* the submitted session_id), so reacting to
|
|
83
|
-
* user close another user's socket by guessing a
|
|
80
|
+
* Acts only on `outcome === 'success'` events — a failure carries
|
|
81
|
+
* attacker-controlled identifiers (e.g. a `session_revoke` that the DB
|
|
82
|
+
* rejected still records the submitted session_id), so reacting to it would
|
|
83
|
+
* let any authenticated user close another user's socket by guessing a
|
|
84
|
+
* session hash or token id. Matching the success rather than skipping the
|
|
85
|
+
* failure keeps an outcome outside the enum inert, as on the Rust spine.
|
|
84
86
|
*
|
|
85
87
|
* @param closer - what to close on — a `BackendWebsocketTransport`, or a
|
|
86
88
|
* `RealtimeCloser` to reach every transport from one listener
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,gBAAgB,EAAE,KAAK,MAAM,KAAG,iBA6D5E,CAAC"}
|
|
@@ -66,10 +66,12 @@ import { to_global_role_revocation, to_revocation_scope } from '../auth/audit_lo
|
|
|
66
66
|
* - `none` → nothing — every other event, and any event type fuz_app does not
|
|
67
67
|
* define
|
|
68
68
|
*
|
|
69
|
-
*
|
|
70
|
-
* identifiers (e.g. a `session_revoke` that the DB
|
|
71
|
-
* the submitted session_id), so reacting to
|
|
72
|
-
* user close another user's socket by guessing a
|
|
69
|
+
* Acts only on `outcome === 'success'` events — a failure carries
|
|
70
|
+
* attacker-controlled identifiers (e.g. a `session_revoke` that the DB
|
|
71
|
+
* rejected still records the submitted session_id), so reacting to it would
|
|
72
|
+
* let any authenticated user close another user's socket by guessing a
|
|
73
|
+
* session hash or token id. Matching the success rather than skipping the
|
|
74
|
+
* failure keeps an outcome outside the enum inert, as on the Rust spine.
|
|
73
75
|
*
|
|
74
76
|
* @param closer - what to close on — a `BackendWebsocketTransport`, or a
|
|
75
77
|
* `RealtimeCloser` to reach every transport from one listener
|
|
@@ -81,8 +83,9 @@ import { to_global_role_revocation, to_revocation_scope } from '../auth/audit_lo
|
|
|
81
83
|
*/
|
|
82
84
|
export const create_ws_auth_guard = (closer, log) => {
|
|
83
85
|
return (event) => {
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
+
// only a committed revocation closes — failed mutations carry
|
|
87
|
+
// attacker-controlled metadata, and anything else is not a revocation
|
|
88
|
+
if (event.outcome !== 'success')
|
|
86
89
|
return;
|
|
87
90
|
const scope = to_revocation_scope(event.event_type);
|
|
88
91
|
switch (scope) {
|
|
@@ -35,7 +35,7 @@ export interface WsEndpointSpec {
|
|
|
35
35
|
* `AppServerOptions.allowed_origins`, the list the HTTP routes check.
|
|
36
36
|
* Set it only to narrow one endpoint: the auth middleware's origin check
|
|
37
37
|
* runs on the upgrade too, so a request reaches the endpoint only when
|
|
38
|
-
* its origin passes both lists.
|
|
38
|
+
* its origin passes both lists. An empty list throws at assembly.
|
|
39
39
|
*/
|
|
40
40
|
allowed_origins?: ReadonlyArray<RegExp>;
|
|
41
41
|
/**
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -37,7 +37,7 @@ over the pool so rows persist when request transactions roll back. See root
|
|
|
37
37
|
- `auth/password_argon2.ts` — `hash_password`, `verify_password`, `verify_dummy`, `argon2_password_deps`.
|
|
38
38
|
- `auth/api_token.ts` — `API_TOKEN_PREFIX` (`secret_fuz_token_`), `hash_api_token`, `generate_api_token`, and the shape precheck `is_api_token_shaped` (`API_TOKEN_REGEX`: the prefix plus exactly `API_TOKEN_BODY_LENGTH` = 43 base64url chars).
|
|
39
39
|
- `auth/daemon_token.ts` — `DaemonToken`, `DAEMON_TOKEN_HEADER` (`X-Daemon-Token`), `generate_daemon_token`, `validate_daemon_token`, `DaemonTokenState`.
|
|
40
|
-
- `auth/bootstrap_account.ts` — `bootstrap_account` (one-shot, `bootstrap_lock`-protected).
|
|
40
|
+
- `auth/bootstrap_account.ts` — `bootstrap_account` (one-shot, `bootstrap_lock`-protected) + `read_bootstrap_token`, the read both it and `check_bootstrap_status` use: the secure read less one trailing LF, a token under `BOOTSTRAP_TOKEN_MIN_BYTES` (32) refused as unreadable with an error log.
|
|
41
41
|
|
|
42
42
|
Cross-cutting notes that don't live on any single symbol:
|
|
43
43
|
|
|
@@ -77,8 +77,8 @@ Convention — `*_schema.ts` is Zod-only; `*_ddl.ts` holds DDL strings.
|
|
|
77
77
|
All take `deps: QueryDeps = {db}` first; `query_validate_api_token` adds `log` and returns a `ValidatedApiToken` — the row with `scope` parsed, refusing (returning `undefined` for) a row whose stored scope is unreadable.
|
|
78
78
|
|
|
79
79
|
- `auth/account_queries.ts` — account CRUD, actor resolution, password update with verify-write race guard, paged `query_admin_account_list`.
|
|
80
|
-
- `auth/actor_lookup_queries.ts` — batched `actor` ⨝ `account` for the labels arc.
|
|
81
|
-
- `auth/actor_search_queries.ts` — case-insensitive prefix search on `actor.name`, scope-filtered when not admin.
|
|
80
|
+
- `auth/actor_lookup_queries.ts` — batched `actor` ⨝ `account` for the labels arc; active actors of active accounts only.
|
|
81
|
+
- `auth/actor_search_queries.ts` — case-insensitive prefix search on `actor.name`, scope-filtered when not admin; active actors of active accounts only.
|
|
82
82
|
- `auth/role_grant_queries.ts` — idempotent create, IDOR-guarded revoke (with in-tx supersede), scope-aware lookup, role/account predicates, `query_role_grant_revoke_for_scope` parent-scope cascade.
|
|
83
83
|
- `auth/role_grant_offer_queries.ts` — offer create/decline/retract/list/history/sweep, atomic `query_accept_offer` with sibling supersede; error classes `RoleGrantOfferSelfTargetError` / `_AlreadyTerminalError` / `_ExpiredError` / `_NotFoundError` / `_ActorAccountMismatchError` / `_ActorMismatchError`.
|
|
84
84
|
- `auth/session_queries.ts` — server-side sessions (blake3-hashed), `query_session_revoke_by_hash_unscoped` (logout only), `query_session_enforce_limit` (transaction-required; returns the evicted sessions' ids — their token hashes — so the caller closes their connections after the commit), `query_session_cleanup_expired` (returns the swept sessions' ids for the same close — `cleanup_expired_sessions` is its caller). No touch/renewal query — `expires_at` is an absolute cap set at mint (`AUTH_SESSION_LIFETIME_MS`).
|
|
@@ -147,12 +147,16 @@ the next event, and one removed mid-fan-out still hears the in-flight event
|
|
|
147
147
|
**failure** row is announced as soon as it is written — the attempt happened
|
|
148
148
|
whatever the transaction does; a **success** row is announced only after the
|
|
149
149
|
request's transaction commits (a thunk on `ctx.post_commit_effects` that awaits
|
|
150
|
-
the write
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
the write; after the handler returns, on a route without one) and never when
|
|
151
|
+
the handler throws. The listeners act on a success row — the revocation guards
|
|
152
|
+
close connections, the audit stream broadcasts it — so announcing one before
|
|
153
|
+
its commit would close while the revoked credential still reads as valid, and
|
|
154
|
+
a rolled-back revocation would close anyway. A route that runs without a
|
|
155
|
+
request transaction and opens its own around just its writes (`/login`,
|
|
156
|
+
`/password`) emits its success row after that transaction commits. The Rust
|
|
157
|
+
spine follows the same order: a success row emitted inside a request
|
|
158
|
+
transaction is written in it with its fan-out queued behind the commit, and
|
|
159
|
+
`/login` and `/password` emit after their own commit. `AuditEmitterContext` is therefore
|
|
156
160
|
`{pending_effects, post_commit_effects}` — `RouteContext` and `ActionContext`
|
|
157
161
|
both satisfy it.
|
|
158
162
|
|
|
@@ -247,8 +251,9 @@ consumer's `validate_data` pass-through callback on `CellActionDeps`.
|
|
|
247
251
|
- `auth/cell_action_specs.ts` — `CellVisibility` enum, `CellPath` brand,
|
|
248
252
|
`CELL_KIND_LENGTH_MAX` (64 — the wire `kind` on `cell_create` and the
|
|
249
253
|
`cell_list` filter; the tag is copied into the cell's audit rows),
|
|
250
|
-
`CellJson`,
|
|
251
|
-
|
|
254
|
+
`CellJson`, `CellPolicy` (a root's contribution policy, `z.looseObject`), the
|
|
255
|
+
generic verb specs (`cell_create / get / update / delete / list / clone /
|
|
256
|
+
moderate / policy_set`), error reasons, and `all_cell_action_specs`
|
|
252
257
|
(spreads in the grant / field / item / audit registries so codegen + UI
|
|
253
258
|
see one cell namespace). `CellJson` has no hub axis; `path` is global. The
|
|
254
259
|
directory-tree columns `parent_id` / `root_id` (containment, immutable in v1)
|
|
@@ -311,24 +316,39 @@ auth, ids)`: two queries for the whole id-set, the cells read through the
|
|
|
311
316
|
the mounted `authorize_create` for **every** cell it writes as a parentless
|
|
312
317
|
create — a denial is the same 403 `cell_create_forbidden`, so clone is no
|
|
313
318
|
side door past the creation policy. The optional
|
|
314
|
-
parent-aware `authorize_create` dep (`CellCreateAuthorize` —
|
|
315
|
-
`{kind, data, parent_id, root_id,
|
|
316
|
-
|
|
319
|
+
parent-aware `authorize_create` dep (`CellCreateAuthorize` — an object whose
|
|
320
|
+
`authorize` takes a wider input `{kind, data, parent_id, root_id, root_policy,
|
|
321
|
+
scope_id}` than `validate_data` and returns a `CellCreateVerdict`
|
|
322
|
+
`{allow:false} | {allow:true, moderation_required}`, and whose optional
|
|
323
|
+
`validate_policy` accepts or refuses a root policy write)
|
|
317
324
|
gates create by capability; it runs after the validator + the parent-resolve
|
|
318
325
|
(which **404**s `cell_not_found` on a missing or soft-deleted root as on a
|
|
319
326
|
missing parent, authorizer or not).
|
|
320
327
|
A **viewable**-parent / root-creation deny is **403** `cell_create_forbidden`
|
|
321
328
|
(the create-path **404** mask is reserved for a hidden parent); `Allow` folds
|
|
322
329
|
the moderation outcome (born `pending`+private vs `approved`+author-visibility).
|
|
323
|
-
The authorizer is **pure** — the handler reads the governing root
|
|
324
|
-
|
|
325
|
-
`
|
|
326
|
-
single-connection deadlock of the in-memory drivers). `None`/omitted = open
|
|
327
|
-
mirrors `validate_data`)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
The authorizer is **pure** — the handler reads the governing root in-tx and
|
|
331
|
+
hands its `policy` column over as `root_policy`, so a directory policy
|
|
332
|
+
resolves `root_policy[kind]` without its own DB read (which also dodges the
|
|
333
|
+
single-connection deadlock of the in-memory drivers). `None`/omitted = open
|
|
334
|
+
create (the default; mirrors `validate_data`), and every non-null policy write
|
|
335
|
+
refused `cell_policy_unsupported`. A root's `policy` (a JSON object, roots only
|
|
336
|
+
by table CHECK, opaque to the spine, readable on `CellJson`) is set at a
|
|
337
|
+
root's `cell_create` and changed only by `cell_policy_set` — the 404 mask, then
|
|
338
|
+
manage-tier (403 `cell_policy_manage_only`, an editor grant included), then
|
|
339
|
+
root-only (400 `cell_policy_root_only`), then the authorizer's
|
|
340
|
+
`validate_policy`; audited with the new and the previous policy. `cell_update`
|
|
341
|
+
never writes it (an editor can rewrite `data`, so a policy read from `data`
|
|
342
|
+
would be the editor's to loosen), and a clone is born without one.
|
|
343
|
+
`cell_moderate` is the `pending → approved | rejected` transition (plus
|
|
344
|
+
`approved → rejected`, the takedown), gated on
|
|
345
|
+
`can_manage_cell` of the **governing root** (admin / root owner — the author
|
|
346
|
+
lands a 403, the anti-self-approval guard; a root owner reaches the check
|
|
347
|
+
through the steward arm); approve flips
|
|
348
|
+
`moderation → approved` + `visibility → public`, reject sets `rejected`
|
|
349
|
+
(visibility untouched). The write carries the from-state: approve moves only
|
|
350
|
+
a pending contribution, reject a pending or approved one (the takedown); any
|
|
351
|
+
other transition answers 400 `cell_not_pending`.
|
|
332
352
|
- `auth/cell_ref_admission.ts` — `require_new_refs_held`, the
|
|
333
353
|
possession gate `cell_create` / `cell_update` / `cell_clone` run last before
|
|
334
354
|
their write: a non-admin write introducing a fact ref (beyond the cell's
|
|
@@ -340,7 +360,10 @@ auth, ids)`: two queries for the whole id-set, the cells read through the
|
|
|
340
360
|
- `auth/cell_grant_actions.ts` / `cell_field_actions.ts` /
|
|
341
361
|
`cell_item_actions.ts` / `cell_audit_actions.ts` — the relation + ACL +
|
|
342
362
|
audit handlers, exporting `to_grant_json` / `to_field_json` /
|
|
343
|
-
`to_item_json` reused by the `cell_get` bundle.
|
|
363
|
+
`to_item_json` reused by the `cell_get` bundle. `cell_grant_create` takes an
|
|
364
|
+
optional `authorize_grant_create` (`CellGrantCreateAuthorize`), asked of a
|
|
365
|
+
manager after the spine's gates and before the write (a level change too);
|
|
366
|
+
`false` is 403 `cell_grant_not_authorized`, nothing written.
|
|
344
367
|
- `auth/all_cell_actions.ts` — `create_all_cell_actions(deps, {roles})`, the
|
|
345
368
|
handler-side twin of `all_cell_action_specs` and sibling of
|
|
346
369
|
`create_standard_rpc_actions`: bundles all five cell factories (CRUD + grant
|
|
@@ -357,8 +380,8 @@ auth, ids)`: two queries for the whole id-set, the cells read through the
|
|
|
357
380
|
Consumers spread it into `extra_events` to register the whole layer in
|
|
358
381
|
one call (aggregator by design, not a compat shim).
|
|
359
382
|
|
|
360
|
-
The cell event types (`cell_create` / `_update` / `_delete` / `_clone
|
|
361
|
-
`cell_grant_create` / `_revoke`, `cell_field_set` / `_delete`,
|
|
383
|
+
The cell event types (`cell_create` / `_update` / `_delete` / `_clone` /
|
|
384
|
+
`_moderate`, `cell_policy_set`, `cell_grant_create` / `_revoke`, `cell_field_set` / `_delete`,
|
|
362
385
|
`cell_item_insert` / `_move` / `_delete`) are consumer-registered extras,
|
|
363
386
|
not built-ins — see §Audit event extensibility.
|
|
364
387
|
|
|
@@ -642,8 +665,8 @@ omits `confirm: true`, `ERROR_CANNOT_DELETE_KEEPER` (403) when `account_delete`
|
|
|
642
665
|
`account_purge` targets a keeper-role account (the keeper account is never
|
|
643
666
|
API-removable — auth + daemon-token both pivot on it),
|
|
644
667
|
`ERROR_CANNOT_DELETE_LAST_ADMIN` (403) when `account_delete` / `account_purge`
|
|
645
|
-
targets the sole remaining active admin (
|
|
646
|
-
|
|
668
|
+
targets the sole remaining active admin (soft-deleted admins don't count; the
|
|
669
|
+
count locks its rows, so concurrent removals can't both pass), `ERROR_INVITE_ACCOUNT_EXISTS_USERNAME` /
|
|
647
670
|
`_EMAIL` / `ERROR_INVITE_DUPLICATE` on invite create, `ERROR_INVITE_NOT_FOUND`
|
|
648
671
|
on invite delete. `invite_create` empty input is rejected at the schema via
|
|
649
672
|
`.refine()` and surfaces as `invalid_params` with `error.data.issues`.
|
|
@@ -694,6 +717,15 @@ event additionally records `credential_type` in metadata (defense in depth).
|
|
|
694
717
|
- `role_grant_offer_list_action_spec` — input `{account_id?}`; output `{offers}`.
|
|
695
718
|
- `role_grant_offer_history_action_spec` — input `{account_id?, limit?, offset?}`; output `{offers}`.
|
|
696
719
|
- `role_grant_revoke_action_spec` — input `{actor_id, role_grant_id, reason?}`; output `{ok, revoked}`.
|
|
720
|
+
A global `admin` grant is guarded: the keeper account's is refused
|
|
721
|
+
(`ERROR_CANNOT_REVOKE_KEEPER_ADMIN`, 403) and so is one whose revoke would
|
|
722
|
+
leave no active admin account (`ERROR_CANNOT_REVOKE_LAST_ADMIN`, 403, under
|
|
723
|
+
the locked count `query_count_active_accounts_with_global_role_for_update`,
|
|
724
|
+
which leaves the revoked grant out). Besides the
|
|
725
|
+
revokee's incoming sibling offers, the revoke supersedes every pending offer
|
|
726
|
+
the revokee's actor made (`query_role_grant_offer_supersede_outgoing`) —
|
|
727
|
+
`account_delete` / `account_purge` do the same for every actor of the account
|
|
728
|
+
(`reason: 'account_deleted'`, audited, no notification).
|
|
697
729
|
- `role_grant_assign_action_spec` — global-admin-only immediate conferral (no
|
|
698
730
|
consent step); input `{to_account_id, to_actor_id?, role, scope_kind?, scope_id?}`
|
|
699
731
|
(the pair as on offer-create); output `{ok, role_grant_id}`. The handler:
|
|
@@ -702,6 +734,9 @@ event additionally records `credential_type` in metadata (defense in depth).
|
|
|
702
734
|
`admin` (`invalid_params` + `ERROR_ROLE_GRANT_BUILTIN_SCOPED`; a scoped
|
|
703
735
|
`keeper` stays 403 `ERROR_ROLE_NOT_WEB_GRANTABLE`, since `keeper` is never
|
|
704
736
|
web-grantable);
|
|
737
|
+
- runs the consumer's `RoleGrantOfferCreateAuthorize` callback (the offer
|
|
738
|
+
policy binds the consent-free path too — a refusal is 403
|
|
739
|
+
`ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED`, failure-audited);
|
|
705
740
|
- resolves the target actor (named `to_actor_id` ∈ the account's active
|
|
706
741
|
actors, else its sole active actor — multi-actor → `invalid_params`,
|
|
707
742
|
actorless → 404);
|
|
@@ -711,6 +746,7 @@ event additionally records `credential_type` in metadata (defense in depth).
|
|
|
711
746
|
|
|
712
747
|
No WS notification in v1. Reasons: `ERROR_INSUFFICIENT_PERMISSIONS`,
|
|
713
748
|
`ERROR_ROLE_NOT_WEB_GRANTABLE`, `ERROR_ROLE_GRANT_BUILTIN_SCOPED`,
|
|
749
|
+
`ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED`,
|
|
714
750
|
`ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH`.
|
|
715
751
|
|
|
716
752
|
Every input carries `acting?: ActingActor` (registry-time invariant 2).
|
|
@@ -726,7 +762,9 @@ holder-propagation (holding the offered role confers no power to offer it). The
|
|
|
726
762
|
global check (`has_scoped_role(_, ROLE_ADMIN, null)`) stops a scoped `admin`
|
|
727
763
|
escalating into global authority over the offer surface. A consumer needing
|
|
728
764
|
scope-aware delegation (e.g. a classroom teacher offering within their own
|
|
729
|
-
scope) supplies a custom `authorize` callback.
|
|
765
|
+
scope) supplies a custom `authorize` callback. The callback receives
|
|
766
|
+
`{to_account_id, role, scope_kind, scope_id}` and also gates
|
|
767
|
+
`role_grant_assign`.
|
|
730
768
|
|
|
731
769
|
**Builtin roles (`admin` / `keeper`) are global-only**, decided by the spine
|
|
732
770
|
ahead of any callback. Offer-create refuses a scoped `admin`
|
|
@@ -778,7 +816,7 @@ Post-commit via `emit_after_commit` (see `http/CLAUDE.md` §Pending Effects):
|
|
|
778
816
|
- Retract — `role_grant_offer_retracted` → recipient.
|
|
779
817
|
- Accept — `role_grant_offer_accepted` → grantor + `_supersede` per sibling.
|
|
780
818
|
- Decline — `role_grant_offer_declined` → grantor.
|
|
781
|
-
- Revoke — `role_grant_revoke` → revokee + `_supersede` per superseded
|
|
819
|
+
- Revoke — `role_grant_revoke` → revokee + `_supersede` per superseded offer (the revokee's incoming siblings and its own outgoing offers, each to its grantor).
|
|
782
820
|
|
|
783
821
|
Spec module is `auth/role_grant_offer_notifications.ts` — six
|
|
784
822
|
`RemoteNotificationActionSpec`s with Zod params schemas and notification
|
|
@@ -832,7 +870,7 @@ required `connection_closer`. Each revoking handler queues its close
|
|
|
832
870
|
after the handler's transaction commits and is dropped if it throws;
|
|
833
871
|
`account_token_create` queues a token close for each token its cap evicts.
|
|
834
872
|
Failure outcomes (`revoked: false`) queue no close — mirrors the listeners'
|
|
835
|
-
`outcome === '
|
|
873
|
+
`outcome === 'success'` guard so attacker-guessable ids can't target
|
|
836
874
|
arbitrary connections.
|
|
837
875
|
|
|
838
876
|
### Standard RPC bundle
|
|
@@ -37,8 +37,9 @@ export interface ActorLookupRow {
|
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Resolve a batch of actor ids to `(id, username, display_name)`. Empty
|
|
40
|
-
* input fast-paths to `[]`. Hard-deleted actors
|
|
41
|
-
*
|
|
40
|
+
* input fast-paths to `[]`. Hard-deleted and soft-deleted actors — and the
|
|
41
|
+
* actors of a soft-deleted account — drop out of the result silently,
|
|
42
|
+
* indistinguishable from never-existed.
|
|
42
43
|
*/
|
|
43
44
|
export declare const query_actors_by_ids: (deps: QueryDeps, ids: ReadonlyArray<Uuid>) => Promise<Array<ActorLookupRow>>;
|
|
44
45
|
//# sourceMappingURL=actor_lookup_queries.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actor_lookup_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/actor_lookup_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED
|
|
1
|
+
{"version":3,"file":"actor_lookup_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/actor_lookup_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,KAAK,aAAa,CAAC,IAAI,CAAC,KACtB,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAW/B,CAAC"}
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
*/
|
|
30
30
|
/**
|
|
31
31
|
* Resolve a batch of actor ids to `(id, username, display_name)`. Empty
|
|
32
|
-
* input fast-paths to `[]`. Hard-deleted actors
|
|
33
|
-
*
|
|
32
|
+
* input fast-paths to `[]`. Hard-deleted and soft-deleted actors — and the
|
|
33
|
+
* actors of a soft-deleted account — drop out of the result silently,
|
|
34
|
+
* indistinguishable from never-existed.
|
|
34
35
|
*/
|
|
35
36
|
export const query_actors_by_ids = async (deps, ids) => {
|
|
36
37
|
if (ids.length === 0)
|
|
@@ -38,5 +39,7 @@ export const query_actors_by_ids = async (deps, ids) => {
|
|
|
38
39
|
return deps.db.query(`SELECT act.id, a.username, act.name AS display_name
|
|
39
40
|
FROM actor act
|
|
40
41
|
JOIN account a ON a.id = act.account_id
|
|
41
|
-
WHERE act.id = ANY($1)
|
|
42
|
+
WHERE act.id = ANY($1)
|
|
43
|
+
AND act.deleted_at IS NULL
|
|
44
|
+
AND a.deleted_at IS NULL`, [ids]);
|
|
42
45
|
};
|