@fuzdev/fuz_app 0.141.0 → 0.142.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 (147) hide show
  1. package/dist/actions/CLAUDE.md +10 -5
  2. package/dist/actions/connection_closer.d.ts +19 -2
  3. package/dist/actions/connection_closer.d.ts.map +1 -1
  4. package/dist/actions/connection_closer.js +25 -2
  5. package/dist/actions/perform_action.d.ts +10 -0
  6. package/dist/actions/perform_action.d.ts.map +1 -1
  7. package/dist/actions/perform_action.js +23 -9
  8. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  9. package/dist/actions/register_ws_endpoint.js +5 -2
  10. package/dist/auth/CLAUDE.md +33 -13
  11. package/dist/auth/account_actions.d.ts.map +1 -1
  12. package/dist/auth/account_actions.js +12 -1
  13. package/dist/auth/account_route_schema.d.ts.map +1 -1
  14. package/dist/auth/account_route_schema.js +8 -0
  15. package/dist/auth/account_routes.d.ts +7 -2
  16. package/dist/auth/account_routes.d.ts.map +1 -1
  17. package/dist/auth/account_routes.js +240 -187
  18. package/dist/auth/admin_action_specs.d.ts +6 -1
  19. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  20. package/dist/auth/admin_action_specs.js +6 -1
  21. package/dist/auth/admin_actions.d.ts.map +1 -1
  22. package/dist/auth/admin_actions.js +14 -2
  23. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  24. package/dist/auth/bootstrap_routes.js +85 -76
  25. package/dist/auth/cell_action_specs.d.ts +7 -3
  26. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  27. package/dist/auth/cell_action_specs.js +9 -5
  28. package/dist/auth/cell_actions.d.ts +3 -3
  29. package/dist/auth/cell_actions.d.ts.map +1 -1
  30. package/dist/auth/cell_actions.js +67 -38
  31. package/dist/auth/cell_authorize.d.ts +69 -8
  32. package/dist/auth/cell_authorize.d.ts.map +1 -1
  33. package/dist/auth/cell_authorize.js +89 -9
  34. package/dist/auth/cell_field_action_specs.d.ts +5 -4
  35. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  36. package/dist/auth/cell_field_action_specs.js +7 -7
  37. package/dist/auth/cell_field_actions.d.ts +7 -5
  38. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  39. package/dist/auth/cell_field_actions.js +24 -22
  40. package/dist/auth/cell_grant_action_specs.d.ts +6 -0
  41. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  42. package/dist/auth/cell_grant_action_specs.js +6 -0
  43. package/dist/auth/cell_grant_actions.d.ts +3 -1
  44. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  45. package/dist/auth/cell_grant_actions.js +16 -2
  46. package/dist/auth/cell_item_action_specs.d.ts +5 -4
  47. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  48. package/dist/auth/cell_item_action_specs.js +7 -7
  49. package/dist/auth/cell_item_actions.d.ts +6 -5
  50. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  51. package/dist/auth/cell_item_actions.js +23 -22
  52. package/dist/auth/cell_relation_list_limit.d.ts +20 -0
  53. package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
  54. package/dist/auth/cell_relation_list_limit.js +27 -0
  55. package/dist/auth/cell_relation_visibility.d.ts +4 -3
  56. package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
  57. package/dist/auth/cell_relation_visibility.js +6 -5
  58. package/dist/auth/cell_steward.d.ts +45 -0
  59. package/dist/auth/cell_steward.d.ts.map +1 -0
  60. package/dist/auth/cell_steward.js +53 -0
  61. package/dist/auth/request_context.d.ts +4 -0
  62. package/dist/auth/request_context.d.ts.map +1 -1
  63. package/dist/auth/request_context.js +6 -1
  64. package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
  65. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  66. package/dist/auth/role_grant_offer_action_specs.js +6 -1
  67. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  68. package/dist/auth/role_grant_offer_actions.js +23 -16
  69. package/dist/auth/role_grant_offer_queries.d.ts +8 -6
  70. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  71. package/dist/auth/role_grant_offer_queries.js +16 -8
  72. package/dist/auth/signup_routes.d.ts +4 -3
  73. package/dist/auth/signup_routes.d.ts.map +1 -1
  74. package/dist/auth/signup_routes.js +157 -134
  75. package/dist/auth/token_scope.d.ts +7 -1
  76. package/dist/auth/token_scope.d.ts.map +1 -1
  77. package/dist/auth/token_scope.js +8 -2
  78. package/dist/db/CLAUDE.md +32 -19
  79. package/dist/db/cell_ddl.d.ts +1 -2
  80. package/dist/db/cell_ddl.d.ts.map +1 -1
  81. package/dist/db/cell_ddl.js +1 -2
  82. package/dist/db/cell_field_queries.d.ts +20 -18
  83. package/dist/db/cell_field_queries.d.ts.map +1 -1
  84. package/dist/db/cell_field_queries.js +28 -22
  85. package/dist/db/cell_item_queries.d.ts +20 -14
  86. package/dist/db/cell_item_queries.d.ts.map +1 -1
  87. package/dist/db/cell_item_queries.js +28 -18
  88. package/dist/db/cell_moderation.d.ts +67 -0
  89. package/dist/db/cell_moderation.d.ts.map +1 -0
  90. package/dist/db/cell_moderation.js +66 -0
  91. package/dist/db/cell_queries.d.ts +44 -14
  92. package/dist/db/cell_queries.d.ts.map +1 -1
  93. package/dist/db/cell_queries.js +68 -45
  94. package/dist/db/cell_view_sql.d.ts +102 -0
  95. package/dist/db/cell_view_sql.d.ts.map +1 -0
  96. package/dist/db/cell_view_sql.js +112 -0
  97. package/dist/db/db_pg.d.ts +1 -1
  98. package/dist/db/db_pg.js +1 -1
  99. package/dist/http/auth_shape.d.ts +5 -2
  100. package/dist/http/auth_shape.d.ts.map +1 -1
  101. package/dist/http/auth_shape.js +5 -2
  102. package/dist/http/ip_canonical.d.ts +30 -3
  103. package/dist/http/ip_canonical.d.ts.map +1 -1
  104. package/dist/http/ip_canonical.js +50 -3
  105. package/dist/primitive_schemas.d.ts +10 -1
  106. package/dist/primitive_schemas.d.ts.map +1 -1
  107. package/dist/primitive_schemas.js +11 -2
  108. package/dist/rate_limiter.d.ts +179 -12
  109. package/dist/rate_limiter.d.ts.map +1 -1
  110. package/dist/rate_limiter.js +352 -42
  111. package/dist/server/serve_fact_route.d.ts +8 -4
  112. package/dist/server/serve_fact_route.d.ts.map +1 -1
  113. package/dist/server/serve_fact_route.js +15 -6
  114. package/dist/testing/CLAUDE.md +10 -3
  115. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  116. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  117. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  118. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  119. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  120. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  121. package/dist/testing/cross_backend/cell_relations.js +145 -2
  122. package/dist/testing/cross_backend/expected_schema.json +0 -1
  123. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  124. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/login_security.js +154 -0
  126. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  127. package/dist/testing/cross_backend/query_shape.js +24 -2
  128. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  129. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  130. package/dist/testing/cross_backend/spine_surface_constants.d.ts +1 -1
  131. package/dist/testing/cross_backend/spine_surface_constants.js +1 -1
  132. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  134. package/dist/testing/schema_introspect.js +1 -1
  135. package/dist/testing/schema_parity.d.ts +4 -3
  136. package/dist/testing/schema_parity.d.ts.map +1 -1
  137. package/dist/testing/schema_parity.js +4 -3
  138. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  139. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  140. package/dist/testing/schema_ready_fixture.js +2 -4
  141. package/dist/ui/auth_state.svelte.d.ts +10 -0
  142. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  143. package/dist/ui/auth_state.svelte.js +22 -4
  144. package/package.json +1 -1
  145. package/dist/db/cell_history_ddl.d.ts +0 -38
  146. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  147. package/dist/db/cell_history_ddl.js +0 -61
@@ -641,16 +641,21 @@ closed) — so a closed transport or registry hosts nothing more. Its Rust
641
641
  counterpart is `ShutdownCloser::close_all_sockets`, a supertrait of
642
642
  `SocketRevoker`.
643
643
 
644
- **Every revocation closes after its commit.** A handler never calls a closer
645
- inline: it calls `queue_connection_close(ctx, deps.connection_closer, target)`,
644
+ **Every revocation closes after its commit.** A handler inside a route or
645
+ action transaction never calls a closer inline: it calls
646
+ `queue_connection_close(ctx, deps.connection_closer, target)`,
646
647
  which puts the close on the request's post-commit queue (`emit_after_commit`).
647
648
  The close runs once the handler's transaction has committed and is discarded
648
649
  when the handler throws — a rolled-back revocation closes nothing. `target` is
649
650
  a `ConnectionCloseTarget` (`{kind: 'session', session_token_hash}` /
650
651
  `{kind: 'token', api_token_id}` / `{kind: 'account', account_id}` /
651
652
  `{kind: 'role', account_id, role}`);
652
- `close_connections(closer, target)` is the immediate form the queue runs. Twin
653
- of `queue_socket_close` / `SocketCloseTarget`.
653
+ `close_connections(closer, target)` is the immediate form the queue runs. A
654
+ handler with no wrapping transaction (`/login`, `/password`) opens its own
655
+ around its writes and closes straight after that commit with
656
+ `close_connections_after_commit(closer, target, log)`, which logs a failing
657
+ close as the post-commit flush does. Twin of `queue_socket_close` /
658
+ `SocketCloseTarget`.
654
659
 
655
660
  The order is load-bearing for admission: a connection registers, then re-reads
656
661
  its credential, so a close that missed the registration must belong to a
@@ -812,7 +817,7 @@ Composes the standard upgrade stack:
812
817
  1. `verify_request_source(allowed_origins)`
813
818
  2. `require_auth`
814
819
  3. `require_token_scope('surface:ws_upgrade')` — a narrowed token cannot hold a socket
815
- 4. Upgrade-time authorization phase — resolves the acting actor, seeds `REQUEST_CONTEXT_KEY` for the inner `register_action_ws`
820
+ 4. Upgrade-time authorization phase — resolves the acting actor (`?acting=` through `parse_acting`; malformed reads as omitted), seeds `REQUEST_CONTEXT_KEY` for the inner `register_action_ws`
816
821
  5. Optional `require_role(required_roles)` — any-of disjunction (coarse upgrade-time gate; per-action `auth` in each spec still applies at dispatch time)
817
822
  6. Delegates to `register_action_ws`
818
823
 
@@ -46,6 +46,7 @@
46
46
  * @module
47
47
  */
48
48
  import type { Uuid } from '../primitive_schemas.js';
49
+ import type { Logger } from '@fuzdev/fuz_util/log.ts';
49
50
  import { type EmitAfterCommitContext } from '../http/pending_effects.js';
50
51
  /**
51
52
  * Close live connections by identity, or all of them.
@@ -127,6 +128,21 @@ export type ConnectionCloseTarget = {
127
128
  * @returns how many connections closed
128
129
  */
129
130
  export declare const close_connections: (closer: ConnectionCloser, target: ConnectionCloseTarget) => number;
131
+ /**
132
+ * Close the connections `target` names, now, for a revocation that has
133
+ * already committed — logging a close that throws instead of throwing it.
134
+ *
135
+ * For a handler with no route transaction, closing straight after its own
136
+ * commit: a throw there would fail a request whose revocation stands, and skip
137
+ * the work after the close (the response, the audit row). A closer over several
138
+ * transports closes the rest before it rethrows, so catching loses no close.
139
+ * The twin of the post-commit flush, which catches and logs the same way.
140
+ *
141
+ * @param closer - the closer to run, `deps.connection_closer` in a handler
142
+ * @param target - the connections to close
143
+ * @param log - where a failing close is reported
144
+ */
145
+ export declare const close_connections_after_commit: (closer: ConnectionCloser, target: ConnectionCloseTarget, log: Logger) => void;
130
146
  /**
131
147
  * Queue a connection close on `ctx`'s post-commit queue, so it runs only after
132
148
  * the request's transaction commits and never when the handler throws.
@@ -135,8 +151,9 @@ export declare const close_connections: (closer: ConnectionCloser, target: Conne
135
151
  * why an in-transaction revocation must not close inline.
136
152
  *
137
153
  * In a handler with no wrapping transaction a throw still drops the queued
138
- * close while an autocommit write stays; close with `close_connections`
139
- * straight after the write there.
154
+ * close while an autocommit write stays; such a handler closes inline straight
155
+ * after its own commit instead, with `close_connections_after_commit`, which
156
+ * catches and logs a failing close as the post-commit flush does.
140
157
  *
141
158
  * @param ctx - the route or action context of the request doing the revoking
142
159
  * @param closer - the closer to run, `deps.connection_closer` in a handler
@@ -1 +1 @@
1
- {"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,4EAA4E;IAC5E,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,IAAI,KAAK,MAAM,CAAC;IACxD;;;;;;;;;;;;OAYG;IACH,sBAAsB,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACnE;;;;;;;OAOG;IACH,iBAAiB,EAAE,MAAM,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,MAaF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK,sBAAsB,EAC3B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,IAIF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,gBAMnC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD;;;;;;;;;;;;;;;OAeG;IACH,GAAG,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC9C,2EAA2E;IAC3E,YAAY,EAAE,MAAM,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAAO,cA8CzC,CAAC"}
1
+ {"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,4EAA4E;IAC5E,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,IAAI,KAAK,MAAM,CAAC;IACxD;;;;;;;;;;;;OAYG;IACH,sBAAsB,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACnE;;;;;;;OAOG;IACH,iBAAiB,EAAE,MAAM,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,MAaF,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,8BAA8B,GAC1C,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,EAC7B,KAAK,MAAM,KACT,IAMF,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK,sBAAsB,EAC3B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,IAIF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,gBAMnC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD;;;;;;;;;;;;;;;OAeG;IACH,GAAG,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC9C,2EAA2E;IAC3E,YAAY,EAAE,MAAM,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAAO,cA8CzC,CAAC"}
@@ -69,6 +69,28 @@ export const close_connections = (closer, target) => {
69
69
  throw new UnreachableError(target);
70
70
  }
71
71
  };
72
+ /**
73
+ * Close the connections `target` names, now, for a revocation that has
74
+ * already committed — logging a close that throws instead of throwing it.
75
+ *
76
+ * For a handler with no route transaction, closing straight after its own
77
+ * commit: a throw there would fail a request whose revocation stands, and skip
78
+ * the work after the close (the response, the audit row). A closer over several
79
+ * transports closes the rest before it rethrows, so catching loses no close.
80
+ * The twin of the post-commit flush, which catches and logs the same way.
81
+ *
82
+ * @param closer - the closer to run, `deps.connection_closer` in a handler
83
+ * @param target - the connections to close
84
+ * @param log - where a failing close is reported
85
+ */
86
+ export const close_connections_after_commit = (closer, target, log) => {
87
+ try {
88
+ close_connections(closer, target);
89
+ }
90
+ catch (err) {
91
+ log.error('connection close after commit failed:', err);
92
+ }
93
+ };
72
94
  /**
73
95
  * Queue a connection close on `ctx`'s post-commit queue, so it runs only after
74
96
  * the request's transaction commits and never when the handler throws.
@@ -77,8 +99,9 @@ export const close_connections = (closer, target) => {
77
99
  * why an in-transaction revocation must not close inline.
78
100
  *
79
101
  * In a handler with no wrapping transaction a throw still drops the queued
80
- * close while an autocommit write stays; close with `close_connections`
81
- * straight after the write there.
102
+ * close while an autocommit write stays; such a handler closes inline straight
103
+ * after its own commit instead, with `close_connections_after_commit`, which
104
+ * catches and logs a failing close as the post-commit flush does.
82
105
  *
83
106
  * @param ctx - the route or action context of the request doing the revoking
84
107
  * @param closer - the closer to run, `deps.connection_closer` in a handler
@@ -163,6 +163,16 @@ export type PerformActionResult = {
163
163
  * the harness omits `account_id`.
164
164
  */
165
165
  export declare const perform_action: (input: PerformActionInput, deps: PerformActionDeps) => Promise<PerformActionResult>;
166
+ /**
167
+ * The credential-type 403 — one builder, so the dispatcher's gate below and a
168
+ * handler that gates a credential channel on its input (`account_delete`'s
169
+ * self path) emit a byte-identical error. Twin of the Rust
170
+ * `fuz_auth::credential_type_required`.
171
+ *
172
+ * @param required_credential_types - the channels that would be admitted
173
+ * @returns the `forbidden` error object with reason `credential_type_required`
174
+ */
175
+ export declare const credential_type_required_error: (required_credential_types: ReadonlyArray<string>) => JsonrpcErrorObject;
166
176
  /**
167
177
  * Build a JSON-RPC response envelope from a `PerformActionResult` for
168
178
  * transports that wire over the JSON-RPC 2.0 message shape (HTTP RPC + WS).
@@ -1 +1 @@
1
- {"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAItC,OAAO,EAEN,KAAK,gBAAgB,EAErB,KAAK,kBAAkB,EACvB,MAAM,oBAAoB,CAAC;AAc5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,KAAK,EAAgC,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAGN,KAAK,UAAU,EACf,MAAM,wBAAwB,CAAC;AAEhC;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,kEAAkE;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,yDAAyD;IACzD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uEAAuE;IACvE,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC;;;;;OAKG;IACH,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB,sFAAsF;IACtF,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,uDAAuD;IACvD,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAE,eAAe,EAAE,cAAc,GAAG,IAAI,CAAA;KAAE,CAAC;CACpD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,gGAAgG;IAChG,EAAE,EAAE,EAAE,CAAC;IACP;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;OAGG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,uEAAuE;IACvE,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC9B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhG;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,OAAO,kBAAkB,EACzB,MAAM,iBAAiB,KACrB,OAAO,CAAC,mBAAmB,CAsL7B,CAAC;AA6DF;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,2BAA2B,aAAa,CAAC,MAAM,CAAC,KAC9C,kBAIA,CAAC;AAyDJ;;;GAGG;AACH,eAAO,MAAM,iCAAiC,GAC7C,IAAI,gBAAgB,EACpB,QAAQ,mBAAmB,KACzB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,gBAAgB,CAAA;CAAE,GAAG,CAC9C;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,kBAAkB,CAAA;CAAE,CAMnD,CAAC"}
@@ -58,6 +58,7 @@ import { DEV } from 'esm-env';
58
58
  import { apply_authorization_phase, has_any_scoped_role } from '../auth/request_context.js';
59
59
  import {} from '../hono_context.js';
60
60
  import { to_input_params } from '../http/schema_helpers.js';
61
+ import { rate_limit_ip_key } from '../http/ip_canonical.js';
61
62
  import { dispatch_with_post_commit_rollback } from '../http/pending_effects.js';
62
63
  import { JSONRPC_VERSION } from '../http/jsonrpc.js';
63
64
  import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, http_status_to_jsonrpc_error_code, JSONRPC_ERROR_CODES, dev_only } from '../http/jsonrpc_errors.js';
@@ -119,14 +120,17 @@ export const perform_action = async (input, deps) => {
119
120
  // budget it should, and so a throttled caller pays no schema work.
120
121
  const rate_limit = spec.rate_limit;
121
122
  if (rate_limit) {
122
- const ip_check = action_ip_rate_limiter && (rate_limit === 'ip' || rate_limit === 'both');
123
+ const ip_limiter = rate_limit === 'ip' || rate_limit === 'both' ? action_ip_rate_limiter : null;
123
124
  const account_keyed_context = action_account_rate_limiter &&
124
125
  request_context !== null &&
125
126
  (rate_limit === 'account' || rate_limit === 'both')
126
127
  ? request_context
127
128
  : null;
128
- if (ip_check) {
129
- const result = action_ip_rate_limiter.check(client_ip);
129
+ // keyed by the address's rate-limit key (an IPv6 client's /64); the
130
+ // audit trail keeps `client_ip` whole
131
+ const ip_key = ip_limiter ? rate_limit_ip_key(client_ip) : '';
132
+ if (ip_limiter) {
133
+ const result = ip_limiter.check(ip_key);
130
134
  if (!result.allowed)
131
135
  return rate_limited_result(result.retry_after);
132
136
  }
@@ -135,8 +139,8 @@ export const perform_action = async (input, deps) => {
135
139
  if (!result.allowed)
136
140
  return rate_limited_result(result.retry_after);
137
141
  }
138
- if (ip_check)
139
- action_ip_rate_limiter.record(client_ip);
142
+ if (ip_limiter)
143
+ ip_limiter.record(ip_key);
140
144
  if (account_keyed_context) {
141
145
  action_account_rate_limiter.record(account_keyed_context.account.id);
142
146
  }
@@ -262,6 +266,19 @@ const check_action_auth_pre_authorization = (auth, account_id) => {
262
266
  }
263
267
  return null;
264
268
  };
269
+ /**
270
+ * The credential-type 403 — one builder, so the dispatcher's gate below and a
271
+ * handler that gates a credential channel on its input (`account_delete`'s
272
+ * self path) emit a byte-identical error. Twin of the Rust
273
+ * `fuz_auth::credential_type_required`.
274
+ *
275
+ * @param required_credential_types - the channels that would be admitted
276
+ * @returns the `forbidden` error object with reason `credential_type_required`
277
+ */
278
+ export const credential_type_required_error = (required_credential_types) => jsonrpc_error_messages.forbidden('forbidden', {
279
+ reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
280
+ required_credential_types
281
+ });
265
282
  /**
266
283
  * Post-authorization auth gate — fires after the authorization phase
267
284
  * resolved the actor + role_grants. Enforces `auth.credential_types` and
@@ -289,10 +306,7 @@ const check_action_auth_pre_authorization = (auth, account_id) => {
289
306
  const check_action_auth_post_authorization = (auth, request_context, credential_type, method, token_scope) => {
290
307
  if (auth.credential_types?.length) {
291
308
  if (!credential_type || !auth.credential_types.includes(credential_type)) {
292
- return jsonrpc_error_messages.forbidden('forbidden', {
293
- reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
294
- required_credential_types: auth.credential_types
295
- });
309
+ return credential_type_required_error(auth.credential_types);
296
310
  }
297
311
  }
298
312
  // Token-scope gate — second of three. See the doc comment for why the order
@@ -1 +1 @@
1
- {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAaH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAoBF,CAAC"}
1
+ {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAcH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AA6CD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAoBF,CAAC"}
@@ -35,6 +35,7 @@
35
35
  import { Logger } from '@fuzdev/fuz_util/log.ts';
36
36
  import { apply_authorization_phase, REQUEST_CONTEXT_KEY, require_auth, require_role, require_token_scope } from '../auth/request_context.js';
37
37
  import { verify_request_source } from '../http/origin.js';
38
+ import { parse_acting } from '../http/auth_shape.js';
38
39
  import { ACCOUNT_ID_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
39
40
  import { register_action_ws } from './register_action_ws.js';
40
41
  /** Synthesized auth shape for WS upgrade: account + actor both required. */
@@ -62,9 +63,11 @@ const create_ws_authorization_middleware = (db) => {
62
63
  await next();
63
64
  return;
64
65
  }
65
- const acting_param = c.req.query('acting');
66
+ // through the `ActingActor` schema like every other selector: lowercased,
67
+ // and a malformed value reads as omitted (`parse_acting`)
68
+ const acting = parse_acting(c.req.query('acting'));
66
69
  const account_id = c.get(ACCOUNT_ID_KEY) ?? null;
67
- const result = await apply_authorization_phase({ db }, account_id, WS_UPGRADE_AUTH, acting_param ?? undefined);
70
+ const result = await apply_authorization_phase({ db }, account_id, WS_UPGRADE_AUTH, acting);
68
71
  if (!result.ok)
69
72
  return c.json(result.body, result.status);
70
73
  if (result.request_context !== null) {
@@ -269,16 +269,33 @@ delete / list / clone / moderate`), error reasons, and `all_cell_action_specs`
269
269
  manage grants or read the audit timeline. NULL
270
270
  `created_by` (system origin) is admin-only for edit/manage (explicit
271
271
  defense-in-depth). "Admin" on every tier is the **global** grant — a
272
- scoped `admin` row bypasses nothing.
273
- - `auth/cell_relation_visibility.ts` — `filter_visible_target_ids(deps,
274
- auth, ids)`: batched strict relation-read filter. Every relation read —
275
- the `cell_get` bundle, **forward and reverse** `cell_field_list` /
276
- `cell_item_list`, and the deep-clone walk (children bulk-loaded via
277
- `query_cell_load_many`) — drops edges whose far endpoint the caller can't
278
- view, so a viewer of a parent can't enumerate private linked cells by id.
279
- Two queries for the whole id-set (avoids the N+1 of a per-row check). The
280
- reverse list verbs additionally cap the fetch by the wire `limit` so a
281
- heavily inbound-linked target can't force an unbounded scan.
272
+ scoped `admin` row bypasses nothing. `can_view_cell`'s public and grant
273
+ arms also require the cell's `moderation` to admit an audience
274
+ (`db/cell_moderation.ts`: unmoderated or `approved`), so a pending or
275
+ rejected contribution is its author's and admins' alone; `can_edit_cell`'s
276
+ editor-grant arm requires the same admission. `can_steward_cell`
277
+ is the separate moderator read: the governing root's manager on a pending or
278
+ rejected contribution under a live root.
279
+ - `auth/cell_steward.ts` — `cell_steward_admits(deps, auth, cell)`: the steward
280
+ arm with its governing-root load, run only after `can_view_cell` denies. Its
281
+ module doc names where the arm applies (`cell_get`, `cell_moderate`, the
282
+ cell-scoped fact route, `cell_list`'s SQL) and where it must not (clone,
283
+ contribution parents, relation reads, `shared_with`, grant recompute).
284
+ - Strict relation-read visibility — every relation read drops edges whose far
285
+ endpoint the caller can't view, so a viewer of a parent can't enumerate
286
+ private linked cells by id. **Forward and reverse** `cell_field_list` /
287
+ `cell_item_list` filter in SQL before their limit
288
+ (`db/cell_view_sql.ts`'s `can_view_cell_sql`, bound through
289
+ `cell_viewer_from_auth`), so a page is up to `limit` edges whose other end
290
+ the caller can view — `CELL_RELATION_LIST_LIMIT_DEFAULT` (50) when omitted,
291
+ `CELL_RELATION_LIST_LIMIT_MAX` (500) at most
292
+ (`auth/cell_relation_list_limit.ts`), and hidden ends never crowd out
293
+ visible ones. The `cell_get` bundle (whose `*_truncated` counts raw edges)
294
+ and the clone walk read with `CELL_VIEWER_UNRESTRICTED` and filter after,
295
+ through `auth/cell_relation_visibility.ts`'s `filter_visible_target_ids(deps,
296
+ auth, ids)`: two queries for the whole id-set, the cells read through the
297
+ narrow `CELL_VIEW_COLUMNS` projection (`query_cell_view_load_many`), never
298
+ the payload.
282
299
  - `auth/cell_actions.ts` — `create_cell_actions(deps)`, the generic
283
300
  handlers. `cell_create` stamps `created_by` + writes the top-level `kind`
284
301
  column; `path` writes are admin-only. It also resolves the directory tree:
@@ -296,7 +313,9 @@ auth, ids)`: batched strict relation-read filter. Every relation read —
296
313
  parent-aware `authorize_create` dep (`CellCreateAuthorize` — wider input
297
314
  `{kind, data, parent_id, root_id, root_data, scope_id}` than `validate_data`,
298
315
  returning a `CellCreateVerdict` `{allow:false} | {allow:true, moderation_required}`)
299
- gates create by capability; it runs after the validator + the parent-resolve.
316
+ gates create by capability; it runs after the validator + the parent-resolve
317
+ (which **404**s `cell_not_found` on a missing or soft-deleted root as on a
318
+ missing parent, authorizer or not).
300
319
  A **viewable**-parent / root-creation deny is **403** `cell_create_forbidden`
301
320
  (the create-path **404** mask is reserved for a hidden parent); `Allow` folds
302
321
  the moderation outcome (born `pending`+private vs `approved`+author-visibility).
@@ -306,7 +325,8 @@ auth, ids)`: batched strict relation-read filter. Every relation read —
306
325
  single-connection deadlock of the in-memory drivers). `None`/omitted = open create (the default;
307
326
  mirrors `validate_data`). `cell_moderate` is the `pending → approved | rejected`
308
327
  transition, gated on `can_manage_cell` of the **governing root** (admin / root
309
- owner — the author lands a 403, the anti-self-approval guard); approve flips
328
+ owner — the author lands a 403, the anti-self-approval guard; a root owner
329
+ reaches the check through the steward arm); approve flips
310
330
  `moderation → approved` + `visibility → public`, reject sets `rejected`.
311
331
  - `auth/cell_grant_actions.ts` / `cell_field_actions.ts` /
312
332
  `cell_item_actions.ts` / `cell_audit_actions.ts` — the relation + ACL +
@@ -588,7 +608,7 @@ reactivation has no self path because a soft-deleted account can't
588
608
  authenticate.
589
609
 
590
610
  - `admin_account_list_action_spec` — read; input `{limit?, offset?, include_deleted?}`; output `{accounts, grantable_roles}`. `include_deleted` (default false) surfaces soft-deleted rows (with `account.deleted_at` set) for the admin UI's reactivation view.
591
- - `account_delete_action_spec` — mutation (soft delete); self-or-admin (`{account, actor}`, handler elevates to admin when `account_id` ≠ self); input `{account_id?}`; output `{ok, deleted}`. Tombstones account + actor(s), revokes sessions/tokens, emits `account_delete` + per-actor `actor_delete`. Refuses a keeper-role target (`ERROR_CANNOT_DELETE_KEEPER`) or the sole active admin (`ERROR_CANNOT_DELETE_LAST_ADMIN`) — both fail-loud with a failure-audit row.
611
+ - `account_delete_action_spec` — mutation (soft delete); self-or-admin (`{account, actor}`, handler elevates to admin when `account_id` ≠ self); the self path is session-only, handler-enforced ahead of the target guards (an API or daemon token gets the dispatcher's `ERROR_CREDENTIAL_TYPE_REQUIRED` 403 via `credential_type_required_error`), while an admin deleting another account keeps any credential; input `{account_id?}`; output `{ok, deleted}`. Tombstones account + actor(s), revokes sessions/tokens, emits `account_delete` + per-actor `actor_delete`. Refuses a keeper-role target (`ERROR_CANNOT_DELETE_KEEPER`) or the sole active admin (`ERROR_CANNOT_DELETE_LAST_ADMIN`) — both fail-loud with a failure-audit row.
592
612
  - `account_purge_action_spec` — mutation (hard purge); keeper-only (`credential_types: ['daemon_token']` + `roles: ['keeper']`) + `confirm: true` + WARN; input `{account_id, confirm?}`; output `{ok, purged}`. Cascading delete; emits `account_purge` + per-actor `actor_purge` (identity snapshot survives in metadata since `audit_log` ids carry no FK). Refuses a keeper-role target (`ERROR_CANNOT_DELETE_KEEPER`) or the sole active admin (`ERROR_CANNOT_DELETE_LAST_ADMIN`).
593
613
  - `account_undelete_action_spec` — mutation (reactivation, inverse of soft delete); admin-only (`roles: ['admin']` — no self path, a tombstoned account can't authenticate); input `{account_id}`; output `{ok, undeleted}`. Clears the `deleted_at` tombstone on the account + its actor(s), emits `account_undelete` + per-actor `actor_undelete`. Does not restore revoked sessions/tokens.
594
614
  - `admin_session_list_action_spec` — read; input `{acting?}` (`.default({})`); output `{sessions}`.
@@ -1 +1 @@
1
- {"version":3,"file":"account_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAsB9F,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAyB3D,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,yBAAyB,EAC/B,UAAS,oBAAyB,KAChC,KAAK,CAAC,SAAS,CA8KjB,CAAC"}
1
+ {"version":3,"file":"account_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAwB9F,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAyB3D,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,yBAAyB,EAC/B,UAAS,oBAAyB,KAChC,KAAK,CAAC,SAAS,CAsLjB,CAAC"}
@@ -27,12 +27,14 @@
27
27
  *
28
28
  * @module
29
29
  */
30
+ import { to_error_message } from '@fuzdev/fuz_util/error.ts';
30
31
  import { rpc_action } from '../actions/action_rpc.js';
32
+ import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
31
33
  import { queue_connection_close } from '../actions/connection_closer.js';
32
34
  import { to_session_account } from './account_schema.js';
33
35
  import { query_session_list_for_account, query_session_revoke_for_account, query_session_revoke_all_for_account } from './session_queries.js';
34
36
  import { query_api_token_enforce_limit, query_api_token_list_for_account, query_create_api_token, query_revoke_api_token_for_account } from './api_token_queries.js';
35
- import { parse_stored_token_scope, token_scope_from_input, token_scope_label } from './token_scope.js';
37
+ import { parse_stored_token_scope, serialize_token_scope, token_scope_from_input, token_scope_label } from './token_scope.js';
36
38
  import { token_lifetime_to_expires_at } from './token_lifetime.js';
37
39
  import { generate_api_token } from './api_token.js';
38
40
  import { DEFAULT_MAX_TOKENS } from './account_route_schema.js';
@@ -108,6 +110,15 @@ export const create_account_actions = (deps, options = {}) => {
108
110
  // There is deliberately no permissive fallback here: an omitted scope is
109
111
  // a validation error upstream, not a full-authority token.
110
112
  const scope = token_scope_from_input(input.scope);
113
+ // Over the stored-document cap is a malformed request, refused before
114
+ // any write (the insert would otherwise throw it as a 500). Twin of the
115
+ // Rust spine's `invalid_params` on `TokenScopeError::TooLarge`.
116
+ try {
117
+ serialize_token_scope(scope);
118
+ }
119
+ catch (err) {
120
+ throw jsonrpc_errors.invalid_params(`invalid scope: ${to_error_message(err)}`);
121
+ }
111
122
  // `input.lifetime` is required for the same reason — an omitted lifetime
112
123
  // is a validation error, never an eternal token.
113
124
  const expires_at = token_lifetime_to_expires_at(input.lifetime);
@@ -1 +1 @@
1
- {"version":3,"file":"account_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4CAA4C;AAC5C,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,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;kBAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,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;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaD,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACxC,8FAA8F;IAC9F,0BAA0B,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,wBAAwB,KAC/B,CACF,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAqD1B,CAAC"}
1
+ {"version":3,"file":"account_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4CAA4C;AAC5C,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,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;kBAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,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;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaD,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACxC,8FAA8F;IAC9F,0BAA0B,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,wBAAwB,KAC/B,CACF,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CA6D1B,CAAC"}
@@ -123,6 +123,10 @@ export const create_account_route_shapes = (options) => [
123
123
  input: LoginInput,
124
124
  output: LoginOutput,
125
125
  rate_limit: 'both',
126
+ // The handler opens its own transaction around just the session writes,
127
+ // so a reservation waiting for a slot, and the Argon2 verify, hold no
128
+ // database connection.
129
+ transaction: false,
126
130
  errors: {
127
131
  400: z.looseObject({
128
132
  error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY])
@@ -151,6 +155,10 @@ export const create_account_route_shapes = (options) => [
151
155
  input: PasswordChangeInput,
152
156
  output: PasswordChangeOutput,
153
157
  rate_limit: options.login_account_rate_limited ? 'both' : 'ip',
158
+ // The handler opens its own transaction around just the password update
159
+ // and the revocations, so a reservation waiting for a slot, and the
160
+ // Argon2 verify and hash, hold no database connection.
161
+ transaction: false,
154
162
  errors: {
155
163
  400: z.looseObject({
156
164
  error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY])
@@ -134,8 +134,13 @@ export interface AccountRouteOptions extends AuthSessionRouteOptions {
134
134
  * Self-service session/token management is on `auth/account_actions.ts`.
135
135
  *
136
136
  * `/login` closes the connections of the sessions its cap evicts, and
137
- * `/logout` and `/password` close the account's, each after the route's
138
- * transaction commits (`queue_connection_close` on `deps.connection_closer`).
137
+ * `/logout` and `/password` close the account's, each once its revocation has
138
+ * committed: `/logout` through the route transaction's post-commit queue
139
+ * (`queue_connection_close`), `/login` and `/password` — which open their own
140
+ * transaction around just their writes — straight after it commits
141
+ * (`close_connections_after_commit`, which logs a failing close rather than
142
+ * failing the committed request), so a later throw can't drop a close a
143
+ * committed revocation owes. All on `deps.connection_closer`.
139
144
  *
140
145
  * @param deps - stateless capabilities (keyring, password, log, audit, connection_closer)
141
146
  * @param options - per-factory configuration (session_options, login_ip_rate_limiter, login_account_rate_limiter, bootstrap_status)
@@ -1 +1 @@
1
- {"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA4B1D,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAExE,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAIlD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gCAAgC,GAAI,UAAU,oBAAoB,KAAG,SA4EhF,CAAC;AAEH,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAQ/C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IACnE;;;;;;;OAOG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,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,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,gBAAgB,EACtB,SAAS,mBAAmB,KAC1B,KAAK,CAAC,SAAS,CA2RjB,CAAC"}
1
+ {"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA4B1D,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIxE,OAAO,EAGN,KAAK,WAAW,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAOlD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gCAAgC,GAAI,UAAU,oBAAoB,KAAG,SAkFhF,CAAC;AAEH,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAQ/C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IACnE;;;;;;;OAOG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,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,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,gBAAgB,EACtB,SAAS,mBAAmB,KAC1B,KAAK,CAAC,SAAS,CA+VjB,CAAC"}