@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.
Files changed (181) hide show
  1. package/dist/actions/CLAUDE.md +525 -827
  2. package/dist/actions/broadcast_api.d.ts +1 -1
  3. package/dist/actions/broadcast_api.js +1 -1
  4. package/dist/actions/cancel.d.ts +2 -2
  5. package/dist/actions/cancel.js +3 -3
  6. package/dist/actions/connection_closer.d.ts +65 -0
  7. package/dist/actions/connection_closer.d.ts.map +1 -0
  8. package/dist/actions/connection_closer.js +38 -0
  9. package/dist/actions/register_action_ws.d.ts +2 -2
  10. package/dist/actions/register_action_ws.d.ts.map +1 -1
  11. package/dist/actions/register_action_ws.js +23 -2
  12. package/dist/actions/register_ws_endpoint.d.ts +12 -10
  13. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  14. package/dist/actions/register_ws_endpoint.js +5 -5
  15. package/dist/actions/transports_ws_auth_guard.d.ts +25 -10
  16. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  17. package/dist/actions/transports_ws_auth_guard.js +24 -9
  18. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  19. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  20. package/dist/actions/ws_endpoint_spec.js +13 -0
  21. package/dist/auth/CLAUDE.md +592 -1808
  22. package/dist/auth/account_action_specs.d.ts +1 -1
  23. package/dist/auth/account_actions.d.ts +13 -0
  24. package/dist/auth/account_actions.d.ts.map +1 -1
  25. package/dist/auth/account_actions.js +31 -1
  26. package/dist/auth/account_routes.d.ts +12 -2
  27. package/dist/auth/account_routes.d.ts.map +1 -1
  28. package/dist/auth/account_routes.js +55 -8
  29. package/dist/auth/account_schema.d.ts +4 -4
  30. package/dist/auth/account_schema.d.ts.map +1 -1
  31. package/dist/auth/admin_action_specs.d.ts +8 -8
  32. package/dist/auth/admin_actions.d.ts +11 -0
  33. package/dist/auth/admin_actions.d.ts.map +1 -1
  34. package/dist/auth/admin_actions.js +25 -0
  35. package/dist/auth/api_token_queries.js +1 -1
  36. package/dist/auth/audit_emitter.d.ts +56 -12
  37. package/dist/auth/audit_emitter.d.ts.map +1 -1
  38. package/dist/auth/audit_emitter.js +38 -12
  39. package/dist/auth/audit_log_ddl.d.ts +1 -1
  40. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  41. package/dist/auth/audit_log_ddl.js +1 -1
  42. package/dist/auth/audit_log_schema.d.ts +5 -3
  43. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  44. package/dist/auth/audit_log_schema.js +5 -3
  45. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  46. package/dist/auth/bootstrap_account.js +1 -5
  47. package/dist/auth/bootstrap_routes.d.ts +8 -2
  48. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  49. package/dist/auth/bootstrap_routes.js +15 -11
  50. package/dist/auth/invite_schema.d.ts +2 -2
  51. package/dist/auth/keyring.d.ts +6 -6
  52. package/dist/auth/keyring.js +8 -8
  53. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  54. package/dist/auth/role_grant_offer_actions.js +4 -2
  55. package/dist/auth/signup_routes.d.ts +1 -1
  56. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  57. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  58. package/dist/auth/standard_rpc_actions.js +1 -0
  59. package/dist/db/create_db.d.ts.map +1 -1
  60. package/dist/db/create_db.js +13 -0
  61. package/dist/dev/setup.d.ts +2 -2
  62. package/dist/dev/setup.js +3 -3
  63. package/dist/http/CLAUDE.md +225 -483
  64. package/dist/http/error_schemas.d.ts +0 -4
  65. package/dist/http/error_schemas.d.ts.map +1 -1
  66. package/dist/http/error_schemas.js +0 -4
  67. package/dist/http/ip_canonical.d.ts +100 -0
  68. package/dist/http/ip_canonical.d.ts.map +1 -0
  69. package/dist/http/ip_canonical.js +195 -0
  70. package/dist/http/origin.d.ts +14 -6
  71. package/dist/http/origin.d.ts.map +1 -1
  72. package/dist/http/origin.js +14 -32
  73. package/dist/http/pending_effects.d.ts +1 -1
  74. package/dist/http/pending_effects.js +1 -1
  75. package/dist/http/proxy.d.ts +13 -5
  76. package/dist/http/proxy.d.ts.map +1 -1
  77. package/dist/http/proxy.js +15 -23
  78. package/dist/http/surface.d.ts +50 -0
  79. package/dist/http/surface.d.ts.map +1 -1
  80. package/dist/http/surface.js +27 -1
  81. package/dist/primitive_schemas.d.ts +20 -4
  82. package/dist/primitive_schemas.d.ts.map +1 -1
  83. package/dist/primitive_schemas.js +25 -4
  84. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  85. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  86. package/dist/realtime/sse_auth_guard.js +15 -3
  87. package/dist/runtime/mock.js +1 -1
  88. package/dist/server/app_backend.d.ts +66 -19
  89. package/dist/server/app_backend.d.ts.map +1 -1
  90. package/dist/server/app_backend.js +57 -34
  91. package/dist/server/app_server.d.ts +101 -10
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +105 -6
  94. package/dist/server/env.d.ts +7 -7
  95. package/dist/server/env.d.ts.map +1 -1
  96. package/dist/server/env.js +14 -14
  97. package/dist/server/startup.d.ts.map +1 -1
  98. package/dist/server/startup.js +12 -0
  99. package/dist/server/static.d.ts +4 -4
  100. package/dist/server/static.js +7 -7
  101. package/dist/testing/CLAUDE.md +269 -59
  102. package/dist/testing/admin_integration.d.ts +18 -23
  103. package/dist/testing/admin_integration.d.ts.map +1 -1
  104. package/dist/testing/admin_integration.js +159 -202
  105. package/dist/testing/adversarial_headers.d.ts +6 -0
  106. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  107. package/dist/testing/adversarial_headers.js +13 -5
  108. package/dist/testing/app_server.d.ts +148 -60
  109. package/dist/testing/app_server.d.ts.map +1 -1
  110. package/dist/testing/app_server.js +143 -54
  111. package/dist/testing/attack_surface.d.ts +8 -7
  112. package/dist/testing/attack_surface.d.ts.map +1 -1
  113. package/dist/testing/attack_surface.js +12 -8
  114. package/dist/testing/audit_completeness.d.ts +23 -22
  115. package/dist/testing/audit_completeness.d.ts.map +1 -1
  116. package/dist/testing/audit_completeness.js +199 -158
  117. package/dist/testing/audit_drift_guard.d.ts +116 -0
  118. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  119. package/dist/testing/audit_drift_guard.js +134 -0
  120. package/dist/testing/bootstrap_success.d.ts +28 -0
  121. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  122. package/dist/testing/bootstrap_success.js +144 -0
  123. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  124. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  125. package/dist/testing/connection_closer_helpers.js +48 -0
  126. package/dist/testing/cross_backend/capabilities.d.ts +64 -0
  127. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/capabilities.js +47 -0
  129. package/dist/testing/cross_backend/setup.d.ts +215 -0
  130. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/setup.js +101 -0
  132. package/dist/testing/data_exposure.d.ts +14 -15
  133. package/dist/testing/data_exposure.d.ts.map +1 -1
  134. package/dist/testing/data_exposure.js +127 -146
  135. package/dist/testing/db_entities.d.ts +11 -1
  136. package/dist/testing/db_entities.d.ts.map +1 -1
  137. package/dist/testing/db_entities.js +13 -1
  138. package/dist/testing/integration.d.ts +35 -21
  139. package/dist/testing/integration.d.ts.map +1 -1
  140. package/dist/testing/integration.js +231 -293
  141. package/dist/testing/integration_helpers.d.ts +16 -6
  142. package/dist/testing/integration_helpers.d.ts.map +1 -1
  143. package/dist/testing/integration_helpers.js +7 -7
  144. package/dist/testing/mock_fs.d.ts.map +1 -1
  145. package/dist/testing/mock_fs.js +0 -2
  146. package/dist/testing/rate_limiting.d.ts.map +1 -1
  147. package/dist/testing/rate_limiting.js +13 -4
  148. package/dist/testing/role_grant_helpers.d.ts +31 -0
  149. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  150. package/dist/testing/role_grant_helpers.js +46 -0
  151. package/dist/testing/round_trip.d.ts +21 -16
  152. package/dist/testing/round_trip.d.ts.map +1 -1
  153. package/dist/testing/round_trip.js +65 -86
  154. package/dist/testing/rpc_helpers.d.ts +2 -1
  155. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  156. package/dist/testing/rpc_round_trip.d.ts +24 -21
  157. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  158. package/dist/testing/rpc_round_trip.js +91 -106
  159. package/dist/testing/schema_introspect.d.ts +106 -0
  160. package/dist/testing/schema_introspect.d.ts.map +1 -0
  161. package/dist/testing/schema_introspect.js +123 -0
  162. package/dist/testing/schema_parity.d.ts +144 -0
  163. package/dist/testing/schema_parity.d.ts.map +1 -0
  164. package/dist/testing/schema_parity.js +233 -0
  165. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  166. package/dist/testing/sse_round_trip.js +12 -6
  167. package/dist/testing/standard.d.ts +57 -25
  168. package/dist/testing/standard.d.ts.map +1 -1
  169. package/dist/testing/standard.js +62 -5
  170. package/dist/testing/stubs.d.ts +22 -3
  171. package/dist/testing/stubs.d.ts.map +1 -1
  172. package/dist/testing/stubs.js +28 -21
  173. package/dist/testing/surface_invariants.d.ts +66 -1
  174. package/dist/testing/surface_invariants.d.ts.map +1 -1
  175. package/dist/testing/surface_invariants.js +103 -1
  176. package/dist/testing/transports/surface_source.d.ts +51 -0
  177. package/dist/testing/transports/surface_source.d.ts.map +1 -0
  178. package/dist/testing/transports/surface_source.js +19 -0
  179. package/dist/ui/SurfaceExplorer.svelte +161 -2
  180. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  181. 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;AAe5F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAwBhD,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,EAC7C,UAAS,oBAAyB,KAChC,KAAK,CAAC,SAAS,CA2GjB,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":"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"}
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;AAChD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AAQtE,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAI9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,4EAA4E;AAC5E,eAAO,MAAM,iCAAiC;;;iBAG5C,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAElG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gCAAgC,GAAI,UAAU,oBAAoB,KAAG,SAmFhF,CAAC;AAEH,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE;QAAC,SAAS,EAAE,OAAO,CAAA;KAAC,CAAC;CACxC;AAED,4CAA4C;AAC5C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAQ/C;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kFAAkF;IAClF,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IACnE,4FAA4F;IAC5F,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C;AAID,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;kBAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,wFAAwF;AACxF,eAAO,MAAM,WAAW;;kBAEtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wFAAwF;AACxF,eAAO,MAAM,YAAY;;;kBAGvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,sHAAsH;AACtH,eAAO,MAAM,mBAAmB;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,uGAAuG;AACvG,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,gBAAgB,EACtB,SAAS,mBAAmB,KAC1B,KAAK,CAAC,SAAS,CA+SjB,CAAC"}
@@ -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
- const { username: raw_username, password: pw } = get_route_input(c);
258
- const username = raw_username.trim().toLowerCase();
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
- // successful verificationreset rate limiters
405
- if (ip_rate_limiter && ip)
406
- ip_rate_limiter.reset(ip);
407
- if (login_account_rate_limiter)
408
- login_account_rate_limiter.reset(ctx.account.id);
423
+ // Verify succeededdo 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 (surfaced in the revokee WS notification once it lands). */
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":"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"}
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;AAE7F,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;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,EAC7C,UAAS,kBAAuB,KAC9B,KAAK,CAAC,SAAS,CA0PjB,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(() => { }) // eslint-disable-line @typescript-eslint/no-empty-function
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` at backend-assembly
6
- * time. Consumers reach for `deps.audit.emit(ctx, input)` and never see the
7
- * pool handlers cannot accidentally emit an audit event against the
8
- * request's transactional `db` (which would be rolled back with the parent
9
- * on a handler throw).
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 mutable so server assembly can append additional listeners
30
- * (e.g. the audit-log SSE registry composed by `create_app_server`) after
31
- * the backend is built but before the first request runs.
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. Append at server assembly to compose the
130
- * factory-managed audit-log SSE on top of the consumer's
131
- * `on_audit_event` callback without shallow-copying `AppDeps`.
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`. Called once at `create_app_backend` time.
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;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;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;CAC/D;AAED,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;CAClC;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,yBAAyB,KAAG,YAoDzE,CAAC"}
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"}