@fuzdev/fuz_app 0.62.0 → 0.64.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 (136) hide show
  1. package/dist/actions/CLAUDE.md +139 -24
  2. package/dist/actions/action_rpc.d.ts +10 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +1 -1
  5. package/dist/actions/action_spec.d.ts +1 -1
  6. package/dist/actions/action_spec.js +1 -1
  7. package/dist/actions/connection_closer.d.ts +68 -0
  8. package/dist/actions/connection_closer.d.ts.map +1 -0
  9. package/dist/actions/connection_closer.js +41 -0
  10. package/dist/actions/perform_action.d.ts.map +1 -1
  11. package/dist/actions/perform_action.js +1 -0
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +23 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +11 -9
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +5 -5
  17. package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
  18. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  19. package/dist/actions/transports_ws_auth_guard.js +23 -7
  20. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  21. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  22. package/dist/actions/ws_endpoint_spec.js +13 -0
  23. package/dist/auth/CLAUDE.md +124 -39
  24. package/dist/auth/account_action_specs.d.ts +7 -1
  25. package/dist/auth/account_action_specs.d.ts.map +1 -1
  26. package/dist/auth/account_action_specs.js +11 -4
  27. package/dist/auth/account_actions.d.ts +13 -0
  28. package/dist/auth/account_actions.d.ts.map +1 -1
  29. package/dist/auth/account_actions.js +40 -5
  30. package/dist/auth/account_routes.d.ts +12 -2
  31. package/dist/auth/account_routes.d.ts.map +1 -1
  32. package/dist/auth/account_routes.js +63 -12
  33. package/dist/auth/account_schema.d.ts +5 -5
  34. package/dist/auth/account_schema.js +2 -2
  35. package/dist/auth/actor_lookup_actions.d.ts +1 -1
  36. package/dist/auth/actor_lookup_actions.js +1 -1
  37. package/dist/auth/actor_lookup_queries.d.ts +1 -1
  38. package/dist/auth/actor_lookup_queries.js +1 -1
  39. package/dist/auth/actor_search_action_specs.d.ts +1 -1
  40. package/dist/auth/actor_search_action_specs.js +1 -1
  41. package/dist/auth/actor_search_actions.d.ts +1 -1
  42. package/dist/auth/actor_search_actions.js +1 -1
  43. package/dist/auth/actor_search_queries.d.ts +1 -1
  44. package/dist/auth/actor_search_queries.js +1 -1
  45. package/dist/auth/admin_action_specs.d.ts +8 -8
  46. package/dist/auth/admin_actions.d.ts +11 -0
  47. package/dist/auth/admin_actions.d.ts.map +1 -1
  48. package/dist/auth/admin_actions.js +25 -0
  49. package/dist/auth/all_action_spec_registries.d.ts +2 -2
  50. package/dist/auth/all_action_spec_registries.js +2 -2
  51. package/dist/auth/audit_emitter.d.ts +56 -12
  52. package/dist/auth/audit_emitter.d.ts.map +1 -1
  53. package/dist/auth/audit_emitter.js +38 -12
  54. package/dist/auth/audit_log_routes.d.ts +1 -1
  55. package/dist/auth/audit_log_routes.js +1 -1
  56. package/dist/auth/audit_log_schema.d.ts +30 -3
  57. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  58. package/dist/auth/audit_log_schema.js +21 -3
  59. package/dist/auth/bootstrap_routes.d.ts +1 -1
  60. package/dist/auth/invite_schema.d.ts +2 -2
  61. package/dist/auth/request_context.d.ts +1 -1
  62. package/dist/auth/signup_routes.d.ts +1 -1
  63. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  64. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  65. package/dist/auth/standard_rpc_actions.js +1 -0
  66. package/dist/env/update_env_variable.js +1 -1
  67. package/dist/http/CLAUDE.md +42 -26
  68. package/dist/http/ip_canonical.d.ts +99 -0
  69. package/dist/http/ip_canonical.d.ts.map +1 -0
  70. package/dist/http/ip_canonical.js +191 -0
  71. package/dist/http/origin.d.ts +13 -5
  72. package/dist/http/origin.d.ts.map +1 -1
  73. package/dist/http/origin.js +13 -31
  74. package/dist/http/pending_effects.d.ts +1 -1
  75. package/dist/http/pending_effects.js +1 -1
  76. package/dist/http/proxy.d.ts +13 -5
  77. package/dist/http/proxy.d.ts.map +1 -1
  78. package/dist/http/proxy.js +15 -23
  79. package/dist/http/surface.d.ts +50 -0
  80. package/dist/http/surface.d.ts.map +1 -1
  81. package/dist/http/surface.js +27 -1
  82. package/dist/primitive_schemas.d.ts +20 -4
  83. package/dist/primitive_schemas.d.ts.map +1 -1
  84. package/dist/primitive_schemas.js +25 -4
  85. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  86. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  87. package/dist/realtime/sse_auth_guard.js +15 -3
  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 +60 -0
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +95 -2
  94. package/dist/server/startup.d.ts.map +1 -1
  95. package/dist/server/startup.js +12 -0
  96. package/dist/testing/CLAUDE.md +91 -71
  97. package/dist/testing/admin_integration.d.ts.map +1 -1
  98. package/dist/testing/admin_integration.js +4 -5
  99. package/dist/testing/adversarial_headers.d.ts +6 -0
  100. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  101. package/dist/testing/adversarial_headers.js +13 -5
  102. package/dist/testing/app_server.d.ts +33 -32
  103. package/dist/testing/app_server.d.ts.map +1 -1
  104. package/dist/testing/app_server.js +4 -13
  105. package/dist/testing/attack_surface.d.ts +8 -7
  106. package/dist/testing/attack_surface.d.ts.map +1 -1
  107. package/dist/testing/attack_surface.js +12 -8
  108. package/dist/testing/audit_completeness.d.ts.map +1 -1
  109. package/dist/testing/audit_completeness.js +20 -6
  110. package/dist/testing/audit_drift_guard.d.ts +116 -0
  111. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  112. package/dist/testing/audit_drift_guard.js +134 -0
  113. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  114. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  115. package/dist/testing/connection_closer_helpers.js +48 -0
  116. package/dist/testing/integration.d.ts.map +1 -1
  117. package/dist/testing/integration.js +7 -9
  118. package/dist/testing/rate_limiting.js +4 -4
  119. package/dist/testing/rpc_helpers.d.ts +2 -1
  120. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  121. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  122. package/dist/testing/rpc_round_trip.js +6 -8
  123. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  124. package/dist/testing/sse_round_trip.js +12 -6
  125. package/dist/testing/stubs.d.ts +11 -0
  126. package/dist/testing/stubs.d.ts.map +1 -1
  127. package/dist/testing/stubs.js +4 -0
  128. package/dist/testing/surface_invariants.d.ts +66 -1
  129. package/dist/testing/surface_invariants.d.ts.map +1 -1
  130. package/dist/testing/surface_invariants.js +103 -1
  131. package/dist/ui/CLAUDE.md +13 -18
  132. package/dist/ui/SurfaceExplorer.svelte +161 -2
  133. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  134. package/dist/ui/keyed_async_slot.svelte.d.ts +1 -1
  135. package/dist/ui/keyed_async_slot.svelte.js +1 -1
  136. package/package.json +1 -1
@@ -90,22 +90,26 @@ export const account_session_list_action_spec = {
90
90
  async: true,
91
91
  description: 'List auth sessions for the current account.',
92
92
  };
93
+ // `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
94
+ // A leaked bearer can otherwise compose `account_session_list` + N×revoke to
95
+ // reach the same effect as `account_session_revoke_all`.
93
96
  export const account_session_revoke_action_spec = {
94
97
  method: 'account_session_revoke',
95
98
  kind: 'request_response',
96
99
  initiator: 'frontend',
97
- auth: { account: 'required', actor: 'none' },
100
+ auth: { account: 'required', actor: 'none', credential_types: ['session'] },
98
101
  side_effects: true,
99
102
  input: SessionRevokeInput,
100
103
  output: SessionRevokeOutput,
101
104
  async: true,
102
105
  description: 'Revoke a single auth session for the current account (IDOR-guarded).',
103
106
  };
107
+ // `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
104
108
  export const account_session_revoke_all_action_spec = {
105
109
  method: 'account_session_revoke_all',
106
110
  kind: 'request_response',
107
111
  initiator: 'frontend',
108
- auth: { account: 'required', actor: 'none' },
112
+ auth: { account: 'required', actor: 'none', credential_types: ['session'] },
109
113
  side_effects: true,
110
114
  input: SessionRevokeAllInput,
111
115
  output: SessionRevokeAllOutput,
@@ -113,6 +117,8 @@ export const account_session_revoke_all_action_spec = {
113
117
  description: 'Revoke every auth session for the current account.',
114
118
  };
115
119
  /**
120
+ * `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
121
+ *
116
122
  * `rate_limit: 'account'` bounds the burn rate of API-token creates. The
117
123
  * outstanding-token count is already capped by `max_tokens` (via
118
124
  * `query_api_token_enforce_limit`), but the per-account *rate* of churn
@@ -124,7 +130,7 @@ export const account_token_create_action_spec = {
124
130
  method: 'account_token_create',
125
131
  kind: 'request_response',
126
132
  initiator: 'frontend',
127
- auth: { account: 'required', actor: 'none' },
133
+ auth: { account: 'required', actor: 'none', credential_types: ['session'] },
128
134
  side_effects: true,
129
135
  input: TokenCreateInput,
130
136
  output: TokenCreateOutput,
@@ -143,11 +149,12 @@ export const account_token_list_action_spec = {
143
149
  async: true,
144
150
  description: 'List API tokens for the current account. Hashes are never returned.',
145
151
  };
152
+ // `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
146
153
  export const account_token_revoke_action_spec = {
147
154
  method: 'account_token_revoke',
148
155
  kind: 'request_response',
149
156
  initiator: 'frontend',
150
- auth: { account: 'required', actor: 'none' },
157
+ auth: { account: 'required', actor: 'none', credential_types: ['session'] },
151
158
  side_effects: true,
152
159
  input: TokenRevokeInput,
153
160
  output: TokenRevokeOutput,
@@ -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,CAsGjB,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,22 +49,49 @@ 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',
55
70
  account_id: ctx.auth.account.id,
56
71
  ip: ctx.client_ip,
57
- metadata: { session_id: input.session_id },
72
+ // `credential_type` defense in depth — see `docs/security.md` §Credential-channel gating.
73
+ metadata: { session_id: input.session_id, credential_type: ctx.credential_type ?? undefined },
58
74
  });
59
75
  return { ok: true, revoked };
60
76
  };
61
77
  const session_revoke_all_handler = async (_input, ctx) => {
62
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
+ }
63
90
  deps.audit.emit(ctx, {
64
91
  event_type: 'session_revoke_all',
65
92
  account_id: ctx.auth.account.id,
66
93
  ip: ctx.client_ip,
67
- metadata: { count },
94
+ metadata: { count, credential_type: ctx.credential_type ?? undefined },
68
95
  });
69
96
  return { ok: true, count };
70
97
  };
@@ -78,7 +105,11 @@ export const create_account_actions = (deps, options = {}) => {
78
105
  event_type: 'token_create',
79
106
  account_id: ctx.auth.account.id,
80
107
  ip: ctx.client_ip,
81
- metadata: { token_id: id, name: input.name },
108
+ metadata: {
109
+ token_id: id,
110
+ name: input.name,
111
+ credential_type: ctx.credential_type ?? undefined,
112
+ },
82
113
  });
83
114
  return { ok: true, token, id, name: input.name };
84
115
  };
@@ -88,12 +119,16 @@ export const create_account_actions = (deps, options = {}) => {
88
119
  };
89
120
  const token_revoke_handler = async (input, ctx) => {
90
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
+ }
91
126
  deps.audit.emit(ctx, {
92
127
  event_type: 'token_revoke',
93
128
  outcome: revoked ? 'success' : 'failure',
94
129
  account_id: ctx.auth.account.id,
95
130
  ip: ctx.client_ip,
96
- metadata: { token_id: input.token_id },
131
+ metadata: { token_id: input.token_id, credential_type: ctx.credential_type ?? undefined },
97
132
  });
98
133
  return { ok: true, revoked };
99
134
  };
@@ -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":"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"}
@@ -29,7 +29,7 @@ import { hash_session_token, query_session_revoke_all_for_account, query_session
29
29
  import { query_account_by_username_or_email, query_update_account_password, } from './account_queries.js';
30
30
  import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
31
31
  import { build_account_context, build_request_context, get_request_context, require_request_context, resolve_acting_actor, } from './request_context.js';
32
- import { ACCOUNT_ID_KEY } from '../hono_context.js';
32
+ import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
33
33
  import { get_route_input } from '../http/route_spec.js';
34
34
  import { get_client_ip } from '../http/proxy.js';
35
35
  import { rate_limit_exceeded_response } from '../rate_limiter.js';
@@ -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
@@ -355,7 +374,8 @@ export const create_account_route_specs = (deps, options) => {
355
374
  {
356
375
  method: 'POST',
357
376
  path: '/password',
358
- auth: { account: 'required', actor: 'none' },
377
+ // `credential_types: ['session']` see `docs/security.md` §Credential-channel gating.
378
+ auth: { account: 'required', actor: 'none', credential_types: ['session'] },
359
379
  description: 'Change password (revokes all sessions and API tokens)',
360
380
  input: PasswordChangeInput,
361
381
  output: PasswordChangeOutput,
@@ -376,6 +396,8 @@ export const create_account_route_specs = (deps, options) => {
376
396
  }
377
397
  const ctx = require_request_context(c);
378
398
  const { current_password, new_password } = get_route_input(c);
399
+ // Defense in depth — see `docs/security.md` §Credential-channel gating.
400
+ const credential_type = c.get(CREDENTIAL_TYPE_KEY) ?? undefined;
379
401
  // per-account rate limit check (after context resolution, before argon2 work)
380
402
  if (login_account_rate_limiter) {
381
403
  const check = login_account_rate_limiter.check(ctx.account.id);
@@ -394,14 +416,14 @@ export const create_account_route_specs = (deps, options) => {
394
416
  outcome: 'failure',
395
417
  account_id: ctx.account.id,
396
418
  ip: get_client_ip(c),
419
+ metadata: { credential_type },
397
420
  });
398
421
  return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
399
422
  }
400
- // successful verificationreset rate limiters
401
- if (ip_rate_limiter && ip)
402
- ip_rate_limiter.reset(ip);
403
- if (login_account_rate_limiter)
404
- 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.
405
427
  const new_hash = await password.hash_password(new_password);
406
428
  // Conditional UPDATE keyed on the verified hash: closes the
407
429
  // verify-write race with a concurrent password change that
@@ -409,6 +431,19 @@ export const create_account_route_specs = (deps, options) => {
409
431
  // operation — `updated_by` stays null (the per-request actor is
410
432
  // incidental; password is account-level state).
411
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);
412
447
  if (!updated) {
413
448
  // A concurrent password change committed first — our
414
449
  // `current_password` was correct at read-time but the row's
@@ -426,13 +461,29 @@ export const create_account_route_specs = (deps, options) => {
426
461
  outcome: 'failure',
427
462
  account_id: ctx.account.id,
428
463
  ip: get_client_ip(c),
429
- metadata: { reason: 'concurrent_change' },
464
+ metadata: { reason: 'concurrent_change', credential_type },
430
465
  });
431
466
  return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
432
467
  }
433
468
  // revoke all sessions and API tokens (force re-auth everywhere)
434
469
  const sessions_revoked = await query_session_revoke_all_for_account(route, ctx.account.id);
435
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
+ }
436
487
  clear_session_cookie(c, session_options);
437
488
  // Account-grain operation — no `actor_id`. The password is
438
489
  // account-level state; which per-request actor was resolved
@@ -442,7 +493,7 @@ export const create_account_route_specs = (deps, options) => {
442
493
  event_type: 'password_change',
443
494
  account_id: ctx.account.id,
444
495
  ip: get_client_ip(c),
445
- metadata: { sessions_revoked, tokens_revoked },
496
+ metadata: { sessions_revoked, tokens_revoked, credential_type },
446
497
  });
447
498
  return c.json({ ok: true, sessions_revoked, tokens_revoked });
448
499
  },
@@ -5,9 +5,9 @@
5
5
  * `Account`, `Actor`, `RoleGrant`, `AuthSession`, and `ApiToken`.
6
6
  *
7
7
  * Identifier primitives (`Username`, `UsernameProvided`, `Email`) live
8
- * in `../primitive_schemas.ts` — they're general validator shapes that
8
+ * in `primitive_schemas.ts` — they're general validator shapes that
9
9
  * don't depend on the auth domain. The auth-shape request-contract
10
- * primitive `ActingActor` lives in `../http/auth_shape.ts` next to
10
+ * primitive `ActingActor` lives in `http/auth_shape.ts` next to
11
11
  * `RouteAuth` (the two pair: `auth.actor !== 'none'` ⟺ input declares
12
12
  * `acting?: ActingActor`).
13
13
  *
@@ -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;
@@ -5,9 +5,9 @@
5
5
  * `Account`, `Actor`, `RoleGrant`, `AuthSession`, and `ApiToken`.
6
6
  *
7
7
  * Identifier primitives (`Username`, `UsernameProvided`, `Email`) live
8
- * in `../primitive_schemas.ts` — they're general validator shapes that
8
+ * in `primitive_schemas.ts` — they're general validator shapes that
9
9
  * don't depend on the auth domain. The auth-shape request-contract
10
- * primitive `ActingActor` lives in `../http/auth_shape.ts` next to
10
+ * primitive `ActingActor` lives in `http/auth_shape.ts` next to
11
11
  * `RouteAuth` (the two pair: `auth.actor !== 'none'` ⟺ input declares
12
12
  * `acting?: ActingActor`).
13
13
  *
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Pure read — no audit, no side effects. Auth (`account: 'required'`) +
5
5
  * rate-limit (`account`-grain) enforced at the spec layer; see
6
- * `./actor_lookup_action_specs.ts` for the info-leak audit.
6
+ * `auth/actor_lookup_action_specs.ts` for the info-leak audit.
7
7
  *
8
8
  * `display_name` is omitted (not `null`) when `actor.name` is blank,
9
9
  * matching the wire shape `display_name?` so the typed client sees an
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Pure read — no audit, no side effects. Auth (`account: 'required'`) +
5
5
  * rate-limit (`account`-grain) enforced at the spec layer; see
6
- * `./actor_lookup_action_specs.ts` for the info-leak audit.
6
+ * `auth/actor_lookup_action_specs.ts` for the info-leak audit.
7
7
  *
8
8
  * `display_name` is omitted (not `null`) when `actor.name` is blank,
9
9
  * matching the wire shape `display_name?` so the typed client sees an
@@ -10,7 +10,7 @@
10
10
  * The inner join still resolves one row per actor — `actor.account_id`
11
11
  * is `NOT NULL` so every actor has exactly one account.
12
12
  *
13
- * Info-leak posture (see `actor_lookup_action_specs.ts` § audit):
13
+ * Info-leak posture (see `actor_lookup_action_specs.ts` §audit):
14
14
  *
15
15
  * - Row shape **omits** `account_id` — the join is control-plane,
16
16
  * not wire-visible.
@@ -10,7 +10,7 @@
10
10
  * The inner join still resolves one row per actor — `actor.account_id`
11
11
  * is `NOT NULL` so every actor has exactly one account.
12
12
  *
13
- * Info-leak posture (see `actor_lookup_action_specs.ts` § audit):
13
+ * Info-leak posture (see `actor_lookup_action_specs.ts` §audit):
14
14
  *
15
15
  * - Row shape **omits** `account_id` — the join is control-plane,
16
16
  * not wire-visible.
@@ -45,7 +45,7 @@
45
45
  * ## Wire shape — info-leak audit
46
46
  *
47
47
  * Output `{actors: [{id, username, display_name?}]}` is identical to
48
- * `actor_lookup`'s — see `./actor_lookup_action_specs.ts` for the full
48
+ * `actor_lookup`'s — see `auth/actor_lookup_action_specs.ts` for the full
49
49
  * field-by-field audit. Same omissions (`account_id`, email,
50
50
  * timestamps, role / role_grants / session state), same `display_name`
51
51
  * omitted-not-null contract, same response-order-unspecified rule.
@@ -45,7 +45,7 @@
45
45
  * ## Wire shape — info-leak audit
46
46
  *
47
47
  * Output `{actors: [{id, username, display_name?}]}` is identical to
48
- * `actor_lookup`'s — see `./actor_lookup_action_specs.ts` for the full
48
+ * `actor_lookup`'s — see `auth/actor_lookup_action_specs.ts` for the full
49
49
  * field-by-field audit. Same omissions (`account_id`, email,
50
50
  * timestamps, role / role_grants / session state), same `display_name`
51
51
  * omitted-not-null contract, same response-order-unspecified rule.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Pure read — no audit, no side effects. Auth (`account: 'required'`,
5
5
  * `actor: 'none'`) + rate-limit (`account`-grain) enforced at the spec
6
- * layer; see `./actor_search_action_specs.ts` for the info-leak audit
6
+ * layer; see `auth/actor_search_action_specs.ts` for the info-leak audit
7
7
  * and threat model.
8
8
  *
9
9
  * The handler adds two checks the spec layer can't express:
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Pure read — no audit, no side effects. Auth (`account: 'required'`,
5
5
  * `actor: 'none'`) + rate-limit (`account`-grain) enforced at the spec
6
- * layer; see `./actor_search_action_specs.ts` for the info-leak audit
6
+ * layer; see `auth/actor_search_action_specs.ts` for the info-leak audit
7
7
  * and threat model.
8
8
  *
9
9
  * The handler adds two checks the spec layer can't express:
@@ -29,7 +29,7 @@
29
29
  * gates), no role_grant join — every actor with a matching prefix is
30
30
  * returned.
31
31
  *
32
- * ## Info-leak posture (see `actor_search_action_specs.ts` § audit)
32
+ * ## Info-leak posture (see `actor_search_action_specs.ts` §audit)
33
33
  *
34
34
  * - Row shape **omits** `account_id` — the join is control-plane, not
35
35
  * wire-visible. Identical to `actor_lookup_queries.ts`.
@@ -29,7 +29,7 @@
29
29
  * gates), no role_grant join — every actor with a matching prefix is
30
30
  * returned.
31
31
  *
32
- * ## Info-leak posture (see `actor_search_action_specs.ts` § audit)
32
+ * ## Info-leak posture (see `actor_search_action_specs.ts` §audit)
33
33
  *
34
34
  * - Row shape **omits** `account_id` — the join is control-plane, not
35
35
  * wire-visible. Identical to `actor_lookup_queries.ts`.
@@ -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;