@fuzdev/fuz_app 0.149.0 → 0.150.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/dist/actions/CLAUDE.md +9 -7
  2. package/dist/actions/action_bridge.d.ts +7 -0
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +10 -0
  5. package/dist/actions/action_event_types.d.ts +1 -1
  6. package/dist/actions/action_event_types.d.ts.map +1 -1
  7. package/dist/actions/perform_action.d.ts.map +1 -1
  8. package/dist/actions/perform_action.js +8 -5
  9. package/dist/actions/register_action_ws.d.ts +8 -2
  10. package/dist/actions/register_action_ws.d.ts.map +1 -1
  11. package/dist/actions/register_action_ws.js +24 -4
  12. package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
  13. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  14. package/dist/actions/transports_ws_auth_guard.js +9 -6
  15. package/dist/actions/ws_endpoint_spec.d.ts +1 -1
  16. package/dist/auth/CLAUDE.md +69 -31
  17. package/dist/auth/actor_lookup_queries.d.ts +3 -2
  18. package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
  19. package/dist/auth/actor_lookup_queries.js +6 -3
  20. package/dist/auth/actor_search_queries.d.ts +5 -2
  21. package/dist/auth/actor_search_queries.d.ts.map +1 -1
  22. package/dist/auth/actor_search_queries.js +11 -4
  23. package/dist/auth/admin_action_specs.d.ts +4 -4
  24. package/dist/auth/admin_action_specs.js +4 -4
  25. package/dist/auth/admin_actions.d.ts.map +1 -1
  26. package/dist/auth/admin_actions.js +35 -6
  27. package/dist/auth/all_cell_actions.d.ts +11 -2
  28. package/dist/auth/all_cell_actions.d.ts.map +1 -1
  29. package/dist/auth/all_cell_actions.js +2 -1
  30. package/dist/auth/audit_emitter.d.ts +9 -3
  31. package/dist/auth/audit_emitter.d.ts.map +1 -1
  32. package/dist/auth/audit_emitter.js +9 -3
  33. package/dist/auth/audit_log_schema.d.ts +1 -0
  34. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  35. package/dist/auth/audit_log_schema.js +9 -5
  36. package/dist/auth/bootstrap_account.d.ts +25 -1
  37. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  38. package/dist/auth/bootstrap_account.js +37 -4
  39. package/dist/auth/bootstrap_routes.d.ts +3 -2
  40. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  41. package/dist/auth/bootstrap_routes.js +5 -4
  42. package/dist/auth/cell_action_specs.d.ts +215 -15
  43. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  44. package/dist/auth/cell_action_specs.js +103 -16
  45. package/dist/auth/cell_actions.d.ts +40 -18
  46. package/dist/auth/cell_actions.d.ts.map +1 -1
  47. package/dist/auth/cell_actions.js +126 -19
  48. package/dist/auth/cell_audit_events.d.ts +1 -1
  49. package/dist/auth/cell_audit_events.d.ts.map +1 -1
  50. package/dist/auth/cell_audit_events.js +3 -2
  51. package/dist/auth/cell_audit_metadata.d.ts +11 -0
  52. package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
  53. package/dist/auth/cell_audit_metadata.js +10 -0
  54. package/dist/auth/cell_grant_action_specs.d.ts +7 -0
  55. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  56. package/dist/auth/cell_grant_action_specs.js +7 -0
  57. package/dist/auth/cell_grant_actions.d.ts +36 -4
  58. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  59. package/dist/auth/cell_grant_actions.js +20 -3
  60. package/dist/auth/credential_type_schema.d.ts +3 -2
  61. package/dist/auth/credential_type_schema.d.ts.map +1 -1
  62. package/dist/auth/credential_type_schema.js +3 -2
  63. package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
  64. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  65. package/dist/auth/role_grant_offer_action_specs.js +25 -3
  66. package/dist/auth/role_grant_offer_actions.d.ts +9 -4
  67. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  68. package/dist/auth/role_grant_offer_actions.js +58 -7
  69. package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
  70. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
  71. package/dist/auth/role_grant_offer_notifications.js +3 -2
  72. package/dist/auth/role_grant_offer_queries.d.ts +20 -0
  73. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  74. package/dist/auth/role_grant_offer_queries.js +35 -0
  75. package/dist/auth/role_grant_queries.d.ts +27 -9
  76. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  77. package/dist/auth/role_grant_queries.js +40 -13
  78. package/dist/auth/role_schema.d.ts +14 -8
  79. package/dist/auth/role_schema.d.ts.map +1 -1
  80. package/dist/auth/role_schema.js +6 -3
  81. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  82. package/dist/auth/self_service_role_actions.js +20 -0
  83. package/dist/db/CLAUDE.md +9 -2
  84. package/dist/db/cell_ddl.d.ts +9 -1
  85. package/dist/db/cell_ddl.d.ts.map +1 -1
  86. package/dist/db/cell_ddl.js +11 -1
  87. package/dist/db/cell_queries.d.ts +53 -7
  88. package/dist/db/cell_queries.d.ts.map +1 -1
  89. package/dist/db/cell_queries.js +61 -7
  90. package/dist/http/body_limit.d.ts +3 -1
  91. package/dist/http/body_limit.d.ts.map +1 -1
  92. package/dist/http/body_limit.js +6 -1
  93. package/dist/http/jsonrpc.d.ts +9 -0
  94. package/dist/http/jsonrpc.d.ts.map +1 -1
  95. package/dist/http/jsonrpc.js +17 -0
  96. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  97. package/dist/http/jsonrpc_errors.js +29 -20
  98. package/dist/http/proxy.d.ts +4 -2
  99. package/dist/http/proxy.d.ts.map +1 -1
  100. package/dist/http/proxy.js +7 -2
  101. package/dist/http/surface.d.ts +3 -1
  102. package/dist/http/surface.d.ts.map +1 -1
  103. package/dist/http/surface.js +7 -4
  104. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  105. package/dist/realtime/sse_auth_guard.js +3 -2
  106. package/dist/schema_meta.d.ts +32 -1
  107. package/dist/schema_meta.d.ts.map +1 -1
  108. package/dist/schema_meta.js +58 -5
  109. package/dist/server/app_server.d.ts +13 -5
  110. package/dist/server/app_server.d.ts.map +1 -1
  111. package/dist/server/app_server.js +32 -8
  112. package/dist/testing/CLAUDE.md +34 -21
  113. package/dist/testing/app_server.d.ts +3 -1
  114. package/dist/testing/app_server.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  116. package/dist/testing/cross_backend/actor_lookup.js +27 -0
  117. package/dist/testing/cross_backend/capabilities.d.ts +2 -1
  118. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  119. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/cell_gated_create.js +109 -13
  121. package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
  122. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
  123. package/dist/testing/cross_backend/cell_moderate.js +68 -4
  124. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
  126. package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
  127. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/conformance_table.js +12 -10
  129. package/dist/testing/cross_backend/differential.d.ts +7 -0
  130. package/dist/testing/cross_backend/differential.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/differential.js +17 -0
  132. package/dist/testing/cross_backend/expected_schema.json +1 -0
  133. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  134. package/dist/testing/cross_backend/full_spine_mount.js +5 -4
  135. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
  137. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
  138. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
  140. package/dist/testing/cross_backend/transport_composition.js +3 -3
  141. package/package.json +1 -1
@@ -26,8 +26,11 @@
26
26
  * multiple matching role_grants in the supplied scope set into one row.
27
27
  *
28
28
  * When `scope_ids` is omitted (admin-only global path; the handler
29
- * gates), no role_grant join — every actor with a matching prefix is
30
- * returned.
29
+ * gates), no role_grant join — every active actor with a matching prefix
30
+ * is returned.
31
+ *
32
+ * Both paths return only active actors of active accounts: a soft-deleted
33
+ * actor, or any actor of a soft-deleted account, is never a search result.
31
34
  *
32
35
  * ## Info-leak posture (see `auth/actor_search_action_specs.ts` §audit)
33
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"actor_search_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/actor_search_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IACrC,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;CACd;AASD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,OAAO,qBAAqB,KAC1B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAmC/B,CAAC"}
1
+ {"version":3,"file":"actor_search_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/actor_search_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IACrC,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;CACd;AASD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,OAAO,qBAAqB,KAC1B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAuC/B,CAAC"}
@@ -26,8 +26,11 @@
26
26
  * multiple matching role_grants in the supplied scope set into one row.
27
27
  *
28
28
  * When `scope_ids` is omitted (admin-only global path; the handler
29
- * gates), no role_grant join — every actor with a matching prefix is
30
- * returned.
29
+ * gates), no role_grant join — every active actor with a matching prefix
30
+ * is returned.
31
+ *
32
+ * Both paths return only active actors of active accounts: a soft-deleted
33
+ * actor, or any actor of a soft-deleted account, is never a search result.
31
34
  *
32
35
  * ## Info-leak posture (see `auth/actor_search_action_specs.ts` §audit)
33
36
  *
@@ -69,7 +72,9 @@ export const query_actor_search = async (deps, input) => {
69
72
  return deps.db.query(`SELECT act.id, a.username, act.name AS display_name
70
73
  FROM actor act
71
74
  JOIN account a ON a.id = act.account_id
72
- WHERE LOWER(act.name) LIKE $1 || '%' ESCAPE '\\'
75
+ WHERE act.deleted_at IS NULL
76
+ AND a.deleted_at IS NULL
77
+ AND LOWER(act.name) LIKE $1 || '%' ESCAPE '\\'
73
78
  ORDER BY display_name, id
74
79
  LIMIT $2`, [escaped_prefix, input.limit]);
75
80
  }
@@ -85,7 +90,9 @@ export const query_actor_search = async (deps, input) => {
85
90
  AND rg.scope_id = ANY($2)
86
91
  AND rg.revoked_at IS NULL
87
92
  AND (rg.expires_at IS NULL OR rg.expires_at > NOW())
88
- WHERE LOWER(act.name) LIKE $1 || '%' ESCAPE '\\'
93
+ WHERE act.deleted_at IS NULL
94
+ AND a.deleted_at IS NULL
95
+ AND LOWER(act.name) LIKE $1 || '%' ESCAPE '\\'
89
96
  ORDER BY display_name, id
90
97
  LIMIT $3`, [escaped_prefix, scope_ids, input.limit]);
91
98
  };
@@ -286,10 +286,10 @@ export declare const ERROR_CANNOT_DELETE_KEEPER: "cannot_delete_keeper";
286
286
  * `data.reason` (403) on `account_delete` / `account_purge` when the target
287
287
  * is the **sole remaining active admin** — removing it would leave the
288
288
  * system with no account that can authenticate into the admin surface (and
289
- * `account_undelete` is itself admin-gated). Unlike the keeper guard this is
290
- * keeper-recoverable (a keeper can re-grant admin), but the guard avoids the
291
- * foot-gun of an admin tombstoning the last admin in one call. Soft-deleted
292
- * admins don't count toward the tally (they can't log in). Mirrors the Rust
289
+ * `account_undelete` is itself admin-gated), and no web path re-grants admin.
290
+ * Soft-deleted admins don't count toward the tally (they can't log in), and
291
+ * the count locks the rows it counted, so two removals racing for the last
292
+ * two admins can't both pass. Mirrors the Rust
293
293
  * `ERROR_CANNOT_DELETE_LAST_ADMIN`.
294
294
  */
295
295
  export declare const ERROR_CANNOT_DELETE_LAST_ADMIN: "cannot_delete_last_admin";
@@ -237,10 +237,10 @@ export const ERROR_CANNOT_DELETE_KEEPER = 'cannot_delete_keeper';
237
237
  * `data.reason` (403) on `account_delete` / `account_purge` when the target
238
238
  * is the **sole remaining active admin** — removing it would leave the
239
239
  * system with no account that can authenticate into the admin surface (and
240
- * `account_undelete` is itself admin-gated). Unlike the keeper guard this is
241
- * keeper-recoverable (a keeper can re-grant admin), but the guard avoids the
242
- * foot-gun of an admin tombstoning the last admin in one call. Soft-deleted
243
- * admins don't count toward the tally (they can't log in). Mirrors the Rust
240
+ * `account_undelete` is itself admin-gated), and no web path re-grants admin.
241
+ * Soft-deleted admins don't count toward the tally (they can't log in), and
242
+ * the count locks the rows it counted, so two removals racing for the last
243
+ * two admins can't both pass. Mirrors the Rust
244
244
  * `ERROR_CANNOT_DELETE_LAST_ADMIN`.
245
245
  */
246
246
  export const ERROR_CANNOT_DELETE_LAST_ADMIN = 'cannot_delete_last_admin';
@@ -1 +1 @@
1
- {"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAI/F,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AAyC1B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AA0D3D,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IAClC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,yBAAyB,EAC/B,UAAS,kBAAuB,KAC9B,KAAK,CAAC,SAAS,CA+gBjB,CAAC"}
1
+ {"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAI/F,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AA0C1B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AA0D3D,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IAClC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,yBAAyB,EAC/B,UAAS,kBAAuB,KAC9B,KAAK,CAAC,SAAS,CAmjBjB,CAAC"}
@@ -34,8 +34,9 @@ import { credential_type_required_error } from '../actions/perform_action.js';
34
34
  import { jsonrpc_errors, ThrownJsonrpcError } from '../http/jsonrpc_errors.js';
35
35
  import { builtin_role_specs_by_name, list_roles_with_grant_path, ROLE_ADMIN, ROLE_KEEPER } from './role_schema.js';
36
36
  import { has_scoped_role } from './request_context.js';
37
- import { query_account_has_global_role, query_account_has_active_global_role, query_count_active_accounts_with_global_role } from './role_grant_queries.js';
37
+ import { query_account_has_global_role, query_account_has_active_global_role, query_count_active_accounts_with_global_role_for_update } from './role_grant_queries.js';
38
38
  import { GRANT_PATH_ADMIN } from './grant_path_schema.js';
39
+ import { query_role_grant_offer_supersede_outgoing } from './role_grant_offer_queries.js';
39
40
  import { query_account_by_email, query_account_by_id, query_account_by_username, query_account_soft_delete, query_account_undelete, query_actor_soft_delete, query_actor_undelete, query_actors_by_account, query_admin_account_list, query_purge_account } from './account_queries.js';
40
41
  import { query_session_list_all_active, query_session_revoke_all_for_account } from './session_queries.js';
41
42
  import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
@@ -245,9 +246,10 @@ export const create_admin_actions = (deps, options = {}) => {
245
246
  // keeper/daemon auth with no recovery (keeper role is non-web-
246
247
  // revocable and purge itself needs keeper auth). Out-of-band only.
247
248
  // 2. **Last admin** — the sole remaining *active* admin is protected
248
- // (soft-deleted admins can't log in, so they don't count). Unlike
249
- // the keeper guard this is keeper-recoverable, but it stops an admin
250
- // tombstoning the last admin in one call.
249
+ // (soft-deleted admins can't log in, so they don't count). The count
250
+ // locks the rows it counted, so two removals racing for the last two
251
+ // admins serialize and the second is refused — no web path re-grants
252
+ // a lost admin. `role_grant_revoke` guards the admin grant the same way.
251
253
  // A missing account holds neither role here and falls through to the
252
254
  // handler's own not-found path. `event_type` selects the audit event so
253
255
  // the failure row matches the operation in flight.
@@ -275,15 +277,38 @@ export const create_admin_actions = (deps, options = {}) => {
275
277
  // admin while another active admin exists. Keeper branch above stays
276
278
  // unconditional; the last-admin branch fires only for an *active* target.
277
279
  if ((await query_account_has_active_global_role(ctx, target_account_id, ROLE_ADMIN)) &&
278
- (await query_count_active_accounts_with_global_role(ctx, ROLE_ADMIN)) <= 1) {
280
+ (await query_count_active_accounts_with_global_role_for_update(ctx, ROLE_ADMIN)) <= 1) {
279
281
  deny(ERROR_CANNOT_DELETE_LAST_ADMIN, `cannot ${verb} the last admin account`);
280
282
  }
281
283
  };
284
+ // Supersede every pending offer the removed account's actors made, audited
285
+ // — the cascade a revoke runs on the revokee's offers. An offer records no
286
+ // authority, so one outliving its grantor's account would still confer on
287
+ // accept. No supersede notification: a cascade notifies the grantor, and the
288
+ // grantor's connections close with its account. `cause_id` is the account.
289
+ const cancel_outgoing_offers = async (ctx, target_account_id, actors) => {
290
+ const superseded = await query_role_grant_offer_supersede_outgoing(ctx, actors.map((a) => a.id));
291
+ for (const offer of superseded) {
292
+ deps.audit.emit_role_grant_target(ctx, ctx.auth, {
293
+ event_type: 'role_grant_offer_supersede',
294
+ target_account_id: offer.to_account_id,
295
+ target_actor_id: offer.to_actor_id,
296
+ metadata: {
297
+ offer_id: offer.id,
298
+ role: offer.role,
299
+ scope_id: offer.scope_id,
300
+ reason: 'account_deleted',
301
+ cause_id: target_account_id
302
+ }
303
+ });
304
+ }
305
+ };
282
306
  // Soft-delete an account (reversible tombstone). Self-or-admin:
283
307
  // deleting another account requires the admin role. A self-delete is
284
308
  // session-only — enforced here, not on the spec, whose gate stays open so
285
309
  // an admin deleting another account keeps any credential. Tombstones the
286
- // account + its actor(s), revokes sessions/tokens, closes sockets, and
310
+ // account + its actor(s), supersedes the pending offers its actors made,
311
+ // revokes sessions/tokens, closes sockets, and
287
312
  // emits `account_delete` + one `actor_delete` per soft-deleted actor —
288
313
  // each carrying its identity snapshot. `delete` = soft.
289
314
  const account_delete_handler = async (input, ctx) => {
@@ -322,6 +347,7 @@ export const create_admin_actions = (deps, options = {}) => {
322
347
  });
323
348
  throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
324
349
  }
350
+ await cancel_outgoing_offers(ctx, target_account_id, actors);
325
351
  const soft_deleted_actors = [];
326
352
  for (const actor of actors) {
327
353
  if (await query_actor_soft_delete(ctx, actor.id, auth.actor.id)) {
@@ -374,6 +400,9 @@ export const create_admin_actions = (deps, options = {}) => {
374
400
  await assert_account_removable(ctx, input.account_id, 'account_purge');
375
401
  // Snapshot actors before the cascade removes them.
376
402
  const actors = await query_actors_by_account(ctx, input.account_id);
403
+ // Before the cascade, whose `ON DELETE CASCADE` would take the offers
404
+ // with no audit row.
405
+ await cancel_outgoing_offers(ctx, input.account_id, actors);
377
406
  const snapshot = await query_purge_account(ctx, input.account_id);
378
407
  if (!snapshot) {
379
408
  deps.audit.emit(ctx, {
@@ -1,6 +1,14 @@
1
1
  import type { RpcAction } from '../actions/action_rpc.js';
2
2
  import { type CellActionDeps } from './cell_actions.js';
3
+ import { type CellGrantCreateAuthorize } from './cell_grant_actions.js';
3
4
  import type { RoleSchemaResult } from './role_schema.js';
5
+ /**
6
+ * Dependencies for `create_all_cell_actions` — the CRUD factory's
7
+ * `CellActionDeps` plus the grant factory's optional `authorize_grant_create`.
8
+ */
9
+ export type AllCellActionDeps = CellActionDeps & {
10
+ authorize_grant_create?: CellGrantCreateAuthorize;
11
+ };
4
12
  /**
5
13
  * Options for `create_all_cell_actions`.
6
14
  *
@@ -27,9 +35,10 @@ export interface AllCellActionsOptions {
27
35
  * Distinct from `create_cell_actions` (the CRUD-only factory this bundles) —
28
36
  * reach for this whenever a backend mounts the complete cell layer.
29
37
  *
30
- * @param deps - `CellActionDeps` (`log`, `audit`, optional `validate_data`)
38
+ * @param deps - `AllCellActionDeps` (`log`, `audit`, the optional `validate_data`,
39
+ * `authorize_create`, and `authorize_grant_create`)
31
40
  * @param options - the role schema for grant validation
32
41
  * @returns every cell `RpcAction`, in mount order
33
42
  */
34
- export declare const create_all_cell_actions: (deps: CellActionDeps, options: AllCellActionsOptions) => Array<RpcAction>;
43
+ export declare const create_all_cell_actions: (deps: AllCellActionDeps, options: AllCellActionsOptions) => Array<RpcAction>;
35
44
  //# sourceMappingURL=all_cell_actions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"all_cell_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/all_cell_actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAK7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,cAAc,EACpB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAMjB,CAAC"}
1
+ {"version":3,"file":"all_cell_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/all_cell_actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAA6B,KAAK,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAInG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAChD,sBAAsB,CAAC,EAAE,wBAAwB,CAAC;CAClD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,iBAAiB,EACvB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAMjB,CAAC"}
@@ -19,7 +19,8 @@ import { create_cell_audit_actions } from './cell_audit_actions.js';
19
19
  * Distinct from `create_cell_actions` (the CRUD-only factory this bundles) —
20
20
  * reach for this whenever a backend mounts the complete cell layer.
21
21
  *
22
- * @param deps - `CellActionDeps` (`log`, `audit`, optional `validate_data`)
22
+ * @param deps - `AllCellActionDeps` (`log`, `audit`, the optional `validate_data`,
23
+ * `authorize_create`, and `authorize_grant_create`)
23
24
  * @param options - the role schema for grant validation
24
25
  * @returns every cell `RpcAction`, in mount order
25
26
  */
@@ -43,7 +43,8 @@
43
43
  * - A **failure** row is announced as soon as it is written. It records an
44
44
  * attempt, which happened whether or not the request's transaction commits.
45
45
  * - A **success** row is announced only after the request's transaction
46
- * commits, and never when the handler throws: the fan-out is queued on
46
+ * commits (after the handler returns, on a route without one), and never
47
+ * when the handler throws: the fan-out is queued on
47
48
  * `ctx.post_commit_effects` and awaits the write. A success row reports a
48
49
  * state change, and the listeners act on it — the revocation listeners close
49
50
  * connections, the audit stream broadcasts it. Announced before the commit,
@@ -53,8 +54,13 @@
53
54
  * A success row emitted by a handler that later throws is still in the table —
54
55
  * the write is outside the transaction — but is never announced.
55
56
  *
56
- * The Rust spine reaches the same order by writing a success row inside the
57
- * transaction and queueing its fan-out behind the commit.
57
+ * A route that runs without a request transaction and opens its own around
58
+ * just its writes (`/login`, `/password`) emits its success row once that
59
+ * transaction has committed, so the row is written after the change it
60
+ * reports. The Rust spine follows the same order: a success row emitted inside
61
+ * a request transaction is written in that transaction and its fan-out queued
62
+ * behind the commit, and `/login` and `/password` emit on the pool after their
63
+ * own transaction commits.
58
64
  *
59
65
  * Listeners are a documented registration seam — `create_app_server`
60
66
  * registers additional listeners via `add_listener` after the backend is
@@ -1 +1 @@
1
- {"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"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"}
@@ -43,7 +43,8 @@
43
43
  * - A **failure** row is announced as soon as it is written. It records an
44
44
  * attempt, which happened whether or not the request's transaction commits.
45
45
  * - A **success** row is announced only after the request's transaction
46
- * commits, and never when the handler throws: the fan-out is queued on
46
+ * commits (after the handler returns, on a route without one), and never
47
+ * when the handler throws: the fan-out is queued on
47
48
  * `ctx.post_commit_effects` and awaits the write. A success row reports a
48
49
  * state change, and the listeners act on it — the revocation listeners close
49
50
  * connections, the audit stream broadcasts it. Announced before the commit,
@@ -53,8 +54,13 @@
53
54
  * A success row emitted by a handler that later throws is still in the table —
54
55
  * the write is outside the transaction — but is never announced.
55
56
  *
56
- * The Rust spine reaches the same order by writing a success row inside the
57
- * transaction and queueing its fan-out behind the commit.
57
+ * A route that runs without a request transaction and opens its own around
58
+ * just its writes (`/login`, `/password`) emits its success row once that
59
+ * transaction has committed, so the row is written after the change it
60
+ * reports. The Rust spine follows the same order: a success row emitted inside
61
+ * a request transaction is written in that transaction and its fan-out queued
62
+ * behind the commit, and `/login` and `/password` emit on the pool after their
63
+ * own transaction commits.
58
64
  *
59
65
  * Listeners are a documented registration seam — `create_app_server`
60
66
  * registers additional listeners via `add_listener` after the backend is
@@ -263,6 +263,7 @@ export declare const audit_metadata_schemas: Readonly<{
263
263
  sibling_accepted: "sibling_accepted";
264
264
  role_grant_revoked: "role_grant_revoked";
265
265
  scope_destroyed: "scope_destroyed";
266
+ account_deleted: "account_deleted";
266
267
  }>;
267
268
  cause_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
268
269
  }, z.core.$loose>;
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
@@ -348,19 +348,23 @@ export const audit_metadata_schemas = Object.freeze({
348
348
  }),
349
349
  // Emitted when an offer is obsoleted by an external event. `reason`
350
350
  // distinguishes the trigger; `cause_id` points to the accepted offer
351
- // (for `sibling_accepted`), the revoked role_grant (for `role_grant_revoked`),
352
- // or the destroyed parent scope row (for `scope_destroyed`).
351
+ // (for `sibling_accepted`), the revoked role_grant (for `role_grant_revoked`
352
+ // — the recipient's grant of the offered role, or a grant of the grantor's),
353
+ // the destroyed parent scope row (for `scope_destroyed`), or the grantor's
354
+ // removed account (for `account_deleted`).
353
355
  role_grant_offer_supersede: z.looseObject({
354
356
  offer_id: Uuid.meta({ description: 'Id of the superseded offer.' }),
355
357
  role: z.string().meta({ description: 'Role that was offered.' }),
356
358
  scope_id: Uuid.nullish().meta({
357
359
  description: 'Scope of the offered role; null for global offers.'
358
360
  }),
359
- reason: z.enum(['sibling_accepted', 'role_grant_revoked', 'scope_destroyed']).meta({
360
- description: 'Trigger that obsoleted the offer: a sibling offer was accepted, the resulting role_grant was revoked, or the parent scope row was destroyed.'
361
+ reason: z
362
+ .enum(['sibling_accepted', 'role_grant_revoked', 'scope_destroyed', 'account_deleted'])
363
+ .meta({
364
+ description: "Trigger that obsoleted the offer: a sibling offer was accepted, the resulting role_grant or one of the grantor's was revoked, the parent scope row was destroyed, or the grantor's account was deleted or purged."
361
365
  }),
362
366
  cause_id: Uuid.meta({
363
- description: 'Row that caused the supersede: accepted offer (`sibling_accepted`), revoked role_grant (`role_grant_revoked`), or destroyed parent scope row (`scope_destroyed`).'
367
+ description: "Row that caused the supersede: accepted offer (`sibling_accepted`), revoked role_grant (`role_grant_revoked`), destroyed parent scope row (`scope_destroyed`), or the grantor's account (`account_deleted`)."
364
368
  })
365
369
  }),
366
370
  invite_create: z.looseObject({
@@ -66,6 +66,29 @@ export interface BootstrapAccountDeps {
66
66
  /** Structured logger instance. */
67
67
  log: Logger;
68
68
  }
69
+ /**
70
+ * The fewest bytes a bootstrap token holds, counted after its one optional
71
+ * trailing LF. A shorter file is read as unreadable: an empty or `"\n"` file
72
+ * would otherwise leave bootstrap advertised as open while nothing can ever
73
+ * match it, and a short token is guessable. Every generator writes at least
74
+ * this much (32 random bytes, base64url or hex encoded). Twin of the Rust
75
+ * `BOOTSTRAP_TOKEN_MIN_BYTES` (`fuz_auth`).
76
+ */
77
+ export declare const BOOTSTRAP_TOKEN_MIN_BYTES = 32;
78
+ /**
79
+ * Read the bootstrap token the way both the availability check and the
80
+ * request-time compare need it: the hardened secure read, less one trailing
81
+ * LF, refused when shorter than `BOOTSTRAP_TOKEN_MIN_BYTES`. A short token
82
+ * logs an error naming the file before the throw — it is a misconfiguration
83
+ * the operator must fix, never a quiet "unavailable". Twin of the Rust
84
+ * `bootstrap_token_from_file`.
85
+ *
86
+ * @param deps - the secure read and a logger for the short-token error
87
+ * @param token_path - the bootstrap token file
88
+ * @returns the token bytes the provided token is compared against
89
+ * @throws Error when the secure read refuses the file or the token is too short
90
+ */
91
+ export declare const read_bootstrap_token: (deps: Pick<BootstrapAccountDeps, "read_secure_file" | "log">, token_path: string) => Promise<Uint8Array>;
69
92
  /**
70
93
  * Bootstrap the first account with keeper and admin privileges.
71
94
  *
@@ -73,7 +96,8 @@ export interface BootstrapAccountDeps {
73
96
  * attempts (TOCTOU). The full flow runs in a single transaction:
74
97
  *
75
98
  * 1. Read and verify the bootstrap token (before transaction) — the file's
76
- * raw bytes less one optional trailing LF, compared in constant time
99
+ * raw bytes less one optional trailing LF, at least
100
+ * `BOOTSTRAP_TOKEN_MIN_BYTES` long, compared in constant time
77
101
  * 2. Hash the password (CPU-intensive, before transaction)
78
102
  * 3. Acquire the bootstrap lock atomically (inside transaction)
79
103
  * 4. Create account + actor
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_account.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_account.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EACN,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,gDAAgD;AAChD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IACrD,wFAAwF;IACxF,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED,gCAAgC;AAChC,MAAM,MAAM,uBAAuB,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,0BAA0B,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GACpE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,wBAAwB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GAClE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC;AAEjE,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,GAAG,uBAAuB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,EAAE,CAAC;IACP,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAClD,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACZ;AAcD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,oBAAoB,EAC1B,gBAAgB,MAAM,EACtB,OAAO,qBAAqB,KAC1B,OAAO,CAAC,sBAAsB,CA6EhC,CAAC"}
1
+ {"version":3,"file":"bootstrap_account.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_account.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EACN,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,gDAAgD;AAChD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IACrD,wFAAwF;IACxF,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED,gCAAgC;AAChC,MAAM,MAAM,uBAAuB,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,0BAA0B,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GACpE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,wBAAwB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GAClE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC;AAEjE,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,GAAG,uBAAuB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,EAAE,CAAC;IACP,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAClD,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACZ;AAcD;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,IAAI,CAAC,oBAAoB,EAAE,kBAAkB,GAAG,KAAK,CAAC,EAC5D,YAAY,MAAM,KAChB,OAAO,CAAC,UAAU,CAQpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,oBAAoB,EAC1B,gBAAgB,MAAM,EACtB,OAAO,qBAAqB,KAC1B,OAAO,CAAC,sBAAsB,CA8EhC,CAAC"}
@@ -22,6 +22,37 @@ import { query_create_role_grant } from './role_grant_queries.js';
22
22
  * `strip_one_trailing_newline` (`fuz_auth`).
23
23
  */
24
24
  const strip_one_trailing_newline = (bytes) => bytes.at(-1) === 0x0a ? bytes.subarray(0, -1) : bytes;
25
+ /**
26
+ * The fewest bytes a bootstrap token holds, counted after its one optional
27
+ * trailing LF. A shorter file is read as unreadable: an empty or `"\n"` file
28
+ * would otherwise leave bootstrap advertised as open while nothing can ever
29
+ * match it, and a short token is guessable. Every generator writes at least
30
+ * this much (32 random bytes, base64url or hex encoded). Twin of the Rust
31
+ * `BOOTSTRAP_TOKEN_MIN_BYTES` (`fuz_auth`).
32
+ */
33
+ export const BOOTSTRAP_TOKEN_MIN_BYTES = 32;
34
+ /**
35
+ * Read the bootstrap token the way both the availability check and the
36
+ * request-time compare need it: the hardened secure read, less one trailing
37
+ * LF, refused when shorter than `BOOTSTRAP_TOKEN_MIN_BYTES`. A short token
38
+ * logs an error naming the file before the throw — it is a misconfiguration
39
+ * the operator must fix, never a quiet "unavailable". Twin of the Rust
40
+ * `bootstrap_token_from_file`.
41
+ *
42
+ * @param deps - the secure read and a logger for the short-token error
43
+ * @param token_path - the bootstrap token file
44
+ * @returns the token bytes the provided token is compared against
45
+ * @throws Error when the secure read refuses the file or the token is too short
46
+ */
47
+ export const read_bootstrap_token = async (deps, token_path) => {
48
+ const token = strip_one_trailing_newline(await deps.read_secure_file(token_path));
49
+ if (token.length < BOOTSTRAP_TOKEN_MIN_BYTES) {
50
+ const message = `bootstrap token at ${token_path} is ${token.length} bytes, under the ${BOOTSTRAP_TOKEN_MIN_BYTES}-byte minimum — regenerate it`;
51
+ deps.log.error(message);
52
+ throw new Error(message);
53
+ }
54
+ return token;
55
+ };
25
56
  /**
26
57
  * Bootstrap the first account with keeper and admin privileges.
27
58
  *
@@ -29,7 +60,8 @@ const strip_one_trailing_newline = (bytes) => bytes.at(-1) === 0x0a ? bytes.suba
29
60
  * attempts (TOCTOU). The full flow runs in a single transaction:
30
61
  *
31
62
  * 1. Read and verify the bootstrap token (before transaction) — the file's
32
- * raw bytes less one optional trailing LF, compared in constant time
63
+ * raw bytes less one optional trailing LF, at least
64
+ * `BOOTSTRAP_TOKEN_MIN_BYTES` long, compared in constant time
33
65
  * 2. Hash the password (CPU-intensive, before transaction)
34
66
  * 3. Acquire the bootstrap lock atomically (inside transaction)
35
67
  * 4. Create account + actor
@@ -48,11 +80,12 @@ export const bootstrap_account = async (deps, provided_token, input) => {
48
80
  const { db, token_path, read_secure_file, delete_file, password, log } = deps;
49
81
  // 1. Read and verify token (non-destructive, before transaction). The
50
82
  // secure read fails loud on a symlink / non-regular file / permissive mode /
51
- // oversized file — every refusal masks as the same TOKEN_FILE_MISSING the
52
- // wire always carried, with the specific reason logged for the operator.
83
+ // oversized file, and the read refuses a token under the minimum length —
84
+ // every refusal masks as the same TOKEN_FILE_MISSING the wire always
85
+ // carried, with the specific reason logged for the operator.
53
86
  let expected_token;
54
87
  try {
55
- expected_token = strip_one_trailing_newline(await read_secure_file(token_path));
88
+ expected_token = await read_bootstrap_token({ read_secure_file, log }, token_path);
56
89
  }
57
90
  catch (err) {
58
91
  log.warn(`bootstrap token file rejected: ${to_error_message(err)}`);
@@ -65,10 +65,11 @@ export interface CheckBootstrapStatusDeps {
65
65
  * Bootstrap is available when:
66
66
  * 1. A token path is configured
67
67
  * 2. The token file passes the secure read (exists, not a symlink, mode
68
- * `0600`/`0400`, within the size cap)
68
+ * `0600`/`0400`, within the size cap) and holds at least
69
+ * `BOOTSTRAP_TOKEN_MIN_BYTES` (a shorter one logs an error)
69
70
  * 3. The `bootstrap_lock` table shows `bootstrapped = false`
70
71
  *
71
- * The probe **reads through the same `read_secure_file` the request-time
72
+ * The probe **reads through the same `read_bootstrap_token` the request-time
72
73
  * read uses**, so "bootstrap is available" and "the token file can actually
73
74
  * be read" can't drift apart — an availability check laxer than the read it
74
75
  * gates is the misconfiguration that reports green at boot and fails at
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAAqB,KAAK,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEzF,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGxE,OAAO,EAGN,KAAK,WAAW,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E;;;;;;;;;;OAUG;IACH,yBAAyB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,kFAAkF;IAClF,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,+EAA+E;IAC/E,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,KACpC,OAAO,CAAC,eAAe,CAyBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CA0HjB,CAAC"}
1
+ {"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAGN,KAAK,uBAAuB,EAC5B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGxE,OAAO,EAGN,KAAK,WAAW,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E;;;;;;;;;;OAUG;IACH,yBAAyB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,kFAAkF;IAClF,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,+EAA+E;IAC/E,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,KACpC,OAAO,CAAC,eAAe,CAyBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CA0HjB,CAAC"}
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { to_error_message } from '@fuzdev/fuz_util/error.ts';
10
10
  import { create_session_and_set_cookie } from './session_middleware.js';
11
- import { bootstrap_account } from './bootstrap_account.js';
11
+ import { bootstrap_account, read_bootstrap_token } from './bootstrap_account.js';
12
12
  import { bootstrap_route_shape } from './bootstrap_route_schema.js';
13
13
  import { get_route_input } from '../http/route_spec.js';
14
14
  import { get_client_ip } from '../http/client_ip.js';
@@ -21,10 +21,11 @@ import { ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING } from '../http/er
21
21
  * Bootstrap is available when:
22
22
  * 1. A token path is configured
23
23
  * 2. The token file passes the secure read (exists, not a symlink, mode
24
- * `0600`/`0400`, within the size cap)
24
+ * `0600`/`0400`, within the size cap) and holds at least
25
+ * `BOOTSTRAP_TOKEN_MIN_BYTES` (a shorter one logs an error)
25
26
  * 3. The `bootstrap_lock` table shows `bootstrapped = false`
26
27
  *
27
- * The probe **reads through the same `read_secure_file` the request-time
28
+ * The probe **reads through the same `read_bootstrap_token` the request-time
28
29
  * read uses**, so "bootstrap is available" and "the token file can actually
29
30
  * be read" can't drift apart — an availability check laxer than the read it
30
31
  * gates is the misconfiguration that reports green at boot and fails at
@@ -41,7 +42,7 @@ export const check_bootstrap_status = async (deps, options) => {
41
42
  return { available: false, token_path: null };
42
43
  }
43
44
  try {
44
- await read_secure_file(token_path);
45
+ await read_bootstrap_token({ read_secure_file, log }, token_path);
45
46
  }
46
47
  catch (err) {
47
48
  log.info(`Bootstrap unavailable: ${to_error_message(err)}`);