@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
|
@@ -98,7 +98,7 @@ export declare const account_verify_action_spec: {
|
|
|
98
98
|
input: z.ZodVoid;
|
|
99
99
|
output: z.ZodObject<{
|
|
100
100
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
101
|
-
username: z.ZodString
|
|
101
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
102
102
|
email: z.ZodNullable<z.ZodEmail>;
|
|
103
103
|
email_verified: z.ZodBoolean;
|
|
104
104
|
created_at: z.ZodString;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* @module
|
|
24
24
|
*/
|
|
25
25
|
import { type RpcAction } from '../actions/action_rpc.js';
|
|
26
|
+
import type { ConnectionCloser } from '../actions/connection_closer.js';
|
|
26
27
|
import type { RouteFactoryDeps } from './deps.js';
|
|
27
28
|
/** Options for `create_account_actions`. */
|
|
28
29
|
export interface AccountActionOptions {
|
|
@@ -33,6 +34,18 @@ export interface AccountActionOptions {
|
|
|
33
34
|
* `DEFAULT_MAX_TOKENS`; pass `null` to disable the cap.
|
|
34
35
|
*/
|
|
35
36
|
max_tokens?: number | null;
|
|
37
|
+
/**
|
|
38
|
+
* Live-connection closer — when set, `account_session_revoke` /
|
|
39
|
+
* `_session_revoke_all` / `account_token_revoke` handlers eagerly close
|
|
40
|
+
* affected WebSocket sockets BEFORE emitting the corresponding audit
|
|
41
|
+
* event. Closes the audit-failure-leaks-WS surface: the listener-based
|
|
42
|
+
* close (`transports_ws_auth_guard`) only fires after the audit INSERT
|
|
43
|
+
* succeeds, so a DB error would leave live sockets stale. `BackendWebsocketTransport`
|
|
44
|
+
* satisfies this interface structurally; consumers pass their transport
|
|
45
|
+
* instance directly. When absent, only the listener-based close runs.
|
|
46
|
+
* Mirrors `zzz_server`'s handler-side `close_sockets_for_*` calls.
|
|
47
|
+
*/
|
|
48
|
+
connection_closer?: ConnectionCloser | null;
|
|
36
49
|
}
|
|
37
50
|
/**
|
|
38
51
|
* Create the self-service account RPC actions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAqC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"account_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAqC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAC5F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AAetE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAwBhD,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,EAC7C,UAAS,oBAAyB,KAChC,KAAK,CAAC,SAAS,CAyIjB,CAAC"}
|
|
@@ -39,7 +39,7 @@ import { account_verify_action_spec, account_session_list_action_spec, account_s
|
|
|
39
39
|
* @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
|
|
40
40
|
*/
|
|
41
41
|
export const create_account_actions = (deps, options = {}) => {
|
|
42
|
-
const { max_tokens = DEFAULT_MAX_TOKENS } = options;
|
|
42
|
+
const { max_tokens = DEFAULT_MAX_TOKENS, connection_closer = null } = options;
|
|
43
43
|
const verify_handler = (_input, ctx) => {
|
|
44
44
|
return to_session_account(ctx.auth.account);
|
|
45
45
|
};
|
|
@@ -49,6 +49,21 @@ export const create_account_actions = (deps, options = {}) => {
|
|
|
49
49
|
};
|
|
50
50
|
const session_revoke_handler = async (input, ctx) => {
|
|
51
51
|
const revoked = await query_session_revoke_for_account(ctx, input.session_id, ctx.auth.account.id);
|
|
52
|
+
// Handler-side belt+suspenders: close the live WS socket bound to this
|
|
53
|
+
// session BEFORE the audit emit, so revocation lands even if the audit
|
|
54
|
+
// INSERT fails. The real ordering invariant is "before the transaction
|
|
55
|
+
// commits": this handler runs inside the dispatcher's transaction
|
|
56
|
+
// (side_effects: true), so any throw between this close and the return
|
|
57
|
+
// would roll back the DB revoke while leaving the socket severed. That
|
|
58
|
+
// is benign — the session is still valid, the client reconnects — but
|
|
59
|
+
// don't introduce a throw here without acknowledging the trade.
|
|
60
|
+
// Only fire on success — failure carries an attacker-guessable
|
|
61
|
+
// session_id and the listener-based close already ignores failure
|
|
62
|
+
// outcomes for the same reason. Idempotent — the audit listener runs a
|
|
63
|
+
// second close on success but matches no sockets the second time.
|
|
64
|
+
if (revoked && connection_closer) {
|
|
65
|
+
connection_closer.close_sockets_for_session(input.session_id);
|
|
66
|
+
}
|
|
52
67
|
deps.audit.emit(ctx, {
|
|
53
68
|
event_type: 'session_revoke',
|
|
54
69
|
outcome: revoked ? 'success' : 'failure',
|
|
@@ -61,6 +76,17 @@ export const create_account_actions = (deps, options = {}) => {
|
|
|
61
76
|
};
|
|
62
77
|
const session_revoke_all_handler = async (_input, ctx) => {
|
|
63
78
|
const count = await query_session_revoke_all_for_account(ctx, ctx.auth.account.id);
|
|
79
|
+
// Handler-side belt+suspenders — see session_revoke_handler comment.
|
|
80
|
+
// Close fires regardless of `count` (today `count >= 1` always — the
|
|
81
|
+
// caller is using the session they're revoking; future bearer / daemon-
|
|
82
|
+
// token-credentialed callers may hit `count: 0`). Symmetric with the
|
|
83
|
+
// admin revoke-all handlers in `admin_actions.ts`, where `count: 0` is
|
|
84
|
+
// a real outcome (target account had no live sessions/tokens) and the
|
|
85
|
+
// eager close still fires to scrub sockets that the audit listener
|
|
86
|
+
// would otherwise miss when the INSERT fails. Idempotent at all counts.
|
|
87
|
+
if (connection_closer) {
|
|
88
|
+
connection_closer.close_sockets_for_account(ctx.auth.account.id);
|
|
89
|
+
}
|
|
64
90
|
deps.audit.emit(ctx, {
|
|
65
91
|
event_type: 'session_revoke_all',
|
|
66
92
|
account_id: ctx.auth.account.id,
|
|
@@ -93,6 +119,10 @@ export const create_account_actions = (deps, options = {}) => {
|
|
|
93
119
|
};
|
|
94
120
|
const token_revoke_handler = async (input, ctx) => {
|
|
95
121
|
const revoked = await query_revoke_api_token_for_account(ctx, input.token_id, ctx.auth.account.id);
|
|
122
|
+
// Handler-side belt+suspenders — see session_revoke_handler comment.
|
|
123
|
+
if (revoked && connection_closer) {
|
|
124
|
+
connection_closer.close_sockets_for_token(input.token_id);
|
|
125
|
+
}
|
|
96
126
|
deps.audit.emit(ctx, {
|
|
97
127
|
event_type: 'token_revoke',
|
|
98
128
|
outcome: revoked ? 'success' : 'failure',
|
|
@@ -26,6 +26,7 @@ import type { SessionOptions } from './session_cookie.js';
|
|
|
26
26
|
import { type RouteSpec } from '../http/route_spec.js';
|
|
27
27
|
import { type RateLimiter } from '../rate_limiter.js';
|
|
28
28
|
import type { RouteFactoryDeps } from './deps.js';
|
|
29
|
+
import type { ConnectionCloser } from '../actions/connection_closer.js';
|
|
29
30
|
/** Input for `GET /api/account/status`. No parameters — caller is the subject. */
|
|
30
31
|
export declare const AccountStatusInput: z.ZodNull;
|
|
31
32
|
export type AccountStatusInput = z.infer<typeof AccountStatusInput>;
|
|
@@ -41,7 +42,7 @@ export type AccountStatusInput = z.infer<typeof AccountStatusInput>;
|
|
|
41
42
|
export declare const AccountStatusOutput: z.ZodObject<{
|
|
42
43
|
account: z.ZodObject<{
|
|
43
44
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
44
|
-
username: z.ZodString
|
|
45
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
45
46
|
email: z.ZodNullable<z.ZodEmail>;
|
|
46
47
|
email_verified: z.ZodBoolean;
|
|
47
48
|
created_at: z.ZodString;
|
|
@@ -143,10 +144,19 @@ export interface AccountRouteOptions extends AuthSessionRouteOptions {
|
|
|
143
144
|
* jitter while keeping the floor. Default `DEFAULT_LOGIN_FAIL_JITTER_MS`.
|
|
144
145
|
*/
|
|
145
146
|
login_fail_jitter_ms?: number;
|
|
147
|
+
/**
|
|
148
|
+
* Live-connection closer — when set, the `logout` and `password` handlers
|
|
149
|
+
* eagerly close affected WebSocket sockets for the account BEFORE
|
|
150
|
+
* emitting the corresponding audit event. Mirrors the self-service
|
|
151
|
+
* action surface (see `AccountActionOptions.connection_closer`). When
|
|
152
|
+
* absent, only the listener-based close (`transports_ws_auth_guard` on
|
|
153
|
+
* `audit.on_event_chain`) runs.
|
|
154
|
+
*/
|
|
155
|
+
connection_closer?: ConnectionCloser | null;
|
|
146
156
|
}
|
|
147
157
|
/** Input for `POST /login`. Accepts a username or email in the `username` field. */
|
|
148
158
|
export declare const LoginInput: z.ZodObject<{
|
|
149
|
-
username: z.ZodString
|
|
159
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
150
160
|
password: z.ZodString;
|
|
151
161
|
}, z.core.$strict>;
|
|
152
162
|
export type LoginInput = z.infer<typeof LoginInput>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AA2BxD,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAElF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -217,7 +217,7 @@ export const PasswordChangeOutput = z.strictObject({
|
|
|
217
217
|
*/
|
|
218
218
|
export const create_account_route_specs = (deps, options) => {
|
|
219
219
|
const { keyring, password } = deps;
|
|
220
|
-
const { session_options, ip_rate_limiter, login_account_rate_limiter, max_sessions = DEFAULT_MAX_SESSIONS, login_fail_floor_ms = DEFAULT_LOGIN_FAIL_FLOOR_MS, login_fail_jitter_ms = DEFAULT_LOGIN_FAIL_JITTER_MS, } = options;
|
|
220
|
+
const { session_options, ip_rate_limiter, login_account_rate_limiter, max_sessions = DEFAULT_MAX_SESSIONS, login_fail_floor_ms = DEFAULT_LOGIN_FAIL_FLOOR_MS, login_fail_jitter_ms = DEFAULT_LOGIN_FAIL_JITTER_MS, connection_closer = null, } = options;
|
|
221
221
|
return [
|
|
222
222
|
{
|
|
223
223
|
method: 'GET',
|
|
@@ -254,8 +254,12 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
254
254
|
return rate_limit_exceeded_response(c, check.retry_after);
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
// `UsernameProvided` canonicalizes via `.trim().toLowerCase()` at
|
|
258
|
+
// parse time — the validated value lands canonical in
|
|
259
|
+
// `c.var.validated_input`, so the per-account rate-limit key,
|
|
260
|
+
// DB lookup, and audit metadata see one form. See
|
|
261
|
+
// `primitive_schemas.ts` for the schema-layer canonicalization.
|
|
262
|
+
const { username, password: pw } = get_route_input(c);
|
|
259
263
|
// DB lookup first so we can key the per-account rate limit by a canonical value
|
|
260
264
|
// (account.id) rather than the submitted identifier. Otherwise an attacker could
|
|
261
265
|
// alternate between username and email to double the per-account bucket.
|
|
@@ -339,6 +343,21 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
339
343
|
if (session_token) {
|
|
340
344
|
const token_hash = hash_session_token(session_token);
|
|
341
345
|
await query_session_revoke_by_hash_unscoped(route, token_hash);
|
|
346
|
+
// Handler-side belt+suspenders: close the live WS bound to
|
|
347
|
+
// this session BEFORE the audit emit so revocation lands
|
|
348
|
+
// even if the audit INSERT fails. Same transaction-commit
|
|
349
|
+
// trade as `password` / RPC `session_revoke` below — a
|
|
350
|
+
// throw between this close and the response rolls back the
|
|
351
|
+
// DB revoke while leaving the socket severed; benign
|
|
352
|
+
// (client reconnects, session still valid) but don't
|
|
353
|
+
// introduce a throw here without acknowledging the trade.
|
|
354
|
+
// The audit listener (`create_ws_logout_closer`) runs an
|
|
355
|
+
// account-wide close on the logout event afterward —
|
|
356
|
+
// broader than this targeted close, but both layers are
|
|
357
|
+
// idempotent. Mirrors `zzz_server::account::logout_inner`.
|
|
358
|
+
if (connection_closer) {
|
|
359
|
+
connection_closer.close_sockets_for_session(token_hash);
|
|
360
|
+
}
|
|
342
361
|
}
|
|
343
362
|
clear_session_cookie(c, session_options);
|
|
344
363
|
// Account-grain operation — no `actor_id` (which actor was
|
|
@@ -401,11 +420,10 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
401
420
|
});
|
|
402
421
|
return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
|
|
403
422
|
}
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
login_account_rate_limiter.reset(ctx.account.id);
|
|
423
|
+
// Verify succeeded — do the throw-y operations FIRST so a fault
|
|
424
|
+
// (Argon2 OOM, native binding error, DB outage on the UPDATE)
|
|
425
|
+
// can't wipe the rate-limit history of a caller observing 500s.
|
|
426
|
+
// Resets happen below, after both calls have settled.
|
|
409
427
|
const new_hash = await password.hash_password(new_password);
|
|
410
428
|
// Conditional UPDATE keyed on the verified hash: closes the
|
|
411
429
|
// verify-write race with a concurrent password change that
|
|
@@ -413,6 +431,19 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
413
431
|
// operation — `updated_by` stays null (the per-request actor is
|
|
414
432
|
// incidental; password is account-level state).
|
|
415
433
|
const updated = await query_update_account_password(route, ctx.account.id, new_hash, null, ctx.account.password_hash);
|
|
434
|
+
// Verify-success contract — the caller proved knowledge, so wipe
|
|
435
|
+
// their failure history. The race-loser branch below re-records
|
|
436
|
+
// one on top of the wiped slate so net cost stays 1 (mirrors the
|
|
437
|
+
// verify-fail branch above's `record`-from-prior+1 outcome when
|
|
438
|
+
// prior was 0; for prior > 0 the race-loser pays exactly 1,
|
|
439
|
+
// matching the OLD pre-S1 behavior). Deferring from "after
|
|
440
|
+
// verify" to "after UPDATE settled" is what closes the S1
|
|
441
|
+
// bypass — a throw between reset and the UPDATE could have
|
|
442
|
+
// wiped an attacker's budget.
|
|
443
|
+
if (ip_rate_limiter && ip)
|
|
444
|
+
ip_rate_limiter.reset(ip);
|
|
445
|
+
if (login_account_rate_limiter)
|
|
446
|
+
login_account_rate_limiter.reset(ctx.account.id);
|
|
416
447
|
if (!updated) {
|
|
417
448
|
// A concurrent password change committed first — our
|
|
418
449
|
// `current_password` was correct at read-time but the row's
|
|
@@ -437,6 +468,22 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
437
468
|
// revoke all sessions and API tokens (force re-auth everywhere)
|
|
438
469
|
const sessions_revoked = await query_session_revoke_all_for_account(route, ctx.account.id);
|
|
439
470
|
const tokens_revoked = await query_revoke_all_api_tokens_for_account(route, ctx.account.id);
|
|
471
|
+
// Handler-side belt+suspenders — close every live WS socket on
|
|
472
|
+
// this account BEFORE the audit emit so the revoke-all cascade
|
|
473
|
+
// lands even if the audit INSERT fails. The real ordering
|
|
474
|
+
// invariant is "before the transaction commits": this route
|
|
475
|
+
// runs with the default `transaction: true`, so a throw between
|
|
476
|
+
// this close and the response would roll back the password
|
|
477
|
+
// update + session/token revokes while leaving sockets severed.
|
|
478
|
+
// Benign — affected clients reconnect with their still-valid
|
|
479
|
+
// session — but don't introduce a throw here without
|
|
480
|
+
// acknowledging the trade. Listener-based close
|
|
481
|
+
// (`transports_ws_auth_guard` on the `password_change` event)
|
|
482
|
+
// runs the same close afterward; idempotent on the second pass.
|
|
483
|
+
// Mirrors `zzz_server::account::password_inner`.
|
|
484
|
+
if (connection_closer) {
|
|
485
|
+
connection_closer.close_sockets_for_account(ctx.account.id);
|
|
486
|
+
}
|
|
440
487
|
clear_session_cookie(c, session_options);
|
|
441
488
|
// Account-grain operation — no `actor_id`. The password is
|
|
442
489
|
// account-level state; which per-request actor was resolved
|
|
@@ -74,7 +74,7 @@ export interface RoleGrant {
|
|
|
74
74
|
expires_at: string | null;
|
|
75
75
|
revoked_at: string | null;
|
|
76
76
|
revoked_by: Uuid | null;
|
|
77
|
-
/** Optional free-form reason attached on revoke (
|
|
77
|
+
/** Optional free-form reason attached on revoke (rides on the `role_grant_revoke` WS notification to the revokee). */
|
|
78
78
|
revoked_reason: string | null;
|
|
79
79
|
granted_by: Uuid | null;
|
|
80
80
|
/** Offer that produced this role_grant (set by `query_accept_offer`). `null` for direct grants. */
|
|
@@ -106,7 +106,7 @@ export interface ApiToken {
|
|
|
106
106
|
/** Zod schema for `SessionAccount` — account without sensitive fields. */
|
|
107
107
|
export declare const SessionAccountJson: z.ZodObject<{
|
|
108
108
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
109
|
-
username: z.ZodString
|
|
109
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
110
110
|
email: z.ZodNullable<z.ZodEmail>;
|
|
111
111
|
email_verified: z.ZodBoolean;
|
|
112
112
|
created_at: z.ZodString;
|
|
@@ -152,7 +152,7 @@ export type ActorSummaryJson = z.infer<typeof ActorSummaryJson>;
|
|
|
152
152
|
/** Zod schema for admin-facing account data — extends `SessionAccountJson` with audit fields. */
|
|
153
153
|
export declare const AdminAccountJson: z.ZodObject<{
|
|
154
154
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
155
|
-
username: z.ZodString
|
|
155
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
156
156
|
email: z.ZodNullable<z.ZodEmail>;
|
|
157
157
|
email_verified: z.ZodBoolean;
|
|
158
158
|
created_at: z.ZodString;
|
|
@@ -188,7 +188,7 @@ export type PendingOfferSummaryJson = z.infer<typeof PendingOfferSummaryJson>;
|
|
|
188
188
|
export declare const AdminAccountEntryJson: z.ZodObject<{
|
|
189
189
|
account: z.ZodObject<{
|
|
190
190
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
191
|
-
username: z.ZodString
|
|
191
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
192
192
|
email: z.ZodNullable<z.ZodEmail>;
|
|
193
193
|
email_verified: z.ZodBoolean;
|
|
194
194
|
created_at: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE5C,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,yBAAyB,CAAC;AAIxD,mEAAmE;AACnE,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,4FAA4F;AAC5F,MAAM,WAAW,KAAK;IACrB,EAAE,EAAE,IAAI,CAAC;IACT,UAAU,EAAE,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,MAAM,CAAC;AAExD,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACzB,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,IAAI,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iGAAiG;IACjG,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,
|
|
1
|
+
{"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,7 +35,7 @@ export declare const AdminAccountListOutput: z.ZodObject<{
|
|
|
35
35
|
accounts: z.ZodArray<z.ZodObject<{
|
|
36
36
|
account: z.ZodObject<{
|
|
37
37
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
38
|
-
username: z.ZodString
|
|
38
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
39
39
|
email: z.ZodNullable<z.ZodEmail>;
|
|
40
40
|
email_verified: z.ZodBoolean;
|
|
41
41
|
created_at: z.ZodString;
|
|
@@ -183,7 +183,7 @@ export type AuditLogRoleGrantHistoryOutput = z.infer<typeof AuditLogRoleGrantHis
|
|
|
183
183
|
/** Input for `invite_create`. At least one of `email` / `username` must be provided. */
|
|
184
184
|
export declare const InviteCreateInput: z.ZodObject<{
|
|
185
185
|
email: z.ZodOptional<z.ZodNullable<z.ZodEmail>>;
|
|
186
|
-
username: z.ZodOptional<z.ZodNullable<z.ZodString
|
|
186
|
+
username: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
187
187
|
acting: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
188
188
|
}, z.core.$strict>;
|
|
189
189
|
export type InviteCreateInput = z.infer<typeof InviteCreateInput>;
|
|
@@ -193,7 +193,7 @@ export declare const InviteCreateOutput: z.ZodObject<{
|
|
|
193
193
|
invite: z.ZodObject<{
|
|
194
194
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
195
195
|
email: z.ZodNullable<z.ZodEmail>;
|
|
196
|
-
username: z.ZodNullable<z.ZodString
|
|
196
|
+
username: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
197
197
|
claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
198
198
|
claimed_at: z.ZodNullable<z.ZodString>;
|
|
199
199
|
created_at: z.ZodString;
|
|
@@ -211,7 +211,7 @@ export declare const InviteListOutput: z.ZodObject<{
|
|
|
211
211
|
invites: z.ZodArray<z.ZodObject<{
|
|
212
212
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
213
213
|
email: z.ZodNullable<z.ZodEmail>;
|
|
214
|
-
username: z.ZodNullable<z.ZodString
|
|
214
|
+
username: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
215
215
|
claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
216
216
|
claimed_at: z.ZodNullable<z.ZodString>;
|
|
217
217
|
created_at: z.ZodString;
|
|
@@ -289,7 +289,7 @@ export declare const admin_account_list_action_spec: {
|
|
|
289
289
|
accounts: z.ZodArray<z.ZodObject<{
|
|
290
290
|
account: z.ZodObject<{
|
|
291
291
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
292
|
-
username: z.ZodString
|
|
292
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
293
293
|
email: z.ZodNullable<z.ZodEmail>;
|
|
294
294
|
email_verified: z.ZodBoolean;
|
|
295
295
|
created_at: z.ZodString;
|
|
@@ -512,7 +512,7 @@ export declare const invite_create_action_spec: {
|
|
|
512
512
|
side_effects: true;
|
|
513
513
|
input: z.ZodObject<{
|
|
514
514
|
email: z.ZodOptional<z.ZodNullable<z.ZodEmail>>;
|
|
515
|
-
username: z.ZodOptional<z.ZodNullable<z.ZodString
|
|
515
|
+
username: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
516
516
|
acting: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
517
517
|
}, z.core.$strict>;
|
|
518
518
|
output: z.ZodObject<{
|
|
@@ -520,7 +520,7 @@ export declare const invite_create_action_spec: {
|
|
|
520
520
|
invite: z.ZodObject<{
|
|
521
521
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
522
522
|
email: z.ZodNullable<z.ZodEmail>;
|
|
523
|
-
username: z.ZodNullable<z.ZodString
|
|
523
|
+
username: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
524
524
|
claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
525
525
|
claimed_at: z.ZodNullable<z.ZodString>;
|
|
526
526
|
created_at: z.ZodString;
|
|
@@ -554,7 +554,7 @@ export declare const invite_list_action_spec: {
|
|
|
554
554
|
invites: z.ZodArray<z.ZodObject<{
|
|
555
555
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
556
556
|
email: z.ZodNullable<z.ZodEmail>;
|
|
557
|
-
username: z.ZodNullable<z.ZodString
|
|
557
|
+
username: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
558
558
|
claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
559
559
|
claimed_at: z.ZodNullable<z.ZodString>;
|
|
560
560
|
created_at: z.ZodString;
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* @module
|
|
29
29
|
*/
|
|
30
30
|
import { type RpcAction } from '../actions/action_rpc.js';
|
|
31
|
+
import type { ConnectionCloser } from '../actions/connection_closer.js';
|
|
31
32
|
import { type RoleSchemaResult } from './role_schema.js';
|
|
32
33
|
import { type AppSettings } from './app_settings_schema.js';
|
|
33
34
|
import type { RouteFactoryDeps } from './deps.js';
|
|
@@ -49,6 +50,16 @@ export interface AdminActionOptions {
|
|
|
49
50
|
* handler and RPC dispatch returns `method_not_found`.
|
|
50
51
|
*/
|
|
51
52
|
app_settings?: AppSettings;
|
|
53
|
+
/**
|
|
54
|
+
* Live-connection closer — when set, `admin_session_revoke_all` and
|
|
55
|
+
* `admin_token_revoke_all` handlers eagerly close affected WebSocket
|
|
56
|
+
* sockets for the target account BEFORE emitting the corresponding
|
|
57
|
+
* audit event. Mirrors the self-service surface (see
|
|
58
|
+
* `AccountActionOptions.connection_closer`). `BackendWebsocketTransport`
|
|
59
|
+
* satisfies this interface structurally. When absent, only the
|
|
60
|
+
* listener-based close (`transports_ws_auth_guard`) runs.
|
|
61
|
+
*/
|
|
62
|
+
connection_closer?: ConnectionCloser | null;
|
|
52
63
|
}
|
|
53
64
|
/**
|
|
54
65
|
* Create the admin-only RPC actions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAC7F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AAEtE,OAAO,EAGN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AAuB1B,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAK1D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AA6ChD,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IAClC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,EAC7C,UAAS,kBAAuB,KAC9B,KAAK,CAAC,SAAS,CAmRjB,CAAC"}
|
|
@@ -56,6 +56,7 @@ import { admin_account_list_action_spec, admin_session_list_action_spec, admin_s
|
|
|
56
56
|
export const create_admin_actions = (deps, options = {}) => {
|
|
57
57
|
const role_specs = options.roles?.role_specs ?? builtin_role_specs_by_name;
|
|
58
58
|
const grantable_roles = list_roles_with_grant_path(role_specs, GRANT_PATH_ADMIN);
|
|
59
|
+
const connection_closer = options.connection_closer ?? null;
|
|
59
60
|
const account_list_handler = async (input, ctx) => {
|
|
60
61
|
const accounts = await query_admin_account_list(ctx, {
|
|
61
62
|
limit: input.limit,
|
|
@@ -88,6 +89,23 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
88
89
|
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
89
90
|
}
|
|
90
91
|
const count = await query_session_revoke_all_for_account(ctx, input.account_id);
|
|
92
|
+
// Handler-side belt+suspenders — close the target account's live WS
|
|
93
|
+
// sockets BEFORE the audit emit so revocation lands even if the audit
|
|
94
|
+
// INSERT fails. Listener-based close (`transports_ws_auth_guard` on
|
|
95
|
+
// `audit.on_event_chain`) stays as a fail-safe for out-of-band emit
|
|
96
|
+
// sites. Idempotent — see `account_actions.ts::session_revoke_handler`.
|
|
97
|
+
if (connection_closer) {
|
|
98
|
+
connection_closer.close_sockets_for_account(input.account_id);
|
|
99
|
+
}
|
|
100
|
+
// TOCTOU window — admin B hard-deletes `input.account_id` between the
|
|
101
|
+
// pre-check above and this emit; the FK rejects the row, the audit
|
|
102
|
+
// emitter logs + swallows, and the operation goes unaudited. Bounded
|
|
103
|
+
// by the audit emitter's failure logging (operator-visible) and by
|
|
104
|
+
// the rarity of concurrent admin hard-deletes. Not switching to the
|
|
105
|
+
// failure-shape (`target_account_id: null + metadata.attempted_account_id`)
|
|
106
|
+
// because the FK linkage powers the username-join in
|
|
107
|
+
// `audit_log_list_with_usernames`; losing it on every success row
|
|
108
|
+
// to harden a corner case isn't worth the query-shape change.
|
|
91
109
|
deps.audit.emit(ctx, {
|
|
92
110
|
event_type: 'session_revoke_all',
|
|
93
111
|
account_id: auth.account.id,
|
|
@@ -117,6 +135,13 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
117
135
|
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
118
136
|
}
|
|
119
137
|
const count = await query_revoke_all_api_tokens_for_account(ctx, input.account_id);
|
|
138
|
+
// Handler-side belt+suspenders — see `session_revoke_all_handler`.
|
|
139
|
+
if (connection_closer) {
|
|
140
|
+
connection_closer.close_sockets_for_account(input.account_id);
|
|
141
|
+
}
|
|
142
|
+
// TOCTOU window — see `session_revoke_all_handler` for the rationale on
|
|
143
|
+
// keeping `target_account_id` populated rather than switching to the
|
|
144
|
+
// failure-shape.
|
|
120
145
|
deps.audit.emit(ctx, {
|
|
121
146
|
event_type: 'token_revoke_all',
|
|
122
147
|
account_id: auth.account.id,
|
|
@@ -51,7 +51,7 @@ export const query_validate_api_token = async (deps, raw_token, ip, pending_effe
|
|
|
51
51
|
ip ?? null,
|
|
52
52
|
row.id,
|
|
53
53
|
])
|
|
54
|
-
.then(() => { })
|
|
54
|
+
.then(() => { })
|
|
55
55
|
.catch((err) => {
|
|
56
56
|
deps.log.error('Failed to update last_used_at:', err);
|
|
57
57
|
});
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
* Bound audit-emit capability.
|
|
3
3
|
*
|
|
4
4
|
* `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
|
|
5
|
-
* subscriber chain, and the optional `AuditLogConfig
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* subscriber chain, and the optional `AuditLogConfig`. Built by the
|
|
6
|
+
* consumer's `audit_factory` callback on `CreateAppBackendOptions` —
|
|
7
|
+
* `create_app_backend` invokes the factory once with its constructed
|
|
8
|
+
* `{db, log}` and lands the result on `AppDeps.audit`. Consumers reach
|
|
9
|
+
* for `deps.audit.emit(ctx, input)` and never see the pool — handlers
|
|
10
|
+
* cannot accidentally emit an audit event against the request's
|
|
11
|
+
* transactional `db` (which would be rolled back with the parent on a
|
|
12
|
+
* handler throw).
|
|
10
13
|
*
|
|
11
14
|
* Four methods cover every fan-out shape the auth domain needs:
|
|
12
15
|
*
|
|
@@ -26,9 +29,13 @@
|
|
|
26
29
|
* the query layer). Runs every listener on the chain; per-listener throws
|
|
27
30
|
* are isolated.
|
|
28
31
|
*
|
|
29
|
-
* The chain is
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
+
* The chain is a documented mutable seam — `create_app_server` appends
|
|
33
|
+
* additional listeners after the backend is built (the factory-managed
|
|
34
|
+
* audit-log SSE, per-endpoint WS auth guards and logout closers, any
|
|
35
|
+
* `extra_audit_handlers` on a `WsEndpointSpec`) before the first request
|
|
36
|
+
* runs. Consumers can also append listeners directly on the emitter
|
|
37
|
+
* they return from `audit_factory` for setups that don't pass through
|
|
38
|
+
* `create_app_server`.
|
|
32
39
|
*
|
|
33
40
|
* @module
|
|
34
41
|
*/
|
|
@@ -126,12 +133,36 @@ export interface AuditEmitter {
|
|
|
126
133
|
*/
|
|
127
134
|
notify(event: AuditLogEvent): void;
|
|
128
135
|
/**
|
|
129
|
-
* Mutable subscriber chain.
|
|
130
|
-
* factory-managed audit-log SSE
|
|
131
|
-
*
|
|
136
|
+
* Mutable subscriber chain. `create_app_server` appends the
|
|
137
|
+
* factory-managed audit-log SSE listener and per-endpoint WS auth
|
|
138
|
+
* guards / logout closers here so SSE + WS fan-out compose on top of
|
|
139
|
+
* the consumer's `on_audit_event` callback without shallow-copying
|
|
140
|
+
* `AppDeps`. Consumers can also append listeners directly for setups
|
|
141
|
+
* that don't run through `create_app_server`.
|
|
132
142
|
*/
|
|
133
143
|
readonly on_event_chain: Array<(event: AuditLogEvent) => void>;
|
|
134
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Signature of `AuditEmitter.emit` — captured by the inner closure so
|
|
147
|
+
* `emit_role_grant_target` reaches the decorated function rather than
|
|
148
|
+
* a `this.emit` lookup. Exposed as a type so `EmitDecorator` can name
|
|
149
|
+
* the inner / outer slot.
|
|
150
|
+
*/
|
|
151
|
+
export type AuditEmitFn = <T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>) => void;
|
|
152
|
+
/**
|
|
153
|
+
* Wrap the bound `emit` before it gets captured by `emit_role_grant_target`'s
|
|
154
|
+
* closure and exposed on the returned `AuditEmitter`. Test instrumentation
|
|
155
|
+
* uses this to record `emit` invocation ordering against external markers
|
|
156
|
+
* (e.g. eager `ConnectionCloser` calls in `connection_closer.db.test.ts`)
|
|
157
|
+
* without paying the freeze-breaking footgun the pre-decorator
|
|
158
|
+
* `patch_audit_emit_capture` hot-patcher had.
|
|
159
|
+
*
|
|
160
|
+
* Because the inner closure captures the decorated function (not the
|
|
161
|
+
* outer slot reference), `emit_role_grant_target` also routes through
|
|
162
|
+
* the wrap — the close-vs-emit ordering helper sees role-grant-shape
|
|
163
|
+
* emissions, not just bare `emit` calls. Production never sets this.
|
|
164
|
+
*/
|
|
165
|
+
export type EmitDecorator = (inner: AuditEmitFn) => AuditEmitFn;
|
|
135
166
|
/** Options for `create_audit_emitter`. */
|
|
136
167
|
export interface CreateAuditEmitterOptions {
|
|
137
168
|
/** Pool-level `Db`. Captured by every emit call. */
|
|
@@ -149,9 +180,22 @@ export interface CreateAuditEmitterOptions {
|
|
|
149
180
|
* registered here once at backend assembly.
|
|
150
181
|
*/
|
|
151
182
|
audit_log_config?: AuditLogConfig;
|
|
183
|
+
/**
|
|
184
|
+
* Test-only hook to wrap `emit` at construction time. The decorated
|
|
185
|
+
* function is captured by `emit_role_grant_target`'s closure and is
|
|
186
|
+
* the function exposed on the returned `AuditEmitter`, so both call
|
|
187
|
+
* shapes route through it — see `EmitDecorator` for the rationale.
|
|
188
|
+
*
|
|
189
|
+
* Leave unset in production. The intended caller is
|
|
190
|
+
* `create_emit_ordering_audit_factory` in `testing/audit_drift_guard.ts`.
|
|
191
|
+
*/
|
|
192
|
+
emit_decorator?: EmitDecorator;
|
|
152
193
|
}
|
|
153
194
|
/**
|
|
154
|
-
* Build a bound `AuditEmitter`.
|
|
195
|
+
* Build a bound `AuditEmitter`. Typical caller is the consumer's
|
|
196
|
+
* `audit_factory` callback on `CreateAppBackendOptions` —
|
|
197
|
+
* `create_app_backend` invokes that callback with its constructed
|
|
198
|
+
* `{db, log}` and lands the result on `AppDeps.audit`.
|
|
155
199
|
*
|
|
156
200
|
* @param options - pool, logger, optional initial subscriber, optional config
|
|
157
201
|
* @returns the bound emitter; closes over the pool + config + listener chain
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,mBAAmB;IACnC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACrE,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChF;;;;;;;OAOG;IACH,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACtC,GAAG,EAAE,yBAAyB,EAC9B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE;QACN,UAAU,EAAE,CAAC,CAAC;QACd,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;QAC7B,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACvC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;KAChC,GACC,IAAI,CAAC;IACR;;;;;;;;;;OAUG;IACH,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;CAC/D;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,SAAS,MAAM,EAC1C,GAAG,EAAE,mBAAmB,EACxB,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,KACnB,IAAI,CAAC;AAEV;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW,CAAC;AAEhE,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACzC,oDAAoD;IACpD,EAAE,EAAE,EAAE,CAAC;IACP,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,yBAAyB,KAAG,YAoEzE,CAAC"}
|