@fuzdev/fuz_app 0.118.0 → 0.119.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 (155) hide show
  1. package/dist/actions/CLAUDE.md +301 -73
  2. package/dist/actions/action_rpc.d.ts.map +1 -1
  3. package/dist/actions/action_rpc.js +16 -1
  4. package/dist/actions/connection_closer.d.ts +136 -51
  5. package/dist/actions/connection_closer.d.ts.map +1 -1
  6. package/dist/actions/connection_closer.js +133 -34
  7. package/dist/actions/register_action_ws.d.ts +176 -24
  8. package/dist/actions/register_action_ws.d.ts.map +1 -1
  9. package/dist/actions/register_action_ws.js +581 -241
  10. package/dist/actions/register_ws_endpoint.d.ts +1 -0
  11. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  12. package/dist/actions/register_ws_endpoint.js +1 -0
  13. package/dist/actions/socket.svelte.d.ts +43 -9
  14. package/dist/actions/socket.svelte.d.ts.map +1 -1
  15. package/dist/actions/socket.svelte.js +59 -8
  16. package/dist/actions/transports.d.ts +55 -1
  17. package/dist/actions/transports.d.ts.map +1 -1
  18. package/dist/actions/transports.js +55 -1
  19. package/dist/actions/transports_ws_auth_guard.d.ts +53 -67
  20. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  21. package/dist/actions/transports_ws_auth_guard.js +87 -111
  22. package/dist/actions/transports_ws_backend.d.ts +212 -30
  23. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  24. package/dist/actions/transports_ws_backend.js +327 -70
  25. package/dist/actions/ws_endpoint_spec.d.ts +26 -13
  26. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  27. package/dist/auth/CLAUDE.md +113 -32
  28. package/dist/auth/account_actions.d.ts +12 -18
  29. package/dist/auth/account_actions.d.ts.map +1 -1
  30. package/dist/auth/account_actions.js +47 -33
  31. package/dist/auth/account_routes.d.ts +5 -11
  32. package/dist/auth/account_routes.d.ts.map +1 -1
  33. package/dist/auth/account_routes.js +40 -36
  34. package/dist/auth/account_schema.d.ts +27 -0
  35. package/dist/auth/account_schema.d.ts.map +1 -1
  36. package/dist/auth/account_schema.js +22 -0
  37. package/dist/auth/admin_actions.d.ts +8 -17
  38. package/dist/auth/admin_actions.d.ts.map +1 -1
  39. package/dist/auth/admin_actions.js +33 -21
  40. package/dist/auth/api_token_queries.d.ts +23 -2
  41. package/dist/auth/api_token_queries.d.ts.map +1 -1
  42. package/dist/auth/api_token_queries.js +39 -3
  43. package/dist/auth/audit_emitter.d.ts +61 -38
  44. package/dist/auth/audit_emitter.d.ts.map +1 -1
  45. package/dist/auth/audit_emitter.js +57 -14
  46. package/dist/auth/audit_log_route_schema.js +7 -7
  47. package/dist/auth/audit_log_routes.d.ts +31 -6
  48. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  49. package/dist/auth/audit_log_routes.js +85 -11
  50. package/dist/auth/audit_log_schema.d.ts +48 -0
  51. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  52. package/dist/auth/audit_log_schema.js +58 -0
  53. package/dist/auth/cleanup.d.ts +203 -28
  54. package/dist/auth/cleanup.d.ts.map +1 -1
  55. package/dist/auth/cleanup.js +222 -26
  56. package/dist/auth/deps.d.ts +28 -0
  57. package/dist/auth/deps.d.ts.map +1 -1
  58. package/dist/auth/resolved_auth.d.ts +105 -0
  59. package/dist/auth/resolved_auth.d.ts.map +1 -0
  60. package/dist/auth/resolved_auth.js +124 -0
  61. package/dist/auth/session_middleware.d.ts +8 -1
  62. package/dist/auth/session_middleware.d.ts.map +1 -1
  63. package/dist/auth/session_middleware.js +9 -3
  64. package/dist/auth/session_queries.d.ts +31 -6
  65. package/dist/auth/session_queries.d.ts.map +1 -1
  66. package/dist/auth/session_queries.js +31 -6
  67. package/dist/auth/standard_rpc_actions.d.ts +9 -9
  68. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  69. package/dist/auth/standard_rpc_actions.js +3 -3
  70. package/dist/db/CLAUDE.md +7 -1
  71. package/dist/db/status.d.ts +58 -1
  72. package/dist/db/status.d.ts.map +1 -1
  73. package/dist/db/status.js +205 -77
  74. package/dist/http/CLAUDE.md +7 -3
  75. package/dist/http/db_routes.d.ts +6 -2
  76. package/dist/http/db_routes.d.ts.map +1 -1
  77. package/dist/http/db_routes.js +2 -2
  78. package/dist/periodic.d.ts +80 -0
  79. package/dist/periodic.d.ts.map +1 -0
  80. package/dist/periodic.js +122 -0
  81. package/dist/realtime/sse_auth_guard.d.ts +79 -49
  82. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  83. package/dist/realtime/sse_auth_guard.js +103 -73
  84. package/dist/realtime/subscriber_registry.d.ts +119 -8
  85. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  86. package/dist/realtime/subscriber_registry.js +184 -25
  87. package/dist/server/app_backend.d.ts +12 -1
  88. package/dist/server/app_backend.d.ts.map +1 -1
  89. package/dist/server/app_backend.js +5 -2
  90. package/dist/server/app_server.d.ts +55 -15
  91. package/dist/server/app_server.d.ts.map +1 -1
  92. package/dist/server/app_server.js +41 -13
  93. package/dist/testing/CLAUDE.md +140 -48
  94. package/dist/testing/app_server.d.ts +2 -2
  95. package/dist/testing/app_server.d.ts.map +1 -1
  96. package/dist/testing/app_server.js +11 -2
  97. package/dist/testing/audit_drift_guard.d.ts +1 -48
  98. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  99. package/dist/testing/audit_drift_guard.js +0 -42
  100. package/dist/testing/connection_closer_helpers.d.ts +9 -22
  101. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  102. package/dist/testing/connection_closer_helpers.js +11 -17
  103. package/dist/testing/cross_backend/capabilities.d.ts +67 -13
  104. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  105. package/dist/testing/cross_backend/capabilities.js +27 -12
  106. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  107. package/dist/testing/cross_backend/default_backend_configs.js +21 -0
  108. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  109. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +13 -4
  110. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  111. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +4 -1
  112. package/dist/testing/cross_backend/session_cap.d.ts +13 -0
  113. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  114. package/dist/testing/cross_backend/session_cap.js +55 -19
  115. package/dist/testing/cross_backend/sse_round_trip.d.ts +10 -2
  116. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  117. package/dist/testing/cross_backend/sse_round_trip.js +93 -26
  118. package/dist/testing/cross_backend/testing_server_bun.js +4 -4
  119. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/ts_spine_backend_config.js +10 -2
  121. package/dist/testing/cross_backend/ws_connection_cap.d.ts +35 -0
  122. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -0
  123. package/dist/testing/cross_backend/ws_connection_cap.js +131 -0
  124. package/dist/testing/cross_backend/ws_round_trip.d.ts +2 -1
  125. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  126. package/dist/testing/cross_backend/ws_round_trip.js +156 -19
  127. package/dist/testing/sse_round_trip.d.ts +6 -4
  128. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  129. package/dist/testing/stubs.d.ts +1 -1
  130. package/dist/testing/stubs.d.ts.map +1 -1
  131. package/dist/testing/stubs.js +6 -4
  132. package/dist/testing/transports/sse_transport.d.ts +1 -1
  133. package/dist/testing/transports/ws_client.d.ts +7 -0
  134. package/dist/testing/transports/ws_client.d.ts.map +1 -1
  135. package/dist/testing/transports/ws_raw_client.d.ts +51 -0
  136. package/dist/testing/transports/ws_raw_client.d.ts.map +1 -0
  137. package/dist/testing/transports/ws_raw_client.js +156 -0
  138. package/dist/testing/transports/ws_transport.d.ts +28 -0
  139. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  140. package/dist/testing/transports/ws_transport.js +45 -1
  141. package/dist/testing/ws_round_trip.d.ts +8 -3
  142. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  143. package/dist/testing/ws_round_trip.js +19 -4
  144. package/dist/ui/AdminAccounts.svelte +4 -3
  145. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  146. package/dist/ui/AdminOverview.svelte +3 -1
  147. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  148. package/dist/ui/CLAUDE.md +22 -6
  149. package/dist/ui/admin_accounts_state.svelte.d.ts +15 -1
  150. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  151. package/dist/ui/admin_accounts_state.svelte.js +16 -0
  152. package/dist/ui/auth_state.svelte.d.ts +25 -1
  153. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  154. package/dist/ui/auth_state.svelte.js +28 -1
  155. package/package.json +1 -1
@@ -1,30 +1,132 @@
1
1
  /**
2
2
  * Periodic auth cleanup — sweeps expired sessions and role_grant offers.
3
3
  *
4
- * Single entry point for consumers scheduling auth maintenance. Internally
5
- * runs every known sweep and emits the corresponding audit events so
6
- * consumer code only manages cadence, not per-task wiring.
7
- *
8
- * The per-task primitives remain exported from their home modules
9
- * (`query_session_cleanup_expired`, `query_role_grant_offer_sweep_expired`);
10
- * `cleanup_expired_role_grant_offers` here wraps the latter with the required
11
- * `role_grant_offer_expire` audit emission and is the piece most likely to be
12
- * reused in a consumer's bespoke scheduler.
13
- *
14
- * Idempotency: the offer sweep claims each expired offer by stamping
15
- * `role_grant_offer.expire_audited_at` in the same transaction as its
16
- * `role_grant_offer_expire` audit row, so every expiry is audited exactly
17
- * once — a second run finds nothing, and concurrent runs claim disjoint rows
18
- * (row locks serialize them on the re-checked stamp). A failed audit insert
19
- * rolls the stamps back with it and the next run retries. A re-offer clears
20
- * the stamp, so the refreshed offer's own expiry is audited too.
4
+ * `run_auth_cleanup` is one pass: it deletes expired sessions and closes the
5
+ * connections they opened (`cleanup_expired_sessions`), then audits newly
6
+ * expired offers (`cleanup_expired_role_grant_offers`), returning both counts.
7
+ * `start_auth_cleanup` schedules it — the one call a server makes, and what
8
+ * `create_app_server`'s `auth_cleanup` option makes for it. The per-pass
9
+ * pieces stay exported for a consumer that wants its own cadence or error
10
+ * isolation per sweep. Twin of the Rust spine's `fuz_auth::auth_cleanup`.
11
+ *
12
+ * ## Expired sessions: the sweep closes what it sweeps
13
+ *
14
+ * A session past `expires_at` fails every validity check, but the connections
15
+ * it opened do not read it again: a WebSocket re-authorizes the actor per
16
+ * message, never the session, and an SSE stream is authorized once at open.
17
+ * While the expired row lingers, `account_session_revoke` is the per-session
18
+ * handle on those connections — it closes for the row it deletes. The sweep
19
+ * deletes that row, so it takes over the close: every session it deletes has
20
+ * its connections closed through `AuthCleanupDeps.connection_closer`, the same
21
+ * close a revocation makes (`WS_CLOSE_SESSION_REVOKED` on a WebSocket; an SSE
22
+ * stream ends). Otherwise a swept session's connections would be reachable
23
+ * only by an account-wide revocation.
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.
31
+ *
32
+ * The closes run after the delete's commit, which is the rule every
33
+ * revocation close keeps (see `actions/connection_closer.ts`). An expired
34
+ * session is refused with or without its row, so the order is not what
35
+ * protects a client rechecking on the close or a connection still opening; it
36
+ * keeps the sweep inside the one invariant admission leans on — a close that
37
+ * missed a registration belongs to a write the re-read can see — rather than
38
+ * on the re-read's clock. A commit whose outcome is unknown still closes: the
39
+ * sessions are expired either way.
40
+ *
41
+ * That is why the delete runs in its own transaction rather than as one
42
+ * pool-level statement. The ids are in hand before the commit is sent, so a
43
+ * connection lost on the delete's response rolls the delete back for the next
44
+ * pass, and one lost on the commit's still closes — no failure leaves rows
45
+ * deleted with their ids unread. A commit that fails outright has closed too;
46
+ * the rows it left are deleted by the next pass, whose closes then find
47
+ * nothing.
48
+ *
49
+ * One pass closes for every session it swept; a session with no live
50
+ * connection costs a lookup and closes nothing. The pass logs one count, not
51
+ * a line per session.
52
+ *
53
+ * No audit row is written for a swept session: expiry is not an event anyone
54
+ * performed, and the session was already refused everywhere. So the audit
55
+ * listeners never see it, and the direct close is the only one.
56
+ *
57
+ * One limit: the closes reach this process's transports, as every revocation
58
+ * close does — a second process on the same database keeps the connections it
59
+ * holds on a session this one swept.
60
+ *
61
+ * ## Offer idempotency
62
+ *
63
+ * An expired pending offer has no terminal state — expiry is computed from
64
+ * `expires_at`, never written — so the offer sweep claims rows instead: it
65
+ * stamps `role_grant_offer.expire_audited_at` in the same transaction as each
66
+ * row's `role_grant_offer_expire` audit row, so every expiry is audited
67
+ * exactly once — a second run finds nothing, and concurrent runs claim
68
+ * disjoint rows (row locks serialize them on the re-checked stamp). A failed
69
+ * audit insert rolls the stamps back with it and the next run retries. A
70
+ * re-offer clears the stamp, so the refreshed offer's own expiry is audited
71
+ * too.
72
+ *
73
+ * ## Scheduling
74
+ *
75
+ * `start_auth_cleanup` runs the pass beside the server: a pass at once, then
76
+ * one per interval (`DEFAULT_AUTH_CLEANUP_INTERVAL_MS` unless the consumer has
77
+ * a reason to differ), until its `stop` is called. A failed pass — the
78
+ * database out of reach — is logged and retried on the next interval.
79
+ *
80
+ * Start it once migrations have run (the startup pass reads the auth tables),
81
+ * with the app's bound `AuditEmitter` so the expiry audits reach its listeners
82
+ * and the closer its revocation handlers close through. `AppDeps` is all of
83
+ * that, so the standard assembly is one option:
84
+ *
85
+ * ```ts
86
+ * const server = await create_app_server({backend, auth_cleanup: true, ...});
87
+ * // on shutdown: stops the schedule, then closes the database
88
+ * await server.close();
89
+ * ```
90
+ *
91
+ * and a hand-assembled server calls it directly:
92
+ *
93
+ * ```ts
94
+ * const auth_cleanup = start_auth_cleanup(backend.deps);
95
+ * // on shutdown, before the database closes
96
+ * await auth_cleanup.stop();
97
+ * ```
98
+ *
99
+ * The loop is `start_periodic` (`periodic.ts`), which keeps the Rust spine's
100
+ * schedule (`fuz_sys::periodic`): passes never overlap, and a pass that
101
+ * overruns its interval delays the next one instead of bursting to catch up.
102
+ * It differs on shutdown. The Rust loop drops a pass in progress when its
103
+ * token is cancelled; a promise cannot be dropped, so `stop` ends the schedule
104
+ * at once and its promise resolves when the pass in progress — if there is
105
+ * one — has finished. Awaiting it before closing the database lets that pass
106
+ * end on a live pool rather than fail on a closed one.
21
107
  *
22
108
  * @module
23
109
  */
24
110
  import type { Logger } from '@fuzdev/fuz_util/log.ts';
25
111
  import type { QueryDeps } from '../db/query_deps.js';
112
+ import { type PeriodicTask } from '../periodic.js';
113
+ import type { ConnectionCloser } from '../actions/connection_closer.js';
26
114
  import type { AuditEmitter } from './audit_emitter.js';
27
- /** Dependencies for the cleanup helpers. */
115
+ /**
116
+ * How often `start_auth_cleanup` runs a pass after its startup pass
117
+ * (10 minutes), unless the consumer passes its own. Equal to the Rust spine's
118
+ * `DEFAULT_AUTH_CLEANUP_INTERVAL`.
119
+ *
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.
124
+ */
125
+ export declare const DEFAULT_AUTH_CLEANUP_INTERVAL_MS: number;
126
+ /**
127
+ * Dependencies for the cleanup helpers. `AppDeps` satisfies it structurally,
128
+ * so a server passes `backend.deps`.
129
+ */
28
130
  export interface AuthCleanupDeps extends QueryDeps {
29
131
  log: Logger;
30
132
  /**
@@ -36,14 +138,47 @@ export interface AuthCleanupDeps extends QueryDeps {
36
138
  * `create_test_audit_emitter()`.
37
139
  */
38
140
  audit: AuditEmitter;
141
+ /**
142
+ * Closes the connections of each session the sweep deletes. Pass the
143
+ * closer the app's revocation handlers hold — `AppDeps.connection_closer`,
144
+ * the `RealtimeCloser` over every transport it serves. Required: a sweep
145
+ * with nothing to close through would strand the swept sessions'
146
+ * connections. `noop_connection_closer` is for a database no other
147
+ * process holds connections on — a sweep run beside a live server from
148
+ * another process deletes the rows that server's own pass would have
149
+ * closed for.
150
+ */
151
+ connection_closer: ConnectionCloser;
39
152
  }
40
- /** Result of `run_auth_cleanup`. */
153
+ /** Counts from one `run_auth_cleanup` pass. The Rust `AuthCleanupResult` is the same shape. */
41
154
  export interface AuthCleanupResult {
42
- /** Number of expired session rows deleted. */
155
+ /** Number of expired session rows deleted — each one's connections closed. */
43
156
  expired_sessions: number;
44
157
  /** Number of expired role_grant offer rows audit-stamped. */
45
158
  expired_offers: number;
46
159
  }
160
+ /**
161
+ * Delete every expired session and close the connections each one opened,
162
+ * returning the count of sessions deleted.
163
+ *
164
+ * The delete runs in its own transaction and the closes follow its commit,
165
+ * so on a successful pass every close is for a row already gone (see the
166
+ * module doc's § Expired sessions). Each swept session is closed by its id —
167
+ * the session-token hash the transports key on — so another session of the
168
+ * same account keeps its connections. A pass that closed something logs the
169
+ * counts at `info`. No audit row is written.
170
+ *
171
+ * A closer that throws for one session does not spare the rest: every swept
172
+ * session is attempted and each failure is logged, as the post-commit queue
173
+ * does for a revocation's close.
174
+ *
175
+ * Twin of the Rust `cleanup_expired_sessions`.
176
+ *
177
+ * @returns how many expired sessions were deleted
178
+ * @mutates `auth_session` table - deletes every row past `expires_at`
179
+ * @throws Error from opening the transaction, the delete, or the commit. The first two close nothing and delete nothing. A failed commit is thrown **after** the closes: the sessions the delete returned are expired whether or not the commit landed, so their connections are closed either way, and the next pass deletes any row the commit left.
180
+ */
181
+ export declare const cleanup_expired_sessions: (deps: AuthCleanupDeps) => Promise<number>;
47
182
  /**
48
183
  * Sweep expired role_grant offers and emit one `role_grant_offer_expire` audit
49
184
  * event per newly expired row.
@@ -67,19 +202,59 @@ export interface AuthCleanupResult {
67
202
  */
68
203
  export declare const cleanup_expired_role_grant_offers: (deps: AuthCleanupDeps) => Promise<number>;
69
204
  /**
70
- * Run every auth cleanup sweep — expired sessions and expired role_grant
71
- * offers — and return the counts.
205
+ * Run every auth cleanup sweep — expired sessions, then expired role_grant
206
+ * offers — and return both counts. Twin of the Rust `run_auth_cleanup`.
72
207
  *
73
- * Consumers call this from a scheduled task (setInterval, cron, etc.)
74
- * alongside their own domain cleanup. Errors from individual sweeps are
75
- * re-thrown so the caller's scheduler can log/alert; use the per-task
76
- * helpers (`query_session_cleanup_expired`, `cleanup_expired_role_grant_offers`)
77
- * directly if you need finer error isolation.
208
+ * The session sweep (`cleanup_expired_sessions`) commits its delete and
209
+ * closes the swept sessions' connections before the offer sweep starts, so a
210
+ * failed offer sweep takes neither back; a session sweep whose commit failed
211
+ * has closed too, and the offer sweep does not run.
212
+ *
213
+ * `start_auth_cleanup` is the scheduler over this; call it directly for a
214
+ * one-off pass or from a scheduler of your own. Errors are re-thrown so the
215
+ * caller can log or alert; use the per-sweep helpers (`cleanup_expired_sessions`,
216
+ * `cleanup_expired_role_grant_offers`) for error isolation per sweep.
78
217
  *
79
218
  * @mutates `auth_session` table - deletes expired sessions
80
219
  * @mutates `role_grant_offer` rows - stamps `expire_audited_at` on expired offers
81
220
  * @mutates `audit_log` table - emits `role_grant_offer_expire` rows for expired offers
82
- * @throws Error re-thrown from any sweep that fails (no per-sweep isolation here)
221
+ * @throws Error re-thrown from the first sweep that fails (no per-sweep isolation here)
83
222
  */
84
223
  export declare const run_auth_cleanup: (deps: AuthCleanupDeps) => Promise<AuthCleanupResult>;
224
+ /** Options for `start_auth_cleanup`. */
225
+ export interface AuthCleanupScheduleOptions {
226
+ /**
227
+ * Milliseconds between the start of one pass and the start of the next.
228
+ * Default `DEFAULT_AUTH_CLEANUP_INTERVAL_MS`. Must be a positive finite
229
+ * number no greater than `PERIODIC_INTERVAL_MS_MAX`.
230
+ */
231
+ interval_ms?: number;
232
+ }
233
+ /**
234
+ * Run `run_auth_cleanup` beside the server: a pass now, then one every
235
+ * `interval_ms`, until `stop` is called.
236
+ *
237
+ * The auth surface's upkeep in one call — start it once migrations have run
238
+ * and stop it before the database closes (see the module doc's § Scheduling).
239
+ * `create_app_server`'s `auth_cleanup` option does both.
240
+ *
241
+ * Built on `start_periodic`, which fixes the loop's rules: the startup pass
242
+ * starts before this returns, passes never overlap, a slow pass delays the
243
+ * schedule rather than bursting, a failed pass — the database out of reach —
244
+ * is logged at `warn` with its error and retried on the next interval, the
245
+ * timer is unref'd, and `stop` ends the schedule at once and resolves when a
246
+ * pass in progress has finished. A pass that swept something logs its counts
247
+ * at `info`, and one that closed a swept session's connections logs that
248
+ * count too.
249
+ *
250
+ * Twin of the Rust `spawn_auth_cleanup` over `fuz_sys::periodic::spawn_periodic`.
251
+ * The schedule is the same; shutdown differs — the Rust task drops a pass in
252
+ * progress when its token is cancelled, which a promise cannot do.
253
+ *
254
+ * @param deps - what a pass needs; `backend.deps` in a server
255
+ * @param options - the interval
256
+ * @returns the schedule's `stop`
257
+ * @throws Error if `interval_ms` is not a positive finite number or exceeds `PERIODIC_INTERVAL_MS_MAX` — at the call site, before any pass starts
258
+ */
259
+ export declare const start_auth_cleanup: (deps: AuthCleanupDeps, options?: AuthCleanupScheduleOptions) => PeriodicTask;
85
260
  //# sourceMappingURL=cleanup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cleanup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,4CAA4C;AAC5C,MAAM,WAAW,eAAgB,SAAQ,SAAS;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,KAAK,EAAE,YAAY,CAAC;CACpB;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IACjC,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iCAAiC,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,CAgC7F,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,iBAAiB,CAIvF,CAAC"}
1
+ {"version":3,"file":"cleanup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4GG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD;;;;;;;;;GASG;AACH,eAAO,MAAM,gCAAgC,QAAiB,CAAC;AAE/D;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB;;;;;;;;;OASG;IACH,iBAAiB,EAAE,gBAAgB,CAAC;CACpC;AAED,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IACjC,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,wBAAwB,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,CAqCpF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iCAAiC,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,MAAM,CAgC7F,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAU,MAAM,eAAe,KAAG,OAAO,CAAC,iBAAiB,CAIvF,CAAC;AAEF,wCAAwC;AACxC,MAAM,WAAW,0BAA0B;IAC1C;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,eAAe,EACrB,UAAS,0BAA+B,KACtC,YAWD,CAAC"}
@@ -1,29 +1,189 @@
1
1
  /**
2
2
  * Periodic auth cleanup — sweeps expired sessions and role_grant offers.
3
3
  *
4
- * Single entry point for consumers scheduling auth maintenance. Internally
5
- * runs every known sweep and emits the corresponding audit events so
6
- * consumer code only manages cadence, not per-task wiring.
7
- *
8
- * The per-task primitives remain exported from their home modules
9
- * (`query_session_cleanup_expired`, `query_role_grant_offer_sweep_expired`);
10
- * `cleanup_expired_role_grant_offers` here wraps the latter with the required
11
- * `role_grant_offer_expire` audit emission and is the piece most likely to be
12
- * reused in a consumer's bespoke scheduler.
13
- *
14
- * Idempotency: the offer sweep claims each expired offer by stamping
15
- * `role_grant_offer.expire_audited_at` in the same transaction as its
16
- * `role_grant_offer_expire` audit row, so every expiry is audited exactly
17
- * once — a second run finds nothing, and concurrent runs claim disjoint rows
18
- * (row locks serialize them on the re-checked stamp). A failed audit insert
19
- * rolls the stamps back with it and the next run retries. A re-offer clears
20
- * the stamp, so the refreshed offer's own expiry is audited too.
4
+ * `run_auth_cleanup` is one pass: it deletes expired sessions and closes the
5
+ * connections they opened (`cleanup_expired_sessions`), then audits newly
6
+ * expired offers (`cleanup_expired_role_grant_offers`), returning both counts.
7
+ * `start_auth_cleanup` schedules it — the one call a server makes, and what
8
+ * `create_app_server`'s `auth_cleanup` option makes for it. The per-pass
9
+ * pieces stay exported for a consumer that wants its own cadence or error
10
+ * isolation per sweep. Twin of the Rust spine's `fuz_auth::auth_cleanup`.
11
+ *
12
+ * ## Expired sessions: the sweep closes what it sweeps
13
+ *
14
+ * A session past `expires_at` fails every validity check, but the connections
15
+ * it opened do not read it again: a WebSocket re-authorizes the actor per
16
+ * message, never the session, and an SSE stream is authorized once at open.
17
+ * While the expired row lingers, `account_session_revoke` is the per-session
18
+ * handle on those connections — it closes for the row it deletes. The sweep
19
+ * deletes that row, so it takes over the close: every session it deletes has
20
+ * its connections closed through `AuthCleanupDeps.connection_closer`, the same
21
+ * close a revocation makes (`WS_CLOSE_SESSION_REVOKED` on a WebSocket; an SSE
22
+ * stream ends). Otherwise a swept session's connections would be reachable
23
+ * only by an account-wide revocation.
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.
31
+ *
32
+ * The closes run after the delete's commit, which is the rule every
33
+ * revocation close keeps (see `actions/connection_closer.ts`). An expired
34
+ * session is refused with or without its row, so the order is not what
35
+ * protects a client rechecking on the close or a connection still opening; it
36
+ * keeps the sweep inside the one invariant admission leans on — a close that
37
+ * missed a registration belongs to a write the re-read can see — rather than
38
+ * on the re-read's clock. A commit whose outcome is unknown still closes: the
39
+ * sessions are expired either way.
40
+ *
41
+ * That is why the delete runs in its own transaction rather than as one
42
+ * pool-level statement. The ids are in hand before the commit is sent, so a
43
+ * connection lost on the delete's response rolls the delete back for the next
44
+ * pass, and one lost on the commit's still closes — no failure leaves rows
45
+ * deleted with their ids unread. A commit that fails outright has closed too;
46
+ * the rows it left are deleted by the next pass, whose closes then find
47
+ * nothing.
48
+ *
49
+ * One pass closes for every session it swept; a session with no live
50
+ * connection costs a lookup and closes nothing. The pass logs one count, not
51
+ * a line per session.
52
+ *
53
+ * No audit row is written for a swept session: expiry is not an event anyone
54
+ * performed, and the session was already refused everywhere. So the audit
55
+ * listeners never see it, and the direct close is the only one.
56
+ *
57
+ * One limit: the closes reach this process's transports, as every revocation
58
+ * close does — a second process on the same database keeps the connections it
59
+ * holds on a session this one swept.
60
+ *
61
+ * ## Offer idempotency
62
+ *
63
+ * An expired pending offer has no terminal state — expiry is computed from
64
+ * `expires_at`, never written — so the offer sweep claims rows instead: it
65
+ * stamps `role_grant_offer.expire_audited_at` in the same transaction as each
66
+ * row's `role_grant_offer_expire` audit row, so every expiry is audited
67
+ * exactly once — a second run finds nothing, and concurrent runs claim
68
+ * disjoint rows (row locks serialize them on the re-checked stamp). A failed
69
+ * audit insert rolls the stamps back with it and the next run retries. A
70
+ * re-offer clears the stamp, so the refreshed offer's own expiry is audited
71
+ * too.
72
+ *
73
+ * ## Scheduling
74
+ *
75
+ * `start_auth_cleanup` runs the pass beside the server: a pass at once, then
76
+ * one per interval (`DEFAULT_AUTH_CLEANUP_INTERVAL_MS` unless the consumer has
77
+ * a reason to differ), until its `stop` is called. A failed pass — the
78
+ * database out of reach — is logged and retried on the next interval.
79
+ *
80
+ * Start it once migrations have run (the startup pass reads the auth tables),
81
+ * with the app's bound `AuditEmitter` so the expiry audits reach its listeners
82
+ * and the closer its revocation handlers close through. `AppDeps` is all of
83
+ * that, so the standard assembly is one option:
84
+ *
85
+ * ```ts
86
+ * const server = await create_app_server({backend, auth_cleanup: true, ...});
87
+ * // on shutdown: stops the schedule, then closes the database
88
+ * await server.close();
89
+ * ```
90
+ *
91
+ * and a hand-assembled server calls it directly:
92
+ *
93
+ * ```ts
94
+ * const auth_cleanup = start_auth_cleanup(backend.deps);
95
+ * // on shutdown, before the database closes
96
+ * await auth_cleanup.stop();
97
+ * ```
98
+ *
99
+ * The loop is `start_periodic` (`periodic.ts`), which keeps the Rust spine's
100
+ * schedule (`fuz_sys::periodic`): passes never overlap, and a pass that
101
+ * overruns its interval delays the next one instead of bursting to catch up.
102
+ * It differs on shutdown. The Rust loop drops a pass in progress when its
103
+ * token is cancelled; a promise cannot be dropped, so `stop` ends the schedule
104
+ * at once and its promise resolves when the pass in progress — if there is
105
+ * one — has finished. Awaiting it before closing the database lets that pass
106
+ * end on a live pool rather than fail on a closed one.
21
107
  *
22
108
  * @module
23
109
  */
110
+ import { start_periodic } from '../periodic.js';
24
111
  import { query_session_cleanup_expired } from './session_queries.js';
25
112
  import { query_role_grant_offer_sweep_expired } from './role_grant_offer_queries.js';
26
113
  import { query_audit_log } from './audit_log_queries.js';
114
+ /**
115
+ * How often `start_auth_cleanup` runs a pass after its startup pass
116
+ * (10 minutes), unless the consumer passes its own. Equal to the Rust spine's
117
+ * `DEFAULT_AUTH_CLEANUP_INTERVAL`.
118
+ *
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.
123
+ */
124
+ export const DEFAULT_AUTH_CLEANUP_INTERVAL_MS = 10 * 60 * 1000;
125
+ /**
126
+ * Delete every expired session and close the connections each one opened,
127
+ * returning the count of sessions deleted.
128
+ *
129
+ * The delete runs in its own transaction and the closes follow its commit,
130
+ * so on a successful pass every close is for a row already gone (see the
131
+ * module doc's § Expired sessions). Each swept session is closed by its id —
132
+ * the session-token hash the transports key on — so another session of the
133
+ * same account keeps its connections. A pass that closed something logs the
134
+ * counts at `info`. No audit row is written.
135
+ *
136
+ * A closer that throws for one session does not spare the rest: every swept
137
+ * session is attempted and each failure is logged, as the post-commit queue
138
+ * does for a revocation's close.
139
+ *
140
+ * Twin of the Rust `cleanup_expired_sessions`.
141
+ *
142
+ * @returns how many expired sessions were deleted
143
+ * @mutates `auth_session` table - deletes every row past `expires_at`
144
+ * @throws Error from opening the transaction, the delete, or the commit. The first two close nothing and delete nothing. A failed commit is thrown **after** the closes: the sessions the delete returned are expired whether or not the commit landed, so their connections are closed either way, and the next pass deletes any row the commit left.
145
+ */
146
+ export const cleanup_expired_sessions = async (deps) => {
147
+ let swept = [];
148
+ let failed = false;
149
+ let failure;
150
+ try {
151
+ await deps.db.transaction(async (tx) => {
152
+ // the ids are in hand before the commit is sent: a connection lost on
153
+ // the delete's response rolls the delete back for the next pass, and
154
+ // one lost on the commit's still closes below
155
+ swept = await query_session_cleanup_expired({ db: tx });
156
+ });
157
+ }
158
+ catch (error) {
159
+ // thrown before the delete answered, `swept` is empty and nothing closes;
160
+ // thrown after, the commit failed or its answer was lost
161
+ failed = true;
162
+ failure = error;
163
+ }
164
+ // the transaction has settled. The closes are synchronous, so nothing can
165
+ // interleave between the first and the last
166
+ let closed_connections = 0;
167
+ for (const session_token_hash of swept) {
168
+ try {
169
+ closed_connections += deps.connection_closer.close_sockets_for_session(session_token_hash);
170
+ }
171
+ catch (error) {
172
+ // keep going — one transport's failure must not leave another
173
+ // expired session's connections open
174
+ deps.log.error("auth cleanup failed to close an expired session's connections:", error);
175
+ }
176
+ }
177
+ if (closed_connections > 0) {
178
+ deps.log.info('auth cleanup closed the connections of expired sessions', {
179
+ expired_sessions: swept.length,
180
+ closed_connections
181
+ });
182
+ }
183
+ if (failed)
184
+ throw failure;
185
+ return swept.length;
186
+ };
27
187
  /**
28
188
  * Sweep expired role_grant offers and emit one `role_grant_offer_expire` audit
29
189
  * event per newly expired row.
@@ -75,22 +235,58 @@ export const cleanup_expired_role_grant_offers = async (deps) => {
75
235
  return events.length;
76
236
  };
77
237
  /**
78
- * Run every auth cleanup sweep — expired sessions and expired role_grant
79
- * offers — and return the counts.
238
+ * Run every auth cleanup sweep — expired sessions, then expired role_grant
239
+ * offers — and return both counts. Twin of the Rust `run_auth_cleanup`.
240
+ *
241
+ * The session sweep (`cleanup_expired_sessions`) commits its delete and
242
+ * closes the swept sessions' connections before the offer sweep starts, so a
243
+ * failed offer sweep takes neither back; a session sweep whose commit failed
244
+ * has closed too, and the offer sweep does not run.
80
245
  *
81
- * Consumers call this from a scheduled task (setInterval, cron, etc.)
82
- * alongside their own domain cleanup. Errors from individual sweeps are
83
- * re-thrown so the caller's scheduler can log/alert; use the per-task
84
- * helpers (`query_session_cleanup_expired`, `cleanup_expired_role_grant_offers`)
85
- * directly if you need finer error isolation.
246
+ * `start_auth_cleanup` is the scheduler over this; call it directly for a
247
+ * one-off pass or from a scheduler of your own. Errors are re-thrown so the
248
+ * caller can log or alert; use the per-sweep helpers (`cleanup_expired_sessions`,
249
+ * `cleanup_expired_role_grant_offers`) for error isolation per sweep.
86
250
  *
87
251
  * @mutates `auth_session` table - deletes expired sessions
88
252
  * @mutates `role_grant_offer` rows - stamps `expire_audited_at` on expired offers
89
253
  * @mutates `audit_log` table - emits `role_grant_offer_expire` rows for expired offers
90
- * @throws Error re-thrown from any sweep that fails (no per-sweep isolation here)
254
+ * @throws Error re-thrown from the first sweep that fails (no per-sweep isolation here)
91
255
  */
92
256
  export const run_auth_cleanup = async (deps) => {
93
- const expired_sessions = await query_session_cleanup_expired(deps);
257
+ const expired_sessions = await cleanup_expired_sessions(deps);
94
258
  const expired_offers = await cleanup_expired_role_grant_offers(deps);
95
259
  return { expired_sessions, expired_offers };
96
260
  };
261
+ /**
262
+ * Run `run_auth_cleanup` beside the server: a pass now, then one every
263
+ * `interval_ms`, until `stop` is called.
264
+ *
265
+ * The auth surface's upkeep in one call — start it once migrations have run
266
+ * and stop it before the database closes (see the module doc's § Scheduling).
267
+ * `create_app_server`'s `auth_cleanup` option does both.
268
+ *
269
+ * Built on `start_periodic`, which fixes the loop's rules: the startup pass
270
+ * starts before this returns, passes never overlap, a slow pass delays the
271
+ * schedule rather than bursting, a failed pass — the database out of reach —
272
+ * is logged at `warn` with its error and retried on the next interval, the
273
+ * timer is unref'd, and `stop` ends the schedule at once and resolves when a
274
+ * pass in progress has finished. A pass that swept something logs its counts
275
+ * at `info`, and one that closed a swept session's connections logs that
276
+ * count too.
277
+ *
278
+ * Twin of the Rust `spawn_auth_cleanup` over `fuz_sys::periodic::spawn_periodic`.
279
+ * The schedule is the same; shutdown differs — the Rust task drops a pass in
280
+ * progress when its token is cancelled, which a promise cannot do.
281
+ *
282
+ * @param deps - what a pass needs; `backend.deps` in a server
283
+ * @param options - the interval
284
+ * @returns the schedule's `stop`
285
+ * @throws Error if `interval_ms` is not a positive finite number or exceeds `PERIODIC_INTERVAL_MS_MAX` — at the call site, before any pass starts
286
+ */
287
+ export const start_auth_cleanup = (deps, options = {}) => start_periodic('auth cleanup', options.interval_ms ?? DEFAULT_AUTH_CLEANUP_INTERVAL_MS, async () => {
288
+ const swept = await run_auth_cleanup(deps);
289
+ if (swept.expired_sessions > 0 || swept.expired_offers > 0) {
290
+ deps.log.info('auth cleanup swept expired rows', swept);
291
+ }
292
+ }, deps.log);
@@ -13,6 +13,7 @@ import type { Keyring } from './keyring.js';
13
13
  import type { PasswordHashDeps } from './password.js';
14
14
  import type { Db } from '../db/db.js';
15
15
  import type { AuditEmitter } from './audit_emitter.js';
16
+ import type { ConnectionCloser, RealtimeCloser } from '../actions/connection_closer.js';
16
17
  /**
17
18
  * Stateless capabilities bundle for fuz_app backends.
18
19
  *
@@ -47,6 +48,16 @@ export interface AppDeps {
47
48
  * context.
48
49
  */
49
50
  audit: AuditEmitter;
51
+ /**
52
+ * Closes live connections — WebSocket and SSE — when a credential is
53
+ * revoked. Every revocation handler queues its close on this
54
+ * (`queue_connection_close`), so it must reach every transport of the
55
+ * backend: `create_app_backend` creates it empty, and `create_app_server`
56
+ * adds each WebSocket transport it mounts and its audit stream registry. A
57
+ * transport mounted by hand is added through the `connection_closer`
58
+ * option of `register_ws_endpoint` / `create_audit_log_sse`.
59
+ */
60
+ connection_closer: RealtimeCloser;
50
61
  /**
51
62
  * Optional content-addressed byte store. Present only on backends that
52
63
  * serve binary content (facts) — minimal consumers leave it unset. The
@@ -78,4 +89,21 @@ export interface ActionFactoryDeps {
78
89
  /** Bound audit emitter for fire-and-forget audit writes. */
79
90
  audit: AuditEmitter;
80
91
  }
92
+ /**
93
+ * Capabilities for the action factories whose handlers end credentials —
94
+ * `create_account_actions`, `create_admin_actions`, and the
95
+ * `create_standard_rpc_actions` bundle over them. `ActionFactoryDeps` plus
96
+ * the closer their revocations close live connections through. `AppDeps`
97
+ * and `RouteFactoryDeps` satisfy it structurally.
98
+ */
99
+ export interface RevokingActionFactoryDeps extends ActionFactoryDeps {
100
+ /**
101
+ * Closes the live connections of a revoked credential, after the
102
+ * revocation commits. Required rather than optional: a revocation that
103
+ * closes nothing leaves an open WebSocket or SSE stream running on a dead
104
+ * credential. A backend with no live-connection surface passes
105
+ * `noop_connection_closer`.
106
+ */
107
+ connection_closer: ConnectionCloser;
108
+ }
81
109
  //# sourceMappingURL=deps.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACvB;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yBAAyB;IACzB,EAAE,EAAE,EAAE,CAAC;IACP,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,KAAK,EAAE,YAAY,CAAC;CACpB"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAExF;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACvB;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yBAAyB;IACzB,EAAE,EAAE,EAAE,CAAC;IACP,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB;;;;;;;;OAQG;IACH,iBAAiB,EAAE,cAAc,CAAC;IAClC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,KAAK,EAAE,YAAY,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,yBAA0B,SAAQ,iBAAiB;IACnE;;;;;;OAMG;IACH,iBAAiB,EAAE,gBAAgB,CAAC;CACpC"}