@fuzdev/fuz_app 0.140.0 → 0.141.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 (182) hide show
  1. package/dist/actions/CLAUDE.md +62 -17
  2. package/dist/actions/action_rpc.d.ts +5 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +9 -1
  5. package/dist/actions/connection_closer.d.ts +25 -2
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +8 -1
  8. package/dist/actions/register_action_ws.d.ts +42 -6
  9. package/dist/actions/register_action_ws.d.ts.map +1 -1
  10. package/dist/actions/register_action_ws.js +87 -19
  11. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  12. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  13. package/dist/actions/register_ws_endpoint.js +5 -2
  14. package/dist/actions/socket.svelte.d.ts +19 -4
  15. package/dist/actions/socket.svelte.d.ts.map +1 -1
  16. package/dist/actions/socket.svelte.js +31 -4
  17. package/dist/actions/transports.d.ts +30 -1
  18. package/dist/actions/transports.d.ts.map +1 -1
  19. package/dist/actions/transports.js +30 -1
  20. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  21. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  22. package/dist/actions/transports_ws_auth_guard.js +24 -10
  23. package/dist/actions/transports_ws_backend.d.ts +51 -5
  24. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_backend.js +62 -9
  26. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  27. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  28. package/dist/auth/CLAUDE.md +11 -9
  29. package/dist/auth/account_route_schema.d.ts +1 -0
  30. package/dist/auth/account_route_schema.d.ts.map +1 -1
  31. package/dist/auth/account_route_schema.js +4 -0
  32. package/dist/auth/api_token.d.ts +20 -0
  33. package/dist/auth/api_token.d.ts.map +1 -1
  34. package/dist/auth/api_token.js +21 -1
  35. package/dist/auth/api_token_queries.d.ts +22 -4
  36. package/dist/auth/api_token_queries.d.ts.map +1 -1
  37. package/dist/auth/api_token_queries.js +11 -6
  38. package/dist/auth/audit_log_routes.d.ts +10 -0
  39. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  40. package/dist/auth/audit_log_routes.js +20 -0
  41. package/dist/auth/audit_log_schema.d.ts +30 -5
  42. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  43. package/dist/auth/audit_log_schema.js +30 -0
  44. package/dist/auth/bearer_auth.d.ts +4 -2
  45. package/dist/auth/bearer_auth.d.ts.map +1 -1
  46. package/dist/auth/bearer_auth.js +19 -8
  47. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  48. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  49. package/dist/auth/bootstrap_route_schema.js +1 -0
  50. package/dist/auth/cleanup.d.ts +9 -9
  51. package/dist/auth/cleanup.js +9 -9
  52. package/dist/auth/credential_deadline.d.ts +95 -0
  53. package/dist/auth/credential_deadline.d.ts.map +1 -0
  54. package/dist/auth/credential_deadline.js +139 -0
  55. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  56. package/dist/auth/daemon_token_middleware.js +4 -1
  57. package/dist/auth/middleware.d.ts +90 -12
  58. package/dist/auth/middleware.d.ts.map +1 -1
  59. package/dist/auth/middleware.js +115 -16
  60. package/dist/auth/request_context.d.ts +4 -2
  61. package/dist/auth/request_context.d.ts.map +1 -1
  62. package/dist/auth/request_context.js +14 -3
  63. package/dist/auth/resolved_auth.d.ts +13 -2
  64. package/dist/auth/resolved_auth.d.ts.map +1 -1
  65. package/dist/auth/resolved_auth.js +5 -4
  66. package/dist/auth/session_cookie.d.ts +4 -0
  67. package/dist/auth/session_cookie.d.ts.map +1 -1
  68. package/dist/auth/session_cookie.js +3 -2
  69. package/dist/auth/session_middleware.d.ts +1 -1
  70. package/dist/auth/session_middleware.d.ts.map +1 -1
  71. package/dist/auth/session_middleware.js +4 -1
  72. package/dist/auth/session_queries.d.ts +19 -2
  73. package/dist/auth/session_queries.d.ts.map +1 -1
  74. package/dist/auth/session_queries.js +8 -2
  75. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  76. package/dist/auth/signup_route_schema.js +1 -0
  77. package/dist/hono_context.d.ts +23 -0
  78. package/dist/hono_context.d.ts.map +1 -1
  79. package/dist/hono_context.js +19 -0
  80. package/dist/http/CLAUDE.md +41 -15
  81. package/dist/http/middleware_spec.d.ts +16 -0
  82. package/dist/http/middleware_spec.d.ts.map +1 -1
  83. package/dist/http/origin.d.ts +113 -18
  84. package/dist/http/origin.d.ts.map +1 -1
  85. package/dist/http/origin.js +247 -127
  86. package/dist/http/route_spec.d.ts +21 -0
  87. package/dist/http/route_spec.d.ts.map +1 -1
  88. package/dist/http/schema_helpers.d.ts +22 -0
  89. package/dist/http/schema_helpers.d.ts.map +1 -1
  90. package/dist/http/schema_helpers.js +24 -0
  91. package/dist/http/surface.d.ts +6 -1
  92. package/dist/http/surface.d.ts.map +1 -1
  93. package/dist/http/surface.js +12 -5
  94. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  95. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  96. package/dist/realtime/sse_auth_guard.js +31 -12
  97. package/dist/realtime/subscriber_registry.d.ts +10 -1
  98. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  99. package/dist/realtime/subscriber_registry.js +10 -1
  100. package/dist/server/app_server.d.ts +20 -7
  101. package/dist/server/app_server.d.ts.map +1 -1
  102. package/dist/server/app_server.js +8 -3
  103. package/dist/server/env.d.ts +2 -1
  104. package/dist/server/env.d.ts.map +1 -1
  105. package/dist/server/env.js +7 -17
  106. package/dist/server/serve_fact_route.d.ts.map +1 -1
  107. package/dist/server/serve_fact_route.js +1 -0
  108. package/dist/testing/CLAUDE.md +154 -22
  109. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  110. package/dist/testing/adversarial_headers.js +13 -6
  111. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  112. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  113. package/dist/testing/connection_closer_helpers.js +4 -0
  114. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/body_size.js +14 -65
  116. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  117. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  118. package/dist/testing/cross_backend/capabilities.js +9 -3
  119. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  120. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  121. package/dist/testing/cross_backend/conformance_case.js +6 -0
  122. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  123. package/dist/testing/cross_backend/conformance_table.js +20 -19
  124. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  125. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  126. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  127. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  129. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  130. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  132. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  133. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  135. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/origin.js +6 -26
  137. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  138. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  139. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  140. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  141. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  142. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/origin_scope.js +140 -0
  144. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  145. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  147. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  148. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  150. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  152. package/dist/testing/cross_backend/spine_surface_constants.d.ts +19 -0
  153. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  154. package/dist/testing/cross_backend/spine_surface_constants.js +22 -0
  155. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  156. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  157. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  158. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  159. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  160. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  161. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  162. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  163. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  164. package/dist/testing/cross_backend/transport_composition.js +364 -0
  165. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  166. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  167. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  168. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  169. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  170. package/dist/testing/entities.d.ts +23 -0
  171. package/dist/testing/entities.d.ts.map +1 -1
  172. package/dist/testing/entities.js +37 -0
  173. package/dist/testing/integration.d.ts.map +1 -1
  174. package/dist/testing/integration.js +3 -1
  175. package/dist/testing/middleware.d.ts +10 -0
  176. package/dist/testing/middleware.d.ts.map +1 -1
  177. package/dist/testing/middleware.js +31 -7
  178. package/dist/testing/stubs.d.ts +16 -3
  179. package/dist/testing/stubs.d.ts.map +1 -1
  180. package/dist/testing/stubs.js +20 -7
  181. package/dist/testing/surface_invariants.js +2 -2
  182. package/package.json +1 -1
@@ -6,12 +6,12 @@
6
6
  * credential and leaves it on the Hono context as separate keys.
7
7
  * `ResolvedAuth` is that credential gathered into one value
8
8
  * (`get_resolved_auth`), so the connections that outlive their request — a
9
- * WebSocket, an SSE stream — can carry it past the handshake and re-read it
10
- * with `revalidate_resolved_auth`.
9
+ * WebSocket, an SSE stream — can carry it past the handshake, re-read it with
10
+ * `revalidate_resolved_auth`, and close at its `credential_deadline`.
11
11
  *
12
12
  * @module
13
13
  */
14
- import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
14
+ import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_DEADLINE_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
15
15
  import { query_account_by_id } from './account_queries.js';
16
16
  import { query_api_token_live_account } from './api_token_queries.js';
17
17
  import { AUTH_SESSION_TOKEN_HASH_KEY } from './request_context.js';
@@ -36,7 +36,8 @@ export const get_resolved_auth = (c) => {
36
36
  account_id,
37
37
  credential_type,
38
38
  token_hash: credential_type === 'session' ? (c.get(AUTH_SESSION_TOKEN_HASH_KEY) ?? null) : null,
39
- api_token_id: credential_type === 'api_token' ? (c.get(AUTH_API_TOKEN_ID_KEY) ?? null) : null
39
+ api_token_id: credential_type === 'api_token' ? (c.get(AUTH_API_TOKEN_ID_KEY) ?? null) : null,
40
+ credential_deadline: credential_type === 'daemon_token' ? null : (c.get(CREDENTIAL_DEADLINE_KEY) ?? null)
40
41
  };
41
42
  };
42
43
  /**
@@ -104,6 +104,8 @@ export interface ParsedSession<TIdentity> {
104
104
  identity: TIdentity;
105
105
  /** Index of the key that verified the signature (0 = primary). */
106
106
  key_index: number;
107
+ /** The signed expiry, in seconds since the Unix epoch — later than the parse. */
108
+ expires_at: number;
107
109
  }
108
110
  /**
109
111
  * Parse a signed session cookie value.
@@ -141,6 +143,8 @@ export interface ProcessSessionResult<TIdentity> {
141
143
  action: 'none' | 'clear';
142
144
  /** The decoded identity if the cookie was valid. */
143
145
  identity?: TIdentity;
146
+ /** The cookie's signed expiry, epoch seconds, if the cookie was valid. */
147
+ expires_at?: number;
144
148
  }
145
149
  /**
146
150
  * Create a session config for raw session token identity.
@@ -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,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,QAAoB,CAAC;AAQjD;;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,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,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,CA+BrD,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,CAAC;IACzB,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;;;;;;;;;;;;;;;;;GAiBG;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,CAgBzC,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,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,QAAoB,CAAC;AAQjD;;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,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,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACnB;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,CAgCrD,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,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;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;;;;;;;;;;;;;;;;;GAiBG;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,CAgBzC,CAAC"}
@@ -78,7 +78,8 @@ export const parse_session = async (signed_value, keyring, options, now_seconds)
78
78
  return null;
79
79
  return {
80
80
  identity,
81
- key_index: result.key_index
81
+ key_index: result.key_index,
82
+ expires_at
82
83
  };
83
84
  };
84
85
  /**
@@ -147,5 +148,5 @@ export const process_session_cookie = async (signed_value, keyring, options, now
147
148
  // Invalid cookie - should be cleared
148
149
  return { valid: false, action: 'clear' };
149
150
  }
150
- return { valid: true, action: 'none', identity: parsed.identity };
151
+ return { valid: true, action: 'none', identity: parsed.identity, expires_at: parsed.expires_at };
151
152
  };
@@ -35,7 +35,7 @@ export declare const clear_session_cookie: <T>(c: Context, options: SessionOptio
35
35
  *
36
36
  * @param keyring - key ring for cookie verification
37
37
  * @param options - session configuration
38
- * @mutates Hono context - sets `options.context_key` and may clear the session cookie
38
+ * @mutates Hono context - sets `options.context_key` and `SESSION_COOKIE_EXPIRES_AT_KEY`, and may clear the session cookie
39
39
  */
40
40
  export declare const create_session_middleware: <TIdentity>(keyring: Keyring, options: SessionOptions<TIdentity>) => MiddlewareHandler;
41
41
  /**
@@ -1 +1 @@
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,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACN,KAAK,cAAc,EAKnB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;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,iBAcF,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;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,gCAAgC,KACvC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAavB,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,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACN,KAAK,cAAc,EAKnB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD;;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;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,gCAAgC,KACvC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAavB,CAAC"}
@@ -7,6 +7,7 @@
7
7
  import { getCookie, setCookie, deleteCookie } from 'hono/cookie';
8
8
  import { session_cookie_options, process_session_cookie, create_session_cookie_value } from './session_cookie.js';
9
9
  import { generate_session_token, hash_session_token, AUTH_SESSION_LIFETIME_MS, query_create_session, query_session_enforce_limit } from './session_queries.js';
10
+ import { SESSION_COOKIE_EXPIRES_AT_KEY } from '../hono_context.js';
10
11
  /**
11
12
  * Read the session cookie value from a request.
12
13
  */
@@ -49,7 +50,7 @@ export const clear_session_cookie = (c, options) => {
49
50
  *
50
51
  * @param keyring - key ring for cookie verification
51
52
  * @param options - session configuration
52
- * @mutates Hono context - sets `options.context_key` and may clear the session cookie
53
+ * @mutates Hono context - sets `options.context_key` and `SESSION_COOKIE_EXPIRES_AT_KEY`, and may clear the session cookie
53
54
  */
54
55
  export const create_session_middleware = (keyring, options) => {
55
56
  return async (c, next) => {
@@ -57,6 +58,8 @@ export const create_session_middleware = (keyring, options) => {
57
58
  const result = await process_session_cookie(signed_value, keyring, options);
58
59
  // Always set identity (null when invalid/missing) for type-safe reads
59
60
  c.set(options.context_key, result.identity ?? null);
61
+ // the signed expiry rides beside it, for the credential deadline
62
+ c.set(SESSION_COOKIE_EXPIRES_AT_KEY, result.expires_at ?? null);
60
63
  if (result.action === 'clear') {
61
64
  clear_session_cookie(c, options);
62
65
  }
@@ -55,12 +55,29 @@ export declare const query_create_session: (deps: QueryDeps, token_hash: string,
55
55
  */
56
56
  export declare const AUTH_SESSION_COLUMNS: readonly ["id", "account_id", "created_at", "expires_at"];
57
57
  /**
58
- * Get a session if it exists, is not expired, and has not been revoked.
58
+ * A live `auth_session` row as `query_session_get_valid` returns it, with how
59
+ * long it has left.
60
+ */
61
+ export interface ValidAuthSession extends AuthSession {
62
+ /**
63
+ * Milliseconds until the session expires — `expires_at - NOW()`, measured on
64
+ * the database clock in the statement that found it unexpired
65
+ * (`EXPIRES_IN_MS_SQL`).
66
+ */
67
+ expires_in_ms: number;
68
+ }
69
+ /**
70
+ * Get a session if it exists, is not expired, and has not been revoked, with
71
+ * how long it has left.
72
+ *
73
+ * The remaining lifetime is read in the same statement that checks the
74
+ * expiry, on the database clock, so it agrees with the check whatever the
75
+ * server's clock says (`auth/credential_deadline.ts`).
59
76
  *
60
77
  * @param deps - query dependencies
61
78
  * @param token_hash - blake3 hash of the session token
62
79
  */
63
- export declare const query_session_get_valid: (deps: QueryDeps, token_hash: string) => Promise<AuthSession | undefined>;
80
+ export declare const query_session_get_valid: (deps: QueryDeps, token_hash: string) => Promise<ValidAuthSession | undefined>;
64
81
  /**
65
82
  * Revoke (delete) a session by its token hash, with no account scoping.
66
83
  *
@@ -1 +1 @@
1
- {"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAQtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAYvB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CASnD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAK1F,CAAC"}
1
+ {"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGlE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAQtD;;;GAGG;AACH,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACpD;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAKtC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAYvB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CASnD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAK1F,CAAC"}
@@ -9,6 +9,7 @@
9
9
  import { hash_blake3 } from '@fuzdev/fuz_util/hash_blake3.ts';
10
10
  import { generate_random_base64url } from '../crypto.js';
11
11
  import { columns_sql, iso8601_timestamp_expr, qualify_columns } from '../db/sql_columns.js';
12
+ import { EXPIRES_IN_MS_SQL } from './credential_deadline.js';
12
13
  /**
13
14
  * Session lifetime in milliseconds (30 days).
14
15
  *
@@ -76,13 +77,18 @@ const auth_session_expr = iso8601_timestamp_expr(AUTH_SESSION_COLUMNS, [
76
77
  'expires_at'
77
78
  ]);
78
79
  /**
79
- * Get a session if it exists, is not expired, and has not been revoked.
80
+ * Get a session if it exists, is not expired, and has not been revoked, with
81
+ * how long it has left.
82
+ *
83
+ * The remaining lifetime is read in the same statement that checks the
84
+ * expiry, on the database clock, so it agrees with the check whatever the
85
+ * server's clock says (`auth/credential_deadline.ts`).
80
86
  *
81
87
  * @param deps - query dependencies
82
88
  * @param token_hash - blake3 hash of the session token
83
89
  */
84
90
  export const query_session_get_valid = async (deps, token_hash) => {
85
- return deps.db.query_one(`SELECT ${columns_sql(AUTH_SESSION_COLUMNS, auth_session_expr(''))} FROM auth_session WHERE id = $1 AND expires_at > NOW()`, [token_hash]);
91
+ return deps.db.query_one(`SELECT ${columns_sql(AUTH_SESSION_COLUMNS, auth_session_expr(''))}, ${EXPIRES_IN_MS_SQL} AS expires_in_ms FROM auth_session WHERE id = $1 AND expires_at > NOW()`, [token_hash]);
86
92
  };
87
93
  /**
88
94
  * Revoke (delete) a session by its token hash, with no account scoping.
@@ -1 +1 @@
1
- {"version":3,"file":"signup_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,wHAAwH;AACxH,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;;;;;;;kBAIvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,4EAA4E;AAC5E,MAAM,WAAW,uBAAuB;IACvC,iFAAiF;IACjF,2BAA2B,EAAE,OAAO,CAAC;CACrC;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACrC,SAAS,uBAAuB,KAC9B,IAAI,CAAC,SAAS,EAAE,SAAS,CAgB1B,CAAC"}
1
+ {"version":3,"file":"signup_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,wHAAwH;AACxH,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;;;;;;;kBAIvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,4EAA4E;AAC5E,MAAM,WAAW,uBAAuB;IACvC,iFAAiF;IACjF,2BAA2B,EAAE,OAAO,CAAC;CACrC;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACrC,SAAS,uBAAuB,KAC9B,IAAI,CAAC,SAAS,EAAE,SAAS,CAiB1B,CAAC"}
@@ -43,6 +43,7 @@ export const create_signup_route_shape = (options) => ({
43
43
  method: 'POST',
44
44
  path: '/signup',
45
45
  auth: { account: 'none', actor: 'none' },
46
+ mints_credential: true,
46
47
  description: 'Create account (invite-gated or open signup)',
47
48
  transaction: false, // manages its own transaction for TOCTOU safety
48
49
  input: SignupInput,
@@ -57,6 +57,25 @@ export declare const TOKEN_SCOPE_KEY = "token_scope";
57
57
  * read it directly.
58
58
  */
59
59
  export declare const ACCOUNT_ID_KEY = "auth_account_id";
60
+ /**
61
+ * Hono context variable name for the credential's deadline — when it stops
62
+ * authenticating, as a monotonic `performance.now()` timestamp
63
+ * (`auth/credential_deadline.ts`), or `null` for one with no expiry (a daemon
64
+ * token, an eternal API token) and for an unauthenticated request.
65
+ *
66
+ * Set by every auth middleware that resolves a credential: session (the
67
+ * earlier of the signed cookie's expiry and the row's), bearer (the row's),
68
+ * daemon token (`null`). A long-lived connection opened on the credential
69
+ * closes at it (`ResolvedAuth.credential_deadline`).
70
+ */
71
+ export declare const CREDENTIAL_DEADLINE_KEY = "credential_deadline";
72
+ /**
73
+ * Hono context variable name for the session cookie's signed expiry, in
74
+ * seconds since the Unix epoch — set by the session middleware on a cookie
75
+ * that verified and has not expired, `null` otherwise. The request-context
76
+ * middleware folds it into `CREDENTIAL_DEADLINE_KEY`.
77
+ */
78
+ export declare const SESSION_COOKIE_EXPIRES_AT_KEY = "auth_session_cookie_expires_at";
60
79
  /**
61
80
  * Hono context variable name for the test-harness pre-baked context flag.
62
81
  *
@@ -112,6 +131,10 @@ declare module 'hono' {
112
131
  * up the token.
113
132
  */
114
133
  auth_api_token_id: string | null;
134
+ /** The credential's deadline (`CREDENTIAL_DEADLINE_KEY`). */
135
+ credential_deadline: number | null;
136
+ /** The session cookie's signed expiry, epoch seconds (`SESSION_COOKIE_EXPIRES_AT_KEY`). */
137
+ auth_session_cookie_expires_at: number | null;
115
138
  /**
116
139
  * Eager fire-and-forget pool writes for this request — audit emits,
117
140
  * session-touch UPDATE, api-token usage tracking. Producers push the
@@ -1 +1 @@
1
- {"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAOhE;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,mDAInB,CAAC;AAEX,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,WAAW,EAAE,UAAU,GAAG,SAAS,CAAC;QACpC;;;;;WAKG;QACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC;;;;;;;WAOG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC;;;;;;;;;;;;WAYG;QACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD;;;;WAIG;QACH,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
1
+ {"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAOhE;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,mDAInB,CAAC;AAEX,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,mCAAmC,CAAC;AAE9E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,WAAW,EAAE,UAAU,GAAG,SAAS,CAAC;QACpC;;;;;WAKG;QACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,6DAA6D;QAC7D,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,2FAA2F;QAC3F,8BAA8B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9C;;;;;;;WAOG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC;;;;;;;;;;;;WAYG;QACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD;;;;WAIG;QACH,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
@@ -55,6 +55,25 @@ export const TOKEN_SCOPE_KEY = 'token_scope';
55
55
  * read it directly.
56
56
  */
57
57
  export const ACCOUNT_ID_KEY = 'auth_account_id';
58
+ /**
59
+ * Hono context variable name for the credential's deadline — when it stops
60
+ * authenticating, as a monotonic `performance.now()` timestamp
61
+ * (`auth/credential_deadline.ts`), or `null` for one with no expiry (a daemon
62
+ * token, an eternal API token) and for an unauthenticated request.
63
+ *
64
+ * Set by every auth middleware that resolves a credential: session (the
65
+ * earlier of the signed cookie's expiry and the row's), bearer (the row's),
66
+ * daemon token (`null`). A long-lived connection opened on the credential
67
+ * closes at it (`ResolvedAuth.credential_deadline`).
68
+ */
69
+ export const CREDENTIAL_DEADLINE_KEY = 'credential_deadline';
70
+ /**
71
+ * Hono context variable name for the session cookie's signed expiry, in
72
+ * seconds since the Unix epoch — set by the session middleware on a cookie
73
+ * that verified and has not expired, `null` otherwise. The request-context
74
+ * middleware folds it into `CREDENTIAL_DEADLINE_KEY`.
75
+ */
76
+ export const SESSION_COOKIE_EXPIRES_AT_KEY = 'auth_session_cookie_expires_at';
58
77
  /**
59
78
  * Hono context variable name for the test-harness pre-baked context flag.
60
79
  *
@@ -25,7 +25,7 @@ effects, see ../../../docs/architecture.md.
25
25
  - `http/proxy.ts` — trusted-proxy middleware, CIDR parsing, rightmost-first XFF resolution. Pulls `hono/utils/ipaddr` for CIDR matching.
26
26
  - `http/client_ip.ts` — `get_client_ip(c)` — reads the resolved IP off the context (set by `proxy.ts`'s middleware). Split out of `proxy.ts` so it's hono-free: dispatch + route modules that only _read_ the IP don't pull `hono/utils/ipaddr`, keeping cross-process test surfaces free of the optional `hono` peer.
27
27
  - `http/ip_canonical.ts` — RFC 5952 IPv6 canonicalization + IPv4-mapped collapse; `IP_LITERAL_CHARS` regex.
28
- - `http/origin.ts` — origin allowlist middleware with wildcard patterns (Origin-only).
28
+ - `http/origin.ts` — origin allowlist: the strict pattern grammar shared with the Rust spine, typed refusals, and the Origin-only middleware.
29
29
  - `http/jsonrpc.ts` — JSON-RPC 2.0 envelope schemas (MCP superset), `JsonrpcErrorCode`, `_meta`.
30
30
  - `http/jsonrpc_errors.ts` — `ThrownJsonrpcError`, `jsonrpc_errors` throwers, HTTP-status mappings.
31
31
  - `http/jsonrpc_helpers.ts` — message builders, type guards, input/result normalizers.
@@ -51,6 +51,8 @@ consumers.
51
51
  - `query?: z.ZodObject` — strict-object schema for URL query string
52
52
  - `input: z.ZodType` — request body schema; `z.null()` for no-body (GET/DELETE)
53
53
  - `output: z.ZodType` — success response schema
54
+ - `mints_credential?: boolean` — the route issues a session cookie while declaring no auth (login, signup, bootstrap); assembly refuses it outside `AUTH_MIDDLEWARE_PATH`, as it does any route whose `auth.account` is not `'none'` (`assert_credential_routes_in_auth_scope` in `auth/middleware.ts`)
55
+ - `reads_credential?: boolean` — the handler reads the resolved account while the route declares no auth gate, answering an anonymous caller itself (account status, the cell-scoped fact read, `create_rpc_endpoint`'s pair, whose dispatcher gates per action); held inside `AUTH_MIDDLEWARE_PATH` by the same assertion
54
56
  - `rate_limit?: RateLimitKey` — metadata only (`'ip' | 'account' | 'both'`); auto-derives 429
55
57
  - `errors?: RouteErrorSchemas` — handler-specific error schemas keyed by HTTP status
56
58
  - `transaction?: boolean` — declarative transaction wrapping (see below)
@@ -237,6 +239,7 @@ Key helpers:
237
239
  - `is_strict_object_schema(schema)` — detects `z.strictObject()` by checking `schema.def.catchall instanceof z.ZodNever`
238
240
  - `schema_to_surface(schema)` — Zod → JSON Schema, with `$schema` and `default` keys stripped recursively (defaults may be non-deterministic; `$schema` is snapshot noise)
239
241
  - `middleware_applies(mw_path, route_path)` — Hono pattern matching: `'*'`, exact, `'/api/*'` prefix (handles `prefix.slice(0, -1)` so `/api/*` also matches the bare `/api`)
242
+ - `middleware_spec_applies(spec, route_path)` — the layer runs on the route: its `path` applies and none of its `carve_outs` does; what surface generation merges middleware errors by
240
243
  - `merge_error_schemas(spec, middleware_errors?)` — the three-layer merge described above
241
244
 
242
245
  ## Surface Generation
@@ -327,9 +330,10 @@ operator configuration in practice.
327
330
  Origin allowlisting for locally-running services — **not** the CSRF layer.
328
331
  CSRF is handled by `SameSite: strict` on session cookies (`auth/session_middleware.ts`).
329
332
 
330
- - `parse_allowed_origins(env_value)` — comma-separated patterns → `Array<RegExp>`
331
- - `should_allow_origin(origin, patterns)` — case-insensitive match
332
- - `verify_request_source(allowed_patterns)` — Hono handler: `Origin` present → must match allowlist or 403 `ERROR_FORBIDDEN_ORIGIN`; no `Origin` → allow through (curl, CLI, token auth is primary control)
333
+ - `parse_allowed_origins(raw)` — comma-separated entries → `Result<{origins: Array<RegExp>}, {errors: Array<AllowedOriginsError>}>`; every refused entry is reported (`{kind, index, entry}`, 1-based), a blank value is `{kind: 'no_origins'}`
334
+ - `OriginPatternErrorKind` / `AllowedOriginsErrorKind` — the refusal kinds, wire names shared with the Rust spine; `ALLOWED_ORIGINS_ERROR_MESSAGES` the reason per kind, `format_allowed_origins_error` one refusal as a boot-error line (entry quoted and escaped)
335
+ - `should_allow_origin(origin, patterns)` — case-insensitive match; a value that isn't visible ASCII matches nothing
336
+ - `verify_request_source(allowed_patterns, carve_outs?)` — Hono handler: `Origin` present → must match allowlist or 403 `ERROR_FORBIDDEN_ORIGIN`; no `Origin` → allow through (curl, CLI, token auth is primary control). A repeated `Origin` reaches it joined (`a, b`) and is refused. A request whose path a `carve_outs` entry (`MiddlewareCarveOut`, `{path, reason}`) matches passes unchecked
333
337
  - `is_browser_context(c)` — true when `Origin` or `Referer` is present (`!== undefined`, so empty-string counts). The bearer + daemon-token middleware discard their credential when true; twin of the Rust spine's `bearer_auth::is_browser_context`. Distinct from `verify_request_source` (allowlist gate) — this only detects browser context
334
338
 
335
339
  **Origin-only by design.** Fetch spec mandates `Origin` on every unsafe
@@ -337,17 +341,39 @@ method, so a real browser request on any state-changing surface always
337
341
  carries it. Non-browser clients don't ship auto-attached session cookies,
338
342
  so CSRF isn't the relevant threat there — auth (bearer / daemon token) is
339
343
  the actual control. A `Referer` fallback would only widen the
340
- accepted-shape envelope without closing a real CSRF hole. Mirrors
341
- `zzz_server::auth::is_request_origin_allowed`.
342
-
343
- Pattern syntax: exact `https://api.fuz.dev`; wildcard subdomain
344
- `https://*.fuz.dev` (matches `api.fuz.dev`, NOT `fuz.dev`); multiple
345
- wildcards `https://*.*.corp.fuz.dev`; port wildcard `http://localhost:*`
346
- (optional port); IPv6 `http://[::1]:3000`, `https://[2001:db8::1]` (no
347
- wildcards inside brackets). Patterns normalize through `URL` constructor.
348
- IPv6 zone identifiers (`%eth0`) not supported. Throws on paths, partial
349
- wildcards (`*fuz.dev`), wildcards inside IPv6 brackets, or missing
350
- protocol.
344
+ accepted-shape envelope without closing a real CSRF hole. The Rust
345
+ spine's `origin_layer` is the twin.
346
+
347
+ **Every path by default.** `create_app_server` mounts the check at `'*'`
348
+ (`ORIGIN_MIDDLEWARE_PATH`), the static fallback included, so a foreign page is
349
+ refused wherever it points — the Rust spine's `fuz_http::compose_stack` checks
350
+ every route group and its fallback the same way. Probes and CLI clients send no
351
+ `Origin` and pass. A path leaves the check only by a named carve-out with its
352
+ reason (`AppServerOptions.origin_carve_outs`), for a route that refuses browser
353
+ callers by other means; one overlapping the auth middleware's scope
354
+ (`AUTH_MIDDLEWARE_PATH`, where every route that reads or mints a credential
355
+ sits) is refused at assembly. A carve-out skips every path it matches, routed
356
+ or not, so an unrouted path under it reaches the static fallback unchecked;
357
+ the Rust stack checks unrouted paths in its fallback group whatever a
358
+ carve-out names.
359
+
360
+ **Grammar** (shared with the Rust spine's `AllowedOrigins::parse`, pinned by
361
+ `testing/cross_backend/origin_conformance.json`): entries are trimmed of ASCII
362
+ whitespace and written as a browser writes an `Origin` — exact
363
+ `https://api.fuz.dev`; wildcard label `https://*.fuz.dev` (one label in any
364
+ position — matches `api.fuz.dev`, NOT `fuz.dev` or `a.b.fuz.dev`); several
365
+ `https://*.*.corp.fuz.dev`; port wildcard `http://localhost:*` (any port or
366
+ none); IPv6 `http://[::1]:3000` (exact, no wildcard inside the brackets).
367
+ **Nothing is normalized** — the matcher is built from the entry as written, so
368
+ an entry a browser would never send is refused rather than rewritten: non-ASCII
369
+ (punycode an IDN host), a scheme other than http/https (`null`, `file://`,
370
+ `chrome-extension://`), a path/query/fragment/trailing slash, userinfo, a
371
+ character no hostname holds (`%`, `\`, `^`, `|`, `<`, `>`, `[`, `]`), a partial
372
+ wildcard, an IPv4 address not written as four canonical decimal numbers, an IPv6
373
+ address not in the browser's form (the URL parser serves as the serializer and
374
+ any difference refuses), a port outside `1..=65535` or with leading zeros, the
375
+ scheme's default port, and an empty entry. The checks run in a fixed order so
376
+ both spines name the same kind for the same entry.
351
377
 
352
378
  ## JSON-RPC (`http/jsonrpc.ts`, `http/jsonrpc_errors.ts`, `http/jsonrpc_helpers.ts`)
353
379
 
@@ -8,6 +8,16 @@
8
8
  */
9
9
  import type { MiddlewareHandler } from 'hono';
10
10
  import type { RouteErrorSchemas } from './error_schemas.js';
11
+ /**
12
+ * Paths a middleware layer explicitly does not run on, and why — the named
13
+ * exception to a layer that covers every path by default (the origin check).
14
+ */
15
+ export interface MiddlewareCarveOut {
16
+ /** Hono-style pattern (`/hooks/*`, `/health`), matched like `MiddlewareSpec.path`. */
17
+ readonly path: string;
18
+ /** Why these paths are safe without the layer — required, so a carve-out is an argument, not an omission. */
19
+ readonly reason: string;
20
+ }
11
21
  /** A named middleware layer. */
12
22
  export interface MiddlewareSpec {
13
23
  name: string;
@@ -15,5 +25,11 @@ export interface MiddlewareSpec {
15
25
  handler: MiddlewareHandler;
16
26
  /** Error response schemas this middleware can produce, keyed by HTTP status code. */
17
27
  errors?: RouteErrorSchemas;
28
+ /**
29
+ * Paths under `path` the layer does not run on. The handler enforces them
30
+ * itself (Hono mounts by `path` alone); the field is what keeps the
31
+ * generated surface from claiming the layer's errors on those routes.
32
+ */
33
+ carve_outs?: ReadonlyArray<MiddlewareCarveOut>;
18
34
  }
19
35
  //# sourceMappingURL=middleware_spec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/middleware_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,iBAAiB,CAAC;IAC3B,qFAAqF;IACrF,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B"}
1
+ {"version":3,"file":"middleware_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/middleware_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6GAA6G;IAC7G,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,iBAAiB,CAAC;IAC3B,qFAAqF;IACrF,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CAC/C"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Request source verification middleware for API protection.
3
3
  *
4
- * Verifies requests are coming from expected origins/referers.
4
+ * Verifies requests carry an `Origin` the server allows, or none.
5
5
  * CSRF protection is `SameSite: strict` on session cookies
6
6
  * (see `auth/session_middleware.ts`) plus the CORS simple-request refusal
7
7
  * (`http/simple_request.ts`) — `SameSite` alone admits same-site pages, which
@@ -10,9 +10,18 @@
10
10
  * from making requests as the user browses the web — and is the only gate on
11
11
  * the WebSocket upgrade, which is never preflighted.
12
12
  *
13
+ * The allowlist grammar is shared with the Rust spine entry for entry:
14
+ * `parse_allowed_origins` accepts and refuses the same entries, naming each
15
+ * refusal with the same `AllowedOriginsErrorKind` string, and an accepted list
16
+ * matches the same origins. The shared conformance table
17
+ * (`testing/cross_backend/origin_conformance.json`) pins both.
18
+ *
13
19
  * @module
14
20
  */
21
+ import type { Result } from '@fuzdev/fuz_util/result.ts';
15
22
  import type { Context, Handler } from 'hono';
23
+ import { z } from 'zod';
24
+ import type { MiddlewareCarveOut } from './middleware_spec.js';
16
25
  /**
17
26
  * True when the request looks like it originated from a browser context — it
18
27
  * carries an `Origin` or `Referer` header. Browsers attach these
@@ -31,29 +40,106 @@ import type { Context, Handler } from 'hono';
31
40
  */
32
41
  export declare const is_browser_context: (c: Context) => boolean;
33
42
  /**
34
- * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
43
+ * Why one allowlist entry was refused. The wire names both spines emit — the
44
+ * Rust spine's `OriginPatternError::kind` returns the same strings.
45
+ */
46
+ export declare const OriginPatternErrorKind: z.ZodEnum<{
47
+ empty_entry: "empty_entry";
48
+ not_printable_ascii: "not_printable_ascii";
49
+ missing_scheme: "missing_scheme";
50
+ unsupported_scheme: "unsupported_scheme";
51
+ has_path: "has_path";
52
+ has_userinfo: "has_userinfo";
53
+ missing_host: "missing_host";
54
+ invalid_host_char: "invalid_host_char";
55
+ unbracketed_ipv6: "unbracketed_ipv6";
56
+ partial_wildcard: "partial_wildcard";
57
+ non_canonical_ipv4: "non_canonical_ipv4";
58
+ unclosed_ipv6: "unclosed_ipv6";
59
+ invalid_ipv6: "invalid_ipv6";
60
+ invalid_after_ipv6: "invalid_after_ipv6";
61
+ invalid_port: "invalid_port";
62
+ default_port: "default_port";
63
+ }>;
64
+ export type OriginPatternErrorKind = z.infer<typeof OriginPatternErrorKind>;
65
+ /** Why an allowlist value was refused: `no_origins` (a blank value) or an entry's kind. */
66
+ export declare const AllowedOriginsErrorKind: z.ZodEnum<{
67
+ empty_entry: "empty_entry";
68
+ not_printable_ascii: "not_printable_ascii";
69
+ missing_scheme: "missing_scheme";
70
+ unsupported_scheme: "unsupported_scheme";
71
+ has_path: "has_path";
72
+ has_userinfo: "has_userinfo";
73
+ missing_host: "missing_host";
74
+ invalid_host_char: "invalid_host_char";
75
+ unbracketed_ipv6: "unbracketed_ipv6";
76
+ partial_wildcard: "partial_wildcard";
77
+ non_canonical_ipv4: "non_canonical_ipv4";
78
+ unclosed_ipv6: "unclosed_ipv6";
79
+ invalid_ipv6: "invalid_ipv6";
80
+ invalid_after_ipv6: "invalid_after_ipv6";
81
+ invalid_port: "invalid_port";
82
+ default_port: "default_port";
83
+ no_origins: "no_origins";
84
+ }>;
85
+ export type AllowedOriginsErrorKind = z.infer<typeof AllowedOriginsErrorKind>;
86
+ /** The operator-facing reason for each refusal kind. */
87
+ export declare const ALLOWED_ORIGINS_ERROR_MESSAGES: Readonly<Record<AllowedOriginsErrorKind, string>>;
88
+ /** One reason `parse_allowed_origins` refused a value. */
89
+ export interface AllowedOriginsError {
90
+ readonly kind: AllowedOriginsErrorKind;
91
+ /** The refused entry's 1-based position; absent for `no_origins`. */
92
+ readonly index?: number;
93
+ /** The refused entry as given, trimmed; absent for `no_origins`. */
94
+ readonly entry?: string;
95
+ }
96
+ /**
97
+ * Render one refusal for a boot error — the entry in double quotes with every
98
+ * character but visible ASCII and the space escaped as `\uXXXX` (and `"` and
99
+ * `\` backslash-escaped), so an env value can't put a control, bidi, or other
100
+ * non-printing character in a log, nor close the quotes it is shown in.
101
+ *
102
+ * @param error - one of `parse_allowed_origins`'s errors
103
+ * @returns the reason as one line
104
+ */
105
+ export declare const format_allowed_origins_error: (error: AllowedOriginsError) => string;
106
+ /**
107
+ * Parses an allowlist value (conventionally the `FUZ_ALLOWED_ORIGINS` env var)
108
+ * into regex matchers for request source verification.
35
109
  * Origin allowlisting for locally-running services — not the CSRF layer
36
110
  * (that's `SameSite: strict` on session cookies plus `http/simple_request.ts`).
37
111
  *
38
- * Accepts comma-separated patterns with limited wildcards:
39
- * - Exact origins: `https://api.fuz.dev`
40
- * - Wildcard subdomains: `https://*.fuz.dev` (matches exactly one subdomain level)
41
- * - Multiple wildcards: `https://*.staging.*.fuz.dev` (for deep subdomains)
42
- * - Wildcard ports: `http://localhost:*` (matches any port or no port)
43
- * - IPv6 addresses: `http://[::1]:3000`, `https://[2001:db8::1]`
112
+ * Entries are comma-separated and trimmed of ASCII whitespace. Each is written
113
+ * the way a browser writes an `Origin` — `scheme://host[:port]` — where a whole
114
+ * host label or the port may be `*`:
115
+ * - Exact origins: `https://api.fuz.dev`, `http://localhost:3000`
116
+ * - Wildcard labels: `https://*.fuz.dev` (each `*` is exactly one label, in any
117
+ * position — `api.fuz.dev` matches, `fuz.dev` and `a.b.fuz.dev` don't)
118
+ * - Wildcard ports: `http://localhost:*` (any port or none)
119
+ * - IPv6 addresses: `http://[::1]:3000`, `https://[2001:db8::1]` (exact)
44
120
  * - Combined: `https://*.fuz.dev:*`
45
121
  *
46
- * Examples:
47
- * - `http://localhost:3000,https://prod.fuz.dev`
48
- * - `https://*.api.fuz.dev,http://127.0.0.1:*`
49
- * - `http://[::1]:*,https://*.*.corp.fuz.dev:*`
122
+ * Refused, nothing normalized: anything but printable ASCII (punycode an
123
+ * international hostname), a scheme other than http or https (`null`,
124
+ * `file://`, `chrome-extension://`), a path, query, fragment or trailing
125
+ * slash, userinfo, a character no hostname holds, a partial wildcard, an IPv4
126
+ * address not written as four canonical decimal numbers (`127.1`), an IPv6
127
+ * address not in the form a browser sends, a port outside `1..=65535` or with
128
+ * leading zeros, the scheme's default port, and an empty entry. Every refused
129
+ * entry is reported, not just the first.
50
130
  *
51
- * @throws Error if any individual pattern is invalid (missing protocol, partial wildcards, etc.)
131
+ * @param raw - the comma-separated allowlist value
132
+ * @returns the matchers, or every refusal — `no_origins` when the value is blank
52
133
  */
53
- export declare const parse_allowed_origins: (env_value: string | undefined) => Array<RegExp>;
134
+ export declare const parse_allowed_origins: (raw: string) => Result<{
135
+ origins: Array<RegExp>;
136
+ }, {
137
+ errors: Array<AllowedOriginsError>;
138
+ }>;
54
139
  /**
55
- * Tests if a request source (origin or referer) matches any of the allowed patterns.
56
- * Pattern matching is case-insensitive for domains (as per web standards).
140
+ * Tests if an `Origin` value matches any of the allowed patterns.
141
+ * Pattern matching is case-insensitive (web hosts and schemes are). A value
142
+ * that isn't visible ASCII matches nothing — a browser never sends one.
57
143
  */
58
144
  export declare const should_allow_origin: (origin: string, allowed_patterns: ReadonlyArray<RegExp>) => boolean;
59
145
  /**
@@ -63,6 +149,9 @@ export declare const should_allow_origin: (origin: string, allowed_patterns: Rea
63
149
  * plus `http/simple_request.ts`):
64
150
  * - Checks the `Origin` header (if present) against the allowlist
65
151
  * - Allows requests without an `Origin` header (direct access, curl, etc.)
152
+ * - Refuses an `Origin` that isn't visible ASCII, and a duplicated one — the
153
+ * runtimes join a repeated header into one value (`a, b`), which carries a
154
+ * space and so matches nothing
66
155
  *
67
156
  * Origin-only by design — Fetch spec mandates `Origin` on every unsafe
68
157
  * method (POST / PUT / DELETE / PATCH) regardless of `Referrer-Policy`,
@@ -71,9 +160,15 @@ export declare const should_allow_origin: (origin: string, allowed_patterns: Rea
71
160
  * attached session cookies, so CSRF isn't the relevant threat there —
72
161
  * auth (bearer / daemon token) is the actual control. A Referer fallback
73
162
  * would only widen the accepted-shape envelope without closing a real
74
- * CSRF hole; mirrors `zzz_server::auth::is_request_origin_allowed`.
163
+ * CSRF hole. The Rust spine's `origin_layer` is the twin.
164
+ *
165
+ * `create_app_server` mounts it on every path (`ORIGIN_MIDDLEWARE_PATH`); a
166
+ * request whose path a `carve_outs` entry matches passes unchecked — the named,
167
+ * explicit exception, each with its reason, as the Rust stack's
168
+ * `origin_carve_out` is.
75
169
  *
76
170
  * @param allowed_patterns - compiled regex patterns from `parse_allowed_origins`
171
+ * @param carve_outs - paths the check skips, matched like a middleware path (`middleware_applies`)
77
172
  */
78
- export declare const verify_request_source: (allowed_patterns: ReadonlyArray<RegExp>) => Handler;
173
+ export declare const verify_request_source: (allowed_patterns: ReadonlyArray<RegExp>, carve_outs?: ReadonlyArray<MiddlewareCarveOut>) => Handler;
79
174
  //# sourceMappingURL=origin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAI7C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,OAAO,KAAG,OAC8B,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,KAAK,CAAC,MAAM,CAO5E,CAAC;AAEP;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC/B,QAAQ,MAAM,EACd,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,OAAuD,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,aAAa,CAAC,MAAM,CAAC,KAAG,OAgB1C,CAAC"}
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,OAAO,KAAG,OAC8B,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;EAiBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,2FAA2F;AAC3F,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;EAA4D,CAAC;AACjG,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,wDAAwD;AACxD,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CAAC,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAwB5F,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACxB;AAKD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GAAI,OAAO,mBAAmB,KAAG,MAOzE,CAAC;AAwBF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,qBAAqB,GACjC,KAAK,MAAM,KACT,MAAM,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAAE,EAAE;IAAE,MAAM,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAA;CAAE,CAgB3E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC/B,QAAQ,MAAM,EACd,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,OAAqF,CAAC;AAEzF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,qBAAqB,GAEhC,kBAAkB,aAAa,CAAC,MAAM,CAAC,EACvC,aAAY,aAAa,CAAC,kBAAkB,CAAM,KAChD,OAaF,CAAC"}