@fuzdev/fuz_app 0.140.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 (311) hide show
  1. package/dist/actions/CLAUDE.md +72 -22
  2. package/dist/actions/action_rpc.d.ts +5 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +9 -1
  5. package/dist/actions/connection_closer.d.ts +44 -4
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +33 -3
  8. package/dist/actions/perform_action.d.ts +10 -0
  9. package/dist/actions/perform_action.d.ts.map +1 -1
  10. package/dist/actions/perform_action.js +23 -9
  11. package/dist/actions/register_action_ws.d.ts +42 -6
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +87 -19
  14. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +10 -4
  17. package/dist/actions/socket.svelte.d.ts +19 -4
  18. package/dist/actions/socket.svelte.d.ts.map +1 -1
  19. package/dist/actions/socket.svelte.js +31 -4
  20. package/dist/actions/transports.d.ts +30 -1
  21. package/dist/actions/transports.d.ts.map +1 -1
  22. package/dist/actions/transports.js +30 -1
  23. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  24. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_auth_guard.js +24 -10
  26. package/dist/actions/transports_ws_backend.d.ts +51 -5
  27. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  28. package/dist/actions/transports_ws_backend.js +62 -9
  29. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  30. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  31. package/dist/auth/CLAUDE.md +44 -22
  32. package/dist/auth/account_actions.d.ts.map +1 -1
  33. package/dist/auth/account_actions.js +12 -1
  34. package/dist/auth/account_route_schema.d.ts +1 -0
  35. package/dist/auth/account_route_schema.d.ts.map +1 -1
  36. package/dist/auth/account_route_schema.js +12 -0
  37. package/dist/auth/account_routes.d.ts +7 -2
  38. package/dist/auth/account_routes.d.ts.map +1 -1
  39. package/dist/auth/account_routes.js +240 -187
  40. package/dist/auth/admin_action_specs.d.ts +6 -1
  41. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  42. package/dist/auth/admin_action_specs.js +6 -1
  43. package/dist/auth/admin_actions.d.ts.map +1 -1
  44. package/dist/auth/admin_actions.js +14 -2
  45. package/dist/auth/api_token.d.ts +20 -0
  46. package/dist/auth/api_token.d.ts.map +1 -1
  47. package/dist/auth/api_token.js +21 -1
  48. package/dist/auth/api_token_queries.d.ts +22 -4
  49. package/dist/auth/api_token_queries.d.ts.map +1 -1
  50. package/dist/auth/api_token_queries.js +11 -6
  51. package/dist/auth/audit_log_routes.d.ts +10 -0
  52. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  53. package/dist/auth/audit_log_routes.js +20 -0
  54. package/dist/auth/audit_log_schema.d.ts +30 -5
  55. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  56. package/dist/auth/audit_log_schema.js +30 -0
  57. package/dist/auth/bearer_auth.d.ts +4 -2
  58. package/dist/auth/bearer_auth.d.ts.map +1 -1
  59. package/dist/auth/bearer_auth.js +19 -8
  60. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  61. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  62. package/dist/auth/bootstrap_route_schema.js +1 -0
  63. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  64. package/dist/auth/bootstrap_routes.js +85 -76
  65. package/dist/auth/cell_action_specs.d.ts +7 -3
  66. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  67. package/dist/auth/cell_action_specs.js +9 -5
  68. package/dist/auth/cell_actions.d.ts +3 -3
  69. package/dist/auth/cell_actions.d.ts.map +1 -1
  70. package/dist/auth/cell_actions.js +67 -38
  71. package/dist/auth/cell_authorize.d.ts +69 -8
  72. package/dist/auth/cell_authorize.d.ts.map +1 -1
  73. package/dist/auth/cell_authorize.js +89 -9
  74. package/dist/auth/cell_field_action_specs.d.ts +5 -4
  75. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  76. package/dist/auth/cell_field_action_specs.js +7 -7
  77. package/dist/auth/cell_field_actions.d.ts +7 -5
  78. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  79. package/dist/auth/cell_field_actions.js +24 -22
  80. package/dist/auth/cell_grant_action_specs.d.ts +6 -0
  81. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  82. package/dist/auth/cell_grant_action_specs.js +6 -0
  83. package/dist/auth/cell_grant_actions.d.ts +3 -1
  84. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  85. package/dist/auth/cell_grant_actions.js +16 -2
  86. package/dist/auth/cell_item_action_specs.d.ts +5 -4
  87. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  88. package/dist/auth/cell_item_action_specs.js +7 -7
  89. package/dist/auth/cell_item_actions.d.ts +6 -5
  90. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  91. package/dist/auth/cell_item_actions.js +23 -22
  92. package/dist/auth/cell_relation_list_limit.d.ts +20 -0
  93. package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
  94. package/dist/auth/cell_relation_list_limit.js +27 -0
  95. package/dist/auth/cell_relation_visibility.d.ts +4 -3
  96. package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
  97. package/dist/auth/cell_relation_visibility.js +6 -5
  98. package/dist/auth/cell_steward.d.ts +45 -0
  99. package/dist/auth/cell_steward.d.ts.map +1 -0
  100. package/dist/auth/cell_steward.js +53 -0
  101. package/dist/auth/cleanup.d.ts +9 -9
  102. package/dist/auth/cleanup.js +9 -9
  103. package/dist/auth/credential_deadline.d.ts +95 -0
  104. package/dist/auth/credential_deadline.d.ts.map +1 -0
  105. package/dist/auth/credential_deadline.js +139 -0
  106. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  107. package/dist/auth/daemon_token_middleware.js +4 -1
  108. package/dist/auth/middleware.d.ts +90 -12
  109. package/dist/auth/middleware.d.ts.map +1 -1
  110. package/dist/auth/middleware.js +115 -16
  111. package/dist/auth/request_context.d.ts +8 -2
  112. package/dist/auth/request_context.d.ts.map +1 -1
  113. package/dist/auth/request_context.js +20 -4
  114. package/dist/auth/resolved_auth.d.ts +13 -2
  115. package/dist/auth/resolved_auth.d.ts.map +1 -1
  116. package/dist/auth/resolved_auth.js +5 -4
  117. package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
  118. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  119. package/dist/auth/role_grant_offer_action_specs.js +6 -1
  120. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  121. package/dist/auth/role_grant_offer_actions.js +23 -16
  122. package/dist/auth/role_grant_offer_queries.d.ts +8 -6
  123. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  124. package/dist/auth/role_grant_offer_queries.js +16 -8
  125. package/dist/auth/session_cookie.d.ts +4 -0
  126. package/dist/auth/session_cookie.d.ts.map +1 -1
  127. package/dist/auth/session_cookie.js +3 -2
  128. package/dist/auth/session_middleware.d.ts +1 -1
  129. package/dist/auth/session_middleware.d.ts.map +1 -1
  130. package/dist/auth/session_middleware.js +4 -1
  131. package/dist/auth/session_queries.d.ts +19 -2
  132. package/dist/auth/session_queries.d.ts.map +1 -1
  133. package/dist/auth/session_queries.js +8 -2
  134. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  135. package/dist/auth/signup_route_schema.js +1 -0
  136. package/dist/auth/signup_routes.d.ts +4 -3
  137. package/dist/auth/signup_routes.d.ts.map +1 -1
  138. package/dist/auth/signup_routes.js +157 -134
  139. package/dist/auth/token_scope.d.ts +7 -1
  140. package/dist/auth/token_scope.d.ts.map +1 -1
  141. package/dist/auth/token_scope.js +8 -2
  142. package/dist/db/CLAUDE.md +32 -19
  143. package/dist/db/cell_ddl.d.ts +1 -2
  144. package/dist/db/cell_ddl.d.ts.map +1 -1
  145. package/dist/db/cell_ddl.js +1 -2
  146. package/dist/db/cell_field_queries.d.ts +20 -18
  147. package/dist/db/cell_field_queries.d.ts.map +1 -1
  148. package/dist/db/cell_field_queries.js +28 -22
  149. package/dist/db/cell_item_queries.d.ts +20 -14
  150. package/dist/db/cell_item_queries.d.ts.map +1 -1
  151. package/dist/db/cell_item_queries.js +28 -18
  152. package/dist/db/cell_moderation.d.ts +67 -0
  153. package/dist/db/cell_moderation.d.ts.map +1 -0
  154. package/dist/db/cell_moderation.js +66 -0
  155. package/dist/db/cell_queries.d.ts +44 -14
  156. package/dist/db/cell_queries.d.ts.map +1 -1
  157. package/dist/db/cell_queries.js +68 -45
  158. package/dist/db/cell_view_sql.d.ts +102 -0
  159. package/dist/db/cell_view_sql.d.ts.map +1 -0
  160. package/dist/db/cell_view_sql.js +112 -0
  161. package/dist/db/db_pg.d.ts +1 -1
  162. package/dist/db/db_pg.js +1 -1
  163. package/dist/hono_context.d.ts +23 -0
  164. package/dist/hono_context.d.ts.map +1 -1
  165. package/dist/hono_context.js +19 -0
  166. package/dist/http/CLAUDE.md +41 -15
  167. package/dist/http/auth_shape.d.ts +5 -2
  168. package/dist/http/auth_shape.d.ts.map +1 -1
  169. package/dist/http/auth_shape.js +5 -2
  170. package/dist/http/ip_canonical.d.ts +30 -3
  171. package/dist/http/ip_canonical.d.ts.map +1 -1
  172. package/dist/http/ip_canonical.js +50 -3
  173. package/dist/http/middleware_spec.d.ts +16 -0
  174. package/dist/http/middleware_spec.d.ts.map +1 -1
  175. package/dist/http/origin.d.ts +113 -18
  176. package/dist/http/origin.d.ts.map +1 -1
  177. package/dist/http/origin.js +247 -127
  178. package/dist/http/route_spec.d.ts +21 -0
  179. package/dist/http/route_spec.d.ts.map +1 -1
  180. package/dist/http/schema_helpers.d.ts +22 -0
  181. package/dist/http/schema_helpers.d.ts.map +1 -1
  182. package/dist/http/schema_helpers.js +24 -0
  183. package/dist/http/surface.d.ts +6 -1
  184. package/dist/http/surface.d.ts.map +1 -1
  185. package/dist/http/surface.js +12 -5
  186. package/dist/primitive_schemas.d.ts +10 -1
  187. package/dist/primitive_schemas.d.ts.map +1 -1
  188. package/dist/primitive_schemas.js +11 -2
  189. package/dist/rate_limiter.d.ts +179 -12
  190. package/dist/rate_limiter.d.ts.map +1 -1
  191. package/dist/rate_limiter.js +352 -42
  192. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  193. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  194. package/dist/realtime/sse_auth_guard.js +31 -12
  195. package/dist/realtime/subscriber_registry.d.ts +10 -1
  196. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  197. package/dist/realtime/subscriber_registry.js +10 -1
  198. package/dist/server/app_server.d.ts +20 -7
  199. package/dist/server/app_server.d.ts.map +1 -1
  200. package/dist/server/app_server.js +8 -3
  201. package/dist/server/env.d.ts +2 -1
  202. package/dist/server/env.d.ts.map +1 -1
  203. package/dist/server/env.js +7 -17
  204. package/dist/server/serve_fact_route.d.ts +8 -4
  205. package/dist/server/serve_fact_route.d.ts.map +1 -1
  206. package/dist/server/serve_fact_route.js +16 -6
  207. package/dist/testing/CLAUDE.md +164 -25
  208. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  209. package/dist/testing/adversarial_headers.js +13 -6
  210. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  211. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  212. package/dist/testing/connection_closer_helpers.js +4 -0
  213. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  214. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  215. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  216. package/dist/testing/cross_backend/body_size.js +14 -65
  217. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  218. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  219. package/dist/testing/cross_backend/capabilities.js +9 -3
  220. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  221. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  222. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  223. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  224. package/dist/testing/cross_backend/cell_relations.js +145 -2
  225. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  226. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  227. package/dist/testing/cross_backend/conformance_case.js +6 -0
  228. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  229. package/dist/testing/cross_backend/conformance_table.js +20 -19
  230. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  231. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  232. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  233. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  234. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  235. package/dist/testing/cross_backend/expected_schema.json +0 -1
  236. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  237. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  238. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  239. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  240. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  241. package/dist/testing/cross_backend/login_security.js +154 -0
  242. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  243. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  244. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  245. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  246. package/dist/testing/cross_backend/origin.js +6 -26
  247. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  248. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  249. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  250. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  251. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  252. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  253. package/dist/testing/cross_backend/origin_scope.js +140 -0
  254. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  255. package/dist/testing/cross_backend/query_shape.js +24 -2
  256. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  257. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  258. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  259. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  260. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  261. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  262. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  263. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  264. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  265. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  266. package/dist/testing/cross_backend/spine_surface_constants.d.ts +20 -1
  267. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  268. package/dist/testing/cross_backend/spine_surface_constants.js +23 -1
  269. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  270. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  271. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  272. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  273. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  274. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  275. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  276. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  277. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  278. package/dist/testing/cross_backend/transport_composition.js +364 -0
  279. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  280. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  281. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  282. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  283. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  284. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  285. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  286. package/dist/testing/entities.d.ts +23 -0
  287. package/dist/testing/entities.d.ts.map +1 -1
  288. package/dist/testing/entities.js +37 -0
  289. package/dist/testing/integration.d.ts.map +1 -1
  290. package/dist/testing/integration.js +3 -1
  291. package/dist/testing/middleware.d.ts +10 -0
  292. package/dist/testing/middleware.d.ts.map +1 -1
  293. package/dist/testing/middleware.js +31 -7
  294. package/dist/testing/schema_introspect.js +1 -1
  295. package/dist/testing/schema_parity.d.ts +4 -3
  296. package/dist/testing/schema_parity.d.ts.map +1 -1
  297. package/dist/testing/schema_parity.js +4 -3
  298. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  299. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  300. package/dist/testing/schema_ready_fixture.js +2 -4
  301. package/dist/testing/stubs.d.ts +16 -3
  302. package/dist/testing/stubs.d.ts.map +1 -1
  303. package/dist/testing/stubs.js +20 -7
  304. package/dist/testing/surface_invariants.js +2 -2
  305. package/dist/ui/auth_state.svelte.d.ts +10 -0
  306. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/auth_state.svelte.js +22 -4
  308. package/package.json +1 -1
  309. package/dist/db/cell_history_ddl.d.ts +0 -38
  310. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  311. package/dist/db/cell_history_ddl.js +0 -61
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The credential deadline — the moment a resolved credential stops
3
+ * authenticating, which a long-lived connection opened on it must not outlive.
4
+ *
5
+ * A WebSocket or an SSE stream is authorized once, at open, and closed on
6
+ * revocation by identity. Expiry has no revocation event: nothing is written
7
+ * when a session or token passes its `expires_at`. So the auth middleware
8
+ * computes the deadline once (`CREDENTIAL_DEADLINE_KEY`, gathered onto
9
+ * `ResolvedAuth.credential_deadline`) and the transport closes the connection
10
+ * when it arrives (`schedule_credential_deadline`).
11
+ *
12
+ * The deadline is the earliest of what the credential carries:
13
+ *
14
+ * - **Session** — the signed cookie's embedded expiry and the `auth_session`
15
+ * row's `expires_at`.
16
+ * - **API token** — the `api_token` row's `expires_at`; an eternal token
17
+ * (`expires_at IS NULL`) has none.
18
+ * - **Daemon token** — none: it has no row and no expiry, and a rotation
19
+ * closes no connection.
20
+ *
21
+ * A row's expiry is read as **remaining time measured on the database clock**
22
+ * (`EXPIRES_IN_MS_SQL`, `expires_at - NOW()` in the same statement that checks
23
+ * `expires_at > NOW()`), then anchored to this process's monotonic clock
24
+ * (`performance.now()`) — so a skew between the database's clock and the
25
+ * server's moves no deadline. The cookie's expiry is a wall-clock second this
26
+ * server signed, read against the same wall clock the cookie check reads.
27
+ *
28
+ * The monotonic clock stops while the host is suspended, so a deadline on it
29
+ * alone would hold a connection past its credential by the time asleep. The
30
+ * wait (`schedule_credential_deadline`) therefore also carries the deadline on
31
+ * the wall clock (`Date.now()`), which keeps running through a suspend, and
32
+ * fires at whichever is reached first: after a resume, within one timer step
33
+ * (`CREDENTIAL_DEADLINE_TIMER_CAP_MS`, an hour). The wall deadline is taken
34
+ * from the time left when the wait starts, so a skew between the database's
35
+ * clock and the server's still moves nothing. A wall clock stepped forward
36
+ * can fire it early; the client reconnects through the gates, the fail-closed
37
+ * side of the trade.
38
+ *
39
+ * The twin of the Rust spine's `fuz_auth::ResolvedAuth::credential_deadline`
40
+ * and `fuz_realtime`'s deadline wait.
41
+ *
42
+ * @module
43
+ */
44
+ /**
45
+ * SQL expression for a row's remaining lifetime in milliseconds, measured on
46
+ * the database clock — `NULL` when `expires_at` is `NULL` (an eternal API
47
+ * token). `float8`, so every driver reads it as a JS number.
48
+ */
49
+ export const EXPIRES_IN_MS_SQL = '(EXTRACT(EPOCH FROM (expires_at - NOW())) * 1000)::float8';
50
+ /**
51
+ * The longest single timer the deadline wait sets — one hour, far inside
52
+ * `setTimeout`'s ceiling (2^31 - 1 ms, about 24.8 days, past which a timer
53
+ * fires at once) and the Rust twin's tokio ceiling, and short enough that a
54
+ * resume from suspend is noticed within it. A longer deadline is reached
55
+ * through several.
56
+ */
57
+ export const CREDENTIAL_DEADLINE_TIMER_CAP_MS = 3_600_000;
58
+ /**
59
+ * The deadline `now` plus the shortest of `remaining_ms`, as a monotonic
60
+ * `performance.now()` timestamp, or `null` when no entry carries one.
61
+ *
62
+ * A negative or `NaN` entry — a row its own `expires_at > NOW()` check
63
+ * already refused, read at the boundary — counts as zero.
64
+ *
65
+ * @param remaining_ms - each source's remaining lifetime in ms, `null` for none
66
+ * @param now - the monotonic clock reading to anchor to
67
+ * @returns the deadline, or `null` for a credential with no expiry
68
+ */
69
+ export const credential_deadline_after = (remaining_ms, now = performance.now()) => {
70
+ let shortest = null;
71
+ for (const ms of remaining_ms) {
72
+ if (ms === null)
73
+ continue;
74
+ const clamped = Number.isNaN(ms) ? 0 : Math.max(0, ms);
75
+ if (shortest === null || clamped < shortest)
76
+ shortest = clamped;
77
+ }
78
+ return shortest === null ? null : now + shortest;
79
+ };
80
+ /**
81
+ * The next timer toward `deadline` from `now`: what remains, capped at
82
+ * `CREDENTIAL_DEADLINE_TIMER_CAP_MS`, or `null` once the deadline is reached.
83
+ *
84
+ * @param deadline - the monotonic deadline
85
+ * @param now - the monotonic clock reading
86
+ * @returns the next delay in ms, or `null` when the deadline has passed
87
+ */
88
+ export const next_credential_deadline_delay = (deadline, now) => {
89
+ const remaining = deadline - now;
90
+ if (!(remaining > 0))
91
+ return null;
92
+ return Math.min(remaining, CREDENTIAL_DEADLINE_TIMER_CAP_MS);
93
+ };
94
+ /**
95
+ * Call `on_expire` once `deadline` has passed, waiting in timers of at most
96
+ * `CREDENTIAL_DEADLINE_TIMER_CAP_MS`, each re-reading the clock against the
97
+ * absolute deadline — so a session's 30 days (past `setTimeout`'s ceiling)
98
+ * does not fire at once. Each wake-up also checks the same deadline on the
99
+ * wall clock, so a host suspend, which stops the monotonic clock, delays the
100
+ * close by at most one timer after the resume (module doc).
101
+ *
102
+ * A `null` deadline schedules nothing. The returned function cancels the wait
103
+ * and is safe to call more than once, before or after it fires.
104
+ *
105
+ * @param deadline - the monotonic deadline (`ResolvedAuth.credential_deadline`)
106
+ * @param on_expire - run once, when the deadline passes
107
+ * @returns a cancel function
108
+ */
109
+ export const schedule_credential_deadline = (deadline, on_expire) => {
110
+ if (deadline === null)
111
+ return () => { };
112
+ // the same deadline on the wall clock, which a suspend does not stop
113
+ const wall_deadline = Date.now() + (deadline - performance.now());
114
+ let timer = null;
115
+ let cancelled = false;
116
+ const step = () => {
117
+ timer = null;
118
+ if (cancelled)
119
+ return;
120
+ const delay = next_credential_deadline_delay(deadline, performance.now());
121
+ if (delay === null || Date.now() >= wall_deadline) {
122
+ cancelled = true;
123
+ on_expire();
124
+ return;
125
+ }
126
+ timer = setTimeout(step, delay);
127
+ // a pending deadline never holds the process open on its own
128
+ if (typeof timer === 'object' && 'unref' in timer)
129
+ timer.unref();
130
+ };
131
+ step();
132
+ return () => {
133
+ cancelled = true;
134
+ if (timer !== null) {
135
+ clearTimeout(timer);
136
+ timer = null;
137
+ }
138
+ };
139
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAWtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,EACf,KAAK,MAAM,KACT,iBA0EF,CAAC"}
1
+ {"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAYtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,EACf,KAAK,MAAM,KACT,iBA6EF,CAAC"}
@@ -14,7 +14,7 @@
14
14
  * @module
15
15
  */
16
16
  import { DEV } from 'esm-env';
17
- import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY } from '../hono_context.js';
17
+ import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_DEADLINE_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY } from '../hono_context.js';
18
18
  import { token_scope_full } from './token_scope.js';
19
19
  import { is_browser_context } from '../http/origin.js';
20
20
  import { query_role_grant_find_account_id_for_role } from './role_grant_queries.js';
@@ -141,6 +141,9 @@ export const create_daemon_token_middleware = (state, deps, log) => {
141
141
  // narrowing could have been chosen.
142
142
  c.set(TOKEN_SCOPE_KEY, token_scope_full());
143
143
  c.set(AUTH_API_TOKEN_ID_KEY, null);
144
+ // No row, no expiry — and it overrides a session cookie sent beside it,
145
+ // whose deadline must not ride along.
146
+ c.set(CREDENTIAL_DEADLINE_KEY, null);
144
147
  await next();
145
148
  };
146
149
  };
@@ -9,26 +9,36 @@
9
9
  import type { SessionOptions } from './session_cookie.js';
10
10
  import type { AppDeps } from './deps.js';
11
11
  import type { DaemonTokenState } from './daemon_token.js';
12
- import type { MiddlewareSpec } from '../http/middleware_spec.js';
12
+ import type { MiddlewareCarveOut, MiddlewareSpec } from '../http/middleware_spec.js';
13
+ import type { RouteSpec } from '../http/route_spec.js';
14
+ /**
15
+ * The Hono path pattern the origin check mounts on — every path. A request to
16
+ * any route, the static fallback included, carrying a foreign `Origin` is
17
+ * refused; one with no `Origin` (a probe, a CLI client) passes. The only way
18
+ * out is a named carve-out with a reason (`AuthMiddlewareOptions.origin_carve_outs`).
19
+ * The Rust spine's `fuz_http::compose_stack` checks every route group the
20
+ * same way.
21
+ */
22
+ export declare const ORIGIN_MIDDLEWARE_PATH = "*";
13
23
  /**
14
24
  * The Hono path pattern the auth middleware stack mounts on by default —
15
- * origin verification, session parsing, request context, bearer and daemon
16
- * token auth all run for the paths it matches, and no others.
25
+ * session parsing, request context, bearer and daemon token auth all run for
26
+ * the paths it matches, and no others (the origin check runs on every path,
27
+ * `ORIGIN_MIDDLEWARE_PATH`).
17
28
  *
18
29
  * `create_app_server` mounts the stack here, and refuses at assembly (via
19
30
  * `assert_endpoint_in_auth_scope`, which `create_test_app_surface_spec` also
20
31
  * runs) an `rpc_endpoints`, `ws_endpoints`, or bootstrap path the pattern does
21
32
  * not match (tested with `middleware_applies`, which follows Hono: `'/api/*'`
22
33
  * matches `/api` and everything under `/api/`): an endpoint outside it would
23
- * get no origin check or session parsing, so every call or upgrade would be
24
- * unauthenticated and any page could send it.
34
+ * get no session or bearer parsing, so every call or upgrade would be
35
+ * unauthenticated.
25
36
  */
26
37
  export declare const AUTH_MIDDLEWARE_PATH = "/api/*";
27
38
  /**
28
39
  * Throw when an endpoint path sits outside the auth middleware's scope
29
- * (`AUTH_MIDDLEWARE_PATH`) — there it would get no origin check and no
30
- * session or bearer parsing, so every call or upgrade would be
31
- * unauthenticated and any page could send it.
40
+ * (`AUTH_MIDDLEWARE_PATH`) — there it would get no session or bearer
41
+ * parsing, so every call or upgrade would be unauthenticated.
32
42
  * `middleware_applies` matches the way Hono matches the middleware's pattern.
33
43
  *
34
44
  * `create_app_server` calls it for every `rpc_endpoints` and `ws_endpoints`
@@ -41,6 +51,60 @@ export declare const AUTH_MIDDLEWARE_PATH = "/api/*";
41
51
  * @throws Error when `path` is outside `AUTH_MIDDLEWARE_PATH`
42
52
  */
43
53
  export declare const assert_endpoint_in_auth_scope: (option: "rpc_endpoints" | "ws_endpoints" | "bootstrap.route_prefix", path: string) => void;
54
+ /**
55
+ * Throw when a route that reads or mints a credential sits outside the auth
56
+ * middleware's scope (`AUTH_MIDDLEWARE_PATH`). A route reads one when its
57
+ * `auth.account` is not `'none'`, or when it is marked `reads_credential` (a
58
+ * handler that reads the resolved account behind no gate: account status) —
59
+ * outside the scope no session or bearer parsing runs, so it would answer
60
+ * every caller as unauthenticated. A route
61
+ * mints one when it is marked `mints_credential` (login, signup, bootstrap) —
62
+ * outside the scope an origin carve-out could cover it
63
+ * (`assert_origin_carve_outs_valid` refuses one only inside), and a forged
64
+ * cross-site request to it is login CSRF.
65
+ *
66
+ * Account and signup route specs are relative and the consumer prefixes them,
67
+ * so this is what holds them inside the scope whatever prefix they get.
68
+ * `create_app_server` runs it over every route it registers, consumer and
69
+ * factory alike, and `create_test_app_surface_spec` does the same.
70
+ *
71
+ * @param route_specs - every route the server registers
72
+ * @throws Error naming the first route that reads or mints a credential outside `AUTH_MIDDLEWARE_PATH`
73
+ */
74
+ export declare const assert_credential_routes_in_auth_scope: (route_specs: ReadonlyArray<RouteSpec>) => void;
75
+ /**
76
+ * Throw unless every origin carve-out names a path and a reason, and stays
77
+ * off the auth middleware's scope: a path that starts with `/` (a Hono pattern
78
+ * like `/hooks/*` or `/health`), a non-blank reason — a carve-out is an
79
+ * argument someone wrote down, never an empty placeholder — and a pattern that
80
+ * matches nothing under `auth_path` (`middleware_paths_overlap`).
81
+ *
82
+ * The scope rule is what keeps a carve-out off every credentialed route.
83
+ * Assembly holds every route that reads a credential (`auth.account` not
84
+ * `'none'`, or `reads_credential`) or mints one (`mints_credential`: login,
85
+ * signup, bootstrap) inside
86
+ * `AUTH_MIDDLEWARE_PATH` (`assert_credential_routes_in_auth_scope`), and every
87
+ * RPC, WS, and bootstrap endpoint besides (`assert_endpoint_in_auth_scope`).
88
+ * The minting routes declare no auth but are what a forged cross-site request
89
+ * targets, so refusing by route `auth` would miss them; refusing by scope
90
+ * catches them. A route outside the scope reads no credential the auth
91
+ * middleware parses, and mints none, so a carve-out there removes no
92
+ * credentialed check.
93
+ *
94
+ * @param carve_outs - the carve-outs to check
95
+ * @param auth_path - the auth middleware's path pattern (default `AUTH_MIDDLEWARE_PATH`)
96
+ * @throws Error naming the first carve-out with no path, no reason, or a pattern overlapping `auth_path`
97
+ */
98
+ export declare const assert_origin_carve_outs_valid: (carve_outs: ReadonlyArray<MiddlewareCarveOut>, auth_path?: string) => void;
99
+ /**
100
+ * Copy origin carve-outs into a frozen array of frozen `{path, reason}`
101
+ * records, so a caller that mutates its own array or objects after assembly
102
+ * cannot change what was validated or what the check skips.
103
+ *
104
+ * @param carve_outs - the caller's carve-outs
105
+ * @returns the frozen copy
106
+ */
107
+ export declare const freeze_origin_carve_outs: (carve_outs: ReadonlyArray<MiddlewareCarveOut>) => ReadonlyArray<MiddlewareCarveOut>;
44
108
  /**
45
109
  * Throw unless a middleware list still mounts every spec of the stack it was
46
110
  * built from — the trusted-proxy spec and the auth middleware — exactly once,
@@ -77,21 +141,35 @@ export declare const assert_middleware_stack_mounted: (stack_specs: ReadonlyArra
77
141
  export interface AuthMiddlewareOptions {
78
142
  allowed_origins: Array<RegExp>;
79
143
  session_options: SessionOptions<string>;
80
- /** Path pattern for middleware (default: `AUTH_MIDDLEWARE_PATH`, `'/api/*'`). */
144
+ /**
145
+ * Path pattern for the session, request-context, bearer, and daemon-token
146
+ * layers (default: `AUTH_MIDDLEWARE_PATH`, `'/api/*'`). The origin check
147
+ * always mounts on every path (`ORIGIN_MIDDLEWARE_PATH`).
148
+ */
81
149
  path?: string;
150
+ /**
151
+ * Paths the origin check skips, each with its reason (default none). Copied
152
+ * at assembly (`freeze_origin_carve_outs`), then checked by
153
+ * `assert_origin_carve_outs_valid` — none may overlap `path`; recorded on
154
+ * the origin spec, so the generated surface leaves the check's `403` off
155
+ * the carved-out routes.
156
+ */
157
+ origin_carve_outs?: ReadonlyArray<MiddlewareCarveOut>;
82
158
  /** Daemon token state for keeper auth. Omit to disable daemon token middleware. */
83
159
  daemon_token_state?: DaemonTokenState;
84
160
  }
85
161
  /**
86
162
  * Create the auth middleware stack.
87
163
  *
88
- * Returns `[origin, session, request_context, bearer_auth]` middleware specs
89
- * for the given path pattern. When `daemon_token_state` is provided, appends
164
+ * Returns `[origin, session, request_context, bearer_auth]` middleware specs:
165
+ * the origin check on every path less its carve-outs, the rest for the given
166
+ * path pattern. When `daemon_token_state` is provided, appends
90
167
  * a `daemon_token` layer. Apps can append extra entries for non-standard
91
168
  * paths (e.g., tx's `/tx` binary endpoint).
92
169
  *
93
170
  * @param deps - stateless capabilities (keyring, db)
94
- * @param options - middleware configuration (allowed_origins, session_options, path, daemon_token_state)
171
+ * @param options - middleware configuration (allowed_origins, session_options, path, daemon_token_state, origin_carve_outs)
172
+ * @throws Error when an origin carve-out has no path or no reason, or overlaps `path`
95
173
  * @returns the middleware spec array
96
174
  */
97
175
  export declare const create_auth_middleware_specs: (deps: AppDeps, options: AuthMiddlewareOptions) => Promise<Array<MiddlewareSpec>>;
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAIjE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,6BAA6B,GACzC,QAAQ,eAAe,GAAG,cAAc,GAAG,wBAAwB,EACnE,MAAM,MAAM,KACV,IAQF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,+BAA+B,GAC3C,aAAa,aAAa,CAAC,cAAc,CAAC,EAC1C,kBAAkB,aAAa,CAAC,cAAc,CAAC,KAC7C,IA+BF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CA4E/B,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,6BAA6B,GACzC,QAAQ,eAAe,GAAG,cAAc,GAAG,wBAAwB,EACnE,MAAM,MAAM,KACV,IAQF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,sCAAsC,GAClD,aAAa,aAAa,CAAC,SAAS,CAAC,KACnC,IAcF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,aAAa,CAAC,kBAAkB,CAAC,EAC7C,YAAW,MAA6B,KACtC,IAsBF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,GACpC,YAAY,aAAa,CAAC,kBAAkB,CAAC,KAC3C,aAAa,CAAC,kBAAkB,CACkD,CAAC;AAEtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,+BAA+B,GAC3C,aAAa,aAAa,CAAC,cAAc,CAAC,EAC1C,kBAAkB,aAAa,CAAC,cAAc,CAAC,KAC7C,IA+BF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACtD,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAgF/B,CAAC"}
@@ -7,26 +7,35 @@
7
7
  * @module
8
8
  */
9
9
  import { ApiError } from '../http/error_schemas.js';
10
- import { middleware_applies } from '../http/schema_helpers.js';
10
+ import { middleware_applies, middleware_paths_overlap } from '../http/schema_helpers.js';
11
+ /**
12
+ * The Hono path pattern the origin check mounts on — every path. A request to
13
+ * any route, the static fallback included, carrying a foreign `Origin` is
14
+ * refused; one with no `Origin` (a probe, a CLI client) passes. The only way
15
+ * out is a named carve-out with a reason (`AuthMiddlewareOptions.origin_carve_outs`).
16
+ * The Rust spine's `fuz_http::compose_stack` checks every route group the
17
+ * same way.
18
+ */
19
+ export const ORIGIN_MIDDLEWARE_PATH = '*';
11
20
  /**
12
21
  * The Hono path pattern the auth middleware stack mounts on by default —
13
- * origin verification, session parsing, request context, bearer and daemon
14
- * token auth all run for the paths it matches, and no others.
22
+ * session parsing, request context, bearer and daemon token auth all run for
23
+ * the paths it matches, and no others (the origin check runs on every path,
24
+ * `ORIGIN_MIDDLEWARE_PATH`).
15
25
  *
16
26
  * `create_app_server` mounts the stack here, and refuses at assembly (via
17
27
  * `assert_endpoint_in_auth_scope`, which `create_test_app_surface_spec` also
18
28
  * runs) an `rpc_endpoints`, `ws_endpoints`, or bootstrap path the pattern does
19
29
  * not match (tested with `middleware_applies`, which follows Hono: `'/api/*'`
20
30
  * matches `/api` and everything under `/api/`): an endpoint outside it would
21
- * get no origin check or session parsing, so every call or upgrade would be
22
- * unauthenticated and any page could send it.
31
+ * get no session or bearer parsing, so every call or upgrade would be
32
+ * unauthenticated.
23
33
  */
24
34
  export const AUTH_MIDDLEWARE_PATH = '/api/*';
25
35
  /**
26
36
  * Throw when an endpoint path sits outside the auth middleware's scope
27
- * (`AUTH_MIDDLEWARE_PATH`) — there it would get no origin check and no
28
- * session or bearer parsing, so every call or upgrade would be
29
- * unauthenticated and any page could send it.
37
+ * (`AUTH_MIDDLEWARE_PATH`) — there it would get no session or bearer
38
+ * parsing, so every call or upgrade would be unauthenticated.
30
39
  * `middleware_applies` matches the way Hono matches the middleware's pattern.
31
40
  *
32
41
  * `create_app_server` calls it for every `rpc_endpoints` and `ws_endpoints`
@@ -41,10 +50,94 @@ export const AUTH_MIDDLEWARE_PATH = '/api/*';
41
50
  export const assert_endpoint_in_auth_scope = (option, path) => {
42
51
  if (!middleware_applies(AUTH_MIDDLEWARE_PATH, path)) {
43
52
  throw new Error(`${option} path ${path} is outside the auth middleware's scope ` +
44
- `(${AUTH_MIDDLEWARE_PATH}) — it would skip the origin check and see every caller ` +
45
- `as unauthenticated — mount it under /api/`);
53
+ `(${AUTH_MIDDLEWARE_PATH}) — it would see every caller as unauthenticated — ` +
54
+ `mount it under /api/`);
46
55
  }
47
56
  };
57
+ /**
58
+ * Throw when a route that reads or mints a credential sits outside the auth
59
+ * middleware's scope (`AUTH_MIDDLEWARE_PATH`). A route reads one when its
60
+ * `auth.account` is not `'none'`, or when it is marked `reads_credential` (a
61
+ * handler that reads the resolved account behind no gate: account status) —
62
+ * outside the scope no session or bearer parsing runs, so it would answer
63
+ * every caller as unauthenticated. A route
64
+ * mints one when it is marked `mints_credential` (login, signup, bootstrap) —
65
+ * outside the scope an origin carve-out could cover it
66
+ * (`assert_origin_carve_outs_valid` refuses one only inside), and a forged
67
+ * cross-site request to it is login CSRF.
68
+ *
69
+ * Account and signup route specs are relative and the consumer prefixes them,
70
+ * so this is what holds them inside the scope whatever prefix they get.
71
+ * `create_app_server` runs it over every route it registers, consumer and
72
+ * factory alike, and `create_test_app_surface_spec` does the same.
73
+ *
74
+ * @param route_specs - every route the server registers
75
+ * @throws Error naming the first route that reads or mints a credential outside `AUTH_MIDDLEWARE_PATH`
76
+ */
77
+ export const assert_credential_routes_in_auth_scope = (route_specs) => {
78
+ for (const spec of route_specs) {
79
+ const reads = spec.auth.account !== 'none' || spec.reads_credential === true;
80
+ if (!reads && !spec.mints_credential)
81
+ continue;
82
+ if (middleware_applies(AUTH_MIDDLEWARE_PATH, spec.path))
83
+ continue;
84
+ throw new Error(`route ${spec.method} ${spec.path} ${reads ? 'reads' : 'mints'} a credential but is ` +
85
+ `outside the auth middleware's scope (${AUTH_MIDDLEWARE_PATH}) — ` +
86
+ (reads
87
+ ? 'it would see every caller as unauthenticated'
88
+ : 'an origin carve-out could leave it open to cross-site requests') +
89
+ ' — mount it under /api/');
90
+ }
91
+ };
92
+ /**
93
+ * Throw unless every origin carve-out names a path and a reason, and stays
94
+ * off the auth middleware's scope: a path that starts with `/` (a Hono pattern
95
+ * like `/hooks/*` or `/health`), a non-blank reason — a carve-out is an
96
+ * argument someone wrote down, never an empty placeholder — and a pattern that
97
+ * matches nothing under `auth_path` (`middleware_paths_overlap`).
98
+ *
99
+ * The scope rule is what keeps a carve-out off every credentialed route.
100
+ * Assembly holds every route that reads a credential (`auth.account` not
101
+ * `'none'`, or `reads_credential`) or mints one (`mints_credential`: login,
102
+ * signup, bootstrap) inside
103
+ * `AUTH_MIDDLEWARE_PATH` (`assert_credential_routes_in_auth_scope`), and every
104
+ * RPC, WS, and bootstrap endpoint besides (`assert_endpoint_in_auth_scope`).
105
+ * The minting routes declare no auth but are what a forged cross-site request
106
+ * targets, so refusing by route `auth` would miss them; refusing by scope
107
+ * catches them. A route outside the scope reads no credential the auth
108
+ * middleware parses, and mints none, so a carve-out there removes no
109
+ * credentialed check.
110
+ *
111
+ * @param carve_outs - the carve-outs to check
112
+ * @param auth_path - the auth middleware's path pattern (default `AUTH_MIDDLEWARE_PATH`)
113
+ * @throws Error naming the first carve-out with no path, no reason, or a pattern overlapping `auth_path`
114
+ */
115
+ export const assert_origin_carve_outs_valid = (carve_outs, auth_path = AUTH_MIDDLEWARE_PATH) => {
116
+ for (const carve_out of carve_outs) {
117
+ if (!carve_out.path.startsWith('/')) {
118
+ throw new Error(`origin carve-out path ${JSON.stringify(carve_out.path)} must start with / ` +
119
+ '(a Hono pattern like /hooks/* or /health)');
120
+ }
121
+ if (carve_out.reason.trim() === '') {
122
+ throw new Error(`origin carve-out ${carve_out.path} needs a reason — say why the paths are safe ` +
123
+ 'without the origin check');
124
+ }
125
+ if (middleware_paths_overlap(carve_out.path, auth_path)) {
126
+ throw new Error(`origin carve-out ${carve_out.path} overlaps the auth middleware's scope ` +
127
+ `(${auth_path}) — the routes there read or mint credentials and always take ` +
128
+ 'the origin check; carve out a path outside it');
129
+ }
130
+ }
131
+ };
132
+ /**
133
+ * Copy origin carve-outs into a frozen array of frozen `{path, reason}`
134
+ * records, so a caller that mutates its own array or objects after assembly
135
+ * cannot change what was validated or what the check skips.
136
+ *
137
+ * @param carve_outs - the caller's carve-outs
138
+ * @returns the frozen copy
139
+ */
140
+ export const freeze_origin_carve_outs = (carve_outs) => Object.freeze(carve_outs.map(({ path, reason }) => Object.freeze({ path, reason })));
48
141
  /**
49
142
  * Throw unless a middleware list still mounts every spec of the stack it was
50
143
  * built from — the trusted-proxy spec and the auth middleware — exactly once,
@@ -107,18 +200,23 @@ export const assert_middleware_stack_mounted = (stack_specs, middleware_specs) =
107
200
  /**
108
201
  * Create the auth middleware stack.
109
202
  *
110
- * Returns `[origin, session, request_context, bearer_auth]` middleware specs
111
- * for the given path pattern. When `daemon_token_state` is provided, appends
203
+ * Returns `[origin, session, request_context, bearer_auth]` middleware specs:
204
+ * the origin check on every path less its carve-outs, the rest for the given
205
+ * path pattern. When `daemon_token_state` is provided, appends
112
206
  * a `daemon_token` layer. Apps can append extra entries for non-standard
113
207
  * paths (e.g., tx's `/tx` binary endpoint).
114
208
  *
115
209
  * @param deps - stateless capabilities (keyring, db)
116
- * @param options - middleware configuration (allowed_origins, session_options, path, daemon_token_state)
210
+ * @param options - middleware configuration (allowed_origins, session_options, path, daemon_token_state, origin_carve_outs)
211
+ * @throws Error when an origin carve-out has no path or no reason, or overlaps `path`
117
212
  * @returns the middleware spec array
118
213
  */
119
214
  export const create_auth_middleware_specs = async (deps, options) => {
120
215
  const { keyring, db } = deps;
121
216
  const { allowed_origins, session_options, path = AUTH_MIDDLEWARE_PATH, daemon_token_state } = options;
217
+ // copied before it is checked, so what is checked is what runs
218
+ const origin_carve_outs = freeze_origin_carve_outs(options.origin_carve_outs ?? []);
219
+ assert_origin_carve_outs_valid(origin_carve_outs, path);
122
220
  const query_deps = { db };
123
221
  // Dynamic imports preserve the bundle-split for type-only consumers of
124
222
  // this module (`MiddlewareSpec`, `AuthMiddlewareOptions`, etc.). The
@@ -137,9 +235,10 @@ export const create_auth_middleware_specs = async (deps, options) => {
137
235
  const specs = [
138
236
  {
139
237
  name: 'origin',
140
- path,
141
- handler: verify_request_source(allowed_origins),
142
- errors: { 403: ApiError }
238
+ path: ORIGIN_MIDDLEWARE_PATH,
239
+ handler: verify_request_source(allowed_origins, origin_carve_outs),
240
+ errors: { 403: ApiError },
241
+ ...(origin_carve_outs.length > 0 && { carve_outs: origin_carve_outs })
143
242
  },
144
243
  { name: 'session', path, handler: session_middleware },
145
244
  { name: 'request_context', path, handler: request_context_middleware },
@@ -212,6 +212,10 @@ export type ResolveActingActorResult = {
212
212
  * The available list is intentionally not echoed in this branch (treat
213
213
  * as opaque rejection).
214
214
  *
215
+ * `acting_actor_id` is compared lowercased, the form actor ids are stored and
216
+ * read back in, so a caller passing an id that skipped the `Uuid` schema
217
+ * still matches its actor whatever its case.
218
+ *
215
219
  * @param deps - query dependencies
216
220
  * @param account_id - the authenticated account
217
221
  * @param acting_actor_id - the requested acting actor id, or `undefined`
@@ -222,7 +226,9 @@ export declare const resolve_acting_actor: (deps: QueryDeps, account_id: string,
222
226
  *
223
227
  * Reads the session identity (set by session middleware), looks up the
224
228
  * `auth_session`, and on a valid session sets `c.var.auth_account_id`,
225
- * `CREDENTIAL_TYPE_KEY = 'session'`, and `AUTH_SESSION_TOKEN_HASH_KEY`.
229
+ * `CREDENTIAL_TYPE_KEY = 'session'`, `AUTH_SESSION_TOKEN_HASH_KEY`, and
230
+ * `CREDENTIAL_DEADLINE_KEY` — the earlier of the cookie's signed expiry
231
+ * (`SESSION_COOKIE_EXPIRES_AT_KEY`) and the row's.
226
232
  * Touches the session (fire-and-forget). Does not load actor or role_grants;
227
233
  * `REQUEST_CONTEXT_KEY` is left null — the route-spec / RPC dispatcher
228
234
  * authorization phase resolves the acting actor and builds the full
@@ -233,7 +239,7 @@ export declare const resolve_acting_actor: (deps: QueryDeps, account_id: string,
233
239
  *
234
240
  * @param deps - query dependencies (pool-level db for middleware)
235
241
  * @param session_context_key - the Hono context key where session middleware stored the session token
236
- * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, and `AUTH_API_TOKEN_ID_KEY`
242
+ * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, `AUTH_API_TOKEN_ID_KEY`, and `CREDENTIAL_DEADLINE_KEY`
237
243
  */
238
244
  export declare const create_request_context_middleware: (deps: QueryDeps, session_context_key?: string) => MiddlewareHandler;
239
245
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}
@@ -38,7 +38,8 @@ import { is_role_grant_active } from './account_schema.js';
38
38
  import { hash_session_token, query_session_get_valid } from './session_queries.js';
39
39
  import { query_account_by_id, query_actor_by_id, query_active_actors_by_account } from './account_queries.js';
40
40
  import { query_role_grant_find_active_for_actor } from './role_grant_queries.js';
41
- import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
41
+ import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_DEADLINE_KEY, CREDENTIAL_TYPE_KEY, SESSION_COOKIE_EXPIRES_AT_KEY, TOKEN_SCOPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
42
+ import { credential_deadline_after } from './credential_deadline.js';
42
43
  import { parse_token_scope_capability, token_scope_admits_capability, token_scope_denied_body, token_scope_full } from './token_scope.js';
43
44
  import { is_public_auth, needs_actor, parse_acting } from '../http/auth_shape.js';
44
45
  import { is_null_schema } from '../http/schema_helpers.js';
@@ -180,6 +181,10 @@ export const has_any_scoped_role = (ctx, roles, scope_id, now = new Date()) => {
180
181
  * The available list is intentionally not echoed in this branch (treat
181
182
  * as opaque rejection).
182
183
  *
184
+ * `acting_actor_id` is compared lowercased, the form actor ids are stored and
185
+ * read back in, so a caller passing an id that skipped the `Uuid` schema
186
+ * still matches its actor whatever its case.
187
+ *
183
188
  * @param deps - query dependencies
184
189
  * @param account_id - the authenticated account
185
190
  * @param acting_actor_id - the requested acting actor id, or `undefined`
@@ -197,7 +202,8 @@ export const resolve_acting_actor = async (deps, account_id, acting_actor_id) =>
197
202
  available: actors.map((a) => ({ id: a.id, name: a.name }))
198
203
  };
199
204
  }
200
- const match = actors.find((a) => a.id === acting_actor_id);
205
+ const acting = acting_actor_id.toLowerCase();
206
+ const match = actors.find((a) => a.id === acting);
201
207
  if (!match)
202
208
  return { ok: false, reason: 'actor_not_on_account' };
203
209
  return { ok: true, actor_id: match.id };
@@ -207,7 +213,9 @@ export const resolve_acting_actor = async (deps, account_id, acting_actor_id) =>
207
213
  *
208
214
  * Reads the session identity (set by session middleware), looks up the
209
215
  * `auth_session`, and on a valid session sets `c.var.auth_account_id`,
210
- * `CREDENTIAL_TYPE_KEY = 'session'`, and `AUTH_SESSION_TOKEN_HASH_KEY`.
216
+ * `CREDENTIAL_TYPE_KEY = 'session'`, `AUTH_SESSION_TOKEN_HASH_KEY`, and
217
+ * `CREDENTIAL_DEADLINE_KEY` — the earlier of the cookie's signed expiry
218
+ * (`SESSION_COOKIE_EXPIRES_AT_KEY`) and the row's.
211
219
  * Touches the session (fire-and-forget). Does not load actor or role_grants;
212
220
  * `REQUEST_CONTEXT_KEY` is left null — the route-spec / RPC dispatcher
213
221
  * authorization phase resolves the acting actor and builds the full
@@ -218,7 +226,7 @@ export const resolve_acting_actor = async (deps, account_id, acting_actor_id) =>
218
226
  *
219
227
  * @param deps - query dependencies (pool-level db for middleware)
220
228
  * @param session_context_key - the Hono context key where session middleware stored the session token
221
- * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, and `AUTH_API_TOKEN_ID_KEY`
229
+ * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, `AUTH_API_TOKEN_ID_KEY`, and `CREDENTIAL_DEADLINE_KEY`
222
230
  */
223
231
  export const create_request_context_middleware = (deps, session_context_key = 'auth_session_id') => {
224
232
  return async (c, next) => {
@@ -227,6 +235,7 @@ export const create_request_context_middleware = (deps, session_context_key = 'a
227
235
  c.set(CREDENTIAL_TYPE_KEY, null);
228
236
  c.set(AUTH_SESSION_TOKEN_HASH_KEY, null);
229
237
  c.set(AUTH_API_TOKEN_ID_KEY, null);
238
+ c.set(CREDENTIAL_DEADLINE_KEY, null);
230
239
  const session_token = c.get(session_context_key) ?? null;
231
240
  if (!session_token) {
232
241
  await next();
@@ -243,6 +252,13 @@ export const create_request_context_middleware = (deps, session_context_key = 'a
243
252
  // A session is full account authority by construction.
244
253
  c.set(TOKEN_SCOPE_KEY, token_scope_full());
245
254
  c.set(AUTH_SESSION_TOKEN_HASH_KEY, token_hash);
255
+ // The earlier of the signed cookie's expiry (this server's wall clock,
256
+ // which signed it) and the row's (read on the database clock).
257
+ const cookie_expires_at = c.get(SESSION_COOKIE_EXPIRES_AT_KEY) ?? null;
258
+ c.set(CREDENTIAL_DEADLINE_KEY, credential_deadline_after([
259
+ cookie_expires_at === null ? null : cookie_expires_at * 1000 - Date.now(),
260
+ session.expires_in_ms
261
+ ]));
246
262
  await next();
247
263
  };
248
264
  };