@rebasepro/server 0.24.0 → 0.24.1-canary.g19596ef

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 (240) hide show
  1. package/README.md +27 -2
  2. package/dist/GCSStorageController-DEga1ULx.js +402 -0
  3. package/dist/GCSStorageController-DEga1ULx.js.map +1 -0
  4. package/dist/S3StorageController-Btx-6bJ5.js +480 -0
  5. package/dist/S3StorageController-Btx-6bJ5.js.map +1 -0
  6. package/dist/api/errors.d.ts +32 -0
  7. package/dist/api/live-schema-routes.d.ts +27 -3
  8. package/dist/api/mount-path.d.ts +30 -0
  9. package/dist/api/mount.d.ts +6 -13
  10. package/dist/api/rest/api-generator.d.ts +53 -1
  11. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  12. package/dist/api/rest/field-access-query.d.ts +28 -8
  13. package/dist/api/rest/field-ops.d.ts +19 -7
  14. package/dist/api/rest/query-body.d.ts +10 -0
  15. package/dist/api/rest/write-validation.d.ts +7 -5
  16. package/dist/api/schema-editor-routes.d.ts +10 -1
  17. package/dist/api/sql-export-routes.d.ts +8 -0
  18. package/dist/api/types.d.ts +35 -0
  19. package/dist/{ast-schema-editor-CWqS_sLJ.js → ast-schema-editor-mCeSC03x.js} +3 -3
  20. package/dist/{ast-schema-editor-CWqS_sLJ.js.map → ast-schema-editor-mCeSC03x.js.map} +1 -1
  21. package/dist/auth/access.d.ts +62 -11
  22. package/dist/auth/admin-user-ops.d.ts +24 -2
  23. package/dist/auth/admin-users-route.d.ts +1 -0
  24. package/dist/auth/api-keys/api-key-middleware.d.ts +10 -3
  25. package/dist/auth/api-keys/api-key-routes.d.ts +4 -1
  26. package/dist/auth/builtin-auth-adapter.d.ts +13 -0
  27. package/dist/auth/email-change-routes.d.ts +3 -0
  28. package/dist/auth/expired-token-sweep.d.ts +5 -1
  29. package/dist/auth/interfaces.d.ts +10 -0
  30. package/dist/auth/jwks-routes.d.ts +5 -1
  31. package/dist/auth/jwt-crypto.d.ts +7 -0
  32. package/dist/auth/jwt-keys.d.ts +4 -0
  33. package/dist/auth/jwt.d.ts +282 -166
  34. package/dist/auth/magic-link-routes.d.ts +3 -0
  35. package/dist/auth/mfa-crypto.d.ts +9 -3
  36. package/dist/auth/mfa-gate.d.ts +4 -2
  37. package/dist/auth/mfa-routes.d.ts +6 -0
  38. package/dist/auth/middleware.d.ts +9 -2
  39. package/dist/auth/otp-routes.d.ts +3 -0
  40. package/dist/auth/rate-limiter.d.ts +61 -34
  41. package/dist/auth/registration-policy.d.ts +17 -0
  42. package/dist/auth/require-auth.d.ts +1 -1
  43. package/dist/auth/routes.d.ts +16 -0
  44. package/dist/auth/session-routes.d.ts +6 -0
  45. package/dist/auth/token-revocation.d.ts +6 -0
  46. package/dist/auth/user-picking.d.ts +37 -0
  47. package/dist/auth/verify-credential.d.ts +25 -4
  48. package/dist/{auth-DMLngxn_.js → auth-CSBky1mt.js} +2927 -1882
  49. package/dist/auth-CSBky1mt.js.map +1 -0
  50. package/dist/auth_adapter-CIItCTc8.js +21 -0
  51. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  52. package/dist/backend/create-backend.d.ts +281 -0
  53. package/dist/backend/default-backend.d.ts +35 -0
  54. package/dist/backend/no-collections.d.ts +19 -0
  55. package/dist/backend/options.d.ts +912 -0
  56. package/dist/backend/scope.d.ts +23 -0
  57. package/dist/backend/services.d.ts +54 -0
  58. package/dist/backend-DTAOsLQc.js.map +1 -1
  59. package/dist/backup/backup-common.d.ts +14 -3
  60. package/dist/backup/backup-routes.d.ts +23 -2
  61. package/dist/backup/backup-storage.d.ts +3 -1
  62. package/dist/backup/index.d.ts +3 -1
  63. package/dist/{backup-CN0s50D2.js → backup-DBGRUVTS.js} +178 -37
  64. package/dist/backup-DBGRUVTS.js.map +1 -0
  65. package/dist/boot/boot.d.ts +2 -2
  66. package/dist/boot/driver.d.ts +3 -0
  67. package/dist/boot/env.d.ts +9 -2
  68. package/dist/boot/http-server.d.ts +22 -0
  69. package/dist/boot/options.d.ts +16 -1
  70. package/dist/boot/provision.d.ts +4 -2
  71. package/dist/boot/resource-adapters.d.ts +11 -14
  72. package/dist/boot/runtime-cors.d.ts +17 -0
  73. package/dist/boot/sources.d.ts +9 -0
  74. package/dist/boot/static-routing.d.ts +9 -0
  75. package/dist/{contract-routes-fz8i4pxs.js → contract-routes-DiFwiLg2.js} +5 -21
  76. package/dist/contract-routes-DiFwiLg2.js.map +1 -0
  77. package/dist/cron/cron-loader.d.ts +77 -18
  78. package/dist/cron/cron-routes.d.ts +1 -1
  79. package/dist/cron/cron-scheduler.d.ts +69 -3
  80. package/dist/cron/cron-store.d.ts +33 -5
  81. package/dist/cron/define-cron.d.ts +6 -2
  82. package/dist/cron/index.d.ts +3 -3
  83. package/dist/cron-loader-NHqRfKF0.js +202 -0
  84. package/dist/cron-loader-NHqRfKF0.js.map +1 -0
  85. package/dist/{cron-routes-Bc-SB0Se.js → cron-routes-DsH8Jwn5.js} +29 -6
  86. package/dist/cron-routes-DsH8Jwn5.js.map +1 -0
  87. package/dist/{cron-scheduler-CYQgco86.js → cron-scheduler-D1DT0nw5.js} +300 -34
  88. package/dist/cron-scheduler-D1DT0nw5.js.map +1 -0
  89. package/dist/cron-store-DEpVstUU.js +577 -0
  90. package/dist/cron-store-DEpVstUU.js.map +1 -0
  91. package/dist/{ddl-bootstrap-BaqMSa4Y.js → ddl-bootstrap-BNqi2NZe.js} +2 -2
  92. package/dist/{ddl-bootstrap-BaqMSa4Y.js.map → ddl-bootstrap-BNqi2NZe.js.map} +1 -1
  93. package/dist/email/smtp-email-service.d.ts +23 -0
  94. package/dist/email/types.d.ts +18 -0
  95. package/dist/env.d.ts +0 -37
  96. package/dist/{errors-D6_y86c5.js → errors-Cqp86KnA.js} +166 -7
  97. package/dist/errors-Cqp86KnA.js.map +1 -0
  98. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  99. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  100. package/dist/{function-loader-D7o5Epjj.js → function-loader-DbCSoNJj.js} +69 -39
  101. package/dist/function-loader-DbCSoNJj.js.map +1 -0
  102. package/dist/{function-routes-CaNG4waN.js → function-routes-BSTClQNa.js} +6 -5
  103. package/dist/function-routes-BSTClQNa.js.map +1 -0
  104. package/dist/functions/context.d.ts +9 -0
  105. package/dist/functions/define-function.d.ts +10 -6
  106. package/dist/functions/function-loader.d.ts +75 -18
  107. package/dist/functions/function-routes.d.ts +2 -1
  108. package/dist/functions/index.d.ts +5 -4
  109. package/dist/functions/index.js +59 -15
  110. package/dist/functions/index.js.map +1 -1
  111. package/dist/functions/internal.d.ts +2 -2
  112. package/dist/functions/proxy.d.ts +6 -2
  113. package/dist/functions/request-timeout.d.ts +7 -5
  114. package/dist/history/history-routes.d.ts +9 -0
  115. package/dist/history/retention-sweep.d.ts +81 -0
  116. package/dist/{history-recorder-Nr8zLvoU.js → history-recorder-RZ1Ow4YY.js} +4 -4
  117. package/dist/{history-recorder-Nr8zLvoU.js.map → history-recorder-RZ1Ow4YY.js.map} +1 -1
  118. package/dist/{history-store-rcAm_xFR.js → history-store-DPofGVDp.js} +2 -2
  119. package/dist/{history-store-rcAm_xFR.js.map → history-store-DPofGVDp.js.map} +1 -1
  120. package/dist/index.d.ts +24 -11
  121. package/dist/index.es.js +24758 -20153
  122. package/dist/index.es.js.map +1 -1
  123. package/dist/init/docs.d.ts +35 -10
  124. package/dist/init/process-safety.d.ts +7 -2
  125. package/dist/init/shutdown.d.ts +18 -3
  126. package/dist/init.d.ts +49 -865
  127. package/dist/internal-tables-L2t39RdT.js +136 -0
  128. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  129. package/dist/jobs/job-queue.d.ts +8 -0
  130. package/dist/jobs/job-store.d.ts +24 -0
  131. package/dist/jobs/types.d.ts +25 -4
  132. package/dist/{jobs-DqYNfquG.js → jobs-8G-_agld.js} +235 -25
  133. package/dist/jobs-8G-_agld.js.map +1 -0
  134. package/dist/{jwt-R6bSPMjk.js → jwt-CBaFc34Y.js} +530 -455
  135. package/dist/jwt-CBaFc34Y.js.map +1 -0
  136. package/dist/{keys-GAVZqbqx.js → keys-COzQ2aE8.js} +2 -2
  137. package/dist/{keys-GAVZqbqx.js.map → keys-COzQ2aE8.js.map} +1 -1
  138. package/dist/load-failure-L5hvfDoG.js +80 -0
  139. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  140. package/dist/{logger-D-S-hO5e.js → logger-Bt32sHFw.js} +17 -4
  141. package/dist/logger-Bt32sHFw.js.map +1 -0
  142. package/dist/{logs-routes-DAdv37GI.js → logs-routes-DQJhrf-b.js} +3 -3
  143. package/dist/{logs-routes-DAdv37GI.js.map → logs-routes-DQJhrf-b.js.map} +1 -1
  144. package/dist/mcp/mcp-routes.d.ts +6 -0
  145. package/dist/mcp/mcp-tools.d.ts +8 -0
  146. package/dist/mcp/oauth-routes.d.ts +7 -0
  147. package/dist/metrics/index.d.ts +18 -0
  148. package/dist/metrics/pool-wait.d.ts +50 -0
  149. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  150. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  151. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  152. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  153. package/dist/{openapi-generator-DAq_XVDu.js → openapi-generator-CeGW2dLC.js} +366 -82
  154. package/dist/openapi-generator-CeGW2dLC.js.map +1 -0
  155. package/dist/policy-PdzX-Opd.js +121 -0
  156. package/dist/policy-PdzX-Opd.js.map +1 -0
  157. package/dist/project_manifest-R6y_KHms.js +59 -0
  158. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  159. package/dist/{proxy-qRlqeUmO.js → proxy-BEKbsil6.js} +5 -4
  160. package/dist/proxy-BEKbsil6.js.map +1 -0
  161. package/dist/{query-parser-BgiKJKvc.js → query-parser-ECxP0nDw.js} +511 -16
  162. package/dist/query-parser-ECxP0nDw.js.map +1 -0
  163. package/dist/{request-timeout-DgH7j8qO.js → request-timeout-BiRelY-U.js} +17 -10
  164. package/dist/request-timeout-BiRelY-U.js.map +1 -0
  165. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  166. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  167. package/dist/schema-edit/apply-schema-change.d.ts +10 -0
  168. package/dist/schema-edit/local-source.d.ts +29 -0
  169. package/dist/schema-edit/remote-source.d.ts +13 -3
  170. package/dist/schema-edit/source-editor.d.ts +28 -0
  171. package/dist/{schema-editor-routes-oIyuWl3L.js → schema-editor-routes-DRXBAbZy.js} +55 -11
  172. package/dist/schema-editor-routes-DRXBAbZy.js.map +1 -0
  173. package/dist/services/outbound-post.d.ts +50 -0
  174. package/dist/services/outbound-url-guard.d.ts +34 -5
  175. package/dist/services/routed-realtime-service.d.ts +5 -0
  176. package/dist/services/webhook-service.d.ts +66 -14
  177. package/dist/singleton.d.ts +9 -5
  178. package/dist/soft-delete-params-CBryZXMq.js +668 -0
  179. package/dist/soft-delete-params-CBryZXMq.js.map +1 -0
  180. package/dist/sql-export-routes-D8ZaRvoR.js +240 -0
  181. package/dist/sql-export-routes-D8ZaRvoR.js.map +1 -0
  182. package/dist/{src-CatHFUym.js → src-D5Y3N-S4.js} +2 -1
  183. package/dist/{src-CatHFUym.js.map → src-D5Y3N-S4.js.map} +1 -1
  184. package/dist/storage/GCSStorageController.d.ts +46 -2
  185. package/dist/storage/LocalStorageController.d.ts +67 -2
  186. package/dist/storage/S3StorageController.d.ts +37 -3
  187. package/dist/storage/image-transform.d.ts +15 -0
  188. package/dist/storage/maintenance-routes.d.ts +23 -0
  189. package/dist/storage/maintenance.d.ts +126 -0
  190. package/dist/storage/rendition-cache.d.ts +9 -11
  191. package/dist/storage/request-principals.d.ts +30 -0
  192. package/dist/storage/routes.d.ts +20 -0
  193. package/dist/storage/source-variables.d.ts +10 -0
  194. package/dist/storage/streams.d.ts +42 -0
  195. package/dist/storage/tus-handler.d.ts +46 -2
  196. package/dist/storage/types.d.ts +82 -7
  197. package/dist/storage/upload-validation.d.ts +13 -0
  198. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  199. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  200. package/dist/strings.es-BeZWAodn.js +256 -0
  201. package/dist/strings.es-BeZWAodn.js.map +1 -0
  202. package/dist/upload-validation-CVobI5xO.js +172 -0
  203. package/dist/upload-validation-CVobI5xO.js.map +1 -0
  204. package/dist/utils/byte-size.d.ts +10 -0
  205. package/dist/utils/compression.d.ts +6 -0
  206. package/dist/utils/load-failure.d.ts +14 -0
  207. package/dist/utils/logger.d.ts +9 -0
  208. package/package.json +5 -5
  209. package/dist/GCSStorageController-BSiP1c-f.js +0 -256
  210. package/dist/GCSStorageController-BSiP1c-f.js.map +0 -1
  211. package/dist/S3StorageController-CAwFRgjV.js +0 -276
  212. package/dist/S3StorageController-CAwFRgjV.js.map +0 -1
  213. package/dist/auth-DMLngxn_.js.map +0 -1
  214. package/dist/backup-CN0s50D2.js.map +0 -1
  215. package/dist/contract-routes-fz8i4pxs.js.map +0 -1
  216. package/dist/cron-loader-CwaANlOG.js +0 -118
  217. package/dist/cron-loader-CwaANlOG.js.map +0 -1
  218. package/dist/cron-routes-Bc-SB0Se.js.map +0 -1
  219. package/dist/cron-scheduler-CYQgco86.js.map +0 -1
  220. package/dist/cron-store-D2Q9-Aco.js +0 -300
  221. package/dist/cron-store-D2Q9-Aco.js.map +0 -1
  222. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  223. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  224. package/dist/errors-D6_y86c5.js.map +0 -1
  225. package/dist/function-loader-D7o5Epjj.js.map +0 -1
  226. package/dist/function-routes-CaNG4waN.js.map +0 -1
  227. package/dist/jobs-DqYNfquG.js.map +0 -1
  228. package/dist/jwt-R6bSPMjk.js.map +0 -1
  229. package/dist/logger-D-S-hO5e.js.map +0 -1
  230. package/dist/openapi-generator-DAq_XVDu.js.map +0 -1
  231. package/dist/proxy-qRlqeUmO.js.map +0 -1
  232. package/dist/query-parser-BgiKJKvc.js.map +0 -1
  233. package/dist/request-timeout-DgH7j8qO.js.map +0 -1
  234. package/dist/schema-editor-routes-oIyuWl3L.js.map +0 -1
  235. package/dist/soft-delete-params-BWPilMPF.js +0 -59
  236. package/dist/soft-delete-params-BWPilMPF.js.map +0 -1
  237. package/dist/src-I3aG1PcY.js +0 -6870
  238. package/dist/src-I3aG1PcY.js.map +0 -1
  239. package/dist/types-BfKcm9do.js +0 -39
  240. package/dist/types-BfKcm9do.js.map +0 -1
@@ -1,3 +1,5 @@
1
+ import type { Context } from "hono";
2
+ import type { HonoEnv } from "../api/types.js";
1
3
  import { type JwtSigningKeyConfig, type PublicJwk } from "./jwt-keys.js";
2
4
  export interface JwtConfig {
3
5
  secret: string;
@@ -115,59 +117,276 @@ export interface AccessTokenPayload {
115
117
  */
116
118
  export declare function customClaimsOf(decoded: Record<string, unknown>): Record<string, unknown> | undefined;
117
119
  /**
118
- * Configure JWT settings - call this during initialization.
119
- * Validates the secret strength to prevent deployment with default/weak secrets.
120
+ * How long a refresh token survives inactivity when no lifetime is configured.
121
+ * Sliding: every rotation re-ups it, so an active user stays signed in.
120
122
  */
121
- export declare function configureJwt(config: JwtConfig): void;
123
+ export declare const DEFAULT_REFRESH_EXPIRES_IN = "30d";
122
124
  /**
123
- * The public keys, in JWKS form, for `/.well-known/jwks.json`.
125
+ * One backend's tokens: what it signs with, what it verifies against, and how
126
+ * long what it mints lives.
124
127
  *
125
- * An empty `keys` array on a backend with no asymmetric keys configured is the
126
- * correct answer rather than a 404: it says "this issuer publishes none",
127
- * which a verifier can act on, where a 404 is indistinguishable from a
128
- * misconfigured URL.
129
- */
130
- export declare function getJwks(): {
131
- keys: PublicJwk[];
132
- };
133
- /**
134
- * The secret `configureJwt` was given, or undefined before it is called. For
135
- * `mfa-crypto.ts`, which falls back to it when no MFA key is set — the secret
136
- * passed in code, not only the one in `JWT_SECRET`.
128
+ * Every backend built by `createRebaseBackend` holds its own
129
+ * (`backend.services.jwt`), so two backends in one process sign and verify
130
+ * with their own secrets and keys and a token one minted means nothing to the
131
+ * other. {@link createJwtService} builds one by hand.
132
+ *
133
+ * The module-level functions of the same names — `generateAccessToken`,
134
+ * `verifyAccessToken`, … — answer for the process default instead: what
135
+ * {@link configureJwt} set, or the backend `initializeRebaseBackend` installed.
137
136
  */
138
- export declare function configuredJwtSecret(): string | undefined;
139
- /** Is this backend signing access tokens asymmetrically? */
140
- export declare function hasAsymmetricSigningKey(): boolean;
137
+ export interface JwtService {
138
+ /** The access-token lifetime, in the grammar {@link parseDurationMs} reads. */
139
+ readonly accessExpiresIn: string;
140
+ /** The refresh-token lifetime, in the grammar {@link parseDurationMs} reads. */
141
+ readonly refreshExpiresIn: string;
142
+ /**
143
+ * Has this service been given a JWT secret?
144
+ *
145
+ * False on every backend that authenticates through an adapter — Firebase,
146
+ * Clerk, anything with its own tokens — because nothing gives it one there.
147
+ * Signing paths still throw when it is false, since asking for a token from
148
+ * a server that cannot mint one is a mistake worth hearing about. Paths
149
+ * whose contract is *"tolerate the absence of auth"* must ask first:
150
+ * `optionalAuth` crashing a request because this backend does not do JWT is
151
+ * a 500 on a route that had already decided anonymous was acceptable.
152
+ */
153
+ isConfigured(): boolean;
154
+ /** Is this service signing access tokens asymmetrically? */
155
+ hasAsymmetricSigningKey(): boolean;
156
+ /**
157
+ * The public keys, in JWKS form, for `/.well-known/jwks.json`.
158
+ *
159
+ * An empty `keys` array on a backend with no asymmetric keys configured is
160
+ * the correct answer rather than a 404: it says "this issuer publishes
161
+ * none", which a verifier can act on, where a 404 is indistinguishable from
162
+ * a misconfigured URL.
163
+ */
164
+ getJwks(): {
165
+ keys: PublicJwk[];
166
+ };
167
+ /**
168
+ * The symmetric secret this service signs purpose-scoped tokens with, or
169
+ * undefined when it has none — for deriving a key of the backend's own.
170
+ * MFA's encryption falls back to it when no `MFA_ENCRYPTION_KEY` is set.
171
+ * A method rather than a field, so serialising the service never prints it.
172
+ */
173
+ hmacSecret(): string | undefined;
174
+ /**
175
+ * Generate an access token (short-lived, 1 hour by default).
176
+ *
177
+ * @param isAnonymous Whether this session is a guest — anonymous sign-in
178
+ * rather than an account. Written into the token so the RLS identity and
179
+ * the WebSocket path can tell the two apart without a database lookup.
180
+ * @param sessionId The sign-in this token belongs to — see
181
+ * {@link AccessTokenPayload.sid}. Every token the auth routes mint
182
+ * carries one.
183
+ */
184
+ generateAccessToken(uid: string, roles: string[], aal?: "aal1" | "aal2", customClaims?: Record<string, unknown>, isAnonymous?: boolean, sessionId?: string): Promise<string>;
185
+ /**
186
+ * Verify and decode an access token.
187
+ *
188
+ * Every token this service issues is signed with the same secret, so what a
189
+ * token *is* comes from its claims, not from its signature. A download
190
+ * token ({@link JwtService.generateDownloadToken}) is therefore a
191
+ * validly-signed string that must never authenticate anybody: it is scoped
192
+ * to one file path and handed out in URLs, which is a far weaker thing to
193
+ * hold than a session.
194
+ *
195
+ * Today it is rejected below for want of an id — but only by luck, since
196
+ * nothing stops a future download token from carrying one. So the purpose
197
+ * is checked explicitly: a token minted for reading a file is not a token
198
+ * for being a user.
199
+ */
200
+ verifyAccessToken(token: string): Promise<AccessTokenPayload | null>;
201
+ /** The access-token lifetime in milliseconds. */
202
+ getAccessTokenExpiryMs(): number;
203
+ /** When an access token minted now expires, in milliseconds since the epoch. */
204
+ getAccessTokenExpiry(): number;
205
+ /**
206
+ * How long a refresh token is valid for, in milliseconds.
207
+ *
208
+ * Every rotation issues a token with a fresh TTL, so this is a sliding
209
+ * window: a user who visits at all keeps their session indefinitely, and
210
+ * one who disappears loses it this long after their last visit. That is
211
+ * what both Firebase and Supabase do by default, and it is the behaviour
212
+ * people mean when they say they expect to still be signed in.
213
+ */
214
+ getRefreshTokenTtlMs(): number;
215
+ /** When a refresh token minted now expires. */
216
+ getRefreshTokenExpiry(): Date;
217
+ /**
218
+ * Mint the short-lived credential handed back with an `MFA_REQUIRED`
219
+ * response.
220
+ *
221
+ * Short-lived on purpose: it is the window in which a caller who has
222
+ * proven the first factor may present the second, not a session to be
223
+ * carried around. Five minutes matches the challenge TTL.
224
+ */
225
+ generateMfaPendingToken(uid: string, expiresInSeconds?: number): Promise<string>;
226
+ /**
227
+ * Verify a pre-auth token and return the user it was minted for.
228
+ *
229
+ * Returns `null` for anything else — including a perfectly valid *access*
230
+ * token, which must not be interchangeable with this one in either
231
+ * direction.
232
+ *
233
+ * `iat` comes back with the uid because this token is a first factor that
234
+ * was accepted at that instant, and a sign-out everywhere after it has to
235
+ * void it the way it voids an access token — see `isAccessTokenRevoked`.
236
+ */
237
+ verifyMfaPendingToken(token: string): Promise<{
238
+ uid: string;
239
+ iat?: number;
240
+ } | null>;
241
+ /**
242
+ * Generate a short-lived download token scoped to a specific file path or
243
+ * prefix *within one storage source*.
244
+ *
245
+ * Both halves of that scope are load-bearing. A key is only unique inside
246
+ * its own bucket, and a project with more than one source routinely holds
247
+ * the same key in several of them — `avatars/u1.png` in `(default)` and in
248
+ * `media` are different objects, quite possibly with different owners. A
249
+ * token that names only the path is therefore a grant on every source at
250
+ * once: authorize a read on the source whose `storageAuthorize` hook says
251
+ * yes, then spend the token against `?storageId=` pointing somewhere else.
252
+ *
253
+ * `storageId` is optional here only because omitting it *is* the default
254
+ * source, which is what the overwhelming majority of deployments have. A
255
+ * mint site that forgets to pass a named source produces a default-scoped
256
+ * token, which fails closed at `/file/*` rather than over-granting.
257
+ *
258
+ * @param principal The user minting it, so the reads it buys are charged
259
+ * to them.
260
+ */
261
+ generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null, principal?: string | null): Promise<string>;
262
+ /**
263
+ * Verify and decode a download token.
264
+ *
265
+ * A token minted before `storageId` existed carries no such claim. It is
266
+ * read as a grant on the **default source** rather than on all of them:
267
+ * that is the fail-closed reading, and it is what such a token almost
268
+ * always was, since a named source has to be asked for explicitly. The
269
+ * cost is bounded by the five-minute TTL — for at most that long after a
270
+ * deploy, an in-flight token for a *named* source is refused and the
271
+ * client re-fetches `/metadata`.
272
+ */
273
+ verifyDownloadToken(token: string): Promise<DownloadTokenPayload | null>;
274
+ /**
275
+ * Sign a short-lived token that carries state across a redirect.
276
+ *
277
+ * The generalisation of the MFA-pending token: a signed blob the server
278
+ * hands out and expects back, where the point is that the holder cannot
279
+ * edit it. The OAuth authorize flow needs one to carry the authorization
280
+ * request across the sign-in hop — without it, the endpoint the consent
281
+ * page posts to would mint a code for whatever `client_id` and
282
+ * `redirect_uri` its caller typed, skipping every check the authorize
283
+ * endpoint performs.
284
+ *
285
+ * The `purpose` is not decoration. {@link JwtService.verifyAccessToken}
286
+ * refuses any token that has one, so nothing minted here can be replayed
287
+ * as a session, and {@link JwtService.verifyPurposeToken} refuses a token
288
+ * whose purpose is not the one the caller named — so a token minted for
289
+ * one hop cannot be spent on another.
290
+ */
291
+ signPurposeToken(purpose: string, claims: Record<string, unknown>, expiresInSeconds: number): Promise<string>;
292
+ /** Verify a token minted by {@link JwtService.signPurposeToken}, for one named purpose. */
293
+ verifyPurposeToken<T>(purpose: string, token: string): Promise<T | null>;
294
+ /**
295
+ * Mint an access token for the MCP endpoint.
296
+ *
297
+ * Deliberately **not** an ordinary session token, and the `purpose` claim
298
+ * is what enforces that: {@link JwtService.verifyAccessToken} refuses any
299
+ * token carrying one, so a credential issued to a third-party MCP client —
300
+ * Claude, an IDE, anything that completed the OAuth flow — cannot be
301
+ * replayed against `/api/data`, `/api/admin` or the websocket. It buys
302
+ * exactly the one resource it names.
303
+ *
304
+ * That asymmetry matters more than it first looks. These tokens are handed
305
+ * to software the user authorized but the operator never vetted, they
306
+ * live in that software's storage, and they are the only credential in
307
+ * this system minted for a third party. Without the purpose separation,
308
+ * "connect Claude to your data" would silently mean "give Claude a full
309
+ * session".
310
+ *
311
+ * The audience is the canonical resource URI, and
312
+ * {@link JwtService.verifyMcpAccessToken} requires the caller to say which
313
+ * resource it is checking for. A token minted for another Rebase project —
314
+ * or another path on this one — fails there.
315
+ *
316
+ * HS256 with the shared secret, like the download token: the authorization
317
+ * server and the resource server are the same process, so nothing outside
318
+ * it ever needs to verify one, and an asymmetric key here would be a
319
+ * second key surface bought for nobody.
320
+ */
321
+ generateMcpAccessToken(payload: Omit<McpAccessTokenPayload, "purpose">, expiresInSeconds: number): Promise<string>;
322
+ /**
323
+ * Verify an MCP access token **for a specific resource**.
324
+ *
325
+ * `expectedAudience` has no default. The MCP specification requires a
326
+ * resource server to validate that a token was issued for it
327
+ * specifically, and a default would be the thing that quietly turns that
328
+ * check off — the caller has to say what it is, every time, so a new call
329
+ * site cannot forget by omission.
330
+ */
331
+ verifyMcpAccessToken(token: string, expectedAudience: string): Promise<McpAccessTokenPayload | null>;
332
+ }
141
333
  /**
142
- * Has this server been given a JWT secret?
334
+ * Build a {@link JwtService} from a configuration, refusing one it would sign
335
+ * badly with: a short or well-known secret, an `activeKid` no key carries, a
336
+ * lifetime that is not one.
143
337
  *
144
- * False on every backend that authenticates through an adapter — Firebase,
145
- * Clerk, anything with its own tokens — because nothing calls
146
- * {@link configureJwt} there. Signing paths still throw when it is false, since
147
- * asking for a token from a server that cannot mint one is a mistake worth
148
- * hearing about. Paths whose contract is *"tolerate the absence of auth"* must
149
- * ask first: `optionalAuth` crashing a request because this backend does not do
150
- * JWT is a 500 on a route that had already decided anonymous was acceptable.
338
+ * Unset lifetimes take the defaults — an hour for access tokens,
339
+ * {@link DEFAULT_REFRESH_EXPIRES_IN} for refresh tokens.
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * const jwt = createJwtService({ secret: process.env.JWT_SECRET! });
344
+ * const token = await jwt.generateAccessToken("u1", ["editor"]);
345
+ * const payload = await jwt.verifyAccessToken(token); // { uid: "u1", roles: ["editor"], … }
346
+ * ```
151
347
  */
152
- export declare function isJwtConfigured(): boolean;
348
+ export declare function createJwtService(config: JwtConfig): JwtService;
153
349
  /**
154
- * Generate an access token (short-lived, 1 hour by default)
350
+ * Configure the process default's JWT settings — what `verifyAccessToken`,
351
+ * `generateAccessToken` and the other module-level functions sign and verify
352
+ * with.
353
+ *
354
+ * Validates the secret strength to prevent deployment with default/weak
355
+ * secrets. A lifetime left unset keeps the one already configured; a
356
+ * configuration that fails validation leaves the previous one standing rather
357
+ * than half-applying a new one.
155
358
  */
156
- export declare function generateAccessToken(uid: string, roles: string[], aal?: "aal1" | "aal2", customClaims?: Record<string, unknown>,
359
+ export declare function configureJwt(config: JwtConfig): void;
157
360
  /**
158
- * Whether this session is a guest — anonymous sign-in rather than an
159
- * account. Written into the token so the RLS identity and the WebSocket
160
- * path can tell the two apart without a database lookup.
361
+ * @internal The process default itself, to install another or to restore it.
362
+ * `setDefaultBackend` and tests use these; an application calls
363
+ * {@link configureJwt} or `setDefaultBackend`.
161
364
  */
162
- isAnonymous?: boolean,
365
+ export declare function defaultJwtService(): JwtService;
366
+ /** @internal See {@link defaultJwtService}. */
367
+ export declare function setDefaultJwtService(service: JwtService): void;
163
368
  /**
164
- * The sign-in this token belongs to — see {@link AccessTokenPayload.sid}.
165
- * Every token the auth routes mint carries one.
369
+ * The JWT service serving this request: the backend's whose route group is
370
+ * answering it, or the process default for a route mounted outside any
371
+ * backend.
166
372
  */
167
- sessionId?: string): Promise<string>;
373
+ export declare function jwtOf(c: Context<HonoEnv>): JwtService;
374
+ /** See {@link JwtService.getJwks}. Answers for the process default. */
375
+ export declare function getJwks(): {
376
+ keys: PublicJwk[];
377
+ };
168
378
  /**
169
- * Get the expiration time of an access token in milliseconds from now
379
+ * The secret {@link configureJwt} was given, or undefined before it is called.
380
+ * Answers for the process default — see {@link JwtService.hmacSecret}.
170
381
  */
382
+ export declare function configuredJwtSecret(): string | undefined;
383
+ /** See {@link JwtService.hasAsymmetricSigningKey}. Answers for the process default. */
384
+ export declare function hasAsymmetricSigningKey(): boolean;
385
+ /** See {@link JwtService.isConfigured}. Answers for the process default. */
386
+ export declare function isJwtConfigured(): boolean;
387
+ /** See {@link JwtService.generateAccessToken}. Answers for the process default. */
388
+ export declare function generateAccessToken(uid: string, roles: string[], aal?: "aal1" | "aal2", customClaims?: Record<string, unknown>, isAnonymous?: boolean, sessionId?: string): Promise<string>;
389
+ /** See {@link JwtService.getAccessTokenExpiryMs}. Answers for the process default. */
171
390
  export declare function getAccessTokenExpiryMs(): number;
172
391
  /**
173
392
  * A lifetime setting in milliseconds, or null when it is not one.
@@ -181,28 +400,13 @@ export declare function getAccessTokenExpiryMs(): number;
181
400
  * (ms reads "3600" as 3.6 seconds) and a lifetime of zero or less.
182
401
  */
183
402
  export declare function parseDurationMs(value: string): number | null;
184
- /**
185
- * Get the expiration timestamp for an access token
186
- */
403
+ /** See {@link JwtService.getAccessTokenExpiry}. Answers for the process default. */
187
404
  export declare function getAccessTokenExpiry(): number;
188
- /**
189
- * Verify and decode an access token.
190
- *
191
- * Every token this server issues is signed with the same secret, so what a
192
- * token *is* comes from its claims, not from its signature. A download token
193
- * ({@link generateDownloadToken}) is therefore a validly-signed string that
194
- * must never authenticate anybody: it is scoped to one file path and handed out
195
- * in URLs, which is a far weaker thing to hold than a session.
196
- *
197
- * Today it is rejected below for want of an id — but only by luck, since
198
- * nothing stops a future download token from carrying one. So the purpose is
199
- * checked explicitly: a token minted for reading a file is not a token for
200
- * being a user.
201
- */
405
+ /** See {@link JwtService.verifyAccessToken}. Answers for the process default. */
202
406
  export declare function verifyAccessToken(token: string): Promise<AccessTokenPayload | null>;
203
407
  /**
204
- * Generate a random refresh token. Long-lived: 400 days by default, sliding —
205
- * see {@link getRefreshTokenTtlMs}.
408
+ * Generate a random refresh token. Long-lived: 30 days by default, sliding —
409
+ * see {@link JwtService.getRefreshTokenTtlMs}.
206
410
  */
207
411
  export declare function generateRefreshToken(): string;
208
412
  /**
@@ -216,49 +420,24 @@ export declare function hashRefreshToken(token: string): Promise<string>;
216
420
  * what is actually enforced, which is worse than knowing the ceiling.
217
421
  */
218
422
  export declare const MAX_COOKIE_AGE_MS: number;
219
- /**
220
- * How long a refresh token is valid for, in milliseconds.
221
- *
222
- * Every rotation issues a token with a fresh TTL, so this is a sliding window:
223
- * a user who visits at all keeps their session indefinitely, and one who
224
- * disappears loses it this long after their last visit. That is what both
225
- * Firebase and Supabase do by default, and it is the behaviour people mean
226
- * when they say they expect to still be signed in.
227
- */
423
+ /** See {@link JwtService.getRefreshTokenTtlMs}. Answers for the process default. */
228
424
  export declare function getRefreshTokenTtlMs(): number;
229
- /**
230
- * Calculate refresh token expiration date
231
- */
425
+ /** See {@link JwtService.getRefreshTokenExpiry}. Answers for the process default. */
232
426
  export declare function getRefreshTokenExpiry(): Date;
233
427
  /**
234
428
  * The `purpose` claim carried by a credential that stands between "first factor
235
429
  * accepted" and "session issued".
236
430
  *
237
431
  * A pre-auth token is NOT a session and must never be usable as one:
238
- * {@link verifyAccessToken} refuses any token carrying a `purpose`, so this
239
- * value cannot authenticate a request no matter which route it is presented to.
240
- * The only thing that reads it is the MFA challenge pair, which exchanges it —
241
- * plus a second factor — for a real session.
432
+ * {@link JwtService.verifyAccessToken} refuses any token carrying a `purpose`,
433
+ * so this value cannot authenticate a request no matter which route it is
434
+ * presented to. The only thing that reads it is the MFA challenge pair, which
435
+ * exchanges it — plus a second factor — for a real session.
242
436
  */
243
437
  export declare const MFA_PENDING_PURPOSE = "mfa-pending";
244
- /**
245
- * Mint the short-lived credential handed back with an `MFA_REQUIRED` response.
246
- *
247
- * Short-lived on purpose: it is the window in which a caller who has proven the
248
- * first factor may present the second, not a session to be carried around. Five
249
- * minutes matches the challenge TTL.
250
- */
438
+ /** See {@link JwtService.generateMfaPendingToken}. Answers for the process default. */
251
439
  export declare function generateMfaPendingToken(uid: string, expiresInSeconds?: number): Promise<string>;
252
- /**
253
- * Verify a pre-auth token and return the user it was minted for.
254
- *
255
- * Returns `null` for anything else — including a perfectly valid *access*
256
- * token, which must not be interchangeable with this one in either direction.
257
- *
258
- * `iat` comes back with the uid because this token is a first factor that was
259
- * accepted at that instant, and a sign-out everywhere after it has to void it
260
- * the way it voids an access token — see `isAccessTokenRevoked`.
261
- */
440
+ /** See {@link JwtService.verifyMfaPendingToken}. Answers for the process default. */
262
441
  export declare function verifyMfaPendingToken(token: string): Promise<{
263
442
  uid: string;
264
443
  iat?: number;
@@ -269,8 +448,8 @@ export interface DownloadTokenPayload {
269
448
  /**
270
449
  * The storage source the grant is good for, canonicalized by
271
450
  * {@link canonicalStorageId}. Always present on a decoded payload; see
272
- * {@link verifyDownloadToken} for how tokens minted before this claim
273
- * existed are read.
451
+ * {@link JwtService.verifyDownloadToken} for how tokens minted before this
452
+ * claim existed are read.
274
453
  */
275
454
  storageId: string;
276
455
  /**
@@ -281,54 +460,15 @@ export interface DownloadTokenPayload {
281
460
  */
282
461
  rl?: string;
283
462
  }
284
- /**
285
- * Generate a short-lived download token scoped to a specific file path or prefix
286
- * *within one storage source*.
287
- *
288
- * Both halves of that scope are load-bearing. A key is only unique inside its
289
- * own bucket, and a project with more than one source routinely holds the same
290
- * key in several of them — `avatars/u1.png` in `(default)` and in `media` are
291
- * different objects, quite possibly with different owners. A token that names
292
- * only the path is therefore a grant on every source at once: authorize a read
293
- * on the source whose `storageAuthorize` hook says yes, then spend the token
294
- * against `?storageId=` pointing somewhere else.
295
- *
296
- * `storageId` is optional here only because omitting it *is* the default
297
- * source, which is what the overwhelming majority of deployments have. A mint
298
- * site that forgets to pass a named source produces a default-scoped token,
299
- * which fails closed at `/file/*` rather than over-granting.
300
- */
463
+ /** See {@link JwtService.generateDownloadToken}. Answers for the process default. */
301
464
  export declare function generateDownloadToken(path: string, expiresInSeconds?: number, storageId?: string | null,
302
465
  /** The user minting it, so the reads it buys are charged to them. */
303
466
  principal?: string | null): Promise<string>;
304
- /**
305
- * Verify and decode a download token.
306
- *
307
- * A token minted before `storageId` existed carries no such claim. It is read
308
- * as a grant on the **default source** rather than on all of them: that is the
309
- * fail-closed reading, and it is what such a token almost always was, since a
310
- * named source has to be asked for explicitly. The cost is bounded by the
311
- * five-minute TTL — for at most that long after a deploy, an in-flight token
312
- * for a *named* source is refused and the client re-fetches `/metadata`.
313
- */
467
+ /** See {@link JwtService.verifyDownloadToken}. Answers for the process default. */
314
468
  export declare function verifyDownloadToken(token: string): Promise<DownloadTokenPayload | null>;
315
- /**
316
- * Sign a short-lived token that carries state across a redirect.
317
- *
318
- * The generalisation of the MFA-pending token: a signed blob the server hands
319
- * out and expects back, where the point is that the holder cannot edit it. The
320
- * OAuth authorize flow needs one to carry the authorization request across the
321
- * sign-in hop — without it, the endpoint the consent page posts to would mint a
322
- * code for whatever `client_id` and `redirect_uri` its caller typed, skipping
323
- * every check the authorize endpoint performs.
324
- *
325
- * The `purpose` is not decoration. {@link verifyAccessToken} refuses any token
326
- * that has one, so nothing minted here can be replayed as a session, and
327
- * {@link verifyPurposeToken} refuses a token whose purpose is not the one the
328
- * caller named — so a token minted for one hop cannot be spent on another.
329
- */
469
+ /** See {@link JwtService.signPurposeToken}. Answers for the process default. */
330
470
  export declare function signPurposeToken(purpose: string, claims: Record<string, unknown>, expiresInSeconds: number): Promise<string>;
331
- /** Verify a token minted by {@link signPurposeToken}, for one named purpose. */
471
+ /** See {@link JwtService.verifyPurposeToken}. Answers for the process default. */
332
472
  export declare function verifyPurposeToken<T>(purpose: string, token: string): Promise<T | null>;
333
473
  /** The `purpose` that marks a token as belonging to the MCP resource. */
334
474
  export declare const MCP_ACCESS_PURPOSE = "mcp-access";
@@ -345,37 +485,13 @@ export interface McpAccessTokenPayload {
345
485
  aud: string;
346
486
  iss: string;
347
487
  }
348
- /**
349
- * Mint an access token for the MCP endpoint.
350
- *
351
- * Deliberately **not** an ordinary session token, and the `purpose` claim is
352
- * what enforces that: {@link verifyAccessToken} refuses any token carrying one,
353
- * so a credential issued to a third-party MCP client — Claude, an IDE, anything
354
- * that completed the OAuth flow — cannot be replayed against `/api/data`,
355
- * `/api/admin` or the websocket. It buys exactly the one resource it names.
356
- *
357
- * That asymmetry matters more than it first looks. These tokens are handed to
358
- * software the user authorized but the operator never vetted, they live in that
359
- * software's storage, and they are the only credential in this system minted
360
- * for a third party. Without the purpose separation, "connect Claude to your
361
- * data" would silently mean "give Claude a full session".
362
- *
363
- * The audience is the canonical resource URI, and {@link verifyMcpAccessToken}
364
- * requires the caller to say which resource it is checking for. A token minted
365
- * for another Rebase project — or another path on this one — fails there.
366
- *
367
- * HS256 with the shared secret, like the download token: the authorization
368
- * server and the resource server are the same process, so nothing outside it
369
- * ever needs to verify one, and an asymmetric key here would be a second key
370
- * surface bought for nobody.
371
- */
488
+ /** See {@link JwtService.generateMcpAccessToken}. Answers for the process default. */
372
489
  export declare function generateMcpAccessToken(payload: Omit<McpAccessTokenPayload, "purpose">, expiresInSeconds: number): Promise<string>;
490
+ /** See {@link JwtService.verifyMcpAccessToken}. Answers for the process default. */
491
+ export declare function verifyMcpAccessToken(token: string, expectedAudience: string): Promise<McpAccessTokenPayload | null>;
373
492
  /**
374
- * Verify an MCP access token **for a specific resource**.
375
- *
376
- * `expectedAudience` has no default. The MCP specification requires a resource
377
- * server to validate that a token was issued for it specifically, and a default
378
- * would be the thing that quietly turns that check off — the caller has to say
379
- * what it is, every time, so a new call site cannot forget by omission.
493
+ * The process default as a service object that follows it — every call reads
494
+ * whatever {@link configureJwt} or `setDefaultBackend` installed last. What a
495
+ * route built outside any backend falls back to.
380
496
  */
381
- export declare function verifyMcpAccessToken(token: string, expectedAudience: string): Promise<McpAccessTokenPayload | null>;
497
+ export declare const processJwtService: JwtService;
@@ -3,6 +3,7 @@ import type { MiddlewareHandler } from "hono";
3
3
  import type { AuthModuleConfig, CreateSessionAndTokens } from "./routes.js";
4
4
  import type { ResolvedAuthHooks } from "./auth-hooks.js";
5
5
  import type { HonoEnv } from "../api/types.js";
6
+ import { type AuthLimiters } from "./rate-limiter.js";
6
7
  import { z } from "zod";
7
8
  import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasepro/types";
8
9
  /**
@@ -13,6 +14,8 @@ import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasep
13
14
  export declare function mountMagicLinkRoutes(deps: {
14
15
  router: Hono<HonoEnv>;
15
16
  config: AuthModuleConfig;
17
+ /** The backend's auth limiters. */
18
+ limiters: AuthLimiters;
16
19
  ops: ResolvedAuthHooks;
17
20
  parseBody: <T>(schema: z.ZodSchema<T>, body: unknown) => T;
18
21
  buildAuthResponse: (user: {
@@ -36,13 +36,16 @@
36
36
  *
37
37
  * @module
38
38
  */
39
+ import { type JwtService } from "./jwt.js";
39
40
  /**
40
41
  * Encrypt a plaintext TOTP secret.
41
42
  *
42
43
  * @param plaintext - The Base32 TOTP secret to encrypt.
44
+ * @param jwt - The backend whose JWT secret is the last key fallback. The
45
+ * process default when omitted.
43
46
  * @returns A string in the format `v1.<keyId>:iv_hex:authTag_hex:ciphertext_hex`.
44
47
  */
45
- export declare function encryptTotpSecret(plaintext: string): string;
48
+ export declare function encryptTotpSecret(plaintext: string, jwt?: JwtService): string;
46
49
  /** What a factor's stored secret opened to, and whether it should be re-stored. */
47
50
  export interface OpenedTotpSecret {
48
51
  /** The Base32 TOTP secret. */
@@ -59,8 +62,10 @@ export interface OpenedTotpSecret {
59
62
  *
60
63
  * Accepts both the key-stamped format and the original unstamped
61
64
  * `iv:authTag:ciphertext`, which is tried against each candidate in turn.
65
+ * `jwt` is the backend whose JWT secret is the last key fallback — the process
66
+ * default when omitted.
62
67
  */
63
- export declare function openTotpSecret(ciphertext: string): OpenedTotpSecret;
68
+ export declare function openTotpSecret(ciphertext: string, jwt?: JwtService): OpenedTotpSecret;
64
69
  /**
65
70
  * Decrypt a previously encrypted TOTP secret.
66
71
  *
@@ -69,6 +74,7 @@ export declare function openTotpSecret(ciphertext: string): OpenedTotpSecret;
69
74
  * on whichever key it was written with.
70
75
  *
71
76
  * @param ciphertext - A stored TOTP secret, in either format.
77
+ * @param jwt - The backend whose JWT secret is the last key fallback.
72
78
  * @returns The original Base32 TOTP secret.
73
79
  */
74
- export declare function decryptTotpSecret(ciphertext: string): string;
80
+ export declare function decryptTotpSecret(ciphertext: string, jwt?: JwtService): string;
@@ -12,6 +12,7 @@
12
12
  *
13
13
  * @module
14
14
  */
15
+ import type { JwtService } from "./jwt.js";
15
16
  import type { AuthRepository } from "./interfaces.js";
16
17
  /** Error code a client watches for to switch a sign-in into its second step. */
17
18
  export declare const MFA_REQUIRED_CODE = "MFA_REQUIRED";
@@ -37,6 +38,7 @@ export interface MfaRequiredDetails {
37
38
  *
38
39
  * A 401 rather than a 200 with a different body: a client that has not been
39
40
  * taught about MFA must fail, not proceed. The credential in `details` is
40
- * useless for anything except the challenge routes.
41
+ * useless for anything except the challenge routes, and is signed by `jwt` —
42
+ * the backend's own key.
41
43
  */
42
- export declare function assertMfaSatisfied(authRepo: AuthRepository, uid: string): Promise<void>;
44
+ export declare function assertMfaSatisfied(authRepo: AuthRepository, uid: string, jwt: JwtService): Promise<void>;
@@ -1,12 +1,18 @@
1
1
  import { Hono, type MiddlewareHandler } from "hono";
2
2
  import { z } from "zod";
3
3
  import { HonoEnv } from "../api/types.js";
4
+ import { type AuthLimiters } from "./rate-limiter.js";
5
+ import type { JwtService } from "./jwt.js";
4
6
  import type { AuthModuleConfig, CreateSessionAndTokens } from "./routes.js";
5
7
  import { resolveAuthHooks } from "./auth-hooks.js";
6
8
  import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasepro/types";
7
9
  interface MfaRoutesConfig {
8
10
  router: Hono<HonoEnv>;
9
11
  config: AuthModuleConfig;
12
+ /** The backend these routes sign and verify for; its secret is also the TOTP key's last fallback. */
13
+ jwt: JwtService;
14
+ /** The backend's auth limiters. */
15
+ limiters: AuthLimiters;
10
16
  /**
11
17
  * The caller's guard for a signed-in user, with the revocation watermark
12
18
  * consulted. Enrolling, confirming or removing a factor is a persistence
@@ -1,7 +1,7 @@
1
1
  import { MiddlewareHandler, Context } from "hono";
2
2
  import { type AccessJudgeRepository } from "./token-revocation.js";
3
3
  import { DataDriver } from "@rebasepro/types";
4
- import { AccessTokenPayload } from "./jwt.js";
4
+ import { AccessTokenPayload, type JwtService } from "./jwt.js";
5
5
  import type { HonoEnv } from "../api/types.js";
6
6
  import { ApiError } from "../api/errors.js";
7
7
  import type { ApiKeyStore } from "./api-keys/api-key-store.js";
@@ -151,7 +151,14 @@ export declare const requireAdmin: MiddlewareHandler<HonoEnv>;
151
151
  */
152
152
  export declare const optionalAuth: MiddlewareHandler<HonoEnv>;
153
153
  /**
154
- * Extract user from token - for WebSocket authentication
154
+ * Who a session token names, as `jwt` verifies it — or "nobody" rather than a
155
+ * throw on a backend with no JWT of its own, so a socket closes as
156
+ * unauthenticated, which is the outcome this describes.
157
+ */
158
+ export declare function userFromToken(jwt: JwtService, token: string): Promise<AccessTokenPayload | null>;
159
+ /**
160
+ * Extract user from token - for WebSocket authentication. Answers for the
161
+ * process default; a backend's own socket verifies with its own keys.
155
162
  */
156
163
  export declare function extractUserFromToken(token: string): Promise<AccessTokenPayload | null>;
157
164
  /**