@fuzdev/fuz_app 0.54.0 → 0.56.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 (348) hide show
  1. package/dist/actions/CLAUDE.md +214 -103
  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 +32 -0
  6. package/dist/actions/action_codegen.d.ts.map +1 -1
  7. package/dist/actions/action_codegen.js +35 -15
  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 +141 -22
  11. package/dist/actions/action_rpc.d.ts.map +1 -1
  12. package/dist/actions/action_rpc.js +106 -187
  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 +46 -40
  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 +15 -10
  34. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  35. package/dist/actions/register_ws_endpoint.js +54 -7
  36. package/dist/actions/transports.d.ts.map +1 -1
  37. package/dist/actions/transports.js +0 -4
  38. package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
  39. package/dist/actions/transports_ws_auth_guard.js +1 -1
  40. package/dist/actions/transports_ws_backend.d.ts +1 -1
  41. package/dist/actions/transports_ws_backend.js +1 -1
  42. package/dist/auth/CLAUDE.md +794 -410
  43. package/dist/auth/account_action_specs.d.ts +28 -7
  44. package/dist/auth/account_action_specs.d.ts.map +1 -1
  45. package/dist/auth/account_action_specs.js +7 -7
  46. package/dist/auth/account_actions.d.ts +7 -13
  47. package/dist/auth/account_actions.d.ts.map +1 -1
  48. package/dist/auth/account_actions.js +26 -35
  49. package/dist/auth/account_queries.d.ts +52 -16
  50. package/dist/auth/account_queries.d.ts.map +1 -1
  51. package/dist/auth/account_queries.js +87 -38
  52. package/dist/auth/account_routes.d.ts +9 -11
  53. package/dist/auth/account_routes.d.ts.map +1 -1
  54. package/dist/auth/account_routes.js +118 -46
  55. package/dist/auth/account_schema.d.ts +46 -35
  56. package/dist/auth/account_schema.d.ts.map +1 -1
  57. package/dist/auth/account_schema.js +21 -28
  58. package/dist/auth/admin_action_specs.d.ts +100 -32
  59. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  60. package/dist/auth/admin_action_specs.js +64 -33
  61. package/dist/auth/admin_actions.d.ts +13 -19
  62. package/dist/auth/admin_actions.d.ts.map +1 -1
  63. package/dist/auth/admin_actions.js +37 -41
  64. package/dist/auth/audit_emitter.d.ts +160 -0
  65. package/dist/auth/audit_emitter.d.ts.map +1 -0
  66. package/dist/auth/audit_emitter.js +83 -0
  67. package/dist/auth/audit_log_queries.d.ts +17 -48
  68. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  69. package/dist/auth/audit_log_queries.js +20 -56
  70. package/dist/auth/audit_log_routes.d.ts +1 -1
  71. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  72. package/dist/auth/audit_log_routes.js +7 -3
  73. package/dist/auth/audit_log_schema.d.ts +92 -32
  74. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  75. package/dist/auth/audit_log_schema.js +75 -46
  76. package/dist/auth/auth_guard_resolver.d.ts +44 -0
  77. package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
  78. package/dist/auth/auth_guard_resolver.js +56 -0
  79. package/dist/auth/bearer_auth.d.ts +9 -7
  80. package/dist/auth/bearer_auth.d.ts.map +1 -1
  81. package/dist/auth/bearer_auth.js +13 -21
  82. package/dist/auth/bootstrap_account.d.ts +7 -7
  83. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  84. package/dist/auth/bootstrap_account.js +7 -7
  85. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  86. package/dist/auth/bootstrap_routes.js +11 -10
  87. package/dist/auth/cleanup.d.ts +20 -26
  88. package/dist/auth/cleanup.d.ts.map +1 -1
  89. package/dist/auth/cleanup.js +33 -42
  90. package/dist/auth/credential_type_schema.d.ts +115 -0
  91. package/dist/auth/credential_type_schema.d.ts.map +1 -0
  92. package/dist/auth/credential_type_schema.js +127 -0
  93. package/dist/auth/daemon_token_middleware.d.ts +23 -11
  94. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  95. package/dist/auth/daemon_token_middleware.js +28 -22
  96. package/dist/auth/ddl.d.ts +2 -2
  97. package/dist/auth/ddl.d.ts.map +1 -1
  98. package/dist/auth/ddl.js +6 -6
  99. package/dist/auth/deps.d.ts +7 -18
  100. package/dist/auth/deps.d.ts.map +1 -1
  101. package/dist/auth/grant_path_schema.d.ts +117 -0
  102. package/dist/auth/grant_path_schema.d.ts.map +1 -0
  103. package/dist/auth/grant_path_schema.js +137 -0
  104. package/dist/auth/invite_queries.d.ts +12 -1
  105. package/dist/auth/invite_queries.d.ts.map +1 -1
  106. package/dist/auth/invite_queries.js +12 -1
  107. package/dist/auth/invite_schema.d.ts +1 -1
  108. package/dist/auth/invite_schema.d.ts.map +1 -1
  109. package/dist/auth/invite_schema.js +1 -1
  110. package/dist/auth/middleware.d.ts.map +1 -1
  111. package/dist/auth/middleware.js +9 -4
  112. package/dist/auth/migrations.d.ts +37 -14
  113. package/dist/auth/migrations.d.ts.map +1 -1
  114. package/dist/auth/migrations.js +79 -32
  115. package/dist/auth/request_context.d.ts +331 -61
  116. package/dist/auth/request_context.d.ts.map +1 -1
  117. package/dist/auth/request_context.js +378 -95
  118. package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +163 -94
  119. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
  120. package/dist/auth/role_grant_offer_action_specs.js +262 -0
  121. package/dist/auth/role_grant_offer_actions.d.ts +104 -0
  122. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
  123. package/dist/auth/role_grant_offer_actions.js +473 -0
  124. package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +90 -70
  125. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
  126. package/dist/auth/role_grant_offer_notifications.js +182 -0
  127. package/dist/auth/role_grant_offer_queries.d.ts +242 -0
  128. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
  129. package/dist/auth/role_grant_offer_queries.js +533 -0
  130. package/dist/auth/role_grant_offer_schema.d.ts +150 -0
  131. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
  132. package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +60 -36
  133. package/dist/auth/role_grant_queries.d.ts +231 -0
  134. package/dist/auth/role_grant_queries.d.ts.map +1 -0
  135. package/dist/auth/role_grant_queries.js +320 -0
  136. package/dist/auth/role_schema.d.ts +150 -40
  137. package/dist/auth/role_schema.d.ts.map +1 -1
  138. package/dist/auth/role_schema.js +144 -45
  139. package/dist/auth/scope_kind_schema.d.ts +96 -0
  140. package/dist/auth/scope_kind_schema.d.ts.map +1 -0
  141. package/dist/auth/scope_kind_schema.js +94 -0
  142. package/dist/auth/self_service_role_action_specs.d.ts +6 -1
  143. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  144. package/dist/auth/self_service_role_action_specs.js +3 -1
  145. package/dist/auth/self_service_role_actions.d.ts +34 -27
  146. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  147. package/dist/auth/self_service_role_actions.js +68 -48
  148. package/dist/auth/session_cookie.d.ts +43 -6
  149. package/dist/auth/session_cookie.d.ts.map +1 -1
  150. package/dist/auth/session_cookie.js +31 -5
  151. package/dist/auth/session_middleware.d.ts +37 -3
  152. package/dist/auth/session_middleware.d.ts.map +1 -1
  153. package/dist/auth/session_middleware.js +33 -7
  154. package/dist/auth/signup_routes.d.ts.map +1 -1
  155. package/dist/auth/signup_routes.js +48 -19
  156. package/dist/auth/standard_action_specs.d.ts +2 -2
  157. package/dist/auth/standard_action_specs.js +4 -4
  158. package/dist/auth/standard_rpc_actions.d.ts +23 -19
  159. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  160. package/dist/auth/standard_rpc_actions.js +12 -12
  161. package/dist/db/migrate.d.ts +12 -8
  162. package/dist/db/migrate.d.ts.map +1 -1
  163. package/dist/db/migrate.js +10 -7
  164. package/dist/dev/setup.d.ts +2 -2
  165. package/dist/dev/setup.d.ts.map +1 -1
  166. package/dist/dev/setup.js +9 -7
  167. package/dist/env/load.d.ts +1 -1
  168. package/dist/env/load.js +1 -1
  169. package/dist/hono_context.d.ts +64 -5
  170. package/dist/hono_context.d.ts.map +1 -1
  171. package/dist/hono_context.js +38 -2
  172. package/dist/http/CLAUDE.md +264 -87
  173. package/dist/http/auth_shape.d.ts +191 -0
  174. package/dist/http/auth_shape.d.ts.map +1 -0
  175. package/dist/http/auth_shape.js +237 -0
  176. package/dist/http/common_routes.js +3 -3
  177. package/dist/http/db_routes.d.ts +4 -0
  178. package/dist/http/db_routes.d.ts.map +1 -1
  179. package/dist/http/db_routes.js +44 -7
  180. package/dist/http/error_schemas.d.ts +132 -19
  181. package/dist/http/error_schemas.d.ts.map +1 -1
  182. package/dist/http/error_schemas.js +132 -40
  183. package/dist/http/jsonrpc_errors.d.ts +27 -2
  184. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  185. package/dist/http/jsonrpc_errors.js +26 -2
  186. package/dist/http/pending_effects.d.ts +71 -18
  187. package/dist/http/pending_effects.d.ts.map +1 -1
  188. package/dist/http/pending_effects.js +87 -18
  189. package/dist/http/proxy.d.ts +52 -5
  190. package/dist/http/proxy.d.ts.map +1 -1
  191. package/dist/http/proxy.js +92 -14
  192. package/dist/http/route_spec.d.ts +113 -41
  193. package/dist/http/route_spec.d.ts.map +1 -1
  194. package/dist/http/route_spec.js +130 -52
  195. package/dist/http/schema_helpers.d.ts +3 -2
  196. package/dist/http/schema_helpers.d.ts.map +1 -1
  197. package/dist/http/schema_helpers.js +9 -2
  198. package/dist/http/surface.d.ts +2 -1
  199. package/dist/http/surface.d.ts.map +1 -1
  200. package/dist/http/surface.js +1 -2
  201. package/dist/http/surface_query.d.ts +39 -35
  202. package/dist/http/surface_query.d.ts.map +1 -1
  203. package/dist/http/surface_query.js +79 -36
  204. package/dist/primitive_schemas.d.ts +39 -0
  205. package/dist/primitive_schemas.d.ts.map +1 -0
  206. package/dist/primitive_schemas.js +40 -0
  207. package/dist/realtime/sse_auth_guard.d.ts +5 -5
  208. package/dist/realtime/sse_auth_guard.js +9 -9
  209. package/dist/runtime/mock.d.ts +1 -1
  210. package/dist/runtime/mock.js +1 -1
  211. package/dist/server/app_backend.d.ts +14 -11
  212. package/dist/server/app_backend.d.ts.map +1 -1
  213. package/dist/server/app_backend.js +12 -8
  214. package/dist/server/app_server.d.ts +7 -7
  215. package/dist/server/app_server.d.ts.map +1 -1
  216. package/dist/server/app_server.js +36 -31
  217. package/dist/server/validate_nginx.d.ts +1 -1
  218. package/dist/server/validate_nginx.js +1 -1
  219. package/dist/testing/CLAUDE.md +73 -55
  220. package/dist/testing/admin_integration.d.ts +5 -6
  221. package/dist/testing/admin_integration.d.ts.map +1 -1
  222. package/dist/testing/admin_integration.js +100 -96
  223. package/dist/testing/adversarial_headers.js +1 -1
  224. package/dist/testing/app_server.d.ts +11 -14
  225. package/dist/testing/app_server.d.ts.map +1 -1
  226. package/dist/testing/app_server.js +18 -17
  227. package/dist/testing/assertions.d.ts.map +1 -1
  228. package/dist/testing/assertions.js +2 -1
  229. package/dist/testing/attack_surface.d.ts.map +1 -1
  230. package/dist/testing/attack_surface.js +15 -9
  231. package/dist/testing/audit_completeness.d.ts +2 -2
  232. package/dist/testing/audit_completeness.d.ts.map +1 -1
  233. package/dist/testing/audit_completeness.js +53 -39
  234. package/dist/testing/auth_apps.d.ts +5 -4
  235. package/dist/testing/auth_apps.d.ts.map +1 -1
  236. package/dist/testing/auth_apps.js +28 -22
  237. package/dist/testing/data_exposure.d.ts.map +1 -1
  238. package/dist/testing/data_exposure.js +5 -5
  239. package/dist/testing/db.d.ts +1 -1
  240. package/dist/testing/db.d.ts.map +1 -1
  241. package/dist/testing/db.js +4 -4
  242. package/dist/testing/db_entities.d.ts +22 -0
  243. package/dist/testing/db_entities.d.ts.map +1 -0
  244. package/dist/testing/db_entities.js +28 -0
  245. package/dist/testing/entities.d.ts +10 -8
  246. package/dist/testing/entities.d.ts.map +1 -1
  247. package/dist/testing/entities.js +22 -18
  248. package/dist/testing/integration.d.ts.map +1 -1
  249. package/dist/testing/integration.js +13 -14
  250. package/dist/testing/integration_helpers.d.ts +8 -6
  251. package/dist/testing/integration_helpers.d.ts.map +1 -1
  252. package/dist/testing/integration_helpers.js +29 -23
  253. package/dist/testing/middleware.d.ts +15 -11
  254. package/dist/testing/middleware.d.ts.map +1 -1
  255. package/dist/testing/middleware.js +75 -32
  256. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  257. package/dist/testing/rpc_attack_surface.js +40 -24
  258. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  259. package/dist/testing/rpc_helpers.js +3 -1
  260. package/dist/testing/rpc_round_trip.d.ts +1 -1
  261. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  262. package/dist/testing/rpc_round_trip.js +14 -13
  263. package/dist/testing/sse_round_trip.d.ts +3 -4
  264. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  265. package/dist/testing/sse_round_trip.js +7 -11
  266. package/dist/testing/standard.d.ts +1 -1
  267. package/dist/testing/stubs.d.ts +25 -0
  268. package/dist/testing/stubs.d.ts.map +1 -1
  269. package/dist/testing/stubs.js +43 -2
  270. package/dist/testing/surface_invariants.d.ts +2 -2
  271. package/dist/testing/ws_round_trip.d.ts +12 -13
  272. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  273. package/dist/testing/ws_round_trip.js +24 -12
  274. package/dist/ui/AdminAccounts.svelte +23 -20
  275. package/dist/ui/AdminOverview.svelte +15 -13
  276. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  277. package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
  278. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
  279. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
  280. package/dist/ui/BootstrapForm.svelte +1 -1
  281. package/dist/ui/CLAUDE.md +65 -59
  282. package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +37 -22
  283. package/dist/ui/RoleGrantOfferForm.svelte.d.ts +20 -0
  284. package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
  285. package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
  286. package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
  287. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
  288. package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
  289. package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
  290. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
  291. package/dist/ui/SignupForm.svelte +1 -1
  292. package/dist/ui/SurfaceExplorer.svelte +35 -15
  293. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  294. package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
  295. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  296. package/dist/ui/account_sessions_state.svelte.js +2 -3
  297. package/dist/ui/admin_accounts_state.svelte.d.ts +25 -18
  298. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  299. package/dist/ui/admin_accounts_state.svelte.js +28 -17
  300. package/dist/ui/admin_rpc_adapters.d.ts +20 -20
  301. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  302. package/dist/ui/admin_rpc_adapters.js +17 -17
  303. package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
  304. package/dist/ui/admin_sessions_state.svelte.js +2 -2
  305. package/dist/ui/audit_log_state.svelte.d.ts +7 -7
  306. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/audit_log_state.svelte.js +6 -6
  308. package/dist/ui/auth_state.svelte.d.ts +3 -3
  309. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  310. package/dist/ui/auth_state.svelte.js +6 -6
  311. package/dist/ui/format_scope.d.ts +2 -2
  312. package/dist/ui/format_scope.js +2 -2
  313. package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +39 -31
  314. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
  315. package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +25 -19
  316. package/dist/ui/ui_format.js +2 -2
  317. package/package.json +3 -3
  318. package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
  319. package/dist/auth/permit_offer_action_specs.js +0 -227
  320. package/dist/auth/permit_offer_actions.d.ts +0 -110
  321. package/dist/auth/permit_offer_actions.d.ts.map +0 -1
  322. package/dist/auth/permit_offer_actions.js +0 -452
  323. package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
  324. package/dist/auth/permit_offer_notifications.js +0 -182
  325. package/dist/auth/permit_offer_queries.d.ts +0 -183
  326. package/dist/auth/permit_offer_queries.d.ts.map +0 -1
  327. package/dist/auth/permit_offer_queries.js +0 -408
  328. package/dist/auth/permit_offer_schema.d.ts +0 -103
  329. package/dist/auth/permit_offer_schema.d.ts.map +0 -1
  330. package/dist/auth/permit_queries.d.ts +0 -210
  331. package/dist/auth/permit_queries.d.ts.map +0 -1
  332. package/dist/auth/permit_queries.js +0 -294
  333. package/dist/auth/require_keeper.d.ts +0 -20
  334. package/dist/auth/require_keeper.d.ts.map +0 -1
  335. package/dist/auth/require_keeper.js +0 -35
  336. package/dist/auth/route_guards.d.ts +0 -21
  337. package/dist/auth/route_guards.d.ts.map +0 -1
  338. package/dist/auth/route_guards.js +0 -32
  339. package/dist/auth/session_lifecycle.d.ts +0 -37
  340. package/dist/auth/session_lifecycle.d.ts.map +0 -1
  341. package/dist/auth/session_lifecycle.js +0 -29
  342. package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
  343. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
  344. package/dist/ui/PermitOfferForm.svelte.d.ts +0 -14
  345. package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
  346. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
  347. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
  348. 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
@@ -33,31 +38,31 @@
33
38
  */
34
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
- const require_request_auth = (auth) => {
42
- if (!auth)
43
- throw new Error('unreachable: action auth guard did not enforce authentication');
44
- return auth;
45
- };
46
47
  /**
47
48
  * Build the unified self-service role toggle RPC action.
48
49
  *
49
- * @param deps - `SelfServiceRoleActionDeps` slice of `AppDeps` (`log`, `on_audit_event`, optional `audit_log_config`)
50
- * @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
51
54
  * @returns the `RpcAction` array to spread into a `create_rpc_endpoint` call
52
- * @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`
53
56
  */
54
- export const create_self_service_role_actions = (deps, options) => {
55
- const eligible = new Set(options.eligible_roles);
56
- if (options.roles) {
57
- 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) {
58
63
  for (const r of eligible) {
59
- if (!role_options.has(r)) {
60
- 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`);
61
66
  }
62
67
  }
63
68
  }
@@ -69,69 +74,84 @@ export const create_self_service_role_actions = (deps, options) => {
69
74
  }
70
75
  };
71
76
  const handler = async (input, ctx) => {
72
- const auth = require_request_auth(ctx.auth);
77
+ const auth = ctx.auth;
73
78
  reject_if_ineligible(input.role);
74
79
  if (input.enabled) {
75
- // Pre-check for idempotent re-grant. `query_grant_permit` is itself
76
- // 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
77
82
  // it doesn't signal "already existed" vs "newly inserted" — so we
78
- // peek first. Reads from the in-memory `auth.permits` snapshot
83
+ // peek first. Reads from the in-memory `auth.role_grants` snapshot
79
84
  // (no DB roundtrip). The TOCTOU window is benign for self-service:
80
- // two concurrent grants both observe "no permit", both call
81
- // `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
82
87
  // query's `ON CONFLICT DO NOTHING`. Worst case both responses report
83
- // `changed: true`; the DB still ends up with exactly one permit.
88
+ // `changed: true`; the DB still ends up with exactly one role_grant.
84
89
  if (has_scoped_role(auth, input.role, null)) {
85
90
  return { ok: true, enabled: true, changed: false };
86
91
  }
87
- const permit = await query_grant_permit(ctx, {
92
+ const role_grant = await query_create_role_grant(ctx, {
88
93
  actor_id: auth.actor.id,
89
94
  role: input.role,
90
95
  scope_id: null,
91
96
  expires_at: null,
92
97
  granted_by: auth.actor.id,
93
98
  });
94
- void audit_log_fire_and_forget(ctx, {
95
- event_type: 'permit_grant',
99
+ // `role_grant_create` is the canonical actor-bound-subject event —
100
+ // populate both target columns even on self-service so the
101
+ // "always populated for role_grant_create" rule holds uniformly
102
+ // regardless of who initiated the grant. On self-service the
103
+ // grantor and grantee are the same identity; admin direct-grant
104
+ // (separate code path) populates the same columns with the
105
+ // grantee actor.
106
+ deps.audit.emit(ctx, {
107
+ event_type: 'role_grant_create',
96
108
  actor_id: auth.actor.id,
97
109
  account_id: auth.account.id,
110
+ target_account_id: auth.account.id,
111
+ target_actor_id: auth.actor.id,
98
112
  ip: ctx.client_ip,
99
113
  metadata: {
100
- role: permit.role,
101
- permit_id: permit.id,
102
- scope_id: permit.scope_id,
114
+ role: role_grant.role,
115
+ role_grant_id: role_grant.id,
116
+ scope_id: role_grant.scope_id,
103
117
  self_service: true,
104
118
  },
105
- }, deps);
119
+ });
106
120
  return { ok: true, enabled: true, changed: true };
107
121
  }
108
- // Find an active global permit for this (actor, role) in the in-memory
109
- // `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
110
124
  // pattern as `has_scoped_role` above (race window is between predicate
111
- // and `query_revoke_permit`'s actual UPDATE, not between predicate and
125
+ // and `query_revoke_role_grant`'s actual UPDATE, not between predicate and
112
126
  // middleware load).
113
127
  const now = new Date();
114
- 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));
115
129
  if (!target) {
116
130
  return { ok: true, enabled: false, changed: false };
117
131
  }
118
- 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);
119
133
  if (!result) {
120
134
  // Raced with another revoker — treat as already revoked.
121
135
  return { ok: true, enabled: false, changed: false };
122
136
  }
123
- void audit_log_fire_and_forget(ctx, {
124
- event_type: 'permit_revoke',
137
+ // Same actor-bound rule as the grant branch — `role_grant_revoke`
138
+ // always populates both target columns even on self-service so
139
+ // forensic queries that filter on `target_actor_id IS NOT NULL`
140
+ // don't silently miss self-toggled role_grants.
141
+ deps.audit.emit(ctx, {
142
+ event_type: 'role_grant_revoke',
125
143
  actor_id: auth.actor.id,
126
144
  account_id: auth.account.id,
145
+ target_account_id: auth.account.id,
146
+ target_actor_id: auth.actor.id,
127
147
  ip: ctx.client_ip,
128
148
  metadata: {
129
149
  role: result.role,
130
- permit_id: result.id,
150
+ role_grant_id: result.id,
131
151
  scope_id: result.scope_id,
132
152
  self_service: true,
133
153
  },
134
- }, deps);
154
+ });
135
155
  return { ok: true, enabled: false, changed: true };
136
156
  };
137
157
  return [rpc_action(self_service_role_set_action_spec, handler)];
@@ -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"}