@rebasepro/server 0.23.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 (287) hide show
  1. package/README.md +28 -3
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/GCSStorageController-DEga1ULx.js +402 -0
  4. package/dist/GCSStorageController-DEga1ULx.js.map +1 -0
  5. package/dist/S3StorageController-Btx-6bJ5.js +480 -0
  6. package/dist/S3StorageController-Btx-6bJ5.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +92 -1
  8. package/dist/api/errors.d.ts +41 -0
  9. package/dist/api/live-schema-routes.d.ts +64 -10
  10. package/dist/api/logs-routes.d.ts +39 -1
  11. package/dist/api/mount-path.d.ts +30 -0
  12. package/dist/api/mount.d.ts +6 -13
  13. package/dist/api/openapi-generator.d.ts +17 -0
  14. package/dist/api/rest/api-generator.d.ts +97 -11
  15. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  16. package/dist/api/rest/field-access-query.d.ts +28 -8
  17. package/dist/api/rest/field-ops.d.ts +19 -7
  18. package/dist/api/rest/query-body.d.ts +10 -0
  19. package/dist/api/rest/write-validation.d.ts +9 -7
  20. package/dist/api/schema-editor-routes.d.ts +10 -1
  21. package/dist/api/sql-export-routes.d.ts +8 -0
  22. package/dist/api/types.d.ts +52 -1
  23. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-mCeSC03x.js} +310 -12
  24. package/dist/ast-schema-editor-mCeSC03x.js.map +1 -0
  25. package/dist/auth/access.d.ts +156 -0
  26. package/dist/auth/adapter-middleware.d.ts +2 -1
  27. package/dist/auth/address-ownership.d.ts +16 -1
  28. package/dist/auth/admin-roles-route.d.ts +4 -2
  29. package/dist/auth/admin-roles.d.ts +17 -20
  30. package/dist/auth/admin-user-ops.d.ts +24 -2
  31. package/dist/auth/admin-users-route.d.ts +2 -0
  32. package/dist/auth/api-keys/api-key-middleware.d.ts +64 -56
  33. package/dist/auth/api-keys/api-key-routes.d.ts +44 -11
  34. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  35. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  36. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  37. package/dist/auth/api-keys/index.d.ts +11 -11
  38. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  39. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  40. package/dist/auth/auth-hooks.d.ts +46 -7
  41. package/dist/auth/builtin-auth-adapter.d.ts +21 -0
  42. package/dist/auth/cookie-utils.d.ts +7 -0
  43. package/dist/auth/deliverable-address.d.ts +6 -0
  44. package/dist/auth/email-change-routes.d.ts +44 -0
  45. package/dist/auth/expired-token-sweep.d.ts +71 -0
  46. package/dist/auth/impersonation.d.ts +110 -0
  47. package/dist/auth/index.d.ts +4 -2
  48. package/dist/auth/interfaces.d.ts +120 -59
  49. package/dist/auth/jwks-routes.d.ts +5 -1
  50. package/dist/auth/jwt-crypto.d.ts +7 -0
  51. package/dist/auth/jwt-keys.d.ts +4 -0
  52. package/dist/auth/jwt.d.ts +319 -157
  53. package/dist/auth/magic-link-routes.d.ts +5 -6
  54. package/dist/auth/mfa-crypto.d.ts +9 -3
  55. package/dist/auth/mfa-gate.d.ts +4 -2
  56. package/dist/auth/mfa-routes.d.ts +8 -9
  57. package/dist/auth/middleware.d.ts +26 -7
  58. package/dist/auth/otp-routes.d.ts +5 -6
  59. package/dist/auth/passwordless-signup.d.ts +27 -0
  60. package/dist/auth/platform-token.d.ts +122 -0
  61. package/dist/auth/rate-limiter.d.ts +99 -31
  62. package/dist/auth/registration-policy.d.ts +17 -0
  63. package/dist/auth/require-auth.d.ts +1 -1
  64. package/dist/auth/routes.d.ts +61 -0
  65. package/dist/auth/scope-routes.d.ts +22 -0
  66. package/dist/auth/session-routes.d.ts +17 -6
  67. package/dist/auth/token-revocation.d.ts +56 -1
  68. package/dist/auth/user-picking.d.ts +37 -0
  69. package/dist/auth/verify-credential.d.ts +49 -0
  70. package/dist/{auth-B-GIMpDG.js → auth-CSBky1mt.js} +4564 -1929
  71. package/dist/auth-CSBky1mt.js.map +1 -0
  72. package/dist/auth_adapter-CIItCTc8.js +21 -0
  73. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  74. package/dist/backend/create-backend.d.ts +281 -0
  75. package/dist/backend/default-backend.d.ts +35 -0
  76. package/dist/backend/no-collections.d.ts +19 -0
  77. package/dist/backend/options.d.ts +912 -0
  78. package/dist/backend/scope.d.ts +23 -0
  79. package/dist/backend/services.d.ts +54 -0
  80. package/dist/backend-DTAOsLQc.js.map +1 -1
  81. package/dist/backup/backup-common.d.ts +24 -3
  82. package/dist/backup/backup-routes.d.ts +46 -5
  83. package/dist/backup/backup-schedule.d.ts +33 -0
  84. package/dist/backup/backup-storage.d.ts +16 -0
  85. package/dist/backup/index.d.ts +5 -1
  86. package/dist/backup-DBGRUVTS.js +585 -0
  87. package/dist/backup-DBGRUVTS.js.map +1 -0
  88. package/dist/boot/boot.d.ts +2 -2
  89. package/dist/boot/bundle.d.ts +19 -0
  90. package/dist/boot/driver.d.ts +3 -0
  91. package/dist/boot/env.d.ts +58 -6
  92. package/dist/boot/http-server.d.ts +22 -0
  93. package/dist/boot/options.d.ts +16 -1
  94. package/dist/boot/provision.d.ts +4 -2
  95. package/dist/boot/resource-adapters.d.ts +11 -14
  96. package/dist/boot/runtime-cors.d.ts +17 -0
  97. package/dist/boot/security-headers.d.ts +26 -0
  98. package/dist/boot/sources.d.ts +9 -0
  99. package/dist/boot/static-routing.d.ts +65 -0
  100. package/dist/collection_patch-BRu-BvDv.js +472 -0
  101. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  102. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-DiFwiLg2.js} +5 -8
  103. package/dist/contract-routes-DiFwiLg2.js.map +1 -0
  104. package/dist/cron/cron-loader.d.ts +77 -18
  105. package/dist/cron/cron-routes.d.ts +1 -1
  106. package/dist/cron/cron-scheduler.d.ts +94 -23
  107. package/dist/cron/cron-store.d.ts +37 -5
  108. package/dist/cron/define-cron.d.ts +6 -2
  109. package/dist/cron/index.d.ts +3 -3
  110. package/dist/cron-loader-NHqRfKF0.js +202 -0
  111. package/dist/cron-loader-NHqRfKF0.js.map +1 -0
  112. package/dist/{cron-routes-eE8nif_b.js → cron-routes-DsH8Jwn5.js} +34 -8
  113. package/dist/cron-routes-DsH8Jwn5.js.map +1 -0
  114. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-D1DT0nw5.js} +349 -65
  115. package/dist/cron-scheduler-D1DT0nw5.js.map +1 -0
  116. package/dist/cron-store-DEpVstUU.js +577 -0
  117. package/dist/cron-store-DEpVstUU.js.map +1 -0
  118. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BNqi2NZe.js} +2 -2
  119. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BNqi2NZe.js.map} +1 -1
  120. package/dist/email/index.d.ts +2 -2
  121. package/dist/email/smtp-email-service.d.ts +23 -0
  122. package/dist/email/templates.d.ts +22 -0
  123. package/dist/email/types.d.ts +44 -0
  124. package/dist/env.d.ts +1 -39
  125. package/dist/{errors-DWsX4yTd.js → errors-Cqp86KnA.js} +263 -10
  126. package/dist/errors-Cqp86KnA.js.map +1 -0
  127. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  128. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  129. package/dist/{function-loader-xnbDAPfa.js → function-loader-DbCSoNJj.js} +69 -39
  130. package/dist/function-loader-DbCSoNJj.js.map +1 -0
  131. package/dist/{function-routes-Chet4-lB.js → function-routes-BSTClQNa.js} +28 -15
  132. package/dist/function-routes-BSTClQNa.js.map +1 -0
  133. package/dist/functions/context.d.ts +26 -6
  134. package/dist/functions/define-function.d.ts +10 -6
  135. package/dist/functions/function-loader.d.ts +75 -18
  136. package/dist/functions/function-routes.d.ts +2 -1
  137. package/dist/functions/guards.d.ts +22 -5
  138. package/dist/functions/index.d.ts +6 -5
  139. package/dist/functions/index.js +148 -50
  140. package/dist/functions/index.js.map +1 -1
  141. package/dist/functions/internal.d.ts +2 -2
  142. package/dist/functions/proxy.d.ts +6 -2
  143. package/dist/functions/request-timeout.d.ts +7 -5
  144. package/dist/history/history-routes.d.ts +9 -0
  145. package/dist/history/retention-sweep.d.ts +81 -0
  146. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-RZ1Ow4YY.js} +4 -4
  147. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-RZ1Ow4YY.js.map} +1 -1
  148. package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
  149. package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
  150. package/dist/index.d.ts +32 -13
  151. package/dist/index.es.js +25329 -18393
  152. package/dist/index.es.js.map +1 -1
  153. package/dist/init/docs.d.ts +35 -10
  154. package/dist/init/health.d.ts +17 -2
  155. package/dist/init/process-safety.d.ts +7 -2
  156. package/dist/init/shutdown.d.ts +26 -1
  157. package/dist/init.d.ts +49 -811
  158. package/dist/internal-tables-L2t39RdT.js +136 -0
  159. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  160. package/dist/jobs/job-queue.d.ts +8 -0
  161. package/dist/jobs/job-store.d.ts +24 -0
  162. package/dist/jobs/types.d.ts +25 -4
  163. package/dist/{jobs-CazMYhyy.js → jobs-8G-_agld.js} +235 -25
  164. package/dist/jobs-8G-_agld.js.map +1 -0
  165. package/dist/{jwt-DnQHNFCl.js → jwt-CBaFc34Y.js} +546 -447
  166. package/dist/jwt-CBaFc34Y.js.map +1 -0
  167. package/dist/{keys-CogCQpxG.js → keys-COzQ2aE8.js} +3 -17
  168. package/dist/{keys-CogCQpxG.js.map → keys-COzQ2aE8.js.map} +1 -1
  169. package/dist/load-failure-L5hvfDoG.js +80 -0
  170. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  171. package/dist/{logger-DO2PZc4i.js → logger-Bt32sHFw.js} +41 -5
  172. package/dist/logger-Bt32sHFw.js.map +1 -0
  173. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DQJhrf-b.js} +48 -8
  174. package/dist/logs-routes-DQJhrf-b.js.map +1 -0
  175. package/dist/mcp/consent-page.d.ts +1 -1
  176. package/dist/mcp/mcp-routes.d.ts +13 -0
  177. package/dist/mcp/mcp-tools.d.ts +23 -9
  178. package/dist/mcp/oauth-metadata.d.ts +21 -16
  179. package/dist/mcp/oauth-routes.d.ts +14 -1
  180. package/dist/metrics/index.d.ts +18 -0
  181. package/dist/metrics/pool-wait.d.ts +50 -0
  182. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  183. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  184. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  185. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  186. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-CeGW2dLC.js} +455 -80
  187. package/dist/openapi-generator-CeGW2dLC.js.map +1 -0
  188. package/dist/policy-PdzX-Opd.js +121 -0
  189. package/dist/policy-PdzX-Opd.js.map +1 -0
  190. package/dist/project_manifest-R6y_KHms.js +59 -0
  191. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  192. package/dist/{proxy-Czngl3p9.js → proxy-BEKbsil6.js} +5 -4
  193. package/dist/proxy-BEKbsil6.js.map +1 -0
  194. package/dist/{query-parser-DGRVFNM3.js → query-parser-ECxP0nDw.js} +497 -52
  195. package/dist/query-parser-ECxP0nDw.js.map +1 -0
  196. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-BiRelY-U.js} +17 -10
  197. package/dist/request-timeout-BiRelY-U.js.map +1 -0
  198. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  199. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  200. package/dist/schema-edit/apply-schema-change.d.ts +73 -3
  201. package/dist/schema-edit/local-source.d.ts +29 -0
  202. package/dist/schema-edit/project-root.d.ts +3 -2
  203. package/dist/schema-edit/remote-source.d.ts +20 -5
  204. package/dist/schema-edit/source-editor.d.ts +28 -0
  205. package/dist/schema-editor-routes-DRXBAbZy.js +136 -0
  206. package/dist/schema-editor-routes-DRXBAbZy.js.map +1 -0
  207. package/dist/serve-spa.d.ts +58 -0
  208. package/dist/services/outbound-post.d.ts +50 -0
  209. package/dist/services/outbound-url-guard.d.ts +34 -5
  210. package/dist/services/routed-realtime-service.d.ts +16 -0
  211. package/dist/services/webhook-service.d.ts +66 -14
  212. package/dist/singleton.d.ts +9 -5
  213. package/dist/soft-delete-params-CBryZXMq.js +668 -0
  214. package/dist/soft-delete-params-CBryZXMq.js.map +1 -0
  215. package/dist/sql-export-routes-D8ZaRvoR.js +240 -0
  216. package/dist/sql-export-routes-D8ZaRvoR.js.map +1 -0
  217. package/dist/{src-vkcwKXbT.js → src-D5Y3N-S4.js} +440 -20
  218. package/dist/src-D5Y3N-S4.js.map +1 -0
  219. package/dist/storage/GCSStorageController.d.ts +48 -2
  220. package/dist/storage/LocalStorageController.d.ts +69 -2
  221. package/dist/storage/S3StorageController.d.ts +39 -3
  222. package/dist/storage/image-transform.d.ts +15 -0
  223. package/dist/storage/index.d.ts +2 -2
  224. package/dist/storage/maintenance-routes.d.ts +23 -0
  225. package/dist/storage/maintenance.d.ts +126 -0
  226. package/dist/storage/property-limits.d.ts +41 -6
  227. package/dist/storage/rendition-cache.d.ts +9 -11
  228. package/dist/storage/request-keys.d.ts +15 -0
  229. package/dist/storage/request-principals.d.ts +30 -0
  230. package/dist/storage/requested-object.d.ts +74 -0
  231. package/dist/storage/routes.d.ts +52 -14
  232. package/dist/storage/source-variables.d.ts +10 -0
  233. package/dist/storage/streams.d.ts +42 -0
  234. package/dist/storage/tus-handler.d.ts +76 -7
  235. package/dist/storage/types.d.ts +101 -7
  236. package/dist/storage/upload-validation.d.ts +13 -0
  237. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  238. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  239. package/dist/strings.es-BeZWAodn.js +256 -0
  240. package/dist/strings.es-BeZWAodn.js.map +1 -0
  241. package/dist/upload-validation-CVobI5xO.js +172 -0
  242. package/dist/upload-validation-CVobI5xO.js.map +1 -0
  243. package/dist/utils/byte-size.d.ts +10 -0
  244. package/dist/utils/compression.d.ts +6 -0
  245. package/dist/utils/load-failure.d.ts +14 -0
  246. package/dist/utils/logger.d.ts +21 -0
  247. package/package.json +5 -5
  248. package/dist/GCSStorageController-CjrA4PMo.js +0 -242
  249. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  250. package/dist/S3StorageController-B6pKDNVj.js +0 -264
  251. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  252. package/dist/admin-roles-vYdp_Pil.js +0 -36
  253. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  254. package/dist/admin_block-DxKLmdiv.js +0 -206
  255. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  256. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  257. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  258. package/dist/auth-B-GIMpDG.js.map +0 -1
  259. package/dist/backup-D7YR94N3.js +0 -253
  260. package/dist/backup-D7YR94N3.js.map +0 -1
  261. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  262. package/dist/cron-loader-DfTj2Hbi.js +0 -118
  263. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  264. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  265. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  266. package/dist/cron-store-TcoGz-xS.js +0 -305
  267. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  268. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  269. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  270. package/dist/errors-DWsX4yTd.js.map +0 -1
  271. package/dist/function-loader-xnbDAPfa.js.map +0 -1
  272. package/dist/function-routes-Chet4-lB.js.map +0 -1
  273. package/dist/jobs-CazMYhyy.js.map +0 -1
  274. package/dist/jwt-DnQHNFCl.js.map +0 -1
  275. package/dist/logger-DO2PZc4i.js.map +0 -1
  276. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  277. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  278. package/dist/proxy-Czngl3p9.js.map +0 -1
  279. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  280. package/dist/request-timeout-C_4C2BeR.js.map +0 -1
  281. package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
  282. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  283. package/dist/src-pmvW7BFx.js +0 -6688
  284. package/dist/src-pmvW7BFx.js.map +0 -1
  285. package/dist/src-vkcwKXbT.js.map +0 -1
  286. package/dist/types-BfKcm9do.js +0 -39
  287. package/dist/types-BfKcm9do.js.map +0 -1
@@ -2,10 +2,10 @@ import { createRequire as __rebaseCreateRequire } from "module";
2
2
  import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
- import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-dW7B1o5h.js";
6
- import { r as logger } from "./logger-DO2PZc4i.js";
7
- import { a as canonicalStorageId } from "./keys-CogCQpxG.js";
8
- import { createPrivateKey, createPublicKey } from "crypto";
5
+ import { i as __toESM, r as __require, t as __commonJSMin } from "./rolldown-runtime-dW7B1o5h.js";
6
+ import { r as logger } from "./logger-Bt32sHFw.js";
7
+ import { a as canonicalStorageId } from "./keys-COzQ2aE8.js";
8
+ import { createPrivateKey, createPublicKey, createSecretKey } from "crypto";
9
9
  //#region src/utils/portable-crypto.ts
10
10
  /**
11
11
  * The cryptography the request path needs, on primitives every runtime has.
@@ -2386,7 +2386,7 @@ var require_verify = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2386
2386
  var validateAsymmetricKey = require_validateAsymmetricKey();
2387
2387
  var PS_SUPPORTED = require_psSupported();
2388
2388
  var jws = require_jws();
2389
- var { KeyObject: KeyObject$1, createSecretKey: createSecretKey$1, createPublicKey: createPublicKey$1 } = __require("crypto");
2389
+ var { KeyObject: KeyObject$1, createSecretKey: createSecretKey$2, createPublicKey: createPublicKey$1 } = __require("crypto");
2390
2390
  var PUB_KEY_ALGS = [
2391
2391
  "RS256",
2392
2392
  "RS384",
@@ -2457,7 +2457,7 @@ var require_verify = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2457
2457
  secretOrPublicKey = createPublicKey$1(secretOrPublicKey);
2458
2458
  } catch (_) {
2459
2459
  try {
2460
- secretOrPublicKey = createSecretKey$1(typeof secretOrPublicKey === "string" ? Buffer.from(secretOrPublicKey) : secretOrPublicKey);
2460
+ secretOrPublicKey = createSecretKey$2(typeof secretOrPublicKey === "string" ? Buffer.from(secretOrPublicKey) : secretOrPublicKey);
2461
2461
  } catch (_) {
2462
2462
  return done(new JsonWebTokenError("secretOrPublicKey is not valid key material"));
2463
2463
  }
@@ -4015,7 +4015,7 @@ var require_sign = /* @__PURE__ */ __commonJSMin(((exports, module) => {
4015
4015
  var isPlainObject = require_lodash_isplainobject();
4016
4016
  var isString = require_lodash_isstring();
4017
4017
  var once = require_lodash_once();
4018
- var { KeyObject, createSecretKey, createPrivateKey: createPrivateKey$1 } = __require("crypto");
4018
+ var { KeyObject, createSecretKey: createSecretKey$1, createPrivateKey: createPrivateKey$1 } = __require("crypto");
4019
4019
  var SUPPORTED_ALGS = [
4020
4020
  "RS256",
4021
4021
  "RS384",
@@ -4159,7 +4159,7 @@ var require_sign = /* @__PURE__ */ __commonJSMin(((exports, module) => {
4159
4159
  secretOrPrivateKey = createPrivateKey$1(secretOrPrivateKey);
4160
4160
  } catch (_) {
4161
4161
  try {
4162
- secretOrPrivateKey = createSecretKey(typeof secretOrPrivateKey === "string" ? Buffer.from(secretOrPrivateKey) : secretOrPrivateKey);
4162
+ secretOrPrivateKey = createSecretKey$1(typeof secretOrPrivateKey === "string" ? Buffer.from(secretOrPrivateKey) : secretOrPrivateKey);
4163
4163
  } catch (_) {
4164
4164
  return failure(/* @__PURE__ */ new Error("secretOrPrivateKey is not valid key material"));
4165
4165
  }
@@ -4258,61 +4258,8 @@ var require_jsonwebtoken = /* @__PURE__ */ __commonJSMin(((exports, module) => {
4258
4258
  };
4259
4259
  }));
4260
4260
  //#endregion
4261
- //#region src/auth/jwt-crypto.ts
4262
- var import_jsonwebtoken = /* @__PURE__ */ __toESM(require_jsonwebtoken(), 1);
4263
- /** Sign a set of claims. Rejects if the key and algorithm disagree. */
4264
- async function signJwt(payload, key, options) {
4265
- return import_jsonwebtoken.default.sign(payload, key, {
4266
- algorithm: options.algorithm,
4267
- ...options.expiresIn === void 0 ? {} : { expiresIn: options.expiresIn },
4268
- ...options.keyid === void 0 ? {} : { keyid: options.keyid }
4269
- });
4270
- }
4271
- /**
4272
- * Verify a token against one key and an explicit algorithm list, and return its
4273
- * claims. Rejects — never returns null — when the token is not valid.
4274
- *
4275
- * `algorithms` has no default and must not acquire one. Letting the verifier
4276
- * read `alg` out of the token's own header is the canonical JWT vulnerability:
4277
- * an attacker takes a published RSA public key, HMACs a payload of their
4278
- * choosing with it, sets `alg: HS256`, and a verifier holding that same public
4279
- * key as "the secret" agrees. The caller decides the algorithm from the key it
4280
- * chose, and {@link decodeProtectedHeader} exists so it can choose that key
4281
- * without trusting anything the token asserts.
4282
- */
4283
- async function verifyJwt(token, key, options) {
4284
- return import_jsonwebtoken.default.verify(token, key, { algorithms: options.algorithms });
4285
- }
4286
- /**
4287
- * The token's unverified header — `kid` and `alg` — or an empty object.
4288
- *
4289
- * Unverified is the point: this is read *in order to* pick the key that will
4290
- * verify it, so it cannot itself be verified first. Nothing but key selection
4291
- * may depend on it, and key selection then pins the algorithm.
4292
- *
4293
- * Implemented here rather than via the JWT library because it is plain
4294
- * base64url and JSON, needs no cryptography, and there is no reason for the
4295
- * portable half of this module to wait on the non-portable half.
4296
- */
4297
- function decodeProtectedHeader(token) {
4298
- const encoded = token.split(".")[0];
4299
- if (!encoded) return {};
4300
- try {
4301
- const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
4302
- const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
4303
- const header = JSON.parse(atob(padded));
4304
- if (typeof header !== "object" || header === null) return {};
4305
- const { kid, alg } = header;
4306
- return {
4307
- ...typeof kid === "string" ? { kid } : {},
4308
- ...typeof alg === "string" ? { alg } : {}
4309
- };
4310
- } catch {
4311
- return {};
4312
- }
4313
- }
4314
- //#endregion
4315
4261
  //#region src/auth/jwt-keys.ts
4262
+ var import_jsonwebtoken = /* @__PURE__ */ __toESM(require_jsonwebtoken(), 1);
4316
4263
  /**
4317
4264
  * The algorithm a key type implies.
4318
4265
  *
@@ -4416,34 +4363,162 @@ function toJwks(keys) {
4416
4363
  use: "sig"
4417
4364
  })) };
4418
4365
  }
4366
+ /**
4367
+ * Key strings handed to the JWT library, imported once each.
4368
+ *
4369
+ * `jsonwebtoken` turns every key that is not a `KeyObject` into one on every
4370
+ * call: `createPublicKey` (verify) or `createPrivateKey` (sign) first, and
4371
+ * when that throws — as it does for every HMAC secret, which is not PEM —
4372
+ * `createSecretKey`. The failed parse and its exception were about 15 of the
4373
+ * 21 µs of each access-token verify. These are that same conversion, in the
4374
+ * same order, done once per string; `jwt-crypto.ts` hands the library the
4375
+ * `KeyObject` it produces. Here rather than there because key parsing lives
4376
+ * here (see `jwt-crypto.ts`), and so does this module's `node:crypto`.
4377
+ *
4378
+ * Keyed by the key string itself, so a rotated secret (`configureJwt`) is a
4379
+ * new entry and the old one is simply no longer asked for; bounded, oldest
4380
+ * first, so a process cycling through secrets does not keep all of them. Only
4381
+ * keys live here — never a verification result. Empty strings and anything
4382
+ * not a string pass through untouched, so `jsonwebtoken` refuses them exactly
4383
+ * as it did ("secret or public key must be provided" and the like), and so
4384
+ * does a string no conversion accepts.
4385
+ */
4386
+ var IMPORTED_KEY_LIMIT = 16;
4387
+ var importedVerificationKeys = /* @__PURE__ */ new Map();
4388
+ var importedSigningKeys = /* @__PURE__ */ new Map();
4389
+ function importedKey(key, cache, asymmetric) {
4390
+ if (typeof key !== "string" || key === "") return key;
4391
+ const cached = cache.get(key);
4392
+ if (cached) return cached;
4393
+ let keyObject;
4394
+ try {
4395
+ keyObject = asymmetric(key);
4396
+ } catch {
4397
+ try {
4398
+ keyObject = createSecretKey(Buffer.from(key));
4399
+ } catch {
4400
+ return key;
4401
+ }
4402
+ }
4403
+ if (cache.size >= IMPORTED_KEY_LIMIT) {
4404
+ const oldest = cache.keys().next();
4405
+ if (!oldest.done) cache.delete(oldest.value);
4406
+ }
4407
+ cache.set(key, keyObject);
4408
+ return keyObject;
4409
+ }
4410
+ /** A verification key, as `jsonwebtoken.verify` would import it, once per string. */
4411
+ function importedVerificationKey(key) {
4412
+ return importedKey(key, importedVerificationKeys, createPublicKey);
4413
+ }
4414
+ /** A signing key, as `jsonwebtoken.sign` would import it, once per string. */
4415
+ function importedSigningKey(key) {
4416
+ return importedKey(key, importedSigningKeys, createPrivateKey);
4417
+ }
4418
+ //#endregion
4419
+ //#region src/auth/jwt-crypto.ts
4420
+ /**
4421
+ * The one module in the request path that knows what signs a JWT.
4422
+ *
4423
+ * `jsonwebtoken` is built on `node:crypto` and is synchronous. Every portable
4424
+ * JWT implementation — WebCrypto directly, or `jose` on top of it — is
4425
+ * asynchronous, because `crypto.subtle` is. So the shape of the boundary
4426
+ * matters more than what is behind it right now: **these functions are async
4427
+ * and `jsonwebtoken` is confined to this file.**
4428
+ *
4429
+ * That is the expensive half of the change, and it is why it was made before
4430
+ * anything actually needed it. Swapping the implementation touches one file.
4431
+ * Going from synchronous to asynchronous verification touches every caller of
4432
+ * every function that verifies a token — which, at the time of writing, is
4433
+ * eleven call sites in `src` and about a hundred and eighty in the suite. Doing
4434
+ * that as a line item inside a runtime port, on top of everything else moving
4435
+ * at once, is how a port stalls.
4436
+ *
4437
+ * What is **not** here: key parsing. `jwt-keys.ts` turns PEM into
4438
+ * `node:crypto` `KeyObject`s, and the portable replacement for that is
4439
+ * `jose`'s `importSPKI`/`importPKCS8` — a dependency this package does not
4440
+ * carry yet. It stays recorded in `contracts/portable-core.txt` rather than
4441
+ * wrapped in something that would imply it had moved.
4442
+ *
4443
+ * ## Replacing this with jose
4444
+ *
4445
+ * Add `jose` (a lockfile change, so it happens in the primary checkout), then:
4446
+ * `signJwt` becomes `new SignJWT(payload).setProtectedHeader({ alg, kid })
4447
+ * .setExpirationTime(...).sign(key)`, `verifyJwt` becomes `jwtVerify(token,
4448
+ * key, { algorithms })`, and `decodeProtectedHeader` is already what jose's
4449
+ * function of that name does. Nothing outside this file changes — with one
4450
+ * exception worth writing down, because nothing would fail loudly:
4451
+ * **`jsonwebtoken` stamps `iat` on every token it signs and jose does not**
4452
+ * (it wants an explicit `.setIssuedAt()`). `iat` is what the revocation
4453
+ * watermark compares against — `logout`, `change-password`, `reset-password`
4454
+ * and `DELETE /auth/sessions` all stamp `tokensValidAfter` on the user, and
4455
+ * every access token issued before that mark is void. Tokens minted without
4456
+ * `iat` would verify perfectly well and simply stop being revocable.
4457
+ *
4458
+ * @module
4459
+ */
4460
+ /** Sign a set of claims. Rejects if the key and algorithm disagree. */
4461
+ async function signJwt(payload, key, options) {
4462
+ return import_jsonwebtoken.default.sign(payload, importedSigningKey(key), {
4463
+ algorithm: options.algorithm,
4464
+ ...options.expiresIn === void 0 ? {} : { expiresIn: options.expiresIn },
4465
+ ...options.keyid === void 0 ? {} : { keyid: options.keyid }
4466
+ });
4467
+ }
4468
+ /**
4469
+ * Verify a token against one key and an explicit algorithm list, and return its
4470
+ * claims. Rejects — never returns null — when the token is not valid.
4471
+ *
4472
+ * `algorithms` has no default and must not acquire one. Letting the verifier
4473
+ * read `alg` out of the token's own header is the canonical JWT vulnerability:
4474
+ * an attacker takes a published RSA public key, HMACs a payload of their
4475
+ * choosing with it, sets `alg: HS256`, and a verifier holding that same public
4476
+ * key as "the secret" agrees. The caller decides the algorithm from the key it
4477
+ * chose, and {@link decodeProtectedHeader} exists so it can choose that key
4478
+ * without trusting anything the token asserts.
4479
+ */
4480
+ async function verifyJwt(token, key, options) {
4481
+ return import_jsonwebtoken.default.verify(token, importedVerificationKey(key), { algorithms: options.algorithms });
4482
+ }
4483
+ /**
4484
+ * Whether a {@link verifyJwt} rejection says only that the token's `exp` has
4485
+ * passed — the routine refusal that sends a client to refresh — rather than
4486
+ * that the token is not one we signed. Here because the library decides how
4487
+ * that is spelled.
4488
+ */
4489
+ function isExpiredJwtError(error) {
4490
+ return error instanceof import_jsonwebtoken.default.TokenExpiredError;
4491
+ }
4492
+ /**
4493
+ * The token's unverified header — `kid` and `alg` — or an empty object.
4494
+ *
4495
+ * Unverified is the point: this is read *in order to* pick the key that will
4496
+ * verify it, so it cannot itself be verified first. Nothing but key selection
4497
+ * may depend on it, and key selection then pins the algorithm.
4498
+ *
4499
+ * Implemented here rather than via the JWT library because it is plain
4500
+ * base64url and JSON, needs no cryptography, and there is no reason for the
4501
+ * portable half of this module to wait on the non-portable half.
4502
+ */
4503
+ function decodeProtectedHeader(token) {
4504
+ const encoded = token.split(".")[0];
4505
+ if (!encoded) return {};
4506
+ try {
4507
+ const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
4508
+ const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
4509
+ const header = JSON.parse(atob(padded));
4510
+ if (typeof header !== "object" || header === null) return {};
4511
+ const { kid, alg } = header;
4512
+ return {
4513
+ ...typeof kid === "string" ? { kid } : {},
4514
+ ...typeof alg === "string" ? { alg } : {}
4515
+ };
4516
+ } catch {
4517
+ return {};
4518
+ }
4519
+ }
4419
4520
  //#endregion
4420
4521
  //#region src/auth/jwt.ts
4421
- var jwt_exports = /* @__PURE__ */ __exportAll({
4422
- MAX_COOKIE_AGE_MS: () => MAX_COOKIE_AGE_MS,
4423
- MCP_ACCESS_PURPOSE: () => MCP_ACCESS_PURPOSE,
4424
- MFA_PENDING_PURPOSE: () => MFA_PENDING_PURPOSE,
4425
- configureJwt: () => configureJwt,
4426
- generateAccessToken: () => generateAccessToken,
4427
- generateDownloadToken: () => generateDownloadToken,
4428
- generateMcpAccessToken: () => generateMcpAccessToken,
4429
- generateMfaPendingToken: () => generateMfaPendingToken,
4430
- generateRefreshToken: () => generateRefreshToken,
4431
- getAccessTokenExpiry: () => getAccessTokenExpiry,
4432
- getAccessTokenExpiryMs: () => getAccessTokenExpiryMs,
4433
- getJwks: () => getJwks,
4434
- getRefreshTokenExpiry: () => getRefreshTokenExpiry,
4435
- getRefreshTokenTtlMs: () => getRefreshTokenTtlMs,
4436
- hasAsymmetricSigningKey: () => hasAsymmetricSigningKey,
4437
- hashRefreshToken: () => hashRefreshToken,
4438
- isJwtConfigured: () => isJwtConfigured,
4439
- parseDurationMs: () => parseDurationMs,
4440
- signPurposeToken: () => signPurposeToken,
4441
- verifyAccessToken: () => verifyAccessToken,
4442
- verifyDownloadToken: () => verifyDownloadToken,
4443
- verifyMcpAccessToken: () => verifyMcpAccessToken,
4444
- verifyMfaPendingToken: () => verifyMfaPendingToken,
4445
- verifyPurposeToken: () => verifyPurposeToken
4446
- });
4447
4522
  /**
4448
4523
  * Claims the token's own identity owns, and which therefore never appear in
4449
4524
  * {@link AccessTokenPayload.claims}.
@@ -4459,6 +4534,7 @@ var RESERVED_TOKEN_CLAIMS = /* @__PURE__ */ new Set([
4459
4534
  "roles",
4460
4535
  "aal",
4461
4536
  "isAnonymous",
4537
+ "sid",
4462
4538
  "purpose",
4463
4539
  "iat",
4464
4540
  "exp",
@@ -4467,7 +4543,13 @@ var RESERVED_TOKEN_CLAIMS = /* @__PURE__ */ new Set([
4467
4543
  "aud",
4468
4544
  "jti"
4469
4545
  ]);
4470
- /** The custom half of a verified token, or nothing when there is none. */
4546
+ /**
4547
+ * The custom half of a verified token, or nothing when there is none.
4548
+ *
4549
+ * Exported for the one other place that builds a request identity's claims
4550
+ * without a token to verify — `resolveUser`, which reads what a token minted
4551
+ * now would carry — so both drop the same identity claims.
4552
+ */
4471
4553
  function customClaimsOf(decoded) {
4472
4554
  const claims = {};
4473
4555
  for (const [key, value] of Object.entries(decoded)) {
@@ -4476,126 +4558,343 @@ function customClaimsOf(decoded) {
4476
4558
  }
4477
4559
  return Object.keys(claims).length > 0 ? claims : void 0;
4478
4560
  }
4479
- var jwtConfig = {
4480
- secret: "",
4481
- accessExpiresIn: "1h",
4482
- refreshExpiresIn: "400d"
4483
- };
4561
+ /** How long an access token lives when no lifetime is configured. */
4562
+ var DEFAULT_ACCESS_EXPIRES_IN = "1h";
4563
+ /** Secrets that are somebody's example, not a secret. */
4564
+ var WEAK_SECRETS = /* @__PURE__ */ new Set([
4565
+ "secret",
4566
+ "jwt-secret",
4567
+ "jwt_secret",
4568
+ "your-secret",
4569
+ "your-super-secret-jwt-key-change-in-production",
4570
+ "super-secret-jwt-key-change-in-production",
4571
+ "change-me",
4572
+ "changeme",
4573
+ "password",
4574
+ "test",
4575
+ "mysecret",
4576
+ "my-secret",
4577
+ "my_secret",
4578
+ "example-secret",
4579
+ "please-change-me",
4580
+ "replace-this-with-a-real-secret",
4581
+ "default-secret",
4582
+ "rebase_saas_jwt_secret_must_be_long_long_long_long",
4583
+ "rebase_saas_service_key_must_be_long_long_long_long"
4584
+ ]);
4484
4585
  /**
4485
- * The parsed signing keys, and which one mints new access tokens.
4586
+ * Build a {@link JwtService} from a configuration, refusing one it would sign
4587
+ * badly with: a short or well-known secret, an `activeKid` no key carries, a
4588
+ * lifetime that is not one.
4486
4589
  *
4487
- * Both are module state beside `jwtConfig` for the same reason it is: every
4488
- * signing and verifying path in the server reads them through this file, and
4489
- * there is exactly one JWT configuration per process.
4490
- */
4491
- var signingKeys = [];
4492
- var activeSigningKey = null;
4493
- /**
4494
- * Configure JWT settings - call this during initialization.
4495
- * Validates the secret strength to prevent deployment with default/weak secrets.
4590
+ * Unset lifetimes take the defaults — an hour for access tokens,
4591
+ * {@link DEFAULT_REFRESH_EXPIRES_IN} for refresh tokens.
4592
+ *
4593
+ * @example
4594
+ * ```ts
4595
+ * const jwt = createJwtService({ secret: process.env.JWT_SECRET! });
4596
+ * const token = await jwt.generateAccessToken("u1", ["editor"]);
4597
+ * const payload = await jwt.verifyAccessToken(token); // { uid: "u1", roles: ["editor"], … }
4598
+ * ```
4496
4599
  */
4497
- function configureJwt(config) {
4498
- const weakSecrets = /* @__PURE__ */ new Set([
4499
- "secret",
4500
- "jwt-secret",
4501
- "jwt_secret",
4502
- "your-secret",
4503
- "your-super-secret-jwt-key-change-in-production",
4504
- "super-secret-jwt-key-change-in-production",
4505
- "change-me",
4506
- "changeme",
4507
- "password",
4508
- "test",
4509
- "mysecret",
4510
- "my-secret",
4511
- "my_secret",
4512
- "example-secret",
4513
- "please-change-me",
4514
- "replace-this-with-a-real-secret",
4515
- "default-secret",
4516
- "rebase_saas_jwt_secret_must_be_long_long_long_long",
4517
- "rebase_saas_service_key_must_be_long_long_long_long"
4518
- ]);
4600
+ function createJwtService(config) {
4601
+ return jwtServiceFrom(checkJwtConfig(config));
4602
+ }
4603
+ function checkJwtConfig(config) {
4519
4604
  if (!config.secret || config.secret.length < 32) throw new Error("JWT secret is too short. Must be at least 32 characters. Generate one with: node -e \"logger.info(require('crypto').randomBytes(48).toString('base64'))\"");
4520
- if (weakSecrets.has(config.secret.toLowerCase())) throw new Error("JWT secret is a known default/weak value. Please use a strong, randomly generated secret. Generate one with: node -e \"logger.info(require('crypto').randomBytes(48).toString('base64'))\"");
4605
+ if (WEAK_SECRETS.has(config.secret.toLowerCase())) throw new Error("JWT secret is a known default/weak value. Please use a strong, randomly generated secret. Generate one with: node -e \"logger.info(require('crypto').randomBytes(48).toString('base64'))\"");
4521
4606
  const resolved = config.signingKeys ? resolveSigningKeys(config.signingKeys) : [];
4522
4607
  let active = null;
4523
4608
  if (resolved.length > 0) if (config.activeKid) {
4524
4609
  active = resolved.find((key) => key.kid === config.activeKid) ?? null;
4525
4610
  if (!active) throw new Error(`auth.activeKid is "${config.activeKid}", which is not among the configured signing keys (${resolved.map((k) => `"${k.kid}"`).join(", ")}). Signing with a key nobody published produces tokens no verifier can check.`);
4526
4611
  } else active = resolved[0];
4527
- const lifetimes = {};
4528
4612
  for (const key of ["accessExpiresIn", "refreshExpiresIn"]) {
4529
4613
  const value = config[key];
4530
4614
  if (value === void 0) continue;
4531
4615
  if (parseDurationMs(value) === null) throw new Error(`auth.${key} is "${value}", which is not a lifetime. Give a positive number and a unit — "15m", "1h", "30d", "1w", "2 weeks" — in the grammar the token signer reads (vercel/ms). A bare number is refused: the signer reads it as milliseconds.`);
4532
- lifetimes[key] = value;
4533
4616
  }
4534
- jwtConfig = {
4535
- ...jwtConfig,
4536
- ...config,
4537
- accessExpiresIn: lifetimes.accessExpiresIn ?? jwtConfig.accessExpiresIn,
4538
- refreshExpiresIn: lifetimes.refreshExpiresIn ?? jwtConfig.refreshExpiresIn
4617
+ return {
4618
+ secret: config.secret,
4619
+ accessExpiresIn: config.accessExpiresIn ?? DEFAULT_ACCESS_EXPIRES_IN,
4620
+ refreshExpiresIn: config.refreshExpiresIn ?? "30d",
4621
+ signingKeys: resolved,
4622
+ activeSigningKey: active
4623
+ };
4624
+ }
4625
+ var UNCONFIGURED = {
4626
+ secret: "",
4627
+ accessExpiresIn: DEFAULT_ACCESS_EXPIRES_IN,
4628
+ refreshExpiresIn: "30d",
4629
+ signingKeys: [],
4630
+ activeSigningKey: null
4631
+ };
4632
+ /** Fallback when `refreshExpiresIn` is unset. {@link createJwtService} refuses an unparseable one. */
4633
+ var DEFAULT_REFRESH_TTL_MS = 720 * 60 * 60 * 1e3;
4634
+ function jwtServiceFrom(state) {
4635
+ const { secret, signingKeys, activeSigningKey } = state;
4636
+ const requireSecret = () => {
4637
+ if (!secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4638
+ return secret;
4639
+ };
4640
+ const getAccessTokenExpiryMs = () => parseDurationMs(state.accessExpiresIn || DEFAULT_ACCESS_EXPIRES_IN) ?? 3600 * 1e3;
4641
+ const getRefreshTokenTtlMs = () => {
4642
+ const duration = state.refreshExpiresIn;
4643
+ return (duration ? parseDurationMs(duration) : null) ?? DEFAULT_REFRESH_TTL_MS;
4644
+ };
4645
+ return {
4646
+ accessExpiresIn: state.accessExpiresIn,
4647
+ refreshExpiresIn: state.refreshExpiresIn,
4648
+ isConfigured: () => Boolean(secret),
4649
+ hasAsymmetricSigningKey: () => activeSigningKey !== null,
4650
+ getJwks: () => toJwks(signingKeys),
4651
+ hmacSecret: () => secret || void 0,
4652
+ async generateAccessToken(uid, roles, aal = "aal1", customClaims, isAnonymous = false, sessionId) {
4653
+ const key = requireSecret();
4654
+ const payload = {
4655
+ ...customClaims,
4656
+ uid,
4657
+ roles,
4658
+ aal,
4659
+ ...isAnonymous ? { isAnonymous: true } : {},
4660
+ ...sessionId ? { sid: sessionId } : {}
4661
+ };
4662
+ if (activeSigningKey) return signJwt(payload, activeSigningKey.privateKey, {
4663
+ expiresIn: state.accessExpiresIn,
4664
+ algorithm: activeSigningKey.algorithm,
4665
+ keyid: activeSigningKey.kid
4666
+ });
4667
+ return signJwt(payload, key, {
4668
+ expiresIn: state.accessExpiresIn,
4669
+ algorithm: "HS256"
4670
+ });
4671
+ },
4672
+ async verifyAccessToken(token) {
4673
+ const key = requireSecret();
4674
+ try {
4675
+ const header = decodeProtectedHeader(token);
4676
+ const namedKey = resolveVerificationKey(signingKeys, header.kid);
4677
+ const decoded = namedKey ? await verifyJwt(token, namedKey.publicKey, { algorithms: [namedKey.algorithm] }) : await verifyJwt(token, key, { algorithms: ["HS256"] });
4678
+ if (decoded.purpose) {
4679
+ logger.error("[JWT] Verification failed: a purpose-scoped token is not an access token", { purpose: decoded.purpose });
4680
+ return null;
4681
+ }
4682
+ const id = decoded.uid || decoded.sub;
4683
+ if (!id) {
4684
+ logger.error("[JWT] Verification failed: missing id in payload", { detail: decoded });
4685
+ return null;
4686
+ }
4687
+ const aal = decoded.aal === "aal1" || decoded.aal === "aal2" ? decoded.aal : "aal1";
4688
+ return {
4689
+ uid: id,
4690
+ roles: decoded.roles || [],
4691
+ aal,
4692
+ ...decoded.isAnonymous === true ? { isAnonymous: true } : {},
4693
+ iat: decoded.iat,
4694
+ ...typeof decoded.exp === "number" ? { exp: decoded.exp } : {},
4695
+ ...typeof decoded.sid === "string" && decoded.sid ? { sid: decoded.sid } : {},
4696
+ ...(() => {
4697
+ const claims = customClaimsOf(decoded);
4698
+ return claims ? { claims } : {};
4699
+ })()
4700
+ };
4701
+ } catch (error) {
4702
+ if (isExpiredJwtError(error)) {
4703
+ logger.debug("[JWT] Access token expired");
4704
+ return null;
4705
+ }
4706
+ logger.error("[JWT] Verification failed", { error });
4707
+ return null;
4708
+ }
4709
+ },
4710
+ getAccessTokenExpiryMs,
4711
+ getAccessTokenExpiry: () => Date.now() + getAccessTokenExpiryMs(),
4712
+ getRefreshTokenTtlMs,
4713
+ getRefreshTokenExpiry: () => new Date(Date.now() + getRefreshTokenTtlMs()),
4714
+ async generateMfaPendingToken(uid, expiresInSeconds = 300) {
4715
+ const key = requireSecret();
4716
+ return signJwt({
4717
+ purpose: MFA_PENDING_PURPOSE,
4718
+ uid
4719
+ }, key, {
4720
+ expiresIn: expiresInSeconds,
4721
+ algorithm: "HS256"
4722
+ });
4723
+ },
4724
+ async verifyMfaPendingToken(token) {
4725
+ const key = requireSecret();
4726
+ try {
4727
+ const decoded = await verifyJwt(token, key, { algorithms: ["HS256"] });
4728
+ if (decoded.purpose !== "mfa-pending" || !decoded.uid) return null;
4729
+ return {
4730
+ uid: decoded.uid,
4731
+ iat: typeof decoded.iat === "number" ? decoded.iat : void 0
4732
+ };
4733
+ } catch {
4734
+ return null;
4735
+ }
4736
+ },
4737
+ async generateDownloadToken(path, expiresInSeconds = 300, storageId, principal) {
4738
+ const key = requireSecret();
4739
+ return signJwt({
4740
+ purpose: "file-read",
4741
+ path,
4742
+ storageId: canonicalStorageId(storageId),
4743
+ ...principal ? { rl: await downloadTokenRateLimitMark(principal) } : {}
4744
+ }, key, {
4745
+ expiresIn: expiresInSeconds,
4746
+ algorithm: "HS256"
4747
+ });
4748
+ },
4749
+ async verifyDownloadToken(token) {
4750
+ const key = requireSecret();
4751
+ try {
4752
+ const decoded = await verifyJwt(token, key, { algorithms: ["HS256"] });
4753
+ if (decoded && decoded.purpose === "file-read" && typeof decoded.path === "string") return {
4754
+ purpose: "file-read",
4755
+ path: decoded.path,
4756
+ storageId: canonicalStorageId(typeof decoded.storageId === "string" ? decoded.storageId : null),
4757
+ ...typeof decoded.rl === "string" ? { rl: decoded.rl } : {}
4758
+ };
4759
+ return null;
4760
+ } catch (error) {
4761
+ logger.debug("[JWT] Download token verification failed", { error });
4762
+ return null;
4763
+ }
4764
+ },
4765
+ async signPurposeToken(purpose, claims, expiresInSeconds) {
4766
+ const key = requireSecret();
4767
+ return signJwt({
4768
+ ...claims,
4769
+ purpose
4770
+ }, key, {
4771
+ expiresIn: expiresInSeconds,
4772
+ algorithm: "HS256"
4773
+ });
4774
+ },
4775
+ async verifyPurposeToken(purpose, token) {
4776
+ const key = requireSecret();
4777
+ if (!token) return null;
4778
+ try {
4779
+ const decoded = await verifyJwt(token, key, { algorithms: ["HS256"] });
4780
+ if (!decoded || decoded.purpose !== purpose) return null;
4781
+ return decoded;
4782
+ } catch (error) {
4783
+ logger.error("[JWT] Purpose token verification failed", {
4784
+ purpose,
4785
+ error
4786
+ });
4787
+ return null;
4788
+ }
4789
+ },
4790
+ async generateMcpAccessToken(payload, expiresInSeconds) {
4791
+ const key = requireSecret();
4792
+ return signJwt({
4793
+ purpose: MCP_ACCESS_PURPOSE,
4794
+ uid: payload.uid,
4795
+ roles: payload.roles,
4796
+ scope: payload.scope,
4797
+ clientId: payload.clientId,
4798
+ aud: payload.aud,
4799
+ iss: payload.iss
4800
+ }, key, {
4801
+ expiresIn: expiresInSeconds,
4802
+ algorithm: "HS256"
4803
+ });
4804
+ },
4805
+ async verifyMcpAccessToken(token, expectedAudience) {
4806
+ const key = requireSecret();
4807
+ try {
4808
+ const decoded = await verifyJwt(token, key, { algorithms: ["HS256"] });
4809
+ if (!decoded || decoded.purpose !== "mcp-access") return null;
4810
+ if (typeof decoded.uid !== "string" || !decoded.uid) return null;
4811
+ if (typeof decoded.aud !== "string" || decoded.aud !== expectedAudience) {
4812
+ logger.error("[JWT] MCP token rejected: audience mismatch", {
4813
+ expected: expectedAudience,
4814
+ got: decoded.aud
4815
+ });
4816
+ return null;
4817
+ }
4818
+ return {
4819
+ purpose: MCP_ACCESS_PURPOSE,
4820
+ uid: decoded.uid,
4821
+ roles: Array.isArray(decoded.roles) ? decoded.roles.map(String) : [],
4822
+ scope: typeof decoded.scope === "string" ? decoded.scope : "",
4823
+ clientId: typeof decoded.clientId === "string" ? decoded.clientId : "",
4824
+ aud: decoded.aud,
4825
+ iss: typeof decoded.iss === "string" ? decoded.iss : ""
4826
+ };
4827
+ } catch (error) {
4828
+ logger.error("[JWT] MCP token verification failed", { error });
4829
+ return null;
4830
+ }
4831
+ }
4539
4832
  };
4540
- signingKeys = resolved;
4541
- activeSigningKey = active;
4542
4833
  }
4543
4834
  /**
4544
- * The public keys, in JWKS form, for `/.well-known/jwks.json`.
4835
+ * The service the module-level functions below answer for.
4545
4836
  *
4546
- * An empty `keys` array on a backend with no asymmetric keys configured is the
4547
- * correct answer rather than a 404: it says "this issuer publishes none",
4548
- * which a verifier can act on, where a 404 is indistinguishable from a
4549
- * misconfigured URL.
4837
+ * Unconfigured until {@link configureJwt} is called or a backend is installed
4838
+ * as the process default (`initializeRebaseBackend`, `setDefaultBackend`).
4839
+ * `createRebaseBackend` never touches it: a backend it builds signs and
4840
+ * verifies with `backend.services.jwt`.
4550
4841
  */
4551
- function getJwks() {
4552
- return toJwks(signingKeys);
4553
- }
4554
- /** Is this backend signing access tokens asymmetrically? */
4555
- function hasAsymmetricSigningKey() {
4556
- return activeSigningKey !== null;
4557
- }
4842
+ var processJwt = jwtServiceFrom(UNCONFIGURED);
4558
4843
  /**
4559
- * Has this server been given a JWT secret?
4844
+ * Configure the process default's JWT settings — what `verifyAccessToken`,
4845
+ * `generateAccessToken` and the other module-level functions sign and verify
4846
+ * with.
4560
4847
  *
4561
- * False on every backend that authenticates through an adapter — Firebase,
4562
- * Clerk, anything with its own tokens — because nothing calls
4563
- * {@link configureJwt} there. Signing paths still throw when it is false, since
4564
- * asking for a token from a server that cannot mint one is a mistake worth
4565
- * hearing about. Paths whose contract is *"tolerate the absence of auth"* must
4566
- * ask first: `optionalAuth` crashing a request because this backend does not do
4567
- * JWT is a 500 on a route that had already decided anonymous was acceptable.
4848
+ * Validates the secret strength to prevent deployment with default/weak
4849
+ * secrets. A lifetime left unset keeps the one already configured; a
4850
+ * configuration that fails validation leaves the previous one standing rather
4851
+ * than half-applying a new one.
4568
4852
  */
4569
- function isJwtConfigured() {
4570
- return Boolean(jwtConfig.secret);
4853
+ function configureJwt(config) {
4854
+ processJwt = createJwtService({
4855
+ ...config,
4856
+ accessExpiresIn: config.accessExpiresIn ?? processJwt.accessExpiresIn,
4857
+ refreshExpiresIn: config.refreshExpiresIn ?? processJwt.refreshExpiresIn
4858
+ });
4859
+ }
4860
+ /** @internal See {@link defaultJwtService}. */
4861
+ function setDefaultJwtService(service) {
4862
+ processJwt = service;
4571
4863
  }
4572
4864
  /**
4573
- * Generate an access token (short-lived, 1 hour by default)
4865
+ * The JWT service serving this request: the backend's whose route group is
4866
+ * answering it, or the process default for a route mounted outside any
4867
+ * backend.
4574
4868
  */
4575
- async function generateAccessToken(uid, roles, aal = "aal1", customClaims, isAnonymous = false) {
4576
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4577
- const payload = {
4578
- ...customClaims,
4579
- uid,
4580
- roles,
4581
- aal,
4582
- ...isAnonymous ? { isAnonymous: true } : {}
4583
- };
4584
- if (activeSigningKey) return signJwt(payload, activeSigningKey.privateKey, {
4585
- expiresIn: jwtConfig.accessExpiresIn,
4586
- algorithm: activeSigningKey.algorithm,
4587
- keyid: activeSigningKey.kid
4588
- });
4589
- return signJwt(payload, jwtConfig.secret, {
4590
- expiresIn: jwtConfig.accessExpiresIn,
4591
- algorithm: "HS256"
4592
- });
4869
+ function jwtOf(c) {
4870
+ return c.get("services")?.jwt ?? processJwtService;
4871
+ }
4872
+ /** See {@link JwtService.getJwks}. Answers for the process default. */
4873
+ function getJwks() {
4874
+ return processJwt.getJwks();
4593
4875
  }
4594
4876
  /**
4595
- * Get the expiration time of an access token in milliseconds from now
4877
+ * The secret {@link configureJwt} was given, or undefined before it is called.
4878
+ * Answers for the process default — see {@link JwtService.hmacSecret}.
4596
4879
  */
4880
+ function configuredJwtSecret() {
4881
+ return processJwt.hmacSecret();
4882
+ }
4883
+ /** See {@link JwtService.hasAsymmetricSigningKey}. Answers for the process default. */
4884
+ function hasAsymmetricSigningKey() {
4885
+ return processJwt.hasAsymmetricSigningKey();
4886
+ }
4887
+ /** See {@link JwtService.isConfigured}. Answers for the process default. */
4888
+ function isJwtConfigured() {
4889
+ return processJwt.isConfigured();
4890
+ }
4891
+ /** See {@link JwtService.generateAccessToken}. Answers for the process default. */
4892
+ async function generateAccessToken(uid, roles, aal = "aal1", customClaims, isAnonymous = false, sessionId) {
4893
+ return processJwt.generateAccessToken(uid, roles, aal, customClaims, isAnonymous, sessionId);
4894
+ }
4895
+ /** See {@link JwtService.getAccessTokenExpiryMs}. Answers for the process default. */
4597
4896
  function getAccessTokenExpiryMs() {
4598
- return parseDurationMs(jwtConfig.accessExpiresIn || "1h") ?? 3600 * 1e3;
4897
+ return processJwt.getAccessTokenExpiryMs();
4599
4898
  }
4600
4899
  var DURATION_UNIT_MS = {
4601
4900
  ms: 1,
@@ -4649,63 +4948,17 @@ function parseDurationMs(value) {
4649
4948
  const ms = parseFloat(match[1]) * unit;
4650
4949
  return ms > 0 && Number.isFinite(ms) ? ms : null;
4651
4950
  }
4652
- /**
4653
- * Get the expiration timestamp for an access token
4654
- */
4951
+ /** See {@link JwtService.getAccessTokenExpiry}. Answers for the process default. */
4655
4952
  function getAccessTokenExpiry() {
4656
- return Date.now() + getAccessTokenExpiryMs();
4953
+ return processJwt.getAccessTokenExpiry();
4657
4954
  }
4658
- /**
4659
- * Verify and decode an access token.
4660
- *
4661
- * Every token this server issues is signed with the same secret, so what a
4662
- * token *is* comes from its claims, not from its signature. A download token
4663
- * ({@link generateDownloadToken}) is therefore a validly-signed string that
4664
- * must never authenticate anybody: it is scoped to one file path and handed out
4665
- * in URLs, which is a far weaker thing to hold than a session.
4666
- *
4667
- * Today it is rejected below for want of an id — but only by luck, since
4668
- * nothing stops a future download token from carrying one. So the purpose is
4669
- * checked explicitly: a token minted for reading a file is not a token for
4670
- * being a user.
4671
- */
4955
+ /** See {@link JwtService.verifyAccessToken}. Answers for the process default. */
4672
4956
  async function verifyAccessToken(token) {
4673
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4674
- try {
4675
- const header = decodeProtectedHeader(token);
4676
- const namedKey = resolveVerificationKey(signingKeys, header.kid);
4677
- const decoded = namedKey ? await verifyJwt(token, namedKey.publicKey, { algorithms: [namedKey.algorithm] }) : await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4678
- if (decoded.purpose) {
4679
- logger.error("[JWT] Verification failed: a purpose-scoped token is not an access token", { purpose: decoded.purpose });
4680
- return null;
4681
- }
4682
- const id = decoded.uid || decoded.sub;
4683
- if (!id) {
4684
- logger.error("[JWT] Verification failed: missing id in payload", { detail: decoded });
4685
- return null;
4686
- }
4687
- const aal = decoded.aal === "aal1" || decoded.aal === "aal2" ? decoded.aal : "aal1";
4688
- return {
4689
- uid: id,
4690
- roles: decoded.roles || [],
4691
- aal,
4692
- ...decoded.isAnonymous === true ? { isAnonymous: true } : {},
4693
- iat: decoded.iat,
4694
- ...(() => {
4695
- const claims = customClaimsOf(decoded);
4696
- return claims ? { claims } : {};
4697
- })()
4698
- };
4699
- } catch (error) {
4700
- logger.error("[JWT] Verification failed", {
4701
- error,
4702
- detail: token.substring(0, 15)
4703
- });
4704
- return null;
4705
- }
4957
+ return processJwt.verifyAccessToken(token);
4706
4958
  }
4707
4959
  /**
4708
- * Generate a random refresh token (long-lived, 30 days by default)
4960
+ * Generate a random refresh token. Long-lived: 30 days by default, sliding —
4961
+ * see {@link JwtService.getRefreshTokenTtlMs}.
4709
4962
  */
4710
4963
  function generateRefreshToken() {
4711
4964
  return randomHex(40);
@@ -4723,249 +4976,95 @@ function hashRefreshToken(token) {
4723
4976
  * what is actually enforced, which is worse than knowing the ceiling.
4724
4977
  */
4725
4978
  var MAX_COOKIE_AGE_MS = 400 * 24 * 60 * 60 * 1e3;
4726
- /** Fallback when `refreshExpiresIn` is unset. {@link configureJwt} refuses an unparseable one. */
4727
- var DEFAULT_REFRESH_TTL_MS = MAX_COOKIE_AGE_MS;
4728
- /**
4729
- * How long a refresh token is valid for, in milliseconds.
4730
- *
4731
- * Every rotation issues a token with a fresh TTL, so this is a sliding window:
4732
- * a user who visits at all keeps their session indefinitely, and one who
4733
- * disappears loses it this long after their last visit. That is what both
4734
- * Firebase and Supabase do by default, and it is the behaviour people mean
4735
- * when they say they expect to still be signed in.
4736
- */
4979
+ /** See {@link JwtService.getRefreshTokenTtlMs}. Answers for the process default. */
4737
4980
  function getRefreshTokenTtlMs() {
4738
- const duration = jwtConfig.refreshExpiresIn;
4739
- return (duration ? parseDurationMs(duration) : null) ?? DEFAULT_REFRESH_TTL_MS;
4981
+ return processJwt.getRefreshTokenTtlMs();
4740
4982
  }
4741
- /**
4742
- * Calculate refresh token expiration date
4743
- */
4983
+ /** See {@link JwtService.getRefreshTokenExpiry}. Answers for the process default. */
4744
4984
  function getRefreshTokenExpiry() {
4745
- return new Date(Date.now() + getRefreshTokenTtlMs());
4985
+ return processJwt.getRefreshTokenExpiry();
4746
4986
  }
4747
4987
  /**
4748
4988
  * The `purpose` claim carried by a credential that stands between "first factor
4749
4989
  * accepted" and "session issued".
4750
4990
  *
4751
4991
  * A pre-auth token is NOT a session and must never be usable as one:
4752
- * {@link verifyAccessToken} refuses any token carrying a `purpose`, so this
4753
- * value cannot authenticate a request no matter which route it is presented to.
4754
- * The only thing that reads it is the MFA challenge pair, which exchanges it —
4755
- * plus a second factor — for a real session.
4992
+ * {@link JwtService.verifyAccessToken} refuses any token carrying a `purpose`,
4993
+ * so this value cannot authenticate a request no matter which route it is
4994
+ * presented to. The only thing that reads it is the MFA challenge pair, which
4995
+ * exchanges it — plus a second factor — for a real session.
4756
4996
  */
4757
4997
  var MFA_PENDING_PURPOSE = "mfa-pending";
4758
- /**
4759
- * Mint the short-lived credential handed back with an `MFA_REQUIRED` response.
4760
- *
4761
- * Short-lived on purpose: it is the window in which a caller who has proven the
4762
- * first factor may present the second, not a session to be carried around. Five
4763
- * minutes matches the challenge TTL.
4764
- */
4998
+ /** See {@link JwtService.generateMfaPendingToken}. Answers for the process default. */
4765
4999
  async function generateMfaPendingToken(uid, expiresInSeconds = 300) {
4766
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4767
- return signJwt({
4768
- purpose: MFA_PENDING_PURPOSE,
4769
- uid
4770
- }, jwtConfig.secret, {
4771
- expiresIn: expiresInSeconds,
4772
- algorithm: "HS256"
4773
- });
5000
+ return processJwt.generateMfaPendingToken(uid, expiresInSeconds);
4774
5001
  }
4775
- /**
4776
- * Verify a pre-auth token and return the user it was minted for.
4777
- *
4778
- * Returns `null` for anything else — including a perfectly valid *access*
4779
- * token, which must not be interchangeable with this one in either direction.
4780
- *
4781
- * `iat` comes back with the uid because this token is a first factor that was
4782
- * accepted at that instant, and a sign-out everywhere after it has to void it
4783
- * the way it voids an access token — see `isAccessTokenRevoked`.
4784
- */
5002
+ /** See {@link JwtService.verifyMfaPendingToken}. Answers for the process default. */
4785
5003
  async function verifyMfaPendingToken(token) {
4786
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4787
- try {
4788
- const decoded = await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4789
- if (decoded.purpose !== "mfa-pending" || !decoded.uid) return null;
4790
- return {
4791
- uid: decoded.uid,
4792
- iat: typeof decoded.iat === "number" ? decoded.iat : void 0
4793
- };
4794
- } catch {
4795
- return null;
4796
- }
5004
+ return processJwt.verifyMfaPendingToken(token);
4797
5005
  }
4798
- /**
4799
- * Generate a short-lived download token scoped to a specific file path or prefix
4800
- * *within one storage source*.
4801
- *
4802
- * Both halves of that scope are load-bearing. A key is only unique inside its
4803
- * own bucket, and a project with more than one source routinely holds the same
4804
- * key in several of them — `avatars/u1.png` in `(default)` and in `media` are
4805
- * different objects, quite possibly with different owners. A token that names
4806
- * only the path is therefore a grant on every source at once: authorize a read
4807
- * on the source whose `storageAuthorize` hook says yes, then spend the token
4808
- * against `?storageId=` pointing somewhere else.
4809
- *
4810
- * `storageId` is optional here only because omitting it *is* the default
4811
- * source, which is what the overwhelming majority of deployments have. A mint
4812
- * site that forgets to pass a named source produces a default-scoped token,
4813
- * which fails closed at `/file/*` rather than over-granting.
4814
- */
4815
- async function generateDownloadToken(path, expiresInSeconds = 300, storageId) {
4816
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4817
- return signJwt({
4818
- purpose: "file-read",
4819
- path,
4820
- storageId: canonicalStorageId(storageId)
4821
- }, jwtConfig.secret, {
4822
- expiresIn: expiresInSeconds,
4823
- algorithm: "HS256"
4824
- });
5006
+ /** See {@link JwtService.generateDownloadToken}. Answers for the process default. */
5007
+ async function generateDownloadToken(path, expiresInSeconds = 300, storageId, principal) {
5008
+ return processJwt.generateDownloadToken(path, expiresInSeconds, storageId, principal);
4825
5009
  }
4826
- /**
4827
- * Verify and decode a download token.
4828
- *
4829
- * A token minted before `storageId` existed carries no such claim. It is read
4830
- * as a grant on the **default source** rather than on all of them: that is the
4831
- * fail-closed reading, and it is what such a token almost always was, since a
4832
- * named source has to be asked for explicitly. The cost is bounded by the
4833
- * five-minute TTL — for at most that long after a deploy, an in-flight token
4834
- * for a *named* source is refused and the client re-fetches `/metadata`.
4835
- */
5010
+ /** See {@link DownloadTokenPayload.rl}. */
5011
+ async function downloadTokenRateLimitMark(principal) {
5012
+ return (await sha256Hex(`download-token-rl:${principal}`)).slice(0, 32);
5013
+ }
5014
+ /** See {@link JwtService.verifyDownloadToken}. Answers for the process default. */
4836
5015
  async function verifyDownloadToken(token) {
4837
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4838
- try {
4839
- const decoded = await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4840
- if (decoded && decoded.purpose === "file-read" && typeof decoded.path === "string") return {
4841
- purpose: "file-read",
4842
- path: decoded.path,
4843
- storageId: canonicalStorageId(typeof decoded.storageId === "string" ? decoded.storageId : null)
4844
- };
4845
- return null;
4846
- } catch (error) {
4847
- logger.error("[JWT] Download token verification failed", { error });
4848
- return null;
4849
- }
5016
+ return processJwt.verifyDownloadToken(token);
4850
5017
  }
4851
- /**
4852
- * Sign a short-lived token that carries state across a redirect.
4853
- *
4854
- * The generalisation of the MFA-pending token: a signed blob the server hands
4855
- * out and expects back, where the point is that the holder cannot edit it. The
4856
- * OAuth authorize flow needs one to carry the authorization request across the
4857
- * sign-in hop — without it, the endpoint the consent page posts to would mint a
4858
- * code for whatever `client_id` and `redirect_uri` its caller typed, skipping
4859
- * every check the authorize endpoint performs.
4860
- *
4861
- * The `purpose` is not decoration. {@link verifyAccessToken} refuses any token
4862
- * that has one, so nothing minted here can be replayed as a session, and
4863
- * {@link verifyPurposeToken} refuses a token whose purpose is not the one the
4864
- * caller named — so a token minted for one hop cannot be spent on another.
4865
- */
5018
+ /** See {@link JwtService.signPurposeToken}. Answers for the process default. */
4866
5019
  async function signPurposeToken(purpose, claims, expiresInSeconds) {
4867
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4868
- return signJwt({
4869
- ...claims,
4870
- purpose
4871
- }, jwtConfig.secret, {
4872
- expiresIn: expiresInSeconds,
4873
- algorithm: "HS256"
4874
- });
5020
+ return processJwt.signPurposeToken(purpose, claims, expiresInSeconds);
4875
5021
  }
4876
- /** Verify a token minted by {@link signPurposeToken}, for one named purpose. */
5022
+ /** See {@link JwtService.verifyPurposeToken}. Answers for the process default. */
4877
5023
  async function verifyPurposeToken(purpose, token) {
4878
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4879
- if (!token) return null;
4880
- try {
4881
- const decoded = await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4882
- if (!decoded || decoded.purpose !== purpose) return null;
4883
- return decoded;
4884
- } catch (error) {
4885
- logger.error("[JWT] Purpose token verification failed", {
4886
- purpose,
4887
- error
4888
- });
4889
- return null;
4890
- }
5024
+ return processJwt.verifyPurposeToken(purpose, token);
4891
5025
  }
4892
5026
  /** The `purpose` that marks a token as belonging to the MCP resource. */
4893
5027
  var MCP_ACCESS_PURPOSE = "mcp-access";
4894
- /**
4895
- * Mint an access token for the MCP endpoint.
4896
- *
4897
- * Deliberately **not** an ordinary session token, and the `purpose` claim is
4898
- * what enforces that: {@link verifyAccessToken} refuses any token carrying one,
4899
- * so a credential issued to a third-party MCP client — Claude, an IDE, anything
4900
- * that completed the OAuth flow — cannot be replayed against `/api/data`,
4901
- * `/api/admin` or the websocket. It buys exactly the one resource it names.
4902
- *
4903
- * That asymmetry matters more than it first looks. These tokens are handed to
4904
- * software the user authorized but the operator never vetted, they live in that
4905
- * software's storage, and they are the only credential in this system minted
4906
- * for a third party. Without the purpose separation, "connect Claude to your
4907
- * data" would silently mean "give Claude a full session".
4908
- *
4909
- * The audience is the canonical resource URI, and {@link verifyMcpAccessToken}
4910
- * requires the caller to say which resource it is checking for. A token minted
4911
- * for another Rebase project — or another path on this one — fails there.
4912
- *
4913
- * HS256 with the shared secret, like the download token: the authorization
4914
- * server and the resource server are the same process, so nothing outside it
4915
- * ever needs to verify one, and an asymmetric key here would be a second key
4916
- * surface bought for nobody.
4917
- */
5028
+ /** See {@link JwtService.generateMcpAccessToken}. Answers for the process default. */
4918
5029
  async function generateMcpAccessToken(payload, expiresInSeconds) {
4919
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4920
- return signJwt({
4921
- purpose: MCP_ACCESS_PURPOSE,
4922
- uid: payload.uid,
4923
- roles: payload.roles,
4924
- scope: payload.scope,
4925
- clientId: payload.clientId,
4926
- aud: payload.aud,
4927
- iss: payload.iss
4928
- }, jwtConfig.secret, {
4929
- expiresIn: expiresInSeconds,
4930
- algorithm: "HS256"
4931
- });
5030
+ return processJwt.generateMcpAccessToken(payload, expiresInSeconds);
4932
5031
  }
4933
- /**
4934
- * Verify an MCP access token **for a specific resource**.
4935
- *
4936
- * `expectedAudience` has no default. The MCP specification requires a resource
4937
- * server to validate that a token was issued for it specifically, and a default
4938
- * would be the thing that quietly turns that check off — the caller has to say
4939
- * what it is, every time, so a new call site cannot forget by omission.
4940
- */
5032
+ /** See {@link JwtService.verifyMcpAccessToken}. Answers for the process default. */
4941
5033
  async function verifyMcpAccessToken(token, expectedAudience) {
4942
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4943
- try {
4944
- const decoded = await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4945
- if (!decoded || decoded.purpose !== "mcp-access") return null;
4946
- if (typeof decoded.uid !== "string" || !decoded.uid) return null;
4947
- if (typeof decoded.aud !== "string" || decoded.aud !== expectedAudience) {
4948
- logger.error("[JWT] MCP token rejected: audience mismatch", {
4949
- expected: expectedAudience,
4950
- got: decoded.aud
4951
- });
4952
- return null;
4953
- }
4954
- return {
4955
- purpose: MCP_ACCESS_PURPOSE,
4956
- uid: decoded.uid,
4957
- roles: Array.isArray(decoded.roles) ? decoded.roles.map(String) : [],
4958
- scope: typeof decoded.scope === "string" ? decoded.scope : "",
4959
- clientId: typeof decoded.clientId === "string" ? decoded.clientId : "",
4960
- aud: decoded.aud,
4961
- iss: typeof decoded.iss === "string" ? decoded.iss : ""
4962
- };
4963
- } catch (error) {
4964
- logger.error("[JWT] MCP token verification failed", { error });
4965
- return null;
4966
- }
5034
+ return processJwt.verifyMcpAccessToken(token, expectedAudience);
4967
5035
  }
5036
+ /**
5037
+ * The process default as a service object that follows it — every call reads
5038
+ * whatever {@link configureJwt} or `setDefaultBackend` installed last. What a
5039
+ * route built outside any backend falls back to.
5040
+ */
5041
+ var processJwtService = {
5042
+ get accessExpiresIn() {
5043
+ return processJwt.accessExpiresIn;
5044
+ },
5045
+ get refreshExpiresIn() {
5046
+ return processJwt.refreshExpiresIn;
5047
+ },
5048
+ isConfigured: isJwtConfigured,
5049
+ hasAsymmetricSigningKey,
5050
+ getJwks,
5051
+ hmacSecret: configuredJwtSecret,
5052
+ generateAccessToken,
5053
+ verifyAccessToken,
5054
+ getAccessTokenExpiryMs,
5055
+ getAccessTokenExpiry,
5056
+ getRefreshTokenTtlMs,
5057
+ getRefreshTokenExpiry,
5058
+ generateMfaPendingToken,
5059
+ verifyMfaPendingToken,
5060
+ generateDownloadToken,
5061
+ verifyDownloadToken,
5062
+ signPurposeToken,
5063
+ verifyPurposeToken,
5064
+ generateMcpAccessToken,
5065
+ verifyMcpAccessToken
5066
+ };
4968
5067
  //#endregion
4969
- export { require_jsonwebtoken as C, sha256Bytes as D, randomInt as E, sha256Hex as O, normalizePemFromEnv as S, randomHex as T, verifyAccessToken as _, generateMcpAccessToken as a, verifyMfaPendingToken as b, getAccessTokenExpiry as c, getRefreshTokenTtlMs as d, hasAsymmetricSigningKey as f, signPurposeToken as g, jwt_exports as h, generateDownloadToken as i, getJwks as l, isJwtConfigured as m, configureJwt as n, generateMfaPendingToken as o, hashRefreshToken as p, generateAccessToken as r, generateRefreshToken as s, MAX_COOKIE_AGE_MS as t, getRefreshTokenExpiry as u, verifyDownloadToken as v, constantTimeEqual as w, verifyPurposeToken as x, verifyMcpAccessToken as y };
5068
+ export { randomHex as C, sha256Hex as E, constantTimeEqual as S, sha256Bytes as T, verifyAccessToken as _, generateAccessToken as a, normalizePemFromEnv as b, getAccessTokenExpiry as c, hasAsymmetricSigningKey as d, hashRefreshToken as f, setDefaultJwtService as g, processJwtService as h, customClaimsOf as i, getJwks as l, jwtOf as m, configureJwt as n, generateDownloadToken as o, isJwtConfigured as p, createJwtService as r, generateRefreshToken as s, MAX_COOKIE_AGE_MS as t, getRefreshTokenExpiry as u, verifyDownloadToken as v, randomInt as w, require_jsonwebtoken as x, verifyJwt as y };
4970
5069
 
4971
- //# sourceMappingURL=jwt-DnQHNFCl.js.map
5070
+ //# sourceMappingURL=jwt-CBaFc34Y.js.map