@fuzdev/fuz_app 0.139.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 +12 -12
@@ -1 +1 @@
1
- {"version":3,"file":"api_token_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/api_token_queries.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,KAAK,UAAU,EAAmD,MAAM,kBAAkB,CAAC;AAEpG,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IACnD,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,0HAUoB,CAAC;AA6BnD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,EAClB,MAAM,MAAM,EACZ,YAAY,MAAM,EAClB,OAAO,UAAU,EACjB,aAAa,IAAI,GAAG,IAAI,KACtB,OAAO,CAAC,QAAQ,CAelB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,iBAAiB,EACvB,WAAW,MAAM,EACjB,IAAI,MAAM,GAAG,SAAS,EACtB,iBAAiB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,KAC/C,OAAO,CAAC,QAAQ,GAAG,SAAS,CAgC9B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,MAAM,GAAG,SAAS,CAM5B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAM7C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAYvB,CAAC"}
1
+ {"version":3,"file":"api_token_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/api_token_queries.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,KAAK,UAAU,EAAmD,MAAM,kBAAkB,CAAC;AAEpG;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG;IACzD,KAAK,EAAE,UAAU,CAAC;IAClB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IACnD,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,0HAUoB,CAAC;AA6BnD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,EAClB,MAAM,MAAM,EACZ,YAAY,MAAM,EAClB,OAAO,UAAU,EACjB,aAAa,IAAI,GAAG,IAAI,KACtB,OAAO,CAAC,QAAQ,CAelB,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,iBAAiB,EACvB,WAAW,MAAM,EACjB,IAAI,MAAM,GAAG,SAAS,EACtB,iBAAiB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,KAC/C,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAkCvC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,MAAM,GAAG,SAAS,CAM5B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAM7C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAYvB,CAAC"}
@@ -6,6 +6,7 @@
6
6
  import { assert_row } from '../db/assert_row.js';
7
7
  import { columns_sql, iso8601_timestamp_expr, omit_columns } from '../db/sql_columns.js';
8
8
  import { hash_api_token } from './api_token.js';
9
+ import { EXPIRES_IN_MS_SQL } from './credential_deadline.js';
9
10
  import { parse_stored_token_scope, serialize_token_scope } from './token_scope.js';
10
11
  /**
11
12
  * The full `api_token` column set, named explicitly so a row read fails loud
@@ -74,23 +75,26 @@ export const query_create_api_token = async (deps, id, account_id, name, token_h
74
75
  return assert_row(row, 'INSERT INTO api_token');
75
76
  };
76
77
  /**
77
- * Validate a raw API token and return the token record.
78
+ * Validate a raw API token and return the token record with its scope parsed.
78
79
  *
79
80
  * Hashes the token with blake3, looks up the hash, and checks
80
- * expiration. Updates `last_used_at` and `last_used_ip` on success
81
+ * expiration, reading how long the token has left on the database clock in
82
+ * the same statement. A row whose stored scope is unreadable is refused like an
83
+ * unknown hash. Updates `last_used_at` and `last_used_ip` on success
81
84
  * (fire-and-forget — errors logged, never thrown).
82
85
  *
83
86
  * @param deps - query dependencies with logger
84
87
  * @param raw_token - the raw API token from the Authorization header
85
88
  * @param ip - the client IP address (for audit)
86
89
  * @param pending_effects - optional array to register the usage-tracking effect for later awaiting
87
- * @returns the token record if valid, or `undefined`
90
+ * @returns the token record with its parsed scope if valid, or `undefined`
88
91
  * @mutates `api_token` row - fire-and-forget UPDATE of `last_used_at` / `last_used_ip` on a valid token
89
92
  * @mutates `pending_effects` - pushes the in-flight tracking promise when provided
90
93
  */
91
94
  export const query_validate_api_token = async (deps, raw_token, ip, pending_effects) => {
92
95
  const token_hash = hash_api_token(raw_token);
93
- const row = await deps.db.query_one(`SELECT ${columns_sql(API_TOKEN_COLUMNS, api_token_expr)} FROM api_token
96
+ const row = await deps.db.query_one(`SELECT ${columns_sql(API_TOKEN_COLUMNS, api_token_expr)}, ${EXPIRES_IN_MS_SQL} AS expires_in_ms
97
+ FROM api_token
94
98
  WHERE token_hash = $1
95
99
  AND ${API_TOKEN_IS_LIVE}`, [token_hash]);
96
100
  if (!row)
@@ -99,7 +103,8 @@ export const query_validate_api_token = async (deps, raw_token, ip, pending_effe
99
103
  // outcome as an unknown token hash. The scope *is* the gate here, so a parse
100
104
  // failure must not fall through to full authority — that would hand a
101
105
  // narrowed token everything precisely when something is already wrong.
102
- if (parse_stored_token_scope(row.scope) === null) {
106
+ const scope = parse_stored_token_scope(row.scope);
107
+ if (scope === null) {
103
108
  deps.log.warn(`api_token.scope is unreadable for ${row.id}; refusing the credential`);
104
109
  return undefined;
105
110
  }
@@ -114,7 +119,7 @@ export const query_validate_api_token = async (deps, raw_token, ip, pending_effe
114
119
  deps.log.error('Failed to update last_used_at:', err);
115
120
  });
116
121
  pending_effects?.push(p);
117
- return row;
122
+ return { ...row, scope };
118
123
  };
119
124
  /**
120
125
  * Look up the owning account of a live API token — one that still exists and
@@ -64,6 +64,16 @@ export interface AuditLogRouteOptions {
64
64
  * Rust spine's `audit_stream_router`, which registers before its role read
65
65
  * rather than repeating it.
66
66
  *
67
+ * ## Credential deadline
68
+ *
69
+ * Expiry writes nothing a revocation listener could hear, so an admitted
70
+ * stream watches its own credential's expiry
71
+ * (`ResolvedAuth.credential_deadline` — for the session-only stock route, the
72
+ * earlier of the cookie's and the row's) and is closed at it, as a revoked
73
+ * stream is: the body ends with no final frame, and the client's reconnect
74
+ * meets the gates. The wait is `schedule_credential_deadline`'s chain of
75
+ * bounded timers, cancelled when the stream closes.
76
+ *
67
77
  * @param options - optional stream wiring + role override
68
78
  * @returns the SSE route spec (when `options.stream` is provided) or an empty array
69
79
  */
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAUvD,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAU7E,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACpC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE;QACR,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;QAC9C,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,4BAA4B,GAAI,UAAU,oBAAoB,KAAG,KAAK,CAAC,SAAS,CAsG5F,CAAC"}
1
+ {"version":3,"file":"audit_log_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAUvD,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAW7E,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACpC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE;QACR,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;QAC9C,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,eAAO,MAAM,4BAA4B,GAAI,UAAU,oBAAoB,KAAG,KAAK,CAAC,SAAS,CAmH5F,CAAC"}
@@ -17,6 +17,7 @@ import { create_audit_log_route_shape, DEFAULT_AUDIT_STREAM_ROLE } from './audit
17
17
  import { create_sse_response } from '../realtime/sse.js';
18
18
  import { AUTH_SESSION_TOKEN_HASH_KEY, has_scoped_role, refresh_role_grants, require_request_context } from './request_context.js';
19
19
  import { get_resolved_auth, revalidate_resolved_auth } from './resolved_auth.js';
20
+ import { schedule_credential_deadline } from './credential_deadline.js';
20
21
  import { AUDIT_LOG_CHANNEL } from '../realtime/sse_auth_guard.js';
21
22
  /**
22
23
  * Create the optional audit-log SSE route spec.
@@ -50,6 +51,16 @@ import { AUDIT_LOG_CHANNEL } from '../realtime/sse_auth_guard.js';
50
51
  * Rust spine's `audit_stream_router`, which registers before its role read
51
52
  * rather than repeating it.
52
53
  *
54
+ * ## Credential deadline
55
+ *
56
+ * Expiry writes nothing a revocation listener could hear, so an admitted
57
+ * stream watches its own credential's expiry
58
+ * (`ResolvedAuth.credential_deadline` — for the session-only stock route, the
59
+ * earlier of the cookie's and the row's) and is closed at it, as a revoked
60
+ * stream is: the body ends with no final frame, and the client's reconnect
61
+ * meets the gates. The wait is `schedule_credential_deadline`'s chain of
62
+ * bounded timers, cancelled when the stream closes.
63
+ *
53
64
  * @param options - optional stream wiring + role override
54
65
  * @returns the SSE route spec (when `options.stream` is provided) or an empty array
55
66
  */
@@ -143,6 +154,15 @@ export const create_audit_log_route_specs = (options) => {
143
154
  log.info('audit stream: closed before admission', ctx.account.id);
144
155
  stream.close();
145
156
  }
157
+ else {
158
+ // The stream ends when the credential it opened on expires —
159
+ // closed as a revocation closes it (TSDoc, "Credential
160
+ // deadline").
161
+ stream.on_close(schedule_credential_deadline(get_resolved_auth(c)?.credential_deadline ?? null, () => {
162
+ log.info('audit stream: credential expired', ctx.account.id);
163
+ stream.close();
164
+ }));
165
+ }
146
166
  return response;
147
167
  }
148
168
  finally {
@@ -60,12 +60,15 @@ export type AuditEventType = z.infer<typeof AuditEventType>;
60
60
  * row's `metadata.token_id`.
61
61
  * - `account` — every connection of `target_account_id ?? account_id`.
62
62
  * - `role` — the target account's connections gated on the row's
63
- * `metadata.role`.
63
+ * `metadata.role`, when the revoked grant was global
64
+ * (`to_global_role_revocation`).
64
65
  *
65
- * The scope says what the row invalidated, not what a transport must do about
66
- * it. The two listeners differ on `role` on purpose: a WebSocket message is
67
- * re-authorized per dispatch, so the socket stays open and its next message
68
- * is refused, while a stream is authorized once at open and must be closed.
66
+ * The scope says what the row invalidated; both listeners act on every scope
67
+ * alike. On `role` that matters for the WebSocket too: per-message dispatch
68
+ * re-authorizes each request against its own action, but a socket admitted by
69
+ * a role gate also receives every server push its endpoint sends, which no
70
+ * dispatch ever re-checks — so a socket that lost the gating role is closed,
71
+ * like a stream.
69
72
  *
70
73
  * Twin of the Rust spine's `RevocationScope`.
71
74
  */
@@ -97,6 +100,28 @@ export declare const audit_event_revocation_scopes: Readonly<Record<AuditEventTy
97
100
  * with `queue_connection_close`.
98
101
  */
99
102
  export declare const to_revocation_scope: (event_type: string) => RevocationScope;
103
+ /**
104
+ * The account and role a successful `role`-scope row took away, when the
105
+ * revoked grant was **global** — `null` otherwise.
106
+ *
107
+ * A role gate (`register_ws_endpoint`'s `required_roles`, the audit stream's
108
+ * `require_role`) admits on global grants only, so revoking a scoped grant
109
+ * takes away nothing a gate admitted on and closes nothing. A row with no
110
+ * `metadata.scope_id` reads as global: when unsure, closing is the safe side.
111
+ * The target is `target_account_id ?? account_id`, as for the `account` scope.
112
+ * Both revocation listeners read it, so a socket and a stream gated on one
113
+ * role close on the same rows.
114
+ *
115
+ * Twin of the Rust spine's `global_role_revocation`.
116
+ *
117
+ * @param event - a row whose scope is `role` (the caller dispatched on it)
118
+ * @returns the account and role to close on, or `null` when the row names no
119
+ * role or target, or revoked a scoped grant
120
+ */
121
+ export declare const to_global_role_revocation: (event: AuditLogEvent) => {
122
+ account_id: Uuid;
123
+ role: string;
124
+ } | null;
100
125
  /**
101
126
  * Letter start, then letters, digits, `_`, `.`, `/`, `-`. Accepts snake_case,
102
127
  * dotted, and namespaced consumer conventions; rejects empty strings, leading
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
@@ -119,6 +119,36 @@ export const audit_event_revocation_scopes = Object.freeze({
119
119
  export const to_revocation_scope = (event_type) => Object.hasOwn(audit_event_revocation_scopes, event_type)
120
120
  ? audit_event_revocation_scopes[event_type]
121
121
  : 'none';
122
+ /**
123
+ * The account and role a successful `role`-scope row took away, when the
124
+ * revoked grant was **global** — `null` otherwise.
125
+ *
126
+ * A role gate (`register_ws_endpoint`'s `required_roles`, the audit stream's
127
+ * `require_role`) admits on global grants only, so revoking a scoped grant
128
+ * takes away nothing a gate admitted on and closes nothing. A row with no
129
+ * `metadata.scope_id` reads as global: when unsure, closing is the safe side.
130
+ * The target is `target_account_id ?? account_id`, as for the `account` scope.
131
+ * Both revocation listeners read it, so a socket and a stream gated on one
132
+ * role close on the same rows.
133
+ *
134
+ * Twin of the Rust spine's `global_role_revocation`.
135
+ *
136
+ * @param event - a row whose scope is `role` (the caller dispatched on it)
137
+ * @returns the account and role to close on, or `null` when the row names no
138
+ * role or target, or revoked a scoped grant
139
+ */
140
+ export const to_global_role_revocation = (event) => {
141
+ const role = event.metadata?.role;
142
+ if (typeof role !== 'string' || role.length === 0)
143
+ return null;
144
+ const scope_id = event.metadata?.scope_id;
145
+ if (scope_id !== undefined && scope_id !== null)
146
+ return null;
147
+ const account_id = event.target_account_id ?? event.account_id;
148
+ if (!account_id)
149
+ return null;
150
+ return { account_id, role };
151
+ };
122
152
  /**
123
153
  * Letter start, then letters, digits, `_`, `.`, `/`, `-`. Accepts snake_case,
124
154
  * dotted, and namespaced consumer conventions; rejects empty strings, leading
@@ -20,7 +20,7 @@ import type { QueryDeps } from '../db/query_deps.js';
20
20
  /**
21
21
  * Create middleware that authenticates via bearer token.
22
22
  *
23
- * Soft-fails for invalid, expired, or empty tokens — calls `next()` without
23
+ * Soft-fails for malformed, invalid, expired, or empty tokens — calls `next()` without
24
24
  * setting account identity, letting downstream auth enforcement (the RPC
25
25
  * dispatcher's pre-authorization / post-authorization auth gates or
26
26
  * `require_auth`) return a consistent JSON-RPC or route-level error. This
@@ -31,6 +31,8 @@ import type { QueryDeps } from '../db/query_deps.js';
31
31
  * Discards bearer tokens when an `Origin` or `Referer` header is present —
32
32
  * `next()` with no identity, as for a bad token — so browsers must use cookie
33
33
  * auth, reducing attack surface.
34
+ * A token that fails the `is_api_token_shaped` precheck soft-fails before
35
+ * the database is touched.
34
36
  * Auth scheme matching is case-insensitive per RFC 7235.
35
37
  * On success, sets `c.var.auth_account_id`, `CREDENTIAL_TYPE_KEY = 'api_token'`,
36
38
  * and `AUTH_API_TOKEN_ID_KEY`. Skips when an account is already authenticated
@@ -48,7 +50,7 @@ import type { QueryDeps } from '../db/query_deps.js';
48
50
  *
49
51
  * @param deps - query dependencies (pool-level db for middleware)
50
52
  * @param log - the logger instance
51
- * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on success
53
+ * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_API_TOKEN_ID_KEY`, and `CREDENTIAL_DEADLINE_KEY` on success
52
54
  */
53
55
  export declare const create_bearer_auth_middleware: (deps: QueryDeps, log: Logger) => MiddlewareHandler;
54
56
  //# sourceMappingURL=bearer_auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAUtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,SAAS,EAAE,KAAK,MAAM,KAAG,iBAuE5E,CAAC"}
1
+ {"version":3,"file":"bearer_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bearer_auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAYtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,SAAS,EAAE,KAAK,MAAM,KAAG,iBAgF5E,CAAC"}
@@ -15,15 +15,16 @@
15
15
  * @module
16
16
  */
17
17
  import { DEV } from 'esm-env';
18
- import { AUTH_API_TOKEN_ID_KEY, ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY } from '../hono_context.js';
19
- import { parse_stored_token_scope, token_scope_full } from './token_scope.js';
18
+ import { AUTH_API_TOKEN_ID_KEY, ACCOUNT_ID_KEY, CREDENTIAL_DEADLINE_KEY, CREDENTIAL_TYPE_KEY, TOKEN_SCOPE_KEY } from '../hono_context.js';
19
+ import { credential_deadline_after } from './credential_deadline.js';
20
+ import { is_api_token_shaped } from './api_token.js';
20
21
  import { query_validate_api_token } from './api_token_queries.js';
21
22
  import { get_client_ip } from '../http/client_ip.js';
22
23
  import { is_browser_context } from '../http/origin.js';
23
24
  /**
24
25
  * Create middleware that authenticates via bearer token.
25
26
  *
26
- * Soft-fails for invalid, expired, or empty tokens — calls `next()` without
27
+ * Soft-fails for malformed, invalid, expired, or empty tokens — calls `next()` without
27
28
  * setting account identity, letting downstream auth enforcement (the RPC
28
29
  * dispatcher's pre-authorization / post-authorization auth gates or
29
30
  * `require_auth`) return a consistent JSON-RPC or route-level error. This
@@ -34,6 +35,8 @@ import { is_browser_context } from '../http/origin.js';
34
35
  * Discards bearer tokens when an `Origin` or `Referer` header is present —
35
36
  * `next()` with no identity, as for a bad token — so browsers must use cookie
36
37
  * auth, reducing attack surface.
38
+ * A token that fails the `is_api_token_shaped` precheck soft-fails before
39
+ * the database is touched.
37
40
  * Auth scheme matching is case-insensitive per RFC 7235.
38
41
  * On success, sets `c.var.auth_account_id`, `CREDENTIAL_TYPE_KEY = 'api_token'`,
39
42
  * and `AUTH_API_TOKEN_ID_KEY`. Skips when an account is already authenticated
@@ -51,7 +54,7 @@ import { is_browser_context } from '../http/origin.js';
51
54
  *
52
55
  * @param deps - query dependencies (pool-level db for middleware)
53
56
  * @param log - the logger instance
54
- * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on success
57
+ * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_API_TOKEN_ID_KEY`, and `CREDENTIAL_DEADLINE_KEY` on success
55
58
  */
56
59
  export const create_bearer_auth_middleware = (deps, log) => {
57
60
  return async (c, next) => {
@@ -92,6 +95,13 @@ export const create_bearer_auth_middleware = (deps, log) => {
92
95
  await next();
93
96
  return;
94
97
  }
98
+ // A value that cannot be a token soft-fails before any database work —
99
+ // no pool checkout, no query — so junk bearers cost nothing to refuse.
100
+ if (!is_api_token_shaped(raw_token)) {
101
+ log.debug('bearer auth soft-fail: malformed token');
102
+ await next();
103
+ return;
104
+ }
95
105
  // `ip` feeds the token's `last_used_ip` bookkeeping, not a limiter.
96
106
  const ip = get_client_ip(c);
97
107
  const api_token = await query_validate_api_token({ ...deps, log }, raw_token, ip, c.var.pending_effects);
@@ -105,10 +115,11 @@ export const create_bearer_auth_middleware = (deps, log) => {
105
115
  c.set(ACCOUNT_ID_KEY, api_token.account_id);
106
116
  c.set(CREDENTIAL_TYPE_KEY, 'api_token');
107
117
  c.set(AUTH_API_TOKEN_ID_KEY, api_token.id);
108
- // `query_validate_api_token` is fail-closed on an unreadable scope, so a
109
- // token that got this far has a readable one. The `?? token_scope_full()`
110
- // is unreachable defensive cover, not a fallback with meaning.
111
- c.set(TOKEN_SCOPE_KEY, parse_stored_token_scope(api_token.scope) ?? token_scope_full());
118
+ // Parsed by the query, which refuses a token whose stored scope is
119
+ // unreadable — there is no default to fall back to.
120
+ c.set(TOKEN_SCOPE_KEY, api_token.scope);
121
+ // the row's expiry, read on the database clock; none for an eternal token
122
+ c.set(CREDENTIAL_DEADLINE_KEY, credential_deadline_after([api_token.expires_in_ms]));
112
123
  await next();
113
124
  };
114
125
  };
@@ -46,6 +46,7 @@ export declare const bootstrap_route_shape: {
46
46
  account: "none";
47
47
  actor: "none";
48
48
  };
49
+ mints_credential: true;
49
50
  description: string;
50
51
  transaction: false;
51
52
  input: z.ZodObject<{
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;;;;;;;kBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiBI,CAAC"}
1
+ {"version":3,"file":"bootstrap_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;;;;;;;kBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkBI,CAAC"}
@@ -39,6 +39,7 @@ export const bootstrap_route_shape = {
39
39
  method: 'POST',
40
40
  path: '/bootstrap',
41
41
  auth: { account: 'none', actor: 'none' },
42
+ mints_credential: true,
42
43
  description: 'Create initial keeper account (one-shot)',
43
44
  transaction: false, // bootstrap_account manages its own transaction
44
45
  input: BootstrapInput,
@@ -22,12 +22,12 @@
22
22
  * stream ends). Otherwise a swept session's connections would be reachable
23
23
  * only by an account-wide revocation.
24
24
  *
25
- * The contract that follows: **a connection outlives its session's expiry by
26
- * at most one cleanup interval** (plus the pass itself). In a process that
27
- * does not schedule the sweep, expiry ends no connection at all. The contract
28
- * is for sessions only: an expired API token's row is not swept, so nothing
29
- * here closes a connection opened on one — and `account_token_revoke` still
30
- * finds the row to close for.
25
+ * The sweep's close is the second on expiry, not the first: every connection
26
+ * already closes at its credential's deadline (`auth/credential_deadline.ts`)
27
+ * in the process holding it, whether or not a sweep runs. The sweep is for
28
+ * sessions only: an expired API token's row is not swept — its connections
29
+ * close at their deadline, and `account_token_revoke` still finds the row to
30
+ * close for.
31
31
  *
32
32
  * The closes run after the delete's commit, which is the rule every
33
33
  * revocation close keeps (see `actions/connection_closer.ts`). An expired
@@ -118,9 +118,9 @@ import type { AuditEmitter } from './audit_emitter.js';
118
118
  * `DEFAULT_AUTH_CLEANUP_INTERVAL`.
119
119
  *
120
120
  * An expired session or offer is already refused by every check that reads
121
- * it, so the interval bounds three things: how long a connection outlives
122
- * the expiry of the session it opened on, how late an offer expiry's audit
123
- * row lands, and how long dead rows linger.
121
+ * it, and a connection closes at its credential's deadline on its own, so the
122
+ * interval bounds two things: how late an offer expiry's audit row lands, and
123
+ * how long dead rows linger.
124
124
  */
125
125
  export declare const DEFAULT_AUTH_CLEANUP_INTERVAL_MS: number;
126
126
  /**
@@ -22,12 +22,12 @@
22
22
  * stream ends). Otherwise a swept session's connections would be reachable
23
23
  * only by an account-wide revocation.
24
24
  *
25
- * The contract that follows: **a connection outlives its session's expiry by
26
- * at most one cleanup interval** (plus the pass itself). In a process that
27
- * does not schedule the sweep, expiry ends no connection at all. The contract
28
- * is for sessions only: an expired API token's row is not swept, so nothing
29
- * here closes a connection opened on one — and `account_token_revoke` still
30
- * finds the row to close for.
25
+ * The sweep's close is the second on expiry, not the first: every connection
26
+ * already closes at its credential's deadline (`auth/credential_deadline.ts`)
27
+ * in the process holding it, whether or not a sweep runs. The sweep is for
28
+ * sessions only: an expired API token's row is not swept — its connections
29
+ * close at their deadline, and `account_token_revoke` still finds the row to
30
+ * close for.
31
31
  *
32
32
  * The closes run after the delete's commit, which is the rule every
33
33
  * revocation close keeps (see `actions/connection_closer.ts`). An expired
@@ -117,9 +117,9 @@ import { query_audit_log } from './audit_log_queries.js';
117
117
  * `DEFAULT_AUTH_CLEANUP_INTERVAL`.
118
118
  *
119
119
  * An expired session or offer is already refused by every check that reads
120
- * it, so the interval bounds three things: how long a connection outlives
121
- * the expiry of the session it opened on, how late an offer expiry's audit
122
- * row lands, and how long dead rows linger.
120
+ * it, and a connection closes at its credential's deadline on its own, so the
121
+ * interval bounds two things: how late an offer expiry's audit row lands, and
122
+ * how long dead rows linger.
123
123
  */
124
124
  export const DEFAULT_AUTH_CLEANUP_INTERVAL_MS = 10 * 60 * 1000;
125
125
  /**
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The credential deadline — the moment a resolved credential stops
3
+ * authenticating, which a long-lived connection opened on it must not outlive.
4
+ *
5
+ * A WebSocket or an SSE stream is authorized once, at open, and closed on
6
+ * revocation by identity. Expiry has no revocation event: nothing is written
7
+ * when a session or token passes its `expires_at`. So the auth middleware
8
+ * computes the deadline once (`CREDENTIAL_DEADLINE_KEY`, gathered onto
9
+ * `ResolvedAuth.credential_deadline`) and the transport closes the connection
10
+ * when it arrives (`schedule_credential_deadline`).
11
+ *
12
+ * The deadline is the earliest of what the credential carries:
13
+ *
14
+ * - **Session** — the signed cookie's embedded expiry and the `auth_session`
15
+ * row's `expires_at`.
16
+ * - **API token** — the `api_token` row's `expires_at`; an eternal token
17
+ * (`expires_at IS NULL`) has none.
18
+ * - **Daemon token** — none: it has no row and no expiry, and a rotation
19
+ * closes no connection.
20
+ *
21
+ * A row's expiry is read as **remaining time measured on the database clock**
22
+ * (`EXPIRES_IN_MS_SQL`, `expires_at - NOW()` in the same statement that checks
23
+ * `expires_at > NOW()`), then anchored to this process's monotonic clock
24
+ * (`performance.now()`) — so a skew between the database's clock and the
25
+ * server's moves no deadline. The cookie's expiry is a wall-clock second this
26
+ * server signed, read against the same wall clock the cookie check reads.
27
+ *
28
+ * The monotonic clock stops while the host is suspended, so a deadline on it
29
+ * alone would hold a connection past its credential by the time asleep. The
30
+ * wait (`schedule_credential_deadline`) therefore also carries the deadline on
31
+ * the wall clock (`Date.now()`), which keeps running through a suspend, and
32
+ * fires at whichever is reached first: after a resume, within one timer step
33
+ * (`CREDENTIAL_DEADLINE_TIMER_CAP_MS`, an hour). The wall deadline is taken
34
+ * from the time left when the wait starts, so a skew between the database's
35
+ * clock and the server's still moves nothing. A wall clock stepped forward
36
+ * can fire it early; the client reconnects through the gates, the fail-closed
37
+ * side of the trade.
38
+ *
39
+ * The twin of the Rust spine's `fuz_auth::ResolvedAuth::credential_deadline`
40
+ * and `fuz_realtime`'s deadline wait.
41
+ *
42
+ * @module
43
+ */
44
+ /**
45
+ * SQL expression for a row's remaining lifetime in milliseconds, measured on
46
+ * the database clock — `NULL` when `expires_at` is `NULL` (an eternal API
47
+ * token). `float8`, so every driver reads it as a JS number.
48
+ */
49
+ export declare const EXPIRES_IN_MS_SQL = "(EXTRACT(EPOCH FROM (expires_at - NOW())) * 1000)::float8";
50
+ /**
51
+ * The longest single timer the deadline wait sets — one hour, far inside
52
+ * `setTimeout`'s ceiling (2^31 - 1 ms, about 24.8 days, past which a timer
53
+ * fires at once) and the Rust twin's tokio ceiling, and short enough that a
54
+ * resume from suspend is noticed within it. A longer deadline is reached
55
+ * through several.
56
+ */
57
+ export declare const CREDENTIAL_DEADLINE_TIMER_CAP_MS = 3600000;
58
+ /**
59
+ * The deadline `now` plus the shortest of `remaining_ms`, as a monotonic
60
+ * `performance.now()` timestamp, or `null` when no entry carries one.
61
+ *
62
+ * A negative or `NaN` entry — a row its own `expires_at > NOW()` check
63
+ * already refused, read at the boundary — counts as zero.
64
+ *
65
+ * @param remaining_ms - each source's remaining lifetime in ms, `null` for none
66
+ * @param now - the monotonic clock reading to anchor to
67
+ * @returns the deadline, or `null` for a credential with no expiry
68
+ */
69
+ export declare const credential_deadline_after: (remaining_ms: ReadonlyArray<number | null>, now?: number) => number | null;
70
+ /**
71
+ * The next timer toward `deadline` from `now`: what remains, capped at
72
+ * `CREDENTIAL_DEADLINE_TIMER_CAP_MS`, or `null` once the deadline is reached.
73
+ *
74
+ * @param deadline - the monotonic deadline
75
+ * @param now - the monotonic clock reading
76
+ * @returns the next delay in ms, or `null` when the deadline has passed
77
+ */
78
+ export declare const next_credential_deadline_delay: (deadline: number, now: number) => number | null;
79
+ /**
80
+ * Call `on_expire` once `deadline` has passed, waiting in timers of at most
81
+ * `CREDENTIAL_DEADLINE_TIMER_CAP_MS`, each re-reading the clock against the
82
+ * absolute deadline — so a session's 30 days (past `setTimeout`'s ceiling)
83
+ * does not fire at once. Each wake-up also checks the same deadline on the
84
+ * wall clock, so a host suspend, which stops the monotonic clock, delays the
85
+ * close by at most one timer after the resume (module doc).
86
+ *
87
+ * A `null` deadline schedules nothing. The returned function cancels the wait
88
+ * and is safe to call more than once, before or after it fires.
89
+ *
90
+ * @param deadline - the monotonic deadline (`ResolvedAuth.credential_deadline`)
91
+ * @param on_expire - run once, when the deadline passes
92
+ * @returns a cancel function
93
+ */
94
+ export declare const schedule_credential_deadline: (deadline: number | null, on_expire: () => void) => (() => void);
95
+ //# sourceMappingURL=credential_deadline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential_deadline.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/credential_deadline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,8DAA8D,CAAC;AAE7F;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,UAAY,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACrC,cAAc,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC,EAC1C,MAAK,MAA0B,KAC7B,MAAM,GAAG,IAQX,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAAI,UAAU,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,IAIvF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAU,MAAM,GAAG,IAAI,EACvB,WAAW,MAAM,IAAI,KACnB,CAAC,MAAM,IAAI,CA2Bb,CAAC"}