@fuzdev/fuz_app 0.55.0 → 0.57.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 (333) hide show
  1. package/dist/actions/CLAUDE.md +211 -155
  2. package/dist/actions/action_bridge.d.ts +8 -5
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +1 -11
  5. package/dist/actions/action_codegen.d.ts +19 -0
  6. package/dist/actions/action_codegen.d.ts.map +1 -1
  7. package/dist/actions/action_codegen.js +20 -14
  8. package/dist/actions/action_registry.d.ts.map +1 -1
  9. package/dist/actions/action_registry.js +5 -2
  10. package/dist/actions/action_rpc.d.ts +110 -44
  11. package/dist/actions/action_rpc.d.ts.map +1 -1
  12. package/dist/actions/action_rpc.js +92 -287
  13. package/dist/actions/action_spec.d.ts +55 -16
  14. package/dist/actions/action_spec.d.ts.map +1 -1
  15. package/dist/actions/action_spec.js +16 -11
  16. package/dist/actions/action_types.d.ts +28 -60
  17. package/dist/actions/action_types.d.ts.map +1 -1
  18. package/dist/actions/action_types.js +13 -5
  19. package/dist/actions/broadcast_api.d.ts +2 -2
  20. package/dist/actions/broadcast_api.js +2 -2
  21. package/dist/actions/compile_action_registry.d.ts +50 -0
  22. package/dist/actions/compile_action_registry.d.ts.map +1 -0
  23. package/dist/actions/compile_action_registry.js +69 -0
  24. package/dist/actions/heartbeat.d.ts +8 -4
  25. package/dist/actions/heartbeat.d.ts.map +1 -1
  26. package/dist/actions/heartbeat.js +5 -4
  27. package/dist/actions/perform_action.d.ts +145 -0
  28. package/dist/actions/perform_action.d.ts.map +1 -0
  29. package/dist/actions/perform_action.js +258 -0
  30. package/dist/actions/register_action_ws.d.ts +44 -38
  31. package/dist/actions/register_action_ws.d.ts.map +1 -1
  32. package/dist/actions/register_action_ws.js +101 -159
  33. package/dist/actions/register_ws_endpoint.d.ts +2 -10
  34. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  35. package/dist/actions/register_ws_endpoint.js +32 -10
  36. package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
  37. package/dist/actions/transports_ws_auth_guard.js +1 -1
  38. package/dist/actions/transports_ws_backend.d.ts +1 -1
  39. package/dist/actions/transports_ws_backend.js +1 -1
  40. package/dist/auth/CLAUDE.md +673 -442
  41. package/dist/auth/account_action_specs.d.ts +28 -7
  42. package/dist/auth/account_action_specs.d.ts.map +1 -1
  43. package/dist/auth/account_action_specs.js +7 -7
  44. package/dist/auth/account_actions.d.ts +8 -14
  45. package/dist/auth/account_actions.d.ts.map +1 -1
  46. package/dist/auth/account_actions.js +26 -32
  47. package/dist/auth/account_queries.d.ts +46 -13
  48. package/dist/auth/account_queries.d.ts.map +1 -1
  49. package/dist/auth/account_queries.js +73 -33
  50. package/dist/auth/account_routes.d.ts +4 -3
  51. package/dist/auth/account_routes.d.ts.map +1 -1
  52. package/dist/auth/account_routes.js +58 -33
  53. package/dist/auth/account_schema.d.ts +46 -54
  54. package/dist/auth/account_schema.d.ts.map +1 -1
  55. package/dist/auth/account_schema.js +21 -48
  56. package/dist/auth/admin_action_specs.d.ts +55 -21
  57. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  58. package/dist/auth/admin_action_specs.js +42 -26
  59. package/dist/auth/admin_actions.d.ts +14 -21
  60. package/dist/auth/admin_actions.d.ts.map +1 -1
  61. package/dist/auth/admin_actions.js +47 -44
  62. package/dist/auth/audit_emitter.d.ts +160 -0
  63. package/dist/auth/audit_emitter.d.ts.map +1 -0
  64. package/dist/auth/audit_emitter.js +83 -0
  65. package/dist/auth/audit_log_queries.d.ts +17 -87
  66. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  67. package/dist/auth/audit_log_queries.js +17 -96
  68. package/dist/auth/audit_log_routes.d.ts +1 -1
  69. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  70. package/dist/auth/audit_log_routes.js +7 -3
  71. package/dist/auth/audit_log_schema.d.ts +48 -42
  72. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  73. package/dist/auth/audit_log_schema.js +56 -43
  74. package/dist/auth/auth_guard_resolver.d.ts +44 -0
  75. package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
  76. package/dist/auth/auth_guard_resolver.js +56 -0
  77. package/dist/auth/bootstrap_account.d.ts +7 -7
  78. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  79. package/dist/auth/bootstrap_account.js +7 -7
  80. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  81. package/dist/auth/bootstrap_routes.js +11 -10
  82. package/dist/auth/cleanup.d.ts +20 -26
  83. package/dist/auth/cleanup.d.ts.map +1 -1
  84. package/dist/auth/cleanup.js +33 -47
  85. package/dist/auth/credential_type_schema.d.ts +115 -0
  86. package/dist/auth/credential_type_schema.d.ts.map +1 -0
  87. package/dist/auth/credential_type_schema.js +127 -0
  88. package/dist/auth/daemon_token_middleware.d.ts +1 -1
  89. package/dist/auth/daemon_token_middleware.js +3 -3
  90. package/dist/auth/ddl.d.ts +2 -2
  91. package/dist/auth/ddl.d.ts.map +1 -1
  92. package/dist/auth/ddl.js +6 -6
  93. package/dist/auth/deps.d.ts +7 -32
  94. package/dist/auth/deps.d.ts.map +1 -1
  95. package/dist/auth/grant_path_schema.d.ts +117 -0
  96. package/dist/auth/grant_path_schema.d.ts.map +1 -0
  97. package/dist/auth/grant_path_schema.js +137 -0
  98. package/dist/auth/invite_queries.d.ts +12 -1
  99. package/dist/auth/invite_queries.d.ts.map +1 -1
  100. package/dist/auth/invite_queries.js +12 -1
  101. package/dist/auth/invite_schema.d.ts +1 -1
  102. package/dist/auth/invite_schema.d.ts.map +1 -1
  103. package/dist/auth/invite_schema.js +1 -1
  104. package/dist/auth/middleware.d.ts.map +1 -1
  105. package/dist/auth/middleware.js +5 -2
  106. package/dist/auth/migrations.d.ts +22 -7
  107. package/dist/auth/migrations.d.ts.map +1 -1
  108. package/dist/auth/migrations.js +64 -25
  109. package/dist/auth/request_context.d.ts +157 -170
  110. package/dist/auth/request_context.d.ts.map +1 -1
  111. package/dist/auth/request_context.js +224 -268
  112. package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +130 -100
  113. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
  114. package/dist/auth/role_grant_offer_action_specs.js +262 -0
  115. package/dist/auth/role_grant_offer_actions.d.ts +104 -0
  116. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
  117. package/dist/auth/{permit_offer_actions.js → role_grant_offer_actions.js} +153 -140
  118. package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +80 -70
  119. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
  120. package/dist/auth/role_grant_offer_notifications.js +182 -0
  121. package/dist/auth/{permit_offer_queries.d.ts → role_grant_offer_queries.d.ts} +64 -64
  122. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
  123. package/dist/auth/{permit_offer_queries.js → role_grant_offer_queries.js} +136 -123
  124. package/dist/auth/role_grant_offer_schema.d.ts +150 -0
  125. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
  126. package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +55 -36
  127. package/dist/auth/role_grant_queries.d.ts +231 -0
  128. package/dist/auth/role_grant_queries.d.ts.map +1 -0
  129. package/dist/auth/role_grant_queries.js +320 -0
  130. package/dist/auth/role_schema.d.ts +150 -40
  131. package/dist/auth/role_schema.d.ts.map +1 -1
  132. package/dist/auth/role_schema.js +144 -45
  133. package/dist/auth/scope_kind_schema.d.ts +96 -0
  134. package/dist/auth/scope_kind_schema.d.ts.map +1 -0
  135. package/dist/auth/scope_kind_schema.js +94 -0
  136. package/dist/auth/self_service_role_action_specs.d.ts +4 -1
  137. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  138. package/dist/auth/self_service_role_action_specs.js +2 -2
  139. package/dist/auth/self_service_role_actions.d.ts +35 -29
  140. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  141. package/dist/auth/self_service_role_actions.js +58 -48
  142. package/dist/auth/session_cookie.d.ts +43 -6
  143. package/dist/auth/session_cookie.d.ts.map +1 -1
  144. package/dist/auth/session_cookie.js +31 -5
  145. package/dist/auth/session_middleware.d.ts +37 -3
  146. package/dist/auth/session_middleware.d.ts.map +1 -1
  147. package/dist/auth/session_middleware.js +33 -7
  148. package/dist/auth/signup_routes.d.ts.map +1 -1
  149. package/dist/auth/signup_routes.js +48 -19
  150. package/dist/auth/standard_action_specs.d.ts +2 -2
  151. package/dist/auth/standard_action_specs.js +4 -4
  152. package/dist/auth/standard_rpc_actions.d.ts +23 -19
  153. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  154. package/dist/auth/standard_rpc_actions.js +12 -12
  155. package/dist/db/migrate.d.ts +1 -1
  156. package/dist/db/migrate.js +1 -1
  157. package/dist/dev/setup.d.ts +2 -2
  158. package/dist/dev/setup.d.ts.map +1 -1
  159. package/dist/dev/setup.js +4 -4
  160. package/dist/env/load.d.ts +1 -1
  161. package/dist/env/load.js +1 -1
  162. package/dist/hono_context.d.ts +27 -45
  163. package/dist/hono_context.d.ts.map +1 -1
  164. package/dist/hono_context.js +14 -28
  165. package/dist/http/CLAUDE.md +235 -121
  166. package/dist/http/auth_shape.d.ts +191 -0
  167. package/dist/http/auth_shape.d.ts.map +1 -0
  168. package/dist/http/auth_shape.js +237 -0
  169. package/dist/http/common_routes.js +3 -3
  170. package/dist/http/db_routes.d.ts +4 -0
  171. package/dist/http/db_routes.d.ts.map +1 -1
  172. package/dist/http/db_routes.js +44 -7
  173. package/dist/http/error_schemas.d.ts +72 -39
  174. package/dist/http/error_schemas.d.ts.map +1 -1
  175. package/dist/http/error_schemas.js +81 -33
  176. package/dist/http/pending_effects.d.ts +71 -18
  177. package/dist/http/pending_effects.d.ts.map +1 -1
  178. package/dist/http/pending_effects.js +87 -18
  179. package/dist/http/proxy.d.ts +52 -5
  180. package/dist/http/proxy.d.ts.map +1 -1
  181. package/dist/http/proxy.js +92 -14
  182. package/dist/http/route_spec.d.ts +89 -75
  183. package/dist/http/route_spec.d.ts.map +1 -1
  184. package/dist/http/route_spec.js +54 -72
  185. package/dist/http/schema_helpers.d.ts +3 -14
  186. package/dist/http/schema_helpers.d.ts.map +1 -1
  187. package/dist/http/schema_helpers.js +2 -14
  188. package/dist/http/surface.d.ts +2 -10
  189. package/dist/http/surface.d.ts.map +1 -1
  190. package/dist/http/surface.js +3 -4
  191. package/dist/http/surface_query.d.ts +39 -35
  192. package/dist/http/surface_query.d.ts.map +1 -1
  193. package/dist/http/surface_query.js +79 -36
  194. package/dist/primitive_schemas.d.ts +39 -0
  195. package/dist/primitive_schemas.d.ts.map +1 -0
  196. package/dist/primitive_schemas.js +40 -0
  197. package/dist/realtime/sse_auth_guard.d.ts +5 -5
  198. package/dist/realtime/sse_auth_guard.js +9 -9
  199. package/dist/runtime/mock.d.ts +1 -1
  200. package/dist/runtime/mock.js +1 -1
  201. package/dist/server/app_backend.d.ts +14 -11
  202. package/dist/server/app_backend.d.ts.map +1 -1
  203. package/dist/server/app_backend.js +12 -8
  204. package/dist/server/app_server.d.ts +7 -7
  205. package/dist/server/app_server.d.ts.map +1 -1
  206. package/dist/server/app_server.js +35 -40
  207. package/dist/server/validate_nginx.d.ts +1 -1
  208. package/dist/server/validate_nginx.js +1 -1
  209. package/dist/testing/CLAUDE.md +50 -38
  210. package/dist/testing/admin_integration.d.ts +5 -6
  211. package/dist/testing/admin_integration.d.ts.map +1 -1
  212. package/dist/testing/admin_integration.js +87 -85
  213. package/dist/testing/app_server.d.ts +11 -14
  214. package/dist/testing/app_server.d.ts.map +1 -1
  215. package/dist/testing/app_server.js +16 -15
  216. package/dist/testing/assertions.d.ts.map +1 -1
  217. package/dist/testing/assertions.js +2 -1
  218. package/dist/testing/attack_surface.d.ts.map +1 -1
  219. package/dist/testing/attack_surface.js +15 -9
  220. package/dist/testing/audit_completeness.d.ts +2 -2
  221. package/dist/testing/audit_completeness.d.ts.map +1 -1
  222. package/dist/testing/audit_completeness.js +36 -36
  223. package/dist/testing/auth_apps.d.ts +5 -4
  224. package/dist/testing/auth_apps.d.ts.map +1 -1
  225. package/dist/testing/auth_apps.js +22 -19
  226. package/dist/testing/data_exposure.d.ts.map +1 -1
  227. package/dist/testing/data_exposure.js +5 -5
  228. package/dist/testing/db.d.ts +1 -1
  229. package/dist/testing/db.d.ts.map +1 -1
  230. package/dist/testing/db.js +4 -4
  231. package/dist/testing/db_entities.d.ts +22 -0
  232. package/dist/testing/db_entities.d.ts.map +1 -0
  233. package/dist/testing/db_entities.js +28 -0
  234. package/dist/testing/entities.d.ts +8 -7
  235. package/dist/testing/entities.d.ts.map +1 -1
  236. package/dist/testing/entities.js +21 -18
  237. package/dist/testing/integration.d.ts.map +1 -1
  238. package/dist/testing/integration.js +13 -14
  239. package/dist/testing/integration_helpers.d.ts +4 -4
  240. package/dist/testing/integration_helpers.d.ts.map +1 -1
  241. package/dist/testing/integration_helpers.js +20 -18
  242. package/dist/testing/middleware.d.ts +4 -4
  243. package/dist/testing/middleware.d.ts.map +1 -1
  244. package/dist/testing/middleware.js +12 -11
  245. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  246. package/dist/testing/rpc_attack_surface.js +40 -24
  247. package/dist/testing/rpc_round_trip.d.ts +1 -1
  248. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  249. package/dist/testing/rpc_round_trip.js +14 -13
  250. package/dist/testing/sse_round_trip.d.ts +3 -4
  251. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  252. package/dist/testing/sse_round_trip.js +7 -11
  253. package/dist/testing/standard.d.ts +1 -1
  254. package/dist/testing/stubs.d.ts +25 -0
  255. package/dist/testing/stubs.d.ts.map +1 -1
  256. package/dist/testing/stubs.js +43 -2
  257. package/dist/testing/surface_invariants.d.ts +14 -6
  258. package/dist/testing/surface_invariants.d.ts.map +1 -1
  259. package/dist/testing/surface_invariants.js +119 -43
  260. package/dist/testing/ws_round_trip.d.ts +12 -13
  261. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  262. package/dist/testing/ws_round_trip.js +19 -11
  263. package/dist/ui/AdminAccounts.svelte +23 -20
  264. package/dist/ui/AdminOverview.svelte +15 -13
  265. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  266. package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
  267. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
  268. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
  269. package/dist/ui/BootstrapForm.svelte +1 -1
  270. package/dist/ui/CLAUDE.md +60 -60
  271. package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
  272. package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
  273. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
  274. package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
  275. package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
  276. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
  277. package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
  278. package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
  279. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
  280. package/dist/ui/SignupForm.svelte +1 -1
  281. package/dist/ui/SurfaceExplorer.svelte +35 -15
  282. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  283. package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
  284. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  285. package/dist/ui/account_sessions_state.svelte.js +2 -3
  286. package/dist/ui/admin_accounts_state.svelte.d.ts +18 -18
  287. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  288. package/dist/ui/admin_accounts_state.svelte.js +16 -16
  289. package/dist/ui/admin_rpc_adapters.d.ts +20 -20
  290. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  291. package/dist/ui/admin_rpc_adapters.js +17 -17
  292. package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
  293. package/dist/ui/admin_sessions_state.svelte.js +2 -2
  294. package/dist/ui/audit_log_state.svelte.d.ts +7 -7
  295. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  296. package/dist/ui/audit_log_state.svelte.js +6 -6
  297. package/dist/ui/auth_state.svelte.d.ts +3 -3
  298. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  299. package/dist/ui/auth_state.svelte.js +6 -6
  300. package/dist/ui/format_scope.d.ts +2 -2
  301. package/dist/ui/format_scope.js +2 -2
  302. package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +30 -30
  303. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
  304. package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +18 -18
  305. package/dist/ui/ui_format.js +2 -2
  306. package/package.json +3 -3
  307. package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
  308. package/dist/auth/permit_offer_action_specs.js +0 -258
  309. package/dist/auth/permit_offer_actions.d.ts +0 -110
  310. package/dist/auth/permit_offer_actions.d.ts.map +0 -1
  311. package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
  312. package/dist/auth/permit_offer_notifications.js +0 -182
  313. package/dist/auth/permit_offer_queries.d.ts.map +0 -1
  314. package/dist/auth/permit_offer_schema.d.ts +0 -125
  315. package/dist/auth/permit_offer_schema.d.ts.map +0 -1
  316. package/dist/auth/permit_queries.d.ts +0 -222
  317. package/dist/auth/permit_queries.d.ts.map +0 -1
  318. package/dist/auth/permit_queries.js +0 -305
  319. package/dist/auth/require_keeper.d.ts +0 -20
  320. package/dist/auth/require_keeper.d.ts.map +0 -1
  321. package/dist/auth/require_keeper.js +0 -35
  322. package/dist/auth/route_guards.d.ts +0 -27
  323. package/dist/auth/route_guards.d.ts.map +0 -1
  324. package/dist/auth/route_guards.js +0 -38
  325. package/dist/auth/session_lifecycle.d.ts +0 -37
  326. package/dist/auth/session_lifecycle.d.ts.map +0 -1
  327. package/dist/auth/session_lifecycle.js +0 -29
  328. package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
  329. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
  330. package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
  331. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
  332. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
  333. package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
@@ -2,19 +2,24 @@
2
2
  * Unified self-service role toggle RPC action.
3
3
  *
4
4
  * One static `request_response` action — `self_service_role_set` — that
5
- * takes `{role, enabled}` and toggles a global permit on the caller for an
5
+ * takes `{role, enabled}` and toggles a global role_grant on the caller for an
6
6
  * allowlisted role. Idempotent in both directions: re-enabling an
7
7
  * already-held role returns `changed: false`; disabling a role the caller
8
8
  * doesn't hold returns `changed: false`.
9
9
  *
10
- * The factory takes an `eligible_roles` allowlist (validated against the
11
- * supplied `roles.role_options` at factory time so typos surface at startup
12
- * instead of at first call). Roles outside the allowlist are rejected
13
- * with `forbidden` + reason `role_not_self_service_eligible`.
10
+ * Eligibility is derived by default from `RoleSpec.grant_paths`
11
+ * every role whose `grant_paths` includes `'self_service'`
12
+ * (`GRANT_PATH_SELF_SERVICE`) is eligible. The factory accepts an
13
+ * optional `eligible_roles` override (validated against the supplied
14
+ * `roles.role_specs` at factory time so typos surface at startup
15
+ * instead of at first call) for deployments that want to lock the
16
+ * surface down further than the role spec declares. Roles outside
17
+ * the eligible set are rejected with `forbidden` + reason
18
+ * `role_not_self_service_eligible`.
14
19
  *
15
20
  * Audit metadata carries `self_service: true` so admin reviewers can
16
- * distinguish self-toggled permits from admin grants/offers. The
17
- * `permit_grant` / `permit_revoke` metadata schemas declare
21
+ * distinguish self-toggled role_grants from admin grants/offers. The
22
+ * `role_grant_create` / `role_grant_revoke` metadata schemas declare
18
23
  * `self_service: z.boolean().optional()` explicitly, so the field is
19
24
  * part of the documented schema surface and is round-trip-validated by
20
25
  * `query_audit_log`.
@@ -22,7 +27,7 @@
22
27
  * Static method name — `role` lives in the input, not the method name —
23
28
  * so the spec is codegen-compatible (`satisfies RequestResponseActionSpec`)
24
29
  * and the surface stays constant as consumers add eligible roles. Mirrors
25
- * the existing `permit_offer_create({role})` precedent rather than
30
+ * the existing `role_grant_offer_create({role})` precedent rather than
26
31
  * generating per-role methods.
27
32
  *
28
33
  * Specs and schemas live in `auth/self_service_role_action_specs.ts` so
@@ -31,28 +36,33 @@
31
36
  *
32
37
  * @module
33
38
  */
34
- import { rpc_actor_action } from '../actions/action_rpc.js';
39
+ import { rpc_action } from '../actions/action_rpc.js';
35
40
  import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
36
- import { query_grant_permit, query_revoke_permit } from './permit_queries.js';
37
- import { audit_log_fire_and_forget } from './audit_log_queries.js';
38
- import { is_permit_active } from './account_schema.js';
41
+ import { BUILTIN_ROLE_SPECS_BY_NAME, list_roles_with_grant_path, } from './role_schema.js';
42
+ import { GRANT_PATH_SELF_SERVICE } from './grant_path_schema.js';
43
+ import { query_create_role_grant, query_revoke_role_grant } from './role_grant_queries.js';
44
+ import { is_role_grant_active } from './account_schema.js';
39
45
  import { has_scoped_role } from './request_context.js';
40
46
  import { ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE, self_service_role_set_action_spec, } from './self_service_role_action_specs.js';
41
47
  /**
42
48
  * Build the unified self-service role toggle RPC action.
43
49
  *
44
- * @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
45
- * @param options - eligible-role allowlist plus optional role schema for typo-checking
50
+ * @param deps - `RouteFactoryDeps` (`log`, `audit`, …); `audit.emit` writes
51
+ * audit rows via the captured pool. The bound emitter encapsulates
52
+ * `on_audit_event` fan-out and the optional `AuditLogConfig`.
53
+ * @param options - optional eligible-role override plus optional role schema for default-eligibility derivation
46
54
  * @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
47
- * @throws Error at factory time if any `eligible_roles` entry is missing from `options.roles.role_options`
55
+ * @throws Error at factory time if any `eligible_roles` entry is missing from `options.roles.role_specs`
48
56
  */
49
- export const create_self_service_role_actions = (deps, options) => {
50
- const eligible = new Set(options.eligible_roles);
51
- if (options.roles) {
52
- const role_options = options.roles.role_options;
57
+ export const create_self_service_role_actions = (deps, options = {}) => {
58
+ const role_specs = options.roles?.role_specs ?? BUILTIN_ROLE_SPECS_BY_NAME;
59
+ const eligible = options.eligible_roles
60
+ ? new Set(options.eligible_roles)
61
+ : new Set(list_roles_with_grant_path(role_specs, GRANT_PATH_SELF_SERVICE));
62
+ if (options.eligible_roles && options.roles) {
53
63
  for (const r of eligible) {
54
- if (!role_options.has(r)) {
55
- throw new Error(`create_self_service_role_actions: eligible_roles entry "${r}" is not registered in roles.role_options — typo or missing call to create_role_schema`);
64
+ if (!role_specs.has(r)) {
65
+ throw new Error(`create_self_service_role_actions: eligible_roles entry "${r}" is not registered in roles.role_specs — typo or missing call to create_role_schema`);
56
66
  }
57
67
  }
58
68
  }
@@ -67,69 +77,69 @@ export const create_self_service_role_actions = (deps, options) => {
67
77
  const auth = ctx.auth;
68
78
  reject_if_ineligible(input.role);
69
79
  if (input.enabled) {
70
- // Pre-check for idempotent re-grant. `query_grant_permit` is itself
71
- // idempotent (returns the existing permit instead of inserting), but
80
+ // Pre-check for idempotent re-grant. `query_create_role_grant` is itself
81
+ // idempotent (returns the existing role_grant instead of inserting), but
72
82
  // it doesn't signal "already existed" vs "newly inserted" — so we
73
- // peek first. Reads from the in-memory `auth.permits` snapshot
83
+ // peek first. Reads from the in-memory `auth.role_grants` snapshot
74
84
  // (no DB roundtrip). The TOCTOU window is benign for self-service:
75
- // two concurrent grants both observe "no permit", both call
76
- // `query_grant_permit`, and one collapses onto the other inside the
85
+ // two concurrent grants both observe "no role_grant", both call
86
+ // `query_create_role_grant`, and one collapses onto the other inside the
77
87
  // query's `ON CONFLICT DO NOTHING`. Worst case both responses report
78
- // `changed: true`; the DB still ends up with exactly one permit.
88
+ // `changed: true`; the DB still ends up with exactly one role_grant.
79
89
  if (has_scoped_role(auth, input.role, null)) {
80
90
  return { ok: true, enabled: true, changed: false };
81
91
  }
82
- const permit = await query_grant_permit(ctx, {
92
+ const role_grant = await query_create_role_grant(ctx, {
83
93
  actor_id: auth.actor.id,
84
94
  role: input.role,
85
95
  scope_id: null,
86
96
  expires_at: null,
87
97
  granted_by: auth.actor.id,
88
98
  });
89
- // `permit_grant` is the canonical actor-bound-subject event —
99
+ // `role_grant_create` is the canonical actor-bound-subject event —
90
100
  // populate both target columns even on self-service so the
91
- // "always populated for permit_grant" rule holds uniformly
101
+ // "always populated for role_grant_create" rule holds uniformly
92
102
  // regardless of who initiated the grant. On self-service the
93
103
  // grantor and grantee are the same identity; admin direct-grant
94
104
  // (separate code path) populates the same columns with the
95
105
  // grantee actor.
96
- void audit_log_fire_and_forget(ctx, {
97
- event_type: 'permit_grant',
106
+ deps.audit.emit(ctx, {
107
+ event_type: 'role_grant_create',
98
108
  actor_id: auth.actor.id,
99
109
  account_id: auth.account.id,
100
110
  target_account_id: auth.account.id,
101
111
  target_actor_id: auth.actor.id,
102
112
  ip: ctx.client_ip,
103
113
  metadata: {
104
- role: permit.role,
105
- permit_id: permit.id,
106
- scope_id: permit.scope_id,
114
+ role: role_grant.role,
115
+ role_grant_id: role_grant.id,
116
+ scope_id: role_grant.scope_id,
107
117
  self_service: true,
108
118
  },
109
- }, deps);
119
+ });
110
120
  return { ok: true, enabled: true, changed: true };
111
121
  }
112
- // Find an active global permit for this (actor, role) in the in-memory
113
- // `auth.permits` snapshot. No DB roundtrip — same correctness-equivalent
122
+ // Find an active global role_grant for this (actor, role) in the in-memory
123
+ // `auth.role_grants` snapshot. No DB roundtrip — same correctness-equivalent
114
124
  // pattern as `has_scoped_role` above (race window is between predicate
115
- // and `query_revoke_permit`'s actual UPDATE, not between predicate and
125
+ // and `query_revoke_role_grant`'s actual UPDATE, not between predicate and
116
126
  // middleware load).
117
127
  const now = new Date();
118
- const target = auth.permits.find((p) => p.role === input.role && p.scope_id === null && is_permit_active(p, now));
128
+ const target = auth.role_grants.find((p) => p.role === input.role && p.scope_id === null && is_role_grant_active(p, now));
119
129
  if (!target) {
120
130
  return { ok: true, enabled: false, changed: false };
121
131
  }
122
- const result = await query_revoke_permit(ctx, target.id, auth.actor.id, auth.actor.id);
132
+ const result = await query_revoke_role_grant(ctx, target.id, auth.actor.id, auth.actor.id);
123
133
  if (!result) {
124
134
  // Raced with another revoker — treat as already revoked.
125
135
  return { ok: true, enabled: false, changed: false };
126
136
  }
127
- // Same actor-bound rule as the grant branch — `permit_revoke`
137
+ // Same actor-bound rule as the grant branch — `role_grant_revoke`
128
138
  // always populates both target columns even on self-service so
129
139
  // forensic queries that filter on `target_actor_id IS NOT NULL`
130
- // don't silently miss self-toggled permits.
131
- void audit_log_fire_and_forget(ctx, {
132
- event_type: 'permit_revoke',
140
+ // don't silently miss self-toggled role_grants.
141
+ deps.audit.emit(ctx, {
142
+ event_type: 'role_grant_revoke',
133
143
  actor_id: auth.actor.id,
134
144
  account_id: auth.account.id,
135
145
  target_account_id: auth.account.id,
@@ -137,12 +147,12 @@ export const create_self_service_role_actions = (deps, options) => {
137
147
  ip: ctx.client_ip,
138
148
  metadata: {
139
149
  role: result.role,
140
- permit_id: result.id,
150
+ role_grant_id: result.id,
141
151
  scope_id: result.scope_id,
142
152
  self_service: true,
143
153
  },
144
- }, deps);
154
+ });
145
155
  return { ok: true, enabled: false, changed: true };
146
156
  };
147
- return [rpc_actor_action(self_service_role_set_action_spec, handler)];
157
+ return [rpc_action(self_service_role_set_action_spec, handler)];
148
158
  };
@@ -12,6 +12,14 @@
12
12
  import type { Keyring } from './keyring.js';
13
13
  /** Cookie max age in seconds (30 days — aligned with AUTH_SESSION_LIFETIME_MS). */
14
14
  export declare const SESSION_AGE_MAX: number;
15
+ /**
16
+ * Threshold (seconds) at which `process_session_cookie` re-signs a still-valid
17
+ * cookie to extend its embedded expiration. Mirrors the DB-side
18
+ * `AUTH_SESSION_EXTEND_THRESHOLD_MS` so a continuously-active user's cookie
19
+ * stays in sync with their server-side session lifetime. Set
20
+ * `SessionOptions.refresh_threshold_seconds = 0` to disable.
21
+ */
22
+ export declare const SESSION_REFRESH_THRESHOLD_S: number;
15
23
  /**
16
24
  * Cookie options for session cookies.
17
25
  */
@@ -47,9 +55,9 @@ export declare const SESSION_COOKIE_OPTIONS: SessionCookieOptions;
47
55
  *
48
56
  * @example
49
57
  * ```ts
50
- * // tx: 3-part format (admin:session_id)
51
- * const tx_config: SessionOptions<string> = {
52
- * cookie_name: 'tx_session',
58
+ * // zap: 3-part format (admin:session_id)
59
+ * const zap_config: SessionOptions<string> = {
60
+ * cookie_name: 'zap_session',
53
61
  * context_key: 'auth_session_id',
54
62
  * encode_identity: (session_id) => `admin:${session_id}`,
55
63
  * decode_identity: (payload) => {
@@ -75,8 +83,23 @@ export interface SessionOptions<TIdentity> {
75
83
  cookie_name: string;
76
84
  /** Hono context variable name for the identity. */
77
85
  context_key: string;
86
+ /**
87
+ * Cookie lifetime in seconds. Single source of truth for both the embedded
88
+ * `expires_at` (via `create_session_cookie_value`) and the cookie's HTTP
89
+ * `Max-Age` attribute (via `set_session_cookie`). Defaults to
90
+ * `SESSION_AGE_MAX` (30 days). The `cookie_options` slot intentionally
91
+ * cannot carry `maxAge` so the two values can't drift.
92
+ */
78
93
  max_age?: number;
79
- cookie_options?: Partial<SessionCookieOptions>;
94
+ /**
95
+ * Threshold (seconds) for expiration-based cookie refresh. When a parsed
96
+ * cookie's `expires_at - now <= refresh_threshold_seconds`,
97
+ * `process_session_cookie` returns `action: 'refresh'` with a freshly-signed
98
+ * value (extending the embedded expiration by `max_age`). Defaults to
99
+ * `SESSION_REFRESH_THRESHOLD_S` (1 day). Set to `0` to disable.
100
+ */
101
+ refresh_threshold_seconds?: number;
102
+ cookie_options?: Partial<Omit<SessionCookieOptions, 'maxAge'>>;
80
103
  /** Encode identity into the cookie payload (before the `:expires_at` suffix). */
81
104
  encode_identity: (identity: TIdentity) => string;
82
105
  /** Decode identity from cookie payload. Return null if invalid. */
@@ -90,6 +113,14 @@ export interface ParsedSession<TIdentity> {
90
113
  identity: TIdentity;
91
114
  /** True if verified with a non-primary key (needs re-signing). */
92
115
  should_refresh_signature: boolean;
116
+ /**
117
+ * True if the embedded `expires_at` is within
118
+ * `options.refresh_threshold_seconds` of `now`. Signals that the cookie is
119
+ * valid but should be re-signed to extend its lifetime — mirrors
120
+ * `query_session_touch`'s DB-side extension so the cookie and server
121
+ * session don't drift. Always false when the threshold is `0`.
122
+ */
123
+ should_refresh_expiration: boolean;
93
124
  /** Index of the key that verified the signature. */
94
125
  key_index: number;
95
126
  }
@@ -97,7 +128,9 @@ export interface ParsedSession<TIdentity> {
97
128
  * Parse a signed session cookie value.
98
129
  *
99
130
  * The signed value format is `${encode(identity)}:${expires_at}`.
100
- * Tries all keys in order to support key rotation.
131
+ * Tries all keys in order to support key rotation. The result's
132
+ * `should_refresh_expiration` flag fires when the cookie is within
133
+ * `options.refresh_threshold_seconds` of `expires_at`.
101
134
  *
102
135
  * @param signed_value - the raw cookie value (signed)
103
136
  * @param keyring - key ring for verification
@@ -139,7 +172,7 @@ export interface ProcessSessionResult<TIdentity> {
139
172
  * server hashes it (blake3) to look up the `auth_session` row.
140
173
  * Only the `cookie_name` varies per app.
141
174
  *
142
- * @param cookie_name - cookie name (e.g. `'tx_session'`, `'visiones_session'`)
175
+ * @param cookie_name - cookie name (e.g. `'zap_session'`, `'visiones_session'`)
143
176
  * @returns a `SessionOptions<string>` ready for use with session middleware
144
177
  */
145
178
  export declare const create_session_config: (cookie_name: string) => SessionOptions<string>;
@@ -148,6 +181,10 @@ export declare const fuz_session_config: SessionOptions<string>;
148
181
  /**
149
182
  * Process a session cookie and determine what action to take.
150
183
  *
184
+ * `action: 'refresh'` fires on key rotation **or** impending expiration
185
+ * (within `options.refresh_threshold_seconds`); both produce a freshly-signed
186
+ * `new_signed_value`.
187
+ *
151
188
  * @param signed_value - the raw cookie value (may be undefined)
152
189
  * @param keyring - key ring for verification and signing
153
190
  * @param options - session configuration
@@ -1 +1 @@
1
- {"version":3,"file":"session_cookie.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAE1C,mFAAmF;AACnF,eAAO,MAAM,eAAe,QAAoB,CAAC;AAKjD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,oBAMpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,WAAW,cAAc,CAAC,SAAS;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC/C,iFAAiF;IACjF,eAAe,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,MAAM,CAAC;IACjD,mEAAmE;IACnE,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;CACvD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,SAAS;IACvC,4BAA4B;IAC5B,QAAQ,EAAE,SAAS,CAAC;IACpB,kEAAkE;IAClE,wBAAwB,EAAE,OAAO,CAAC;IAClC,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,GAAU,SAAS,EAC5C,cAAc,MAAM,GAAG,SAAS,EAChC,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,CA4BrD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GAAU,SAAS,EAC1D,SAAS,OAAO,EAChB,UAAU,SAAS,EACnB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,SAAS;IAC9C,oCAAoC;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IACrC,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAa,MAAM,KAAG,cAAc,CAAC,MAAM,CAK/E,CAAC;AAEH,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAE/F;;;;;;;;GAQG;AAEH,eAAO,MAAM,sBAAsB,GAAU,SAAS,EACrD,cAAc,MAAM,GAAG,SAAS,EAChC,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,CA4BzC,CAAC"}
1
+ {"version":3,"file":"session_cookie.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAE1C,mFAAmF;AACnF,eAAO,MAAM,eAAe,QAAoB,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,QAAe,CAAC;AAQxD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,oBAMpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,WAAW,cAAc,CAAC,SAAS;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,iFAAiF;IACjF,eAAe,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,MAAM,CAAC;IACjD,mEAAmE;IACnE,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;CACvD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,SAAS;IACvC,4BAA4B;IAC5B,QAAQ,EAAE,SAAS,CAAC;IACpB,kEAAkE;IAClE,wBAAwB,EAAE,OAAO,CAAC;IAClC;;;;;;OAMG;IACH,yBAAyB,EAAE,OAAO,CAAC;IACnC,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GAAU,SAAS,EAC5C,cAAc,MAAM,GAAG,SAAS,EAChC,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,CAoCrD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GAAU,SAAS,EAC1D,SAAS,OAAO,EAChB,UAAU,SAAS,EACnB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,SAAS;IAC9C,oCAAoC;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IACrC,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAa,MAAM,KAAG,cAAc,CAAC,MAAM,CAK/E,CAAC;AAEH,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAE/F;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,sBAAsB,GAAU,SAAS,EACrD,cAAc,MAAM,GAAG,SAAS,EAChC,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,cAAc,MAAM,KAClB,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,CA8BzC,CAAC"}
@@ -11,8 +11,18 @@
11
11
  */
12
12
  /** Cookie max age in seconds (30 days — aligned with AUTH_SESSION_LIFETIME_MS). */
13
13
  export const SESSION_AGE_MAX = 60 * 60 * 24 * 30;
14
+ /**
15
+ * Threshold (seconds) at which `process_session_cookie` re-signs a still-valid
16
+ * cookie to extend its embedded expiration. Mirrors the DB-side
17
+ * `AUTH_SESSION_EXTEND_THRESHOLD_MS` so a continuously-active user's cookie
18
+ * stays in sync with their server-side session lifetime. Set
19
+ * `SessionOptions.refresh_threshold_seconds = 0` to disable.
20
+ */
21
+ export const SESSION_REFRESH_THRESHOLD_S = 60 * 60 * 24;
14
22
  /** Separator between identity payload and expires_at in signed value. */
15
23
  const VALUE_SEPARATOR = ':';
24
+ /** Strict integer matcher for the embedded `expires_at` field. */
25
+ const EXPIRES_AT_INTEGER_RE = /^\d+$/;
16
26
  /**
17
27
  * Default cookie options for session cookies.
18
28
  *
@@ -32,7 +42,9 @@ export const SESSION_COOKIE_OPTIONS = {
32
42
  * Parse a signed session cookie value.
33
43
  *
34
44
  * The signed value format is `${encode(identity)}:${expires_at}`.
35
- * Tries all keys in order to support key rotation.
45
+ * Tries all keys in order to support key rotation. The result's
46
+ * `should_refresh_expiration` flag fires when the cookie is within
47
+ * `options.refresh_threshold_seconds` of `expires_at`.
36
48
  *
37
49
  * @param signed_value - the raw cookie value (signed)
38
50
  * @param keyring - key ring for verification
@@ -55,6 +67,11 @@ export const parse_session = async (signed_value, keyring, options, now_seconds)
55
67
  const identity = options.decode_identity(identity_payload);
56
68
  if (identity === null)
57
69
  return null;
70
+ // Strict integer match — `parseInt` would accept `'123abc'` as `123` and
71
+ // `' 123'` as `123`. HMAC integrity makes such a tampered value
72
+ // theoretical, but stricter parsing closes the gap as defense-in-depth.
73
+ if (!EXPIRES_AT_INTEGER_RE.test(expires_at_str))
74
+ return null;
58
75
  const expires_at = parseInt(expires_at_str, 10);
59
76
  if (!Number.isFinite(expires_at))
60
77
  return null;
@@ -62,9 +79,12 @@ export const parse_session = async (signed_value, keyring, options, now_seconds)
62
79
  const now = now_seconds ?? Math.floor(Date.now() / 1000);
63
80
  if (expires_at <= now)
64
81
  return null;
82
+ const refresh_threshold = options.refresh_threshold_seconds ?? SESSION_REFRESH_THRESHOLD_S;
83
+ const should_refresh_expiration = refresh_threshold > 0 && expires_at - now <= refresh_threshold;
65
84
  return {
66
85
  identity,
67
86
  should_refresh_signature: result.key_index > 0,
87
+ should_refresh_expiration,
68
88
  key_index: result.key_index,
69
89
  };
70
90
  };
@@ -94,7 +114,7 @@ export const create_session_cookie_value = async (keyring, identity, options, no
94
114
  * server hashes it (blake3) to look up the `auth_session` row.
95
115
  * Only the `cookie_name` varies per app.
96
116
  *
97
- * @param cookie_name - cookie name (e.g. `'tx_session'`, `'visiones_session'`)
117
+ * @param cookie_name - cookie name (e.g. `'zap_session'`, `'visiones_session'`)
98
118
  * @returns a `SessionOptions<string>` ready for use with session middleware
99
119
  */
100
120
  export const create_session_config = (cookie_name) => ({
@@ -108,6 +128,10 @@ export const fuz_session_config = create_session_config('fuz_session');
108
128
  /**
109
129
  * Process a session cookie and determine what action to take.
110
130
  *
131
+ * `action: 'refresh'` fires on key rotation **or** impending expiration
132
+ * (within `options.refresh_threshold_seconds`); both produce a freshly-signed
133
+ * `new_signed_value`.
134
+ *
111
135
  * @param signed_value - the raw cookie value (may be undefined)
112
136
  * @param keyring - key ring for verification and signing
113
137
  * @param options - session configuration
@@ -125,9 +149,11 @@ export const process_session_cookie = async (signed_value, keyring, options, now
125
149
  // Invalid cookie - should be cleared
126
150
  return { valid: false, action: 'clear' };
127
151
  }
128
- // Valid session
129
- if (parsed.should_refresh_signature) {
130
- // Re-sign with current key (extends expiration)
152
+ // Valid session — re-sign if the verifying key isn't primary OR if the
153
+ // embedded expiration is approaching the threshold. The latter mirrors
154
+ // `query_session_touch`'s DB-side extension so a continuously-active user's
155
+ // cookie doesn't hard-expire while their server session is still alive.
156
+ if (parsed.should_refresh_signature || parsed.should_refresh_expiration) {
131
157
  const new_signed_value = await create_session_cookie_value(keyring, parsed.identity, options, now);
132
158
  return { valid: true, action: 'refresh', new_signed_value, identity: parsed.identity };
133
159
  }
@@ -1,19 +1,26 @@
1
1
  /**
2
- * Hono session middleware using generic session management.
3
- *
4
- * Thin wrapper that gets/sets cookies and delegates to session processing.
2
+ * Hono session boundary cookie I/O, request-time middleware, and the
3
+ * session-creation helper shared by login / signup / bootstrap.
5
4
  *
6
5
  * @module
7
6
  */
8
7
  import type { Context, MiddlewareHandler } from 'hono';
9
8
  import type { Keyring } from './keyring.js';
10
9
  import { type SessionOptions } from './session_cookie.js';
10
+ import type { QueryDeps } from '../db/query_deps.js';
11
11
  /**
12
12
  * Read the session cookie value from a request.
13
13
  */
14
14
  export declare const get_session_cookie: <T>(c: Context, options: SessionOptions<T>) => string | undefined;
15
15
  /**
16
16
  * Set the session cookie on a response.
17
+ *
18
+ * `options.max_age` is the single source of truth for cookie lifetime: it
19
+ * drives both the embedded `expires_at` (via `create_session_cookie_value`)
20
+ * and the cookie's HTTP `Max-Age` attribute set here. Falls back to
21
+ * `SESSION_COOKIE_OPTIONS.maxAge` (= `SESSION_AGE_MAX`) when unset.
22
+ * `options.cookie_options` cannot carry `maxAge` (omitted in the type) so
23
+ * the two values can't drift.
17
24
  */
18
25
  export declare const set_session_cookie: <T>(c: Context, value: string, options: SessionOptions<T>) => void;
19
26
  /**
@@ -31,4 +38,31 @@ export declare const clear_session_cookie: <T>(c: Context, options: SessionOptio
31
38
  * @mutates Hono context - sets `options.context_key` and may refresh or clear the session cookie
32
39
  */
33
40
  export declare const create_session_middleware: <TIdentity>(keyring: Keyring, options: SessionOptions<TIdentity>) => MiddlewareHandler;
41
+ /**
42
+ * Options for `create_session_and_set_cookie`.
43
+ */
44
+ export interface CreateSessionAndSetCookieOptions {
45
+ /** Keyring for cookie signing. */
46
+ keyring: Keyring;
47
+ /** Query deps (needs db for session creation). */
48
+ deps: QueryDeps;
49
+ /** Hono context for setting the cookie. */
50
+ c: Context;
51
+ /** The account to create a session for. */
52
+ account_id: string;
53
+ /** Session cookie configuration. */
54
+ session_options: SessionOptions<string>;
55
+ /** Per-account session cap (`null` to skip enforcement). */
56
+ max_sessions?: number | null;
57
+ }
58
+ /**
59
+ * Create an auth session and set the session cookie on the response.
60
+ *
61
+ * Shared by login, signup, and bootstrap — generates a token, hashes it,
62
+ * persists the session row, optionally enforces a per-account session limit,
63
+ * and sets the signed cookie.
64
+ *
65
+ * @mutates `auth_session` table - inserts the new session row (and evicts older rows when `max_sessions` is set)
66
+ */
67
+ export declare const create_session_and_set_cookie: (options: CreateSessionAndSetCookieOptions) => Promise<void>;
34
68
  //# sourceMappingURL=session_middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAGrD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAC1C,OAAO,EACN,KAAK,cAAc,EAInB,MAAM,qBAAqB,CAAC;AAE7B;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EACnC,GAAG,OAAO,EACV,SAAS,cAAc,CAAC,CAAC,CAAC,KACxB,MAAM,GAAG,SAEX,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EACnC,GAAG,OAAO,EACV,OAAO,MAAM,EACb,SAAS,cAAc,CAAC,CAAC,CAAC,KACxB,IASF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,cAAc,CAAC,CAAC,CAAC,KAAG,IAMhF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,EAClD,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,KAChC,iBAgBF,CAAC"}
1
+ {"version":3,"file":"session_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_middleware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAGrD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAC1C,OAAO,EACN,KAAK,cAAc,EAKnB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EACnC,GAAG,OAAO,EACV,SAAS,cAAc,CAAC,CAAC,CAAC,KACxB,MAAM,GAAG,SAEX,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EACnC,GAAG,OAAO,EACV,OAAO,MAAM,EACb,SAAS,cAAc,CAAC,CAAC,CAAC,KACxB,IAOF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,cAAc,CAAC,CAAC,CAAC,KAAG,IAMhF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,EAClD,SAAS,OAAO,EAChB,SAAS,cAAc,CAAC,SAAS,CAAC,KAChC,iBAgBF,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAChD,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,IAAI,EAAE,SAAS,CAAC;IAChB,2CAA2C;IAC3C,CAAC,EAAE,OAAO,CAAC;IACX,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,gCAAgC,KACvC,OAAO,CAAC,IAAI,CAad,CAAC"}
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Hono session middleware using generic session management.
3
- *
4
- * Thin wrapper that gets/sets cookies and delegates to session processing.
2
+ * Hono session boundary cookie I/O, request-time middleware, and the
3
+ * session-creation helper shared by login / signup / bootstrap.
5
4
  *
6
5
  * @module
7
6
  */
8
7
  import { getCookie, setCookie, deleteCookie } from 'hono/cookie';
9
- import { SESSION_COOKIE_OPTIONS, process_session_cookie, } from './session_cookie.js';
8
+ import { SESSION_COOKIE_OPTIONS, process_session_cookie, create_session_cookie_value, } from './session_cookie.js';
9
+ import { generate_session_token, hash_session_token, AUTH_SESSION_LIFETIME_MS, query_create_session, query_session_enforce_limit, } from './session_queries.js';
10
10
  /**
11
11
  * Read the session cookie value from a request.
12
12
  */
@@ -15,15 +15,20 @@ export const get_session_cookie = (c, options) => {
15
15
  };
16
16
  /**
17
17
  * Set the session cookie on a response.
18
+ *
19
+ * `options.max_age` is the single source of truth for cookie lifetime: it
20
+ * drives both the embedded `expires_at` (via `create_session_cookie_value`)
21
+ * and the cookie's HTTP `Max-Age` attribute set here. Falls back to
22
+ * `SESSION_COOKIE_OPTIONS.maxAge` (= `SESSION_AGE_MAX`) when unset.
23
+ * `options.cookie_options` cannot carry `maxAge` (omitted in the type) so
24
+ * the two values can't drift.
18
25
  */
19
26
  export const set_session_cookie = (c, value, options) => {
20
27
  const cookie_options = {
21
28
  ...SESSION_COOKIE_OPTIONS,
22
29
  ...options.cookie_options,
30
+ maxAge: options.max_age ?? SESSION_COOKIE_OPTIONS.maxAge,
23
31
  };
24
- if (options.max_age !== undefined) {
25
- cookie_options.maxAge = options.max_age;
26
- }
27
32
  setCookie(c, options.cookie_name, value, cookie_options);
28
33
  };
29
34
  /**
@@ -61,3 +66,24 @@ export const create_session_middleware = (keyring, options) => {
61
66
  await next();
62
67
  };
63
68
  };
69
+ /**
70
+ * Create an auth session and set the session cookie on the response.
71
+ *
72
+ * Shared by login, signup, and bootstrap — generates a token, hashes it,
73
+ * persists the session row, optionally enforces a per-account session limit,
74
+ * and sets the signed cookie.
75
+ *
76
+ * @mutates `auth_session` table - inserts the new session row (and evicts older rows when `max_sessions` is set)
77
+ */
78
+ export const create_session_and_set_cookie = async (options) => {
79
+ const { keyring, deps, c, account_id, session_options, max_sessions } = options;
80
+ const session_token = generate_session_token();
81
+ const token_hash = hash_session_token(session_token);
82
+ const expires_at = new Date(Date.now() + AUTH_SESSION_LIFETIME_MS);
83
+ await query_create_session(deps, token_hash, account_id, expires_at);
84
+ if (max_sessions != null) {
85
+ await query_session_enforce_limit(deps, account_id, max_sessions);
86
+ }
87
+ const cookie_value = await create_session_cookie_value(keyring, session_token, session_options);
88
+ set_session_cookie(c, cookie_value, session_options);
89
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"signup_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAStB,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAQhD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAClE,6FAA6F;IAC7F,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,yFAAyF;IACzF,YAAY,EAAE,WAAW,CAAC;CAC1B;AAID,0FAA0F;AAC1F,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,YAAY;;kBAEvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,gBAAgB,EACtB,SAAS,kBAAkB,KACzB,KAAK,CAAC,SAAS,CA2HjB,CAAC"}
1
+ {"version":3,"file":"signup_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAStB,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAOhD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAClE,6FAA6F;IAC7F,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,yFAAyF;IACzF,YAAY,EAAE,WAAW,CAAC;CAC1B;AAID,0FAA0F;AAC1F,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,YAAY;;kBAEvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,gBAAgB,EACtB,SAAS,kBAAkB,KACzB,KAAK,CAAC,SAAS,CAmJjB,CAAC"}