@rebasepro/server 0.24.0 → 0.24.1-canary.gcb918fc

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 (224) hide show
  1. package/README.md +27 -2
  2. package/dist/GCSStorageController-fv6JAZ87.js +402 -0
  3. package/dist/GCSStorageController-fv6JAZ87.js.map +1 -0
  4. package/dist/S3StorageController-BNg-pXiY.js +480 -0
  5. package/dist/S3StorageController-BNg-pXiY.js.map +1 -0
  6. package/dist/api/mount-path.d.ts +30 -0
  7. package/dist/api/mount.d.ts +6 -13
  8. package/dist/api/rest/api-generator.d.ts +53 -1
  9. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  10. package/dist/api/rest/field-access-query.d.ts +28 -8
  11. package/dist/api/rest/field-ops.d.ts +19 -7
  12. package/dist/api/rest/query-body.d.ts +10 -0
  13. package/dist/api/rest/write-validation.d.ts +7 -5
  14. package/dist/api/schema-editor-routes.d.ts +10 -1
  15. package/dist/api/sql-export-routes.d.ts +8 -0
  16. package/dist/api/types.d.ts +35 -0
  17. package/dist/{ast-schema-editor-CWqS_sLJ.js → ast-schema-editor-mCeSC03x.js} +3 -3
  18. package/dist/{ast-schema-editor-CWqS_sLJ.js.map → ast-schema-editor-mCeSC03x.js.map} +1 -1
  19. package/dist/auth/access.d.ts +57 -8
  20. package/dist/auth/admin-user-ops.d.ts +24 -2
  21. package/dist/auth/admin-users-route.d.ts +1 -0
  22. package/dist/auth/api-keys/api-key-middleware.d.ts +10 -3
  23. package/dist/auth/api-keys/api-key-routes.d.ts +4 -1
  24. package/dist/auth/builtin-auth-adapter.d.ts +13 -0
  25. package/dist/auth/email-change-routes.d.ts +3 -0
  26. package/dist/auth/expired-token-sweep.d.ts +5 -1
  27. package/dist/auth/interfaces.d.ts +10 -0
  28. package/dist/auth/jwks-routes.d.ts +5 -1
  29. package/dist/auth/jwt-crypto.d.ts +7 -0
  30. package/dist/auth/jwt-keys.d.ts +4 -0
  31. package/dist/auth/jwt.d.ts +282 -166
  32. package/dist/auth/magic-link-routes.d.ts +3 -0
  33. package/dist/auth/mfa-crypto.d.ts +9 -3
  34. package/dist/auth/mfa-gate.d.ts +4 -2
  35. package/dist/auth/mfa-routes.d.ts +6 -0
  36. package/dist/auth/middleware.d.ts +9 -2
  37. package/dist/auth/otp-routes.d.ts +3 -0
  38. package/dist/auth/rate-limiter.d.ts +61 -34
  39. package/dist/auth/registration-policy.d.ts +17 -0
  40. package/dist/auth/require-auth.d.ts +1 -1
  41. package/dist/auth/routes.d.ts +16 -0
  42. package/dist/auth/session-routes.d.ts +6 -0
  43. package/dist/auth/user-picking.d.ts +37 -0
  44. package/dist/auth/verify-credential.d.ts +25 -4
  45. package/dist/{auth-DMLngxn_.js → auth-GnONzteu.js} +2869 -1871
  46. package/dist/auth-GnONzteu.js.map +1 -0
  47. package/dist/auth_adapter-CIItCTc8.js +21 -0
  48. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  49. package/dist/backend/create-backend.d.ts +281 -0
  50. package/dist/backend/default-backend.d.ts +35 -0
  51. package/dist/backend/options.d.ts +912 -0
  52. package/dist/backend/scope.d.ts +23 -0
  53. package/dist/backend/services.d.ts +54 -0
  54. package/dist/backend-DTAOsLQc.js.map +1 -1
  55. package/dist/backup/backup-common.d.ts +14 -3
  56. package/dist/backup/backup-routes.d.ts +23 -2
  57. package/dist/backup/backup-storage.d.ts +3 -1
  58. package/dist/backup/index.d.ts +3 -1
  59. package/dist/{backup-CN0s50D2.js → backup-BnvkSLHi.js} +176 -35
  60. package/dist/backup-BnvkSLHi.js.map +1 -0
  61. package/dist/boot/boot.d.ts +2 -2
  62. package/dist/boot/driver.d.ts +3 -0
  63. package/dist/boot/env.d.ts +9 -2
  64. package/dist/boot/http-server.d.ts +22 -0
  65. package/dist/boot/options.d.ts +16 -1
  66. package/dist/boot/provision.d.ts +4 -2
  67. package/dist/boot/runtime-cors.d.ts +17 -0
  68. package/dist/boot/sources.d.ts +2 -0
  69. package/dist/boot/static-routing.d.ts +9 -0
  70. package/dist/{contract-routes-fz8i4pxs.js → contract-routes-Ey6ftnKH.js} +4 -20
  71. package/dist/contract-routes-Ey6ftnKH.js.map +1 -0
  72. package/dist/cron/cron-loader.d.ts +77 -18
  73. package/dist/cron/cron-routes.d.ts +1 -1
  74. package/dist/cron/cron-scheduler.d.ts +69 -3
  75. package/dist/cron/cron-store.d.ts +33 -5
  76. package/dist/cron/define-cron.d.ts +6 -2
  77. package/dist/cron/index.d.ts +3 -3
  78. package/dist/cron-loader-B-u0snqq.js +202 -0
  79. package/dist/cron-loader-B-u0snqq.js.map +1 -0
  80. package/dist/{cron-routes-Bc-SB0Se.js → cron-routes-OD6BstUj.js} +27 -4
  81. package/dist/cron-routes-OD6BstUj.js.map +1 -0
  82. package/dist/{cron-scheduler-CYQgco86.js → cron-scheduler-idtLPoN-.js} +299 -33
  83. package/dist/cron-scheduler-idtLPoN-.js.map +1 -0
  84. package/dist/cron-store-CZ30ZBi-.js +577 -0
  85. package/dist/cron-store-CZ30ZBi-.js.map +1 -0
  86. package/dist/email/smtp-email-service.d.ts +23 -0
  87. package/dist/email/types.d.ts +18 -0
  88. package/dist/env.d.ts +0 -37
  89. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  90. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  91. package/dist/{function-loader-D7o5Epjj.js → function-loader-iKhOZatS.js} +68 -38
  92. package/dist/function-loader-iKhOZatS.js.map +1 -0
  93. package/dist/{function-routes-CaNG4waN.js → function-routes-j9TwpvQe.js} +5 -4
  94. package/dist/function-routes-j9TwpvQe.js.map +1 -0
  95. package/dist/functions/context.d.ts +9 -0
  96. package/dist/functions/define-function.d.ts +10 -6
  97. package/dist/functions/function-loader.d.ts +75 -18
  98. package/dist/functions/function-routes.d.ts +2 -1
  99. package/dist/functions/index.d.ts +5 -4
  100. package/dist/functions/index.js +42 -13
  101. package/dist/functions/index.js.map +1 -1
  102. package/dist/functions/internal.d.ts +2 -2
  103. package/dist/functions/proxy.d.ts +6 -2
  104. package/dist/functions/request-timeout.d.ts +7 -5
  105. package/dist/history/history-routes.d.ts +9 -0
  106. package/dist/history/retention-sweep.d.ts +81 -0
  107. package/dist/{history-recorder-Nr8zLvoU.js → history-recorder-BfWRwmPy.js} +3 -3
  108. package/dist/{history-recorder-Nr8zLvoU.js.map → history-recorder-BfWRwmPy.js.map} +1 -1
  109. package/dist/{history-store-rcAm_xFR.js → history-store-DPofGVDp.js} +2 -2
  110. package/dist/{history-store-rcAm_xFR.js.map → history-store-DPofGVDp.js.map} +1 -1
  111. package/dist/index.d.ts +23 -10
  112. package/dist/index.es.js +24423 -20038
  113. package/dist/index.es.js.map +1 -1
  114. package/dist/init/docs.d.ts +27 -10
  115. package/dist/init/process-safety.d.ts +7 -2
  116. package/dist/init/shutdown.d.ts +18 -3
  117. package/dist/init.d.ts +49 -865
  118. package/dist/internal-tables-L2t39RdT.js +136 -0
  119. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  120. package/dist/jobs/job-queue.d.ts +8 -0
  121. package/dist/jobs/job-store.d.ts +24 -0
  122. package/dist/jobs/types.d.ts +25 -4
  123. package/dist/{jobs-DqYNfquG.js → jobs-BCbflZj2.js} +234 -24
  124. package/dist/jobs-BCbflZj2.js.map +1 -0
  125. package/dist/{jwt-R6bSPMjk.js → jwt-BlvuKoR6.js} +529 -454
  126. package/dist/jwt-BlvuKoR6.js.map +1 -0
  127. package/dist/{keys-GAVZqbqx.js → keys-COzQ2aE8.js} +2 -2
  128. package/dist/{keys-GAVZqbqx.js.map → keys-COzQ2aE8.js.map} +1 -1
  129. package/dist/load-failure-L5hvfDoG.js +80 -0
  130. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  131. package/dist/mcp/mcp-routes.d.ts +6 -0
  132. package/dist/mcp/mcp-tools.d.ts +8 -0
  133. package/dist/mcp/oauth-routes.d.ts +7 -0
  134. package/dist/metrics/index.d.ts +18 -0
  135. package/dist/metrics/pool-wait.d.ts +50 -0
  136. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  137. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  138. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  139. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  140. package/dist/{openapi-generator-DAq_XVDu.js → openapi-generator-D9zUQmEp.js} +366 -82
  141. package/dist/openapi-generator-D9zUQmEp.js.map +1 -0
  142. package/dist/policy-PdzX-Opd.js +121 -0
  143. package/dist/policy-PdzX-Opd.js.map +1 -0
  144. package/dist/project_manifest-R6y_KHms.js +59 -0
  145. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  146. package/dist/{proxy-qRlqeUmO.js → proxy-CkTlMGmh.js} +4 -3
  147. package/dist/proxy-CkTlMGmh.js.map +1 -0
  148. package/dist/{query-parser-BgiKJKvc.js → query-parser-4WkMoA_K.js} +510 -15
  149. package/dist/query-parser-4WkMoA_K.js.map +1 -0
  150. package/dist/{request-timeout-DgH7j8qO.js → request-timeout-Dnl2_OBc.js} +15 -8
  151. package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
  152. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  153. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  154. package/dist/schema-edit/apply-schema-change.d.ts +10 -0
  155. package/dist/schema-edit/local-source.d.ts +29 -0
  156. package/dist/schema-edit/remote-source.d.ts +13 -3
  157. package/dist/schema-edit/source-editor.d.ts +28 -0
  158. package/dist/{schema-editor-routes-oIyuWl3L.js → schema-editor-routes-C11Tumyd.js} +54 -10
  159. package/dist/schema-editor-routes-C11Tumyd.js.map +1 -0
  160. package/dist/services/outbound-post.d.ts +50 -0
  161. package/dist/services/outbound-url-guard.d.ts +34 -5
  162. package/dist/services/routed-realtime-service.d.ts +5 -0
  163. package/dist/services/webhook-service.d.ts +66 -14
  164. package/dist/singleton.d.ts +9 -5
  165. package/dist/soft-delete-params-DfV3vVN5.js +668 -0
  166. package/dist/soft-delete-params-DfV3vVN5.js.map +1 -0
  167. package/dist/sql-export-routes-ClRwzIsC.js +240 -0
  168. package/dist/sql-export-routes-ClRwzIsC.js.map +1 -0
  169. package/dist/{src-CatHFUym.js → src-D5Y3N-S4.js} +2 -1
  170. package/dist/{src-CatHFUym.js.map → src-D5Y3N-S4.js.map} +1 -1
  171. package/dist/storage/GCSStorageController.d.ts +46 -2
  172. package/dist/storage/LocalStorageController.d.ts +67 -2
  173. package/dist/storage/S3StorageController.d.ts +37 -3
  174. package/dist/storage/image-transform.d.ts +5 -0
  175. package/dist/storage/maintenance-routes.d.ts +23 -0
  176. package/dist/storage/maintenance.d.ts +126 -0
  177. package/dist/storage/rendition-cache.d.ts +9 -11
  178. package/dist/storage/request-principals.d.ts +30 -0
  179. package/dist/storage/routes.d.ts +20 -0
  180. package/dist/storage/source-variables.d.ts +10 -0
  181. package/dist/storage/streams.d.ts +42 -0
  182. package/dist/storage/tus-handler.d.ts +46 -2
  183. package/dist/storage/types.d.ts +82 -7
  184. package/dist/storage/upload-validation.d.ts +13 -0
  185. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  186. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  187. package/dist/strings.es-BeZWAodn.js +256 -0
  188. package/dist/strings.es-BeZWAodn.js.map +1 -0
  189. package/dist/upload-validation-Dd-fnsbA.js +172 -0
  190. package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
  191. package/dist/utils/byte-size.d.ts +10 -0
  192. package/dist/utils/compression.d.ts +6 -0
  193. package/dist/utils/load-failure.d.ts +14 -0
  194. package/package.json +5 -5
  195. package/dist/GCSStorageController-BSiP1c-f.js +0 -256
  196. package/dist/GCSStorageController-BSiP1c-f.js.map +0 -1
  197. package/dist/S3StorageController-CAwFRgjV.js +0 -276
  198. package/dist/S3StorageController-CAwFRgjV.js.map +0 -1
  199. package/dist/auth-DMLngxn_.js.map +0 -1
  200. package/dist/backup-CN0s50D2.js.map +0 -1
  201. package/dist/contract-routes-fz8i4pxs.js.map +0 -1
  202. package/dist/cron-loader-CwaANlOG.js +0 -118
  203. package/dist/cron-loader-CwaANlOG.js.map +0 -1
  204. package/dist/cron-routes-Bc-SB0Se.js.map +0 -1
  205. package/dist/cron-scheduler-CYQgco86.js.map +0 -1
  206. package/dist/cron-store-D2Q9-Aco.js +0 -300
  207. package/dist/cron-store-D2Q9-Aco.js.map +0 -1
  208. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  209. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  210. package/dist/function-loader-D7o5Epjj.js.map +0 -1
  211. package/dist/function-routes-CaNG4waN.js.map +0 -1
  212. package/dist/jobs-DqYNfquG.js.map +0 -1
  213. package/dist/jwt-R6bSPMjk.js.map +0 -1
  214. package/dist/openapi-generator-DAq_XVDu.js.map +0 -1
  215. package/dist/proxy-qRlqeUmO.js.map +0 -1
  216. package/dist/query-parser-BgiKJKvc.js.map +0 -1
  217. package/dist/request-timeout-DgH7j8qO.js.map +0 -1
  218. package/dist/schema-editor-routes-oIyuWl3L.js.map +0 -1
  219. package/dist/soft-delete-params-BWPilMPF.js +0 -59
  220. package/dist/soft-delete-params-BWPilMPF.js.map +0 -1
  221. package/dist/src-I3aG1PcY.js +0 -6870
  222. package/dist/src-I3aG1PcY.js.map +0 -1
  223. package/dist/types-BfKcm9do.js +0 -39
  224. 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";
5
+ import { i as __toESM, r as __require, t as __commonJSMin } from "./rolldown-runtime-dW7B1o5h.js";
6
6
  import { r as logger } from "./logger-D-S-hO5e.js";
7
- import { a as canonicalStorageId } from "./keys-GAVZqbqx.js";
8
- import { createPrivateKey, createPublicKey } from "crypto";
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,36 +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
- configuredJwtSecret: () => configuredJwtSecret,
4427
- customClaimsOf: () => customClaimsOf,
4428
- generateAccessToken: () => generateAccessToken,
4429
- generateDownloadToken: () => generateDownloadToken,
4430
- generateMcpAccessToken: () => generateMcpAccessToken,
4431
- generateMfaPendingToken: () => generateMfaPendingToken,
4432
- generateRefreshToken: () => generateRefreshToken,
4433
- getAccessTokenExpiry: () => getAccessTokenExpiry,
4434
- getAccessTokenExpiryMs: () => getAccessTokenExpiryMs,
4435
- getJwks: () => getJwks,
4436
- getRefreshTokenExpiry: () => getRefreshTokenExpiry,
4437
- getRefreshTokenTtlMs: () => getRefreshTokenTtlMs,
4438
- hasAsymmetricSigningKey: () => hasAsymmetricSigningKey,
4439
- hashRefreshToken: () => hashRefreshToken,
4440
- isJwtConfigured: () => isJwtConfigured,
4441
- parseDurationMs: () => parseDurationMs,
4442
- signPurposeToken: () => signPurposeToken,
4443
- verifyAccessToken: () => verifyAccessToken,
4444
- verifyDownloadToken: () => verifyDownloadToken,
4445
- verifyMcpAccessToken: () => verifyMcpAccessToken,
4446
- verifyMfaPendingToken: () => verifyMfaPendingToken,
4447
- verifyPurposeToken: () => verifyPurposeToken
4448
- });
4449
4522
  /**
4450
4523
  * Claims the token's own identity owns, and which therefore never appear in
4451
4524
  * {@link AccessTokenPayload.claims}.
@@ -4485,135 +4558,343 @@ function customClaimsOf(decoded) {
4485
4558
  }
4486
4559
  return Object.keys(claims).length > 0 ? claims : void 0;
4487
4560
  }
4488
- var jwtConfig = {
4489
- secret: "",
4490
- accessExpiresIn: "1h",
4491
- refreshExpiresIn: "400d"
4492
- };
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
+ ]);
4493
4585
  /**
4494
- * 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.
4495
4589
  *
4496
- * Both are module state beside `jwtConfig` for the same reason it is: every
4497
- * signing and verifying path in the server reads them through this file, and
4498
- * there is exactly one JWT configuration per process.
4499
- */
4500
- var signingKeys = [];
4501
- var activeSigningKey = null;
4502
- /**
4503
- * Configure JWT settings - call this during initialization.
4504
- * 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
+ * ```
4505
4599
  */
4506
- function configureJwt(config) {
4507
- const weakSecrets = /* @__PURE__ */ new Set([
4508
- "secret",
4509
- "jwt-secret",
4510
- "jwt_secret",
4511
- "your-secret",
4512
- "your-super-secret-jwt-key-change-in-production",
4513
- "super-secret-jwt-key-change-in-production",
4514
- "change-me",
4515
- "changeme",
4516
- "password",
4517
- "test",
4518
- "mysecret",
4519
- "my-secret",
4520
- "my_secret",
4521
- "example-secret",
4522
- "please-change-me",
4523
- "replace-this-with-a-real-secret",
4524
- "default-secret",
4525
- "rebase_saas_jwt_secret_must_be_long_long_long_long",
4526
- "rebase_saas_service_key_must_be_long_long_long_long"
4527
- ]);
4600
+ function createJwtService(config) {
4601
+ return jwtServiceFrom(checkJwtConfig(config));
4602
+ }
4603
+ function checkJwtConfig(config) {
4528
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'))\"");
4529
- 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'))\"");
4530
4606
  const resolved = config.signingKeys ? resolveSigningKeys(config.signingKeys) : [];
4531
4607
  let active = null;
4532
4608
  if (resolved.length > 0) if (config.activeKid) {
4533
4609
  active = resolved.find((key) => key.kid === config.activeKid) ?? null;
4534
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.`);
4535
4611
  } else active = resolved[0];
4536
- const lifetimes = {};
4537
4612
  for (const key of ["accessExpiresIn", "refreshExpiresIn"]) {
4538
4613
  const value = config[key];
4539
4614
  if (value === void 0) continue;
4540
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.`);
4541
- lifetimes[key] = value;
4542
4616
  }
4543
- jwtConfig = {
4544
- ...jwtConfig,
4545
- ...config,
4546
- accessExpiresIn: lifetimes.accessExpiresIn ?? jwtConfig.accessExpiresIn,
4547
- 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
+ }
4548
4832
  };
4549
- signingKeys = resolved;
4550
- activeSigningKey = active;
4551
4833
  }
4552
4834
  /**
4553
- * The public keys, in JWKS form, for `/.well-known/jwks.json`.
4835
+ * The service the module-level functions below answer for.
4836
+ *
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`.
4841
+ */
4842
+ var processJwt = jwtServiceFrom(UNCONFIGURED);
4843
+ /**
4844
+ * Configure the process default's JWT settings — what `verifyAccessToken`,
4845
+ * `generateAccessToken` and the other module-level functions sign and verify
4846
+ * with.
4554
4847
  *
4555
- * An empty `keys` array on a backend with no asymmetric keys configured is the
4556
- * correct answer rather than a 404: it says "this issuer publishes none",
4557
- * which a verifier can act on, where a 404 is indistinguishable from a
4558
- * misconfigured URL.
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.
4559
4852
  */
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;
4863
+ }
4864
+ /**
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.
4868
+ */
4869
+ function jwtOf(c) {
4870
+ return c.get("services")?.jwt ?? processJwtService;
4871
+ }
4872
+ /** See {@link JwtService.getJwks}. Answers for the process default. */
4560
4873
  function getJwks() {
4561
- return toJwks(signingKeys);
4874
+ return processJwt.getJwks();
4562
4875
  }
4563
4876
  /**
4564
- * The secret `configureJwt` was given, or undefined before it is called. For
4565
- * `mfa-crypto.ts`, which falls back to it when no MFA key is set — the secret
4566
- * passed in code, not only the one in `JWT_SECRET`.
4877
+ * The secret {@link configureJwt} was given, or undefined before it is called.
4878
+ * Answers for the process default — see {@link JwtService.hmacSecret}.
4567
4879
  */
4568
4880
  function configuredJwtSecret() {
4569
- return jwtConfig.secret || void 0;
4881
+ return processJwt.hmacSecret();
4570
4882
  }
4571
- /** Is this backend signing access tokens asymmetrically? */
4883
+ /** See {@link JwtService.hasAsymmetricSigningKey}. Answers for the process default. */
4572
4884
  function hasAsymmetricSigningKey() {
4573
- return activeSigningKey !== null;
4885
+ return processJwt.hasAsymmetricSigningKey();
4574
4886
  }
4575
- /**
4576
- * Has this server been given a JWT secret?
4577
- *
4578
- * False on every backend that authenticates through an adapter — Firebase,
4579
- * Clerk, anything with its own tokens — because nothing calls
4580
- * {@link configureJwt} there. Signing paths still throw when it is false, since
4581
- * asking for a token from a server that cannot mint one is a mistake worth
4582
- * hearing about. Paths whose contract is *"tolerate the absence of auth"* must
4583
- * ask first: `optionalAuth` crashing a request because this backend does not do
4584
- * JWT is a 500 on a route that had already decided anonymous was acceptable.
4585
- */
4887
+ /** See {@link JwtService.isConfigured}. Answers for the process default. */
4586
4888
  function isJwtConfigured() {
4587
- return Boolean(jwtConfig.secret);
4889
+ return processJwt.isConfigured();
4588
4890
  }
4589
- /**
4590
- * Generate an access token (short-lived, 1 hour by default)
4591
- */
4891
+ /** See {@link JwtService.generateAccessToken}. Answers for the process default. */
4592
4892
  async function generateAccessToken(uid, roles, aal = "aal1", customClaims, isAnonymous = false, sessionId) {
4593
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4594
- const payload = {
4595
- ...customClaims,
4596
- uid,
4597
- roles,
4598
- aal,
4599
- ...isAnonymous ? { isAnonymous: true } : {},
4600
- ...sessionId ? { sid: sessionId } : {}
4601
- };
4602
- if (activeSigningKey) return signJwt(payload, activeSigningKey.privateKey, {
4603
- expiresIn: jwtConfig.accessExpiresIn,
4604
- algorithm: activeSigningKey.algorithm,
4605
- keyid: activeSigningKey.kid
4606
- });
4607
- return signJwt(payload, jwtConfig.secret, {
4608
- expiresIn: jwtConfig.accessExpiresIn,
4609
- algorithm: "HS256"
4610
- });
4893
+ return processJwt.generateAccessToken(uid, roles, aal, customClaims, isAnonymous, sessionId);
4611
4894
  }
4612
- /**
4613
- * Get the expiration time of an access token in milliseconds from now
4614
- */
4895
+ /** See {@link JwtService.getAccessTokenExpiryMs}. Answers for the process default. */
4615
4896
  function getAccessTokenExpiryMs() {
4616
- return parseDurationMs(jwtConfig.accessExpiresIn || "1h") ?? 3600 * 1e3;
4897
+ return processJwt.getAccessTokenExpiryMs();
4617
4898
  }
4618
4899
  var DURATION_UNIT_MS = {
4619
4900
  ms: 1,
@@ -4667,63 +4948,17 @@ function parseDurationMs(value) {
4667
4948
  const ms = parseFloat(match[1]) * unit;
4668
4949
  return ms > 0 && Number.isFinite(ms) ? ms : null;
4669
4950
  }
4670
- /**
4671
- * Get the expiration timestamp for an access token
4672
- */
4951
+ /** See {@link JwtService.getAccessTokenExpiry}. Answers for the process default. */
4673
4952
  function getAccessTokenExpiry() {
4674
- return Date.now() + getAccessTokenExpiryMs();
4953
+ return processJwt.getAccessTokenExpiry();
4675
4954
  }
4676
- /**
4677
- * Verify and decode an access token.
4678
- *
4679
- * Every token this server issues is signed with the same secret, so what a
4680
- * token *is* comes from its claims, not from its signature. A download token
4681
- * ({@link generateDownloadToken}) is therefore a validly-signed string that
4682
- * must never authenticate anybody: it is scoped to one file path and handed out
4683
- * in URLs, which is a far weaker thing to hold than a session.
4684
- *
4685
- * Today it is rejected below for want of an id — but only by luck, since
4686
- * nothing stops a future download token from carrying one. So the purpose is
4687
- * checked explicitly: a token minted for reading a file is not a token for
4688
- * being a user.
4689
- */
4955
+ /** See {@link JwtService.verifyAccessToken}. Answers for the process default. */
4690
4956
  async function verifyAccessToken(token) {
4691
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4692
- try {
4693
- const header = decodeProtectedHeader(token);
4694
- const namedKey = resolveVerificationKey(signingKeys, header.kid);
4695
- const decoded = namedKey ? await verifyJwt(token, namedKey.publicKey, { algorithms: [namedKey.algorithm] }) : await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4696
- if (decoded.purpose) {
4697
- logger.error("[JWT] Verification failed: a purpose-scoped token is not an access token", { purpose: decoded.purpose });
4698
- return null;
4699
- }
4700
- const id = decoded.uid || decoded.sub;
4701
- if (!id) {
4702
- logger.error("[JWT] Verification failed: missing id in payload", { detail: decoded });
4703
- return null;
4704
- }
4705
- const aal = decoded.aal === "aal1" || decoded.aal === "aal2" ? decoded.aal : "aal1";
4706
- return {
4707
- uid: id,
4708
- roles: decoded.roles || [],
4709
- aal,
4710
- ...decoded.isAnonymous === true ? { isAnonymous: true } : {},
4711
- iat: decoded.iat,
4712
- ...typeof decoded.exp === "number" ? { exp: decoded.exp } : {},
4713
- ...typeof decoded.sid === "string" && decoded.sid ? { sid: decoded.sid } : {},
4714
- ...(() => {
4715
- const claims = customClaimsOf(decoded);
4716
- return claims ? { claims } : {};
4717
- })()
4718
- };
4719
- } catch (error) {
4720
- logger.error("[JWT] Verification failed", { error });
4721
- return null;
4722
- }
4957
+ return processJwt.verifyAccessToken(token);
4723
4958
  }
4724
4959
  /**
4725
- * Generate a random refresh token. Long-lived: 400 days by default, sliding —
4726
- * see {@link getRefreshTokenTtlMs}.
4960
+ * Generate a random refresh token. Long-lived: 30 days by default, sliding —
4961
+ * see {@link JwtService.getRefreshTokenTtlMs}.
4727
4962
  */
4728
4963
  function generateRefreshToken() {
4729
4964
  return randomHex(40);
@@ -4741,255 +4976,95 @@ function hashRefreshToken(token) {
4741
4976
  * what is actually enforced, which is worse than knowing the ceiling.
4742
4977
  */
4743
4978
  var MAX_COOKIE_AGE_MS = 400 * 24 * 60 * 60 * 1e3;
4744
- /** Fallback when `refreshExpiresIn` is unset. {@link configureJwt} refuses an unparseable one. */
4745
- var DEFAULT_REFRESH_TTL_MS = MAX_COOKIE_AGE_MS;
4746
- /**
4747
- * How long a refresh token is valid for, in milliseconds.
4748
- *
4749
- * Every rotation issues a token with a fresh TTL, so this is a sliding window:
4750
- * a user who visits at all keeps their session indefinitely, and one who
4751
- * disappears loses it this long after their last visit. That is what both
4752
- * Firebase and Supabase do by default, and it is the behaviour people mean
4753
- * when they say they expect to still be signed in.
4754
- */
4979
+ /** See {@link JwtService.getRefreshTokenTtlMs}. Answers for the process default. */
4755
4980
  function getRefreshTokenTtlMs() {
4756
- const duration = jwtConfig.refreshExpiresIn;
4757
- return (duration ? parseDurationMs(duration) : null) ?? DEFAULT_REFRESH_TTL_MS;
4981
+ return processJwt.getRefreshTokenTtlMs();
4758
4982
  }
4759
- /**
4760
- * Calculate refresh token expiration date
4761
- */
4983
+ /** See {@link JwtService.getRefreshTokenExpiry}. Answers for the process default. */
4762
4984
  function getRefreshTokenExpiry() {
4763
- return new Date(Date.now() + getRefreshTokenTtlMs());
4985
+ return processJwt.getRefreshTokenExpiry();
4764
4986
  }
4765
4987
  /**
4766
4988
  * The `purpose` claim carried by a credential that stands between "first factor
4767
4989
  * accepted" and "session issued".
4768
4990
  *
4769
4991
  * A pre-auth token is NOT a session and must never be usable as one:
4770
- * {@link verifyAccessToken} refuses any token carrying a `purpose`, so this
4771
- * value cannot authenticate a request no matter which route it is presented to.
4772
- * The only thing that reads it is the MFA challenge pair, which exchanges it —
4773
- * 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.
4774
4996
  */
4775
4997
  var MFA_PENDING_PURPOSE = "mfa-pending";
4776
- /**
4777
- * Mint the short-lived credential handed back with an `MFA_REQUIRED` response.
4778
- *
4779
- * Short-lived on purpose: it is the window in which a caller who has proven the
4780
- * first factor may present the second, not a session to be carried around. Five
4781
- * minutes matches the challenge TTL.
4782
- */
4998
+ /** See {@link JwtService.generateMfaPendingToken}. Answers for the process default. */
4783
4999
  async function generateMfaPendingToken(uid, expiresInSeconds = 300) {
4784
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4785
- return signJwt({
4786
- purpose: MFA_PENDING_PURPOSE,
4787
- uid
4788
- }, jwtConfig.secret, {
4789
- expiresIn: expiresInSeconds,
4790
- algorithm: "HS256"
4791
- });
5000
+ return processJwt.generateMfaPendingToken(uid, expiresInSeconds);
4792
5001
  }
4793
- /**
4794
- * Verify a pre-auth token and return the user it was minted for.
4795
- *
4796
- * Returns `null` for anything else — including a perfectly valid *access*
4797
- * token, which must not be interchangeable with this one in either direction.
4798
- *
4799
- * `iat` comes back with the uid because this token is a first factor that was
4800
- * accepted at that instant, and a sign-out everywhere after it has to void it
4801
- * the way it voids an access token — see `isAccessTokenRevoked`.
4802
- */
5002
+ /** See {@link JwtService.verifyMfaPendingToken}. Answers for the process default. */
4803
5003
  async function verifyMfaPendingToken(token) {
4804
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4805
- try {
4806
- const decoded = await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4807
- if (decoded.purpose !== "mfa-pending" || !decoded.uid) return null;
4808
- return {
4809
- uid: decoded.uid,
4810
- iat: typeof decoded.iat === "number" ? decoded.iat : void 0
4811
- };
4812
- } catch {
4813
- return null;
4814
- }
5004
+ return processJwt.verifyMfaPendingToken(token);
4815
5005
  }
4816
- /**
4817
- * Generate a short-lived download token scoped to a specific file path or prefix
4818
- * *within one storage source*.
4819
- *
4820
- * Both halves of that scope are load-bearing. A key is only unique inside its
4821
- * own bucket, and a project with more than one source routinely holds the same
4822
- * key in several of them — `avatars/u1.png` in `(default)` and in `media` are
4823
- * different objects, quite possibly with different owners. A token that names
4824
- * only the path is therefore a grant on every source at once: authorize a read
4825
- * on the source whose `storageAuthorize` hook says yes, then spend the token
4826
- * against `?storageId=` pointing somewhere else.
4827
- *
4828
- * `storageId` is optional here only because omitting it *is* the default
4829
- * source, which is what the overwhelming majority of deployments have. A mint
4830
- * site that forgets to pass a named source produces a default-scoped token,
4831
- * which fails closed at `/file/*` rather than over-granting.
4832
- */
5006
+ /** See {@link JwtService.generateDownloadToken}. Answers for the process default. */
4833
5007
  async function generateDownloadToken(path, expiresInSeconds = 300, storageId, principal) {
4834
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4835
- return signJwt({
4836
- purpose: "file-read",
4837
- path,
4838
- storageId: canonicalStorageId(storageId),
4839
- ...principal ? { rl: await downloadTokenRateLimitMark(principal) } : {}
4840
- }, jwtConfig.secret, {
4841
- expiresIn: expiresInSeconds,
4842
- algorithm: "HS256"
4843
- });
5008
+ return processJwt.generateDownloadToken(path, expiresInSeconds, storageId, principal);
4844
5009
  }
4845
5010
  /** See {@link DownloadTokenPayload.rl}. */
4846
5011
  async function downloadTokenRateLimitMark(principal) {
4847
5012
  return (await sha256Hex(`download-token-rl:${principal}`)).slice(0, 32);
4848
5013
  }
4849
- /**
4850
- * Verify and decode a download token.
4851
- *
4852
- * A token minted before `storageId` existed carries no such claim. It is read
4853
- * as a grant on the **default source** rather than on all of them: that is the
4854
- * fail-closed reading, and it is what such a token almost always was, since a
4855
- * named source has to be asked for explicitly. The cost is bounded by the
4856
- * five-minute TTL — for at most that long after a deploy, an in-flight token
4857
- * for a *named* source is refused and the client re-fetches `/metadata`.
4858
- */
5014
+ /** See {@link JwtService.verifyDownloadToken}. Answers for the process default. */
4859
5015
  async function verifyDownloadToken(token) {
4860
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4861
- try {
4862
- const decoded = await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4863
- if (decoded && decoded.purpose === "file-read" && typeof decoded.path === "string") return {
4864
- purpose: "file-read",
4865
- path: decoded.path,
4866
- storageId: canonicalStorageId(typeof decoded.storageId === "string" ? decoded.storageId : null),
4867
- ...typeof decoded.rl === "string" ? { rl: decoded.rl } : {}
4868
- };
4869
- return null;
4870
- } catch (error) {
4871
- logger.error("[JWT] Download token verification failed", { error });
4872
- return null;
4873
- }
5016
+ return processJwt.verifyDownloadToken(token);
4874
5017
  }
4875
- /**
4876
- * Sign a short-lived token that carries state across a redirect.
4877
- *
4878
- * The generalisation of the MFA-pending token: a signed blob the server hands
4879
- * out and expects back, where the point is that the holder cannot edit it. The
4880
- * OAuth authorize flow needs one to carry the authorization request across the
4881
- * sign-in hop — without it, the endpoint the consent page posts to would mint a
4882
- * code for whatever `client_id` and `redirect_uri` its caller typed, skipping
4883
- * every check the authorize endpoint performs.
4884
- *
4885
- * The `purpose` is not decoration. {@link verifyAccessToken} refuses any token
4886
- * that has one, so nothing minted here can be replayed as a session, and
4887
- * {@link verifyPurposeToken} refuses a token whose purpose is not the one the
4888
- * caller named — so a token minted for one hop cannot be spent on another.
4889
- */
5018
+ /** See {@link JwtService.signPurposeToken}. Answers for the process default. */
4890
5019
  async function signPurposeToken(purpose, claims, expiresInSeconds) {
4891
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4892
- return signJwt({
4893
- ...claims,
4894
- purpose
4895
- }, jwtConfig.secret, {
4896
- expiresIn: expiresInSeconds,
4897
- algorithm: "HS256"
4898
- });
5020
+ return processJwt.signPurposeToken(purpose, claims, expiresInSeconds);
4899
5021
  }
4900
- /** Verify a token minted by {@link signPurposeToken}, for one named purpose. */
5022
+ /** See {@link JwtService.verifyPurposeToken}. Answers for the process default. */
4901
5023
  async function verifyPurposeToken(purpose, token) {
4902
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4903
- if (!token) return null;
4904
- try {
4905
- const decoded = await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4906
- if (!decoded || decoded.purpose !== purpose) return null;
4907
- return decoded;
4908
- } catch (error) {
4909
- logger.error("[JWT] Purpose token verification failed", {
4910
- purpose,
4911
- error
4912
- });
4913
- return null;
4914
- }
5024
+ return processJwt.verifyPurposeToken(purpose, token);
4915
5025
  }
4916
5026
  /** The `purpose` that marks a token as belonging to the MCP resource. */
4917
5027
  var MCP_ACCESS_PURPOSE = "mcp-access";
4918
- /**
4919
- * Mint an access token for the MCP endpoint.
4920
- *
4921
- * Deliberately **not** an ordinary session token, and the `purpose` claim is
4922
- * what enforces that: {@link verifyAccessToken} refuses any token carrying one,
4923
- * so a credential issued to a third-party MCP client — Claude, an IDE, anything
4924
- * that completed the OAuth flow — cannot be replayed against `/api/data`,
4925
- * `/api/admin` or the websocket. It buys exactly the one resource it names.
4926
- *
4927
- * That asymmetry matters more than it first looks. These tokens are handed to
4928
- * software the user authorized but the operator never vetted, they live in that
4929
- * software's storage, and they are the only credential in this system minted
4930
- * for a third party. Without the purpose separation, "connect Claude to your
4931
- * data" would silently mean "give Claude a full session".
4932
- *
4933
- * The audience is the canonical resource URI, and {@link verifyMcpAccessToken}
4934
- * requires the caller to say which resource it is checking for. A token minted
4935
- * for another Rebase project — or another path on this one — fails there.
4936
- *
4937
- * HS256 with the shared secret, like the download token: the authorization
4938
- * server and the resource server are the same process, so nothing outside it
4939
- * ever needs to verify one, and an asymmetric key here would be a second key
4940
- * surface bought for nobody.
4941
- */
5028
+ /** See {@link JwtService.generateMcpAccessToken}. Answers for the process default. */
4942
5029
  async function generateMcpAccessToken(payload, expiresInSeconds) {
4943
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4944
- return signJwt({
4945
- purpose: MCP_ACCESS_PURPOSE,
4946
- uid: payload.uid,
4947
- roles: payload.roles,
4948
- scope: payload.scope,
4949
- clientId: payload.clientId,
4950
- aud: payload.aud,
4951
- iss: payload.iss
4952
- }, jwtConfig.secret, {
4953
- expiresIn: expiresInSeconds,
4954
- algorithm: "HS256"
4955
- });
5030
+ return processJwt.generateMcpAccessToken(payload, expiresInSeconds);
4956
5031
  }
4957
- /**
4958
- * Verify an MCP access token **for a specific resource**.
4959
- *
4960
- * `expectedAudience` has no default. The MCP specification requires a resource
4961
- * server to validate that a token was issued for it specifically, and a default
4962
- * would be the thing that quietly turns that check off — the caller has to say
4963
- * what it is, every time, so a new call site cannot forget by omission.
4964
- */
5032
+ /** See {@link JwtService.verifyMcpAccessToken}. Answers for the process default. */
4965
5033
  async function verifyMcpAccessToken(token, expectedAudience) {
4966
- if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4967
- try {
4968
- const decoded = await verifyJwt(token, jwtConfig.secret, { algorithms: ["HS256"] });
4969
- if (!decoded || decoded.purpose !== "mcp-access") return null;
4970
- if (typeof decoded.uid !== "string" || !decoded.uid) return null;
4971
- if (typeof decoded.aud !== "string" || decoded.aud !== expectedAudience) {
4972
- logger.error("[JWT] MCP token rejected: audience mismatch", {
4973
- expected: expectedAudience,
4974
- got: decoded.aud
4975
- });
4976
- return null;
4977
- }
4978
- return {
4979
- purpose: MCP_ACCESS_PURPOSE,
4980
- uid: decoded.uid,
4981
- roles: Array.isArray(decoded.roles) ? decoded.roles.map(String) : [],
4982
- scope: typeof decoded.scope === "string" ? decoded.scope : "",
4983
- clientId: typeof decoded.clientId === "string" ? decoded.clientId : "",
4984
- aud: decoded.aud,
4985
- iss: typeof decoded.iss === "string" ? decoded.iss : ""
4986
- };
4987
- } catch (error) {
4988
- logger.error("[JWT] MCP token verification failed", { error });
4989
- return null;
4990
- }
5034
+ return processJwt.verifyMcpAccessToken(token, expectedAudience);
4991
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
+ };
4992
5067
  //#endregion
4993
- export { sha256Bytes as A, verifyPurposeToken as C, constantTimeEqual as D, require_jsonwebtoken as E, randomHex as O, verifyMfaPendingToken as S, verifyJwt as T, jwt_exports as _, generateAccessToken as a, verifyDownloadToken as b, generateMfaPendingToken as c, getJwks as d, getRefreshTokenExpiry as f, isJwtConfigured as g, hashRefreshToken as h, customClaimsOf as i, sha256Hex as j, randomInt as k, generateRefreshToken as l, hasAsymmetricSigningKey as m, configureJwt as n, generateDownloadToken as o, getRefreshTokenTtlMs as p, configuredJwtSecret as r, generateMcpAccessToken as s, MAX_COOKIE_AGE_MS as t, getAccessTokenExpiry as u, signPurposeToken as v, normalizePemFromEnv as w, verifyMcpAccessToken as x, verifyAccessToken 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 };
4994
5069
 
4995
- //# sourceMappingURL=jwt-R6bSPMjk.js.map
5070
+ //# sourceMappingURL=jwt-BlvuKoR6.js.map