@absolutejs/auth 0.57.9 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +38 -0
  2. package/dist/cli/migrate.js +31 -12
  3. package/dist/cli/migrate.js.map +4 -4
  4. package/dist/index.js +522 -95
  5. package/dist/index.js.map +17 -15
  6. package/dist/manifest.js +8 -1
  7. package/dist/manifest.js.map +3 -3
  8. package/dist/mfa/challenge.d.ts +5 -3
  9. package/dist/mfa/config.d.ts +9 -0
  10. package/dist/mfa/management.d.ts +3 -3
  11. package/dist/mfa/postgresMfaStore.d.ts +60 -0
  12. package/dist/mfa/recentAuth.d.ts +2 -0
  13. package/dist/mfa/routes.d.ts +15 -10
  14. package/dist/mfa/sms.d.ts +45 -7
  15. package/dist/mfa/totp.d.ts +3 -3
  16. package/dist/mfa/types.d.ts +35 -0
  17. package/dist/server.js +521 -96
  18. package/dist/server.js.map +18 -16
  19. package/dist/types.d.ts +5 -1
  20. package/dist/verification/types.d.ts +58 -0
  21. package/package.json +1 -1
  22. package/dist/agents/index.d.ts +0 -13
  23. package/dist/cli/import/auth0.d.ts +0 -2
  24. package/dist/cli/import/clerk.d.ts +0 -2
  25. package/dist/cli/import/index.d.ts +0 -10
  26. package/dist/cli/import/lucia.d.ts +0 -2
  27. package/dist/cli/import/nextauth.d.ts +0 -2
  28. package/dist/cli/import/supabase.d.ts +0 -2
  29. package/dist/cli/import/types.d.ts +0 -27
  30. package/dist/cli/migrate.d.ts +0 -2
  31. package/dist/client/components/react/SignIn.d.ts +0 -32
  32. package/dist/client/components/react/SignUp.d.ts +0 -29
  33. package/dist/client/components/react/UserButton.d.ts +0 -36
  34. package/dist/client/createAuthClient.d.ts +0 -285
  35. package/dist/client/index.d.ts +0 -3
  36. package/dist/client/passkeyHelpers.d.ts +0 -19
  37. package/dist/client/react.d.ts +0 -87
  38. package/dist/client/sessionExpiry.d.ts +0 -37
  39. package/dist/client/solid.d.ts +0 -86
  40. package/dist/client/svelte.d.ts +0 -86
  41. package/dist/client/vue.d.ts +0 -88
  42. package/dist/fingerprint-client/index.d.ts +0 -26
  43. package/dist/htmx/index.d.ts +0 -2
  44. package/dist/index.d.ts +0 -3637
  45. package/dist/linkedProviders/index.d.ts +0 -3
  46. package/dist/manifest.d.ts +0 -18
  47. package/dist/manifest.json +0 -364
  48. package/dist/oidc/index.d.ts +0 -12
  49. package/dist/oidc/operator.d.ts +0 -14
  50. package/dist/plugins/denyDisposableEmail.d.ts +0 -7
  51. package/dist/plugins/discordAlert.d.ts +0 -7
  52. package/dist/plugins/geoBlock.d.ts +0 -8
  53. package/dist/plugins/index.d.ts +0 -12
  54. package/dist/plugins/pagerdutyAlert.d.ts +0 -9
  55. package/dist/plugins/posthogIdentify.d.ts +0 -7
  56. package/dist/plugins/slackAlert.d.ts +0 -7
  57. package/dist/providers/index.d.ts +0 -2
  58. package/dist/saml.d.ts +0 -1
  59. package/dist/server.d.ts +0 -31
  60. package/dist/sso/nodeSamlAdapter.d.ts +0 -8
  61. package/dist/vault/index.d.ts +0 -5
  62. package/dist/webauthn/simpleWebAuthnAdapter.d.ts +0 -3
  63. package/dist/webauthn.d.ts +0 -1
package/dist/server.js CHANGED
@@ -6844,11 +6844,13 @@ var generateBackupCodes = async (count) => {
6844
6844
  init_constants();
6845
6845
  var DEFAULT_BACKUP_CODE_COUNT = 10;
6846
6846
  var DEFAULT_MFA_ISSUER = "AbsoluteAuth";
6847
+ var DEFAULT_MFA_MANAGEMENT_AUTH_MAX_AGE_MS = 5 * SECONDS_IN_A_MINUTE * MILLISECONDS_IN_A_SECOND;
6847
6848
  var DEFAULT_MFA_SESSION_TTL_MS = MILLISECONDS_IN_A_DAY;
6848
6849
  var DEFAULT_SMS_CODE_LENGTH = 6;
6849
6850
  var SMS_CODE_TTL_MINUTES = 5;
6850
6851
  var DEFAULT_SMS_CODE_TTL_MS = SMS_CODE_TTL_MINUTES * SECONDS_IN_A_MINUTE * MILLISECONDS_IN_A_SECOND;
6851
6852
  var DEFAULT_SMS_MAX_ATTEMPTS = 3;
6853
+ var DEFAULT_SMS_RESEND_COOLDOWN_MS = 30 * MILLISECONDS_IN_A_SECOND;
6852
6854
  var DEFAULT_TOTP_MAX_ATTEMPTS = 5;
6853
6855
 
6854
6856
  // src/mfa/secret.ts
@@ -6859,6 +6861,23 @@ var encryptTotpSecret = (secret, encryptionKey) => encryptionKey ? encryptSecret
6859
6861
  // src/mfa/sms.ts
6860
6862
  init_crypto();
6861
6863
  import { Elysia as Elysia17, t as t11 } from "elysia";
6864
+
6865
+ // src/verification/types.ts
6866
+ class VerificationProviderError extends Error {
6867
+ kind;
6868
+ provider;
6869
+ constructor(input) {
6870
+ super(input.message, { cause: input.cause });
6871
+ this.name = "VerificationProviderError";
6872
+ this.kind = input.kind;
6873
+ this.provider = input.provider;
6874
+ }
6875
+ }
6876
+
6877
+ // src/mfa/recentAuth.ts
6878
+ var hasRecentAuthentication = (session, maxAgeMs, now = Date.now()) => session.authenticatedAt !== undefined && now >= session.authenticatedAt && now - session.authenticatedAt <= maxAgeMs;
6879
+
6880
+ // src/mfa/sms.ts
6862
6881
  var DECIMAL_RADIX2 = 10;
6863
6882
  var MASK_VISIBLE_DIGITS = 4;
6864
6883
  var E164_PATTERN = /^\+[1-9]\d{7,14}$/u;
@@ -6873,28 +6892,147 @@ var issueSmsCode = async (codeLength, ttlMs) => {
6873
6892
  const expiresAt = Date.now() + ttlMs;
6874
6893
  return { code, expiresAt, hash };
6875
6894
  };
6895
+ var startProviderSmsChallenge = async (input) => {
6896
+ let started;
6897
+ try {
6898
+ started = await input.verificationProvider.start({
6899
+ channel: "sms",
6900
+ purpose: input.purpose,
6901
+ subject: input.userId,
6902
+ to: input.phone
6903
+ });
6904
+ } catch (error) {
6905
+ await input.mfaStore.rollbackSmsChallenge({
6906
+ challengeId: input.challengeId,
6907
+ previous: input.previousEnrollment,
6908
+ userId: input.userId
6909
+ });
6910
+ throw error;
6911
+ }
6912
+ const finalized = await input.mfaStore.finalizeSmsChallenge({
6913
+ challengeId: input.challengeId,
6914
+ expiresAt: started.expiresAt,
6915
+ providerReference: started.reference,
6916
+ userId: input.userId
6917
+ });
6918
+ if (finalized)
6919
+ return started;
6920
+ await input.verificationProvider.cancel({
6921
+ channel: "sms",
6922
+ purpose: input.purpose,
6923
+ reference: started.reference,
6924
+ subject: input.userId,
6925
+ to: input.phone
6926
+ }).catch(() => {
6927
+ return;
6928
+ });
6929
+ throw new SmsChallengeConflictError("SMS challenge claim was lost");
6930
+ };
6931
+
6932
+ class SmsChallengeConflictError extends Error {
6933
+ name = "SmsChallengeConflictError";
6934
+ }
6935
+ var checkWithVerificationProvider = async (provider, input) => {
6936
+ try {
6937
+ return { result: await provider.check(input) };
6938
+ } catch (error) {
6939
+ const mapped = mapVerificationProviderError(error);
6940
+ if (mapped === undefined)
6941
+ throw error;
6942
+ return { error: mapped };
6943
+ }
6944
+ };
6876
6945
  var isE164Phone = (phone) => E164_PATTERN.test(phone);
6877
6946
  var issueAndStoreSmsCode = async ({
6878
6947
  codeLength,
6879
6948
  enrollment,
6880
6949
  mfaStore,
6881
6950
  onSendSmsCode,
6882
- ttlMs
6951
+ previousEnrollment,
6952
+ purpose,
6953
+ resendCooldownMs,
6954
+ verificationProvider,
6955
+ ttlMs,
6956
+ userId
6883
6957
  }) => {
6884
6958
  const phone = enrollment.smsPhone;
6885
6959
  if (phone === undefined)
6886
6960
  return;
6961
+ const challengeId = crypto.randomUUID();
6962
+ const now = Date.now();
6963
+ const claimed = await mfaStore.claimSmsChallenge({
6964
+ challengeId,
6965
+ cooldownCutoff: now - resendCooldownMs,
6966
+ enrollment: {
6967
+ ...enrollment,
6968
+ smsChallengeId: challengeId,
6969
+ smsCodeSentAt: now,
6970
+ smsFailedAttempts: 0,
6971
+ smsPendingCodeExpiresAt: now + ttlMs,
6972
+ smsPendingCodeHash: undefined,
6973
+ smsPendingPurpose: purpose,
6974
+ smsProviderReference: undefined,
6975
+ updatedAt: now
6976
+ }
6977
+ });
6978
+ if (!claimed)
6979
+ throw new SmsChallengeConflictError("SMS resend cooldown active");
6980
+ if (verificationProvider !== undefined) {
6981
+ const started = await startProviderSmsChallenge({
6982
+ challengeId,
6983
+ mfaStore,
6984
+ phone,
6985
+ previousEnrollment,
6986
+ purpose,
6987
+ userId,
6988
+ verificationProvider
6989
+ });
6990
+ return started.expiresAt;
6991
+ }
6887
6992
  const { code, expiresAt, hash } = await issueSmsCode(codeLength, ttlMs);
6888
- await mfaStore.saveEnrollment({
6889
- ...enrollment,
6890
- smsFailedAttempts: 0,
6891
- smsPendingCodeExpiresAt: expiresAt,
6892
- smsPendingCodeHash: hash,
6893
- updatedAt: Date.now()
6993
+ const finalized = await mfaStore.finalizeSmsChallenge({
6994
+ challengeId,
6995
+ expiresAt,
6996
+ hash,
6997
+ userId
6894
6998
  });
6895
- await onSendSmsCode?.({ code, expiresAt, phone });
6999
+ if (!finalized)
7000
+ throw new SmsChallengeConflictError("SMS challenge claim was lost");
7001
+ try {
7002
+ await onSendSmsCode?.({ code, expiresAt, phone, purpose, userId });
7003
+ } catch (error) {
7004
+ await mfaStore.rollbackSmsChallenge({
7005
+ challengeId,
7006
+ previous: previousEnrollment,
7007
+ userId
7008
+ });
7009
+ throw error;
7010
+ }
6896
7011
  return expiresAt;
6897
7012
  };
7013
+ var mapVerificationProviderError = (error) => {
7014
+ if (error instanceof SmsChallengeConflictError) {
7015
+ return { message: error.message, status: "Too Many Requests" };
7016
+ }
7017
+ if (!(error instanceof VerificationProviderError))
7018
+ return;
7019
+ if (error.kind === "rate_limited") {
7020
+ return {
7021
+ message: "Verification provider rate limit reached",
7022
+ status: "Too Many Requests"
7023
+ };
7024
+ }
7025
+ if (error.kind === "invalid_destination") {
7026
+ return {
7027
+ message: "Phone number cannot receive verification codes",
7028
+ status: "Bad Request"
7029
+ };
7030
+ }
7031
+ return {
7032
+ message: "Verification provider unavailable",
7033
+ status: "Service Unavailable"
7034
+ };
7035
+ };
6898
7036
  var maskPhone = (phone) => {
6899
7037
  const visible = phone.slice(-MASK_VISIBLE_DIGITS);
6900
7038
  const maskedLength = Math.max(phone.length - MASK_VISIBLE_DIGITS, 0);
@@ -6904,11 +7042,14 @@ var mfaSmsRoutes = ({
6904
7042
  authSessionStore,
6905
7043
  getUserId,
6906
7044
  mfaStore,
7045
+ managementAuthMaxAgeMs = DEFAULT_MFA_MANAGEMENT_AUTH_MAX_AGE_MS,
6907
7046
  onMfaEnrolled,
6908
7047
  onSendSmsCode,
7048
+ verificationProvider,
6909
7049
  smsCodeLength = DEFAULT_SMS_CODE_LENGTH,
6910
7050
  smsCodeTtlMs = DEFAULT_SMS_CODE_TTL_MS,
6911
7051
  smsMaxAttempts = DEFAULT_SMS_MAX_ATTEMPTS,
7052
+ smsResendCooldownMs = DEFAULT_SMS_RESEND_COOLDOWN_MS,
6912
7053
  smsSetupRoute = "/auth/mfa/sms/setup",
6913
7054
  smsVerifyRoute = "/auth/mfa/sms/verify"
6914
7055
  }) => new Elysia17().use(sessionStore()).post(smsSetupRoute, async ({
@@ -6917,7 +7058,7 @@ var mfaSmsRoutes = ({
6917
7058
  status,
6918
7059
  store: { session }
6919
7060
  }) => {
6920
- if (onSendSmsCode === undefined) {
7061
+ if (onSendSmsCode === undefined && verificationProvider === undefined) {
6921
7062
  return status("Not Implemented", "SMS MFA is not configured");
6922
7063
  }
6923
7064
  const userSession = await loadSessionFromSource({
@@ -6928,27 +7069,49 @@ var mfaSmsRoutes = ({
6928
7069
  if (!userSession) {
6929
7070
  return status("Unauthorized", "Authentication required");
6930
7071
  }
7072
+ if (!hasRecentAuthentication(userSession, managementAuthMaxAgeMs)) {
7073
+ return status("Unauthorized", "Recent authentication required");
7074
+ }
6931
7075
  if (!isE164Phone(phone)) {
6932
7076
  return status("Bad Request", "Phone must be in E.164 format");
6933
7077
  }
6934
7078
  const userId = getUserId(userSession.user);
6935
7079
  const existing = await mfaStore.getEnrollment(userId);
6936
7080
  const now = Date.now();
6937
- const { code, expiresAt, hash } = await issueSmsCode(smsCodeLength, smsCodeTtlMs);
6938
- await mfaStore.saveEnrollment({
6939
- backupCodeHashes: existing?.backupCodeHashes ?? [],
6940
- createdAt: existing?.createdAt ?? now,
6941
- smsFailedAttempts: 0,
6942
- smsPendingCodeExpiresAt: expiresAt,
6943
- smsPendingCodeHash: hash,
6944
- smsPhone: phone,
6945
- smsVerified: false,
6946
- totpSecretCiphertext: existing?.totpSecretCiphertext,
6947
- totpVerified: existing?.totpVerified ?? false,
6948
- updatedAt: now,
6949
- userId
6950
- });
6951
- await onSendSmsCode({ code, expiresAt, phone });
7081
+ if (existing?.smsCodeSentAt !== undefined && now - existing.smsCodeSentAt < smsResendCooldownMs) {
7082
+ return status("Too Many Requests", "SMS resend cooldown active");
7083
+ }
7084
+ try {
7085
+ await issueAndStoreSmsCode({
7086
+ codeLength: smsCodeLength,
7087
+ enrollment: {
7088
+ backupCodeHashes: existing?.backupCodeHashes ?? [],
7089
+ createdAt: existing?.createdAt ?? now,
7090
+ lastUsedAt: existing?.lastUsedAt,
7091
+ smsFailedAttempts: 0,
7092
+ smsPhone: phone,
7093
+ smsVerified: false,
7094
+ totpFailedAttempts: existing?.totpFailedAttempts ?? 0,
7095
+ totpSecretCiphertext: existing?.totpSecretCiphertext,
7096
+ totpVerified: existing?.totpVerified ?? false,
7097
+ updatedAt: now,
7098
+ userId
7099
+ },
7100
+ mfaStore,
7101
+ onSendSmsCode,
7102
+ previousEnrollment: existing,
7103
+ purpose: "mfa_enrollment",
7104
+ resendCooldownMs: smsResendCooldownMs,
7105
+ ttlMs: smsCodeTtlMs,
7106
+ userId,
7107
+ verificationProvider
7108
+ });
7109
+ } catch (error) {
7110
+ const mapped = mapVerificationProviderError(error);
7111
+ if (mapped === undefined)
7112
+ throw error;
7113
+ return status(mapped.status, mapped.message);
7114
+ }
6952
7115
  return status("OK", { phone: maskPhone(phone) });
6953
7116
  }, {
6954
7117
  body: t11.Object({ phone: t11.String() }),
@@ -6967,34 +7130,65 @@ var mfaSmsRoutes = ({
6967
7130
  if (!userSession) {
6968
7131
  return status("Unauthorized", "Authentication required");
6969
7132
  }
7133
+ if (!hasRecentAuthentication(userSession, managementAuthMaxAgeMs)) {
7134
+ return status("Unauthorized", "Recent authentication required");
7135
+ }
6970
7136
  const userId = getUserId(userSession.user);
6971
7137
  const enrollment = await mfaStore.getEnrollment(userId);
6972
- if (!enrollment?.smsPendingCodeHash || enrollment.smsPendingCodeExpiresAt === undefined) {
7138
+ if (!enrollment?.smsPhone) {
7139
+ return status("Bad Request", "No SMS enrollment in progress");
7140
+ }
7141
+ const localCodeHash = enrollment.smsPendingCodeHash;
7142
+ const challengeId = enrollment.smsChallengeId;
7143
+ const localCodeExpiresAt = enrollment.smsPendingCodeExpiresAt;
7144
+ const providerReference = enrollment.smsProviderReference;
7145
+ if (enrollment.smsPendingPurpose !== "mfa_enrollment" || localCodeExpiresAt === undefined || challengeId === undefined) {
6973
7146
  return status("Bad Request", "No SMS enrollment in progress");
6974
7147
  }
6975
- if (Date.now() > enrollment.smsPendingCodeExpiresAt) {
7148
+ if (verificationProvider === undefined && localCodeHash === undefined) {
7149
+ return status("Bad Request", "No SMS enrollment in progress");
7150
+ }
7151
+ if (Date.now() > localCodeExpiresAt) {
6976
7152
  return status("Bad Request", "SMS code expired");
6977
7153
  }
6978
7154
  if ((enrollment.smsFailedAttempts ?? 0) >= smsMaxAttempts) {
6979
7155
  return status("Too Many Requests", "Too many attempts");
6980
7156
  }
6981
- const codeValid = await constantTimeEqual(await hashToken(code), enrollment.smsPendingCodeHash);
7157
+ let providerResult;
7158
+ if (verificationProvider !== undefined) {
7159
+ if (providerReference === undefined) {
7160
+ return status("Bad Request", "No SMS enrollment in progress");
7161
+ }
7162
+ const checked = await checkWithVerificationProvider(verificationProvider, {
7163
+ channel: "sms",
7164
+ code,
7165
+ purpose: "mfa_enrollment",
7166
+ reference: providerReference,
7167
+ subject: userId,
7168
+ to: enrollment.smsPhone
7169
+ });
7170
+ if (checked.error !== undefined) {
7171
+ return status(checked.error.status, checked.error.message);
7172
+ }
7173
+ providerResult = checked.result;
7174
+ }
7175
+ const codeValid = providerResult ? providerResult.status === "approved" : localCodeHash !== undefined && await constantTimeEqual(await hashToken(code), localCodeHash);
6982
7176
  if (!codeValid) {
6983
- await mfaStore.saveEnrollment({
6984
- ...enrollment,
6985
- smsFailedAttempts: (enrollment.smsFailedAttempts ?? 0) + 1,
6986
- updatedAt: Date.now()
7177
+ const attempts = await mfaStore.recordSmsFailure({
7178
+ challengeId,
7179
+ maxAttempts: smsMaxAttempts,
7180
+ userId
6987
7181
  });
6988
- return status("Bad Request", "Invalid SMS code");
7182
+ return status(providerResult?.status === "max_attempts_reached" || attempts === undefined ? "Too Many Requests" : "Bad Request", providerResult?.status === "expired" ? "SMS code expired" : "Invalid SMS code");
6989
7183
  }
6990
- await mfaStore.saveEnrollment({
6991
- ...enrollment,
6992
- smsFailedAttempts: 0,
6993
- smsPendingCodeExpiresAt: undefined,
6994
- smsPendingCodeHash: undefined,
7184
+ const completed = await mfaStore.completeSmsChallenge({
7185
+ challengeId,
6995
7186
  smsVerified: true,
6996
- updatedAt: Date.now()
7187
+ userId
6997
7188
  });
7189
+ if (!completed) {
7190
+ return status("Bad Request", "SMS challenge is no longer active");
7191
+ }
6998
7192
  await onMfaEnrolled?.({ userId });
6999
7193
  return status("OK", { status: "enrolled" });
7000
7194
  }, {
@@ -7014,10 +7208,12 @@ var mfaChallenge = ({
7014
7208
  onMfaChallengeError,
7015
7209
  onMfaChallengeSuccess,
7016
7210
  onSendSmsCode,
7211
+ verificationProvider,
7017
7212
  sessionDurationMs = DEFAULT_MFA_SESSION_TTL_MS,
7018
7213
  smsCodeLength = DEFAULT_SMS_CODE_LENGTH,
7019
7214
  smsCodeTtlMs = DEFAULT_SMS_CODE_TTL_MS,
7020
7215
  smsMaxAttempts = DEFAULT_SMS_MAX_ATTEMPTS,
7216
+ smsResendCooldownMs = DEFAULT_SMS_RESEND_COOLDOWN_MS,
7021
7217
  totpMaxAttempts = DEFAULT_TOTP_MAX_ATTEMPTS
7022
7218
  }) => new Elysia18().use(sessionStore()).post(challengeRoute, async ({
7023
7219
  body: { action, code, factor },
@@ -7059,27 +7255,51 @@ var mfaChallenge = ({
7059
7255
  await onMfaChallengeSuccess?.({ user, userSessionId });
7060
7256
  return status("OK", { status: "authenticated" });
7061
7257
  };
7062
- const runSmsChallenge = async () => {
7063
- if (!enrollment.smsVerified || !enrollment.smsPhone) {
7064
- return status("Unauthorized", "No MFA challenge in progress");
7258
+ const sendSmsChallenge = async () => {
7259
+ if (enrollment.smsCodeSentAt !== undefined && Date.now() - enrollment.smsCodeSentAt < smsResendCooldownMs) {
7260
+ return status("Too Many Requests", "SMS resend cooldown active");
7065
7261
  }
7066
- if (action === "send") {
7262
+ try {
7067
7263
  await issueAndStoreSmsCode({
7068
7264
  codeLength: smsCodeLength,
7069
7265
  enrollment,
7070
7266
  mfaStore,
7071
7267
  onSendSmsCode,
7072
- ttlMs: smsCodeTtlMs
7268
+ previousEnrollment: enrollment,
7269
+ purpose: "mfa_challenge",
7270
+ resendCooldownMs: smsResendCooldownMs,
7271
+ ttlMs: smsCodeTtlMs,
7272
+ userId: getUserId(user),
7273
+ verificationProvider
7073
7274
  });
7074
- return status("OK", { status: "sent" });
7275
+ } catch (error) {
7276
+ const mapped = mapVerificationProviderError(error);
7277
+ if (mapped === undefined)
7278
+ throw error;
7279
+ return status(mapped.status, mapped.message);
7075
7280
  }
7281
+ return status("OK", { status: "sent" });
7282
+ };
7283
+ const runSmsChallenge = async () => {
7284
+ if (!enrollment.smsVerified || !enrollment.smsPhone) {
7285
+ return status("Unauthorized", "No MFA challenge in progress");
7286
+ }
7287
+ if (action === "send")
7288
+ return sendSmsChallenge();
7076
7289
  if (code === undefined) {
7077
7290
  return status("Bad Request", "SMS code required");
7078
7291
  }
7079
- if (!enrollment.smsPendingCodeHash || enrollment.smsPendingCodeExpiresAt === undefined) {
7292
+ const localCodeHash = enrollment.smsPendingCodeHash;
7293
+ const challengeId = enrollment.smsChallengeId;
7294
+ const localCodeExpiresAt = enrollment.smsPendingCodeExpiresAt;
7295
+ const providerReference = enrollment.smsProviderReference;
7296
+ if (enrollment.smsPendingPurpose !== "mfa_challenge" || localCodeExpiresAt === undefined || challengeId === undefined) {
7297
+ return status("Bad Request", "No SMS code in progress");
7298
+ }
7299
+ if (verificationProvider === undefined && localCodeHash === undefined) {
7080
7300
  return status("Bad Request", "No SMS code in progress");
7081
7301
  }
7082
- if (Date.now() > enrollment.smsPendingCodeExpiresAt) {
7302
+ if (Date.now() > localCodeExpiresAt) {
7083
7303
  return status("Unauthorized", "SMS code expired");
7084
7304
  }
7085
7305
  if ((enrollment.smsFailedAttempts ?? 0) >= smsMaxAttempts) {
@@ -7089,27 +7309,46 @@ var mfaChallenge = ({
7089
7309
  });
7090
7310
  return status("Unauthorized", "Too many attempts");
7091
7311
  }
7092
- const smsValid = await constantTimeEqual(await hashToken(code), enrollment.smsPendingCodeHash);
7312
+ let providerResult;
7313
+ if (verificationProvider !== undefined) {
7314
+ if (providerReference === undefined) {
7315
+ return status("Bad Request", "No SMS code in progress");
7316
+ }
7317
+ const checked = await checkWithVerificationProvider(verificationProvider, {
7318
+ channel: "sms",
7319
+ code,
7320
+ purpose: "mfa_challenge",
7321
+ reference: providerReference,
7322
+ subject: getUserId(user),
7323
+ to: enrollment.smsPhone
7324
+ });
7325
+ if (checked.error !== undefined) {
7326
+ return status(checked.error.status, checked.error.message);
7327
+ }
7328
+ providerResult = checked.result;
7329
+ }
7330
+ const smsValid = providerResult ? providerResult.status === "approved" : localCodeHash !== undefined && await constantTimeEqual(await hashToken(code), localCodeHash);
7093
7331
  if (!smsValid) {
7094
- await mfaStore.saveEnrollment({
7095
- ...enrollment,
7096
- smsFailedAttempts: (enrollment.smsFailedAttempts ?? 0) + 1,
7097
- updatedAt: Date.now()
7332
+ const attempts = await mfaStore.recordSmsFailure({
7333
+ challengeId,
7334
+ maxAttempts: smsMaxAttempts,
7335
+ userId: getUserId(user)
7098
7336
  });
7099
7337
  await onMfaChallengeError?.({
7100
7338
  error: new Error("invalid_mfa_code"),
7101
7339
  userId: getUserId(user)
7102
7340
  });
7103
- return status("Unauthorized", "Invalid MFA code");
7341
+ return status(providerResult?.status === "max_attempts_reached" || attempts === undefined ? "Too Many Requests" : "Unauthorized", providerResult?.status === "expired" ? "SMS code expired" : "Invalid MFA code");
7104
7342
  }
7105
- await mfaStore.saveEnrollment({
7106
- ...enrollment,
7343
+ const completed = await mfaStore.completeSmsChallenge({
7344
+ challengeId,
7107
7345
  lastUsedAt: Date.now(),
7108
- smsFailedAttempts: 0,
7109
- smsPendingCodeExpiresAt: undefined,
7110
- smsPendingCodeHash: undefined,
7111
- updatedAt: Date.now()
7346
+ smsVerified: true,
7347
+ userId: getUserId(user)
7112
7348
  });
7349
+ if (!completed) {
7350
+ return status("Unauthorized", "SMS challenge is no longer active");
7351
+ }
7113
7352
  return promote();
7114
7353
  };
7115
7354
  if (factor === "sms")
@@ -7171,6 +7410,7 @@ var mfaManagementRoutes = ({
7171
7410
  authSessionStore,
7172
7411
  getUserId,
7173
7412
  managementRoute = "/auth/mfa",
7413
+ managementAuthMaxAgeMs = DEFAULT_MFA_MANAGEMENT_AUTH_MAX_AGE_MS,
7174
7414
  mfaStore
7175
7415
  }) => new Elysia19().use(sessionStore()).get(managementRoute, async ({
7176
7416
  cookie: { user_session_id },
@@ -7209,6 +7449,9 @@ var mfaManagementRoutes = ({
7209
7449
  if (!userSession) {
7210
7450
  return status("Unauthorized", "Authentication required");
7211
7451
  }
7452
+ if (!hasRecentAuthentication(userSession, managementAuthMaxAgeMs)) {
7453
+ return status("Unauthorized", "Recent authentication required");
7454
+ }
7212
7455
  await mfaStore.removeEnrollment(getUserId(userSession.user));
7213
7456
  return status("OK", { status: "disabled" });
7214
7457
  }, { cookie: t13.Cookie({ user_session_id: userSessionIdTypebox }) });
@@ -7222,6 +7465,7 @@ var mfaTotpRoutes = ({
7222
7465
  encryptionKey,
7223
7466
  getUserId,
7224
7467
  issuer = DEFAULT_MFA_ISSUER,
7468
+ managementAuthMaxAgeMs = DEFAULT_MFA_MANAGEMENT_AUTH_MAX_AGE_MS,
7225
7469
  mfaStore,
7226
7470
  onMfaEnrolled,
7227
7471
  totpSetupRoute = "/auth/mfa/totp/setup",
@@ -7239,6 +7483,9 @@ var mfaTotpRoutes = ({
7239
7483
  if (!userSession) {
7240
7484
  return status("Unauthorized", "Authentication required");
7241
7485
  }
7486
+ if (!hasRecentAuthentication(userSession, managementAuthMaxAgeMs)) {
7487
+ return status("Unauthorized", "Recent authentication required");
7488
+ }
7242
7489
  const userId = getUserId(userSession.user);
7243
7490
  const secret = generateTotpSecret();
7244
7491
  const existing = await mfaStore.getEnrollment(userId);
@@ -7278,6 +7525,9 @@ var mfaTotpRoutes = ({
7278
7525
  if (!userSession) {
7279
7526
  return status("Unauthorized", "Authentication required");
7280
7527
  }
7528
+ if (!hasRecentAuthentication(userSession, managementAuthMaxAgeMs)) {
7529
+ return status("Unauthorized", "Recent authentication required");
7530
+ }
7281
7531
  const userId = getUserId(userSession.user);
7282
7532
  const enrollment = await mfaStore.getEnrollment(userId);
7283
7533
  if (!enrollment?.totpSecretCiphertext) {
@@ -32816,14 +33066,85 @@ var cloneEnrollment = (value) => ({
32816
33066
  var createInMemoryMfaStore = () => {
32817
33067
  const enrollments = new Map;
32818
33068
  return {
33069
+ claimSmsChallenge: async ({
33070
+ challengeId,
33071
+ cooldownCutoff,
33072
+ enrollment
33073
+ }) => {
33074
+ const current = enrollments.get(enrollment.userId);
33075
+ if (current?.smsCodeSentAt !== undefined && current.smsCodeSentAt > cooldownCutoff) {
33076
+ return false;
33077
+ }
33078
+ enrollments.set(enrollment.userId, cloneEnrollment({
33079
+ ...enrollment,
33080
+ smsChallengeId: challengeId
33081
+ }));
33082
+ return true;
33083
+ },
33084
+ completeSmsChallenge: async ({
33085
+ challengeId,
33086
+ lastUsedAt,
33087
+ smsVerified,
33088
+ userId
33089
+ }) => {
33090
+ const current = enrollments.get(userId);
33091
+ if (current?.smsChallengeId !== challengeId)
33092
+ return false;
33093
+ enrollments.set(userId, cloneEnrollment({
33094
+ ...current,
33095
+ lastUsedAt: lastUsedAt ?? current.lastUsedAt,
33096
+ smsChallengeId: undefined,
33097
+ smsFailedAttempts: 0,
33098
+ smsPendingCodeExpiresAt: undefined,
33099
+ smsPendingCodeHash: undefined,
33100
+ smsPendingPurpose: undefined,
33101
+ smsProviderReference: undefined,
33102
+ smsVerified,
33103
+ updatedAt: Date.now()
33104
+ }));
33105
+ return true;
33106
+ },
33107
+ finalizeSmsChallenge: async (input) => {
33108
+ const current = enrollments.get(input.userId);
33109
+ if (current?.smsChallengeId !== input.challengeId)
33110
+ return false;
33111
+ enrollments.set(input.userId, cloneEnrollment({
33112
+ ...current,
33113
+ smsPendingCodeExpiresAt: input.expiresAt,
33114
+ smsPendingCodeHash: input.hash,
33115
+ smsProviderReference: input.providerReference,
33116
+ updatedAt: Date.now()
33117
+ }));
33118
+ return true;
33119
+ },
32819
33120
  getEnrollment: async (userId) => {
32820
33121
  const enrollment = enrollments.get(userId);
32821
33122
  return enrollment ? cloneEnrollment(enrollment) : undefined;
32822
33123
  },
32823
33124
  listEnrollments: async () => Array.from(enrollments.values()).map(cloneEnrollment),
33125
+ recordSmsFailure: async ({ challengeId, maxAttempts, userId }) => {
33126
+ const current = enrollments.get(userId);
33127
+ if (current?.smsChallengeId !== challengeId || (current.smsFailedAttempts ?? 0) >= maxAttempts)
33128
+ return;
33129
+ const attempts = (current.smsFailedAttempts ?? 0) + 1;
33130
+ enrollments.set(userId, cloneEnrollment({
33131
+ ...current,
33132
+ smsFailedAttempts: attempts,
33133
+ updatedAt: Date.now()
33134
+ }));
33135
+ return attempts;
33136
+ },
32824
33137
  removeEnrollment: async (userId) => {
32825
33138
  enrollments.delete(userId);
32826
33139
  },
33140
+ rollbackSmsChallenge: async ({ challengeId, previous, userId }) => {
33141
+ if (enrollments.get(userId)?.smsChallengeId !== challengeId)
33142
+ return;
33143
+ if (previous)
33144
+ enrollments.set(userId, cloneEnrollment(previous));
33145
+ else
33146
+ enrollments.delete(userId);
33147
+ },
32827
33148
  saveEnrollment: async (enrollment) => {
32828
33149
  enrollments.set(enrollment.userId, cloneEnrollment(enrollment));
32829
33150
  }
@@ -32836,12 +33157,16 @@ var mfaEnrollmentsTable = pgTable("auth_mfa_enrollments", {
32836
33157
  backup_code_hashes: jsonb("backup_code_hashes").$type().notNull().default([]),
32837
33158
  created_at_ms: bigint("created_at_ms", { mode: "number" }).notNull(),
32838
33159
  last_used_at_ms: bigint("last_used_at_ms", { mode: "number" }),
33160
+ sms_challenge_id: text("sms_challenge_id"),
33161
+ sms_code_sent_at_ms: bigint("sms_code_sent_at_ms", { mode: "number" }),
32839
33162
  sms_failed_attempts: smallint("sms_failed_attempts").notNull().default(0),
32840
33163
  sms_pending_code_expires_at_ms: bigint("sms_pending_code_expires_at_ms", {
32841
33164
  mode: "number"
32842
33165
  }),
32843
33166
  sms_pending_code_hash: text("sms_pending_code_hash"),
33167
+ sms_pending_purpose: text("sms_pending_purpose").$type(),
32844
33168
  sms_phone: varchar("sms_phone", { length: PHONE_LENGTH }),
33169
+ sms_provider_reference: text("sms_provider_reference"),
32845
33170
  sms_verified: boolean("sms_verified").notNull().default(false),
32846
33171
  totp_failed_attempts: smallint("totp_failed_attempts").notNull().default(0),
32847
33172
  totp_secret_ciphertext: text("totp_secret_ciphertext"),
@@ -32853,10 +33178,14 @@ var toEnrollment = (row) => ({
32853
33178
  backupCodeHashes: row.backup_code_hashes,
32854
33179
  createdAt: row.created_at_ms,
32855
33180
  lastUsedAt: row.last_used_at_ms ?? undefined,
33181
+ smsChallengeId: row.sms_challenge_id ?? undefined,
33182
+ smsCodeSentAt: row.sms_code_sent_at_ms ?? undefined,
32856
33183
  smsFailedAttempts: row.sms_failed_attempts,
32857
33184
  smsPendingCodeExpiresAt: row.sms_pending_code_expires_at_ms ?? undefined,
32858
33185
  smsPendingCodeHash: row.sms_pending_code_hash ?? undefined,
33186
+ smsPendingPurpose: row.sms_pending_purpose ?? undefined,
32859
33187
  smsPhone: row.sms_phone ?? undefined,
33188
+ smsProviderReference: row.sms_provider_reference ?? undefined,
32860
33189
  smsVerified: row.sms_verified,
32861
33190
  totpFailedAttempts: row.totp_failed_attempts,
32862
33191
  totpSecretCiphertext: row.totp_secret_ciphertext ?? undefined,
@@ -32865,40 +33194,118 @@ var toEnrollment = (row) => ({
32865
33194
  userId: row.user_id
32866
33195
  });
32867
33196
  var createNeonMfaStore = (databaseUrl) => createPostgresMfaStore(createNeonDatabase(databaseUrl));
32868
- var createPostgresMfaStore = (db) => ({
32869
- getEnrollment: async (userId) => {
32870
- const [row] = await db.select().from(mfaEnrollmentsTable).where(eq(mfaEnrollmentsTable.user_id, userId)).limit(1);
32871
- return row ? toEnrollment(row) : undefined;
32872
- },
32873
- listEnrollments: async () => {
32874
- const rows = await db.select().from(mfaEnrollmentsTable);
32875
- return rows.map(toEnrollment);
32876
- },
32877
- removeEnrollment: async (userId) => {
32878
- await db.delete(mfaEnrollmentsTable).where(eq(mfaEnrollmentsTable.user_id, userId));
32879
- },
32880
- saveEnrollment: async (enrollment) => {
32881
- const values = {
32882
- backup_code_hashes: enrollment.backupCodeHashes,
32883
- created_at_ms: enrollment.createdAt,
32884
- last_used_at_ms: enrollment.lastUsedAt ?? null,
32885
- sms_failed_attempts: enrollment.smsFailedAttempts ?? 0,
32886
- sms_pending_code_expires_at_ms: enrollment.smsPendingCodeExpiresAt ?? null,
32887
- sms_pending_code_hash: enrollment.smsPendingCodeHash ?? null,
32888
- sms_phone: enrollment.smsPhone ?? null,
32889
- sms_verified: enrollment.smsVerified,
32890
- totp_failed_attempts: enrollment.totpFailedAttempts ?? 0,
32891
- totp_secret_ciphertext: enrollment.totpSecretCiphertext ?? null,
32892
- totp_verified: enrollment.totpVerified,
32893
- updated_at_ms: enrollment.updatedAt,
32894
- user_id: enrollment.userId
32895
- };
32896
- await db.insert(mfaEnrollmentsTable).values(values).onConflictDoUpdate({
32897
- set: values,
32898
- target: mfaEnrollmentsTable.user_id
32899
- });
32900
- }
32901
- });
33197
+ var createPostgresMfaStore = (db) => {
33198
+ const toValues = (enrollment) => ({
33199
+ backup_code_hashes: enrollment.backupCodeHashes,
33200
+ created_at_ms: enrollment.createdAt,
33201
+ last_used_at_ms: enrollment.lastUsedAt ?? null,
33202
+ sms_challenge_id: enrollment.smsChallengeId ?? null,
33203
+ sms_code_sent_at_ms: enrollment.smsCodeSentAt ?? null,
33204
+ sms_failed_attempts: enrollment.smsFailedAttempts ?? 0,
33205
+ sms_pending_code_expires_at_ms: enrollment.smsPendingCodeExpiresAt ?? null,
33206
+ sms_pending_code_hash: enrollment.smsPendingCodeHash ?? null,
33207
+ sms_pending_purpose: enrollment.smsPendingPurpose ?? null,
33208
+ sms_phone: enrollment.smsPhone ?? null,
33209
+ sms_provider_reference: enrollment.smsProviderReference ?? null,
33210
+ sms_verified: enrollment.smsVerified,
33211
+ totp_failed_attempts: enrollment.totpFailedAttempts ?? 0,
33212
+ totp_secret_ciphertext: enrollment.totpSecretCiphertext ?? null,
33213
+ totp_verified: enrollment.totpVerified,
33214
+ updated_at_ms: enrollment.updatedAt,
33215
+ user_id: enrollment.userId
33216
+ });
33217
+ return {
33218
+ claimSmsChallenge: async ({
33219
+ challengeId,
33220
+ cooldownCutoff,
33221
+ enrollment
33222
+ }) => {
33223
+ const values = toValues({
33224
+ ...enrollment,
33225
+ smsChallengeId: challengeId
33226
+ });
33227
+ const rows = await db.insert(mfaEnrollmentsTable).values(values).onConflictDoUpdate({
33228
+ set: {
33229
+ sms_challenge_id: challengeId,
33230
+ sms_code_sent_at_ms: enrollment.smsCodeSentAt ?? null,
33231
+ sms_failed_attempts: 0,
33232
+ sms_pending_code_expires_at_ms: enrollment.smsPendingCodeExpiresAt ?? null,
33233
+ sms_pending_code_hash: null,
33234
+ sms_pending_purpose: enrollment.smsPendingPurpose ?? null,
33235
+ sms_phone: enrollment.smsPhone ?? null,
33236
+ sms_provider_reference: null,
33237
+ sms_verified: enrollment.smsVerified,
33238
+ updated_at_ms: enrollment.updatedAt
33239
+ },
33240
+ setWhere: or(isNull(mfaEnrollmentsTable.sms_code_sent_at_ms), lte(mfaEnrollmentsTable.sms_code_sent_at_ms, cooldownCutoff)),
33241
+ target: mfaEnrollmentsTable.user_id
33242
+ }).returning({ userId: mfaEnrollmentsTable.user_id });
33243
+ return rows.length === 1;
33244
+ },
33245
+ completeSmsChallenge: async ({
33246
+ challengeId,
33247
+ lastUsedAt,
33248
+ smsVerified,
33249
+ userId
33250
+ }) => {
33251
+ const rows = await db.update(mfaEnrollmentsTable).set({
33252
+ last_used_at_ms: lastUsedAt,
33253
+ sms_challenge_id: null,
33254
+ sms_failed_attempts: 0,
33255
+ sms_pending_code_expires_at_ms: null,
33256
+ sms_pending_code_hash: null,
33257
+ sms_pending_purpose: null,
33258
+ sms_provider_reference: null,
33259
+ sms_verified: smsVerified,
33260
+ updated_at_ms: Date.now()
33261
+ }).where(and(eq(mfaEnrollmentsTable.user_id, userId), eq(mfaEnrollmentsTable.sms_challenge_id, challengeId))).returning({ userId: mfaEnrollmentsTable.user_id });
33262
+ return rows.length === 1;
33263
+ },
33264
+ finalizeSmsChallenge: async (input) => {
33265
+ const rows = await db.update(mfaEnrollmentsTable).set({
33266
+ sms_pending_code_expires_at_ms: input.expiresAt,
33267
+ sms_pending_code_hash: input.hash ?? null,
33268
+ sms_provider_reference: input.providerReference ?? null,
33269
+ updated_at_ms: Date.now()
33270
+ }).where(and(eq(mfaEnrollmentsTable.user_id, input.userId), eq(mfaEnrollmentsTable.sms_challenge_id, input.challengeId))).returning({ userId: mfaEnrollmentsTable.user_id });
33271
+ return rows.length === 1;
33272
+ },
33273
+ getEnrollment: async (userId) => {
33274
+ const [row] = await db.select().from(mfaEnrollmentsTable).where(eq(mfaEnrollmentsTable.user_id, userId)).limit(1);
33275
+ return row ? toEnrollment(row) : undefined;
33276
+ },
33277
+ listEnrollments: async () => {
33278
+ const rows = await db.select().from(mfaEnrollmentsTable);
33279
+ return rows.map(toEnrollment);
33280
+ },
33281
+ recordSmsFailure: async ({ challengeId, maxAttempts, userId }) => {
33282
+ const rows = await db.update(mfaEnrollmentsTable).set({
33283
+ sms_failed_attempts: sql`${mfaEnrollmentsTable.sms_failed_attempts} + 1`,
33284
+ updated_at_ms: Date.now()
33285
+ }).where(and(eq(mfaEnrollmentsTable.user_id, userId), eq(mfaEnrollmentsTable.sms_challenge_id, challengeId), lte(mfaEnrollmentsTable.sms_failed_attempts, maxAttempts - 1))).returning({
33286
+ attempts: mfaEnrollmentsTable.sms_failed_attempts
33287
+ });
33288
+ return rows[0]?.attempts;
33289
+ },
33290
+ removeEnrollment: async (userId) => {
33291
+ await db.delete(mfaEnrollmentsTable).where(eq(mfaEnrollmentsTable.user_id, userId));
33292
+ },
33293
+ rollbackSmsChallenge: async ({ challengeId, previous, userId }) => {
33294
+ if (previous) {
33295
+ await db.update(mfaEnrollmentsTable).set(toValues(previous)).where(and(eq(mfaEnrollmentsTable.user_id, userId), eq(mfaEnrollmentsTable.sms_challenge_id, challengeId)));
33296
+ return;
33297
+ }
33298
+ await db.delete(mfaEnrollmentsTable).where(and(eq(mfaEnrollmentsTable.user_id, userId), eq(mfaEnrollmentsTable.sms_challenge_id, challengeId)));
33299
+ },
33300
+ saveEnrollment: async (enrollment) => {
33301
+ const values = toValues(enrollment);
33302
+ await db.insert(mfaEnrollmentsTable).values(values).onConflictDoUpdate({
33303
+ set: values,
33304
+ target: mfaEnrollmentsTable.user_id
33305
+ });
33306
+ }
33307
+ };
33308
+ };
32902
33309
  // src/audit/types.ts
32903
33310
  var AUDIT_EVENT_TYPES = [
32904
33311
  "account_deleted",
@@ -37234,6 +37641,19 @@ var mfaTotpLockoutMigration = {
37234
37641
  id: "0003_totp_lockout",
37235
37642
  sql: 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "totp_failed_attempts" smallint NOT NULL DEFAULT 0;'
37236
37643
  };
37644
+ var mfaSmsDeliveryPolicyMigration = {
37645
+ id: "0004_sms_delivery_policy",
37646
+ sql: [
37647
+ 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_code_sent_at_ms" bigint;',
37648
+ 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_pending_purpose" text;',
37649
+ 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_provider_reference" text;'
37650
+ ].join(`
37651
+ `)
37652
+ };
37653
+ var mfaSmsAtomicChallengeMigration = {
37654
+ id: "0005_sms_atomic_challenge",
37655
+ sql: 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "sms_challenge_id" text;'
37656
+ };
37237
37657
  var oidcResourceAudienceMigration = {
37238
37658
  id: "0002_resource_audience",
37239
37659
  sql: [
@@ -37288,7 +37708,9 @@ var blockMigrations = {
37288
37708
  migrations: [
37289
37709
  ...initMigration("mfa", [mfaEnrollmentsTable]).migrations,
37290
37710
  mfaSmsColumnsMigration,
37291
- mfaTotpLockoutMigration
37711
+ mfaTotpLockoutMigration,
37712
+ mfaSmsDeliveryPolicyMigration,
37713
+ mfaSmsAtomicChallengeMigration
37292
37714
  ]
37293
37715
  },
37294
37716
  oidc: {
@@ -37801,6 +38223,7 @@ var buildAuthApplications = async (configuration) => {
37801
38223
  credentials,
37802
38224
  customProviders,
37803
38225
  mfa,
38226
+ verificationProvider,
37804
38227
  passwordless,
37805
38228
  lockout,
37806
38229
  sessions,
@@ -38033,7 +38456,8 @@ var buildAuthApplications = async (configuration) => {
38033
38456
  auditedMfa ? mfaRoutes({
38034
38457
  ...auditedMfa,
38035
38458
  authSessionStore,
38036
- cookieSecure: resolvedCookieSecure
38459
+ cookieSecure: resolvedCookieSecure,
38460
+ verificationProvider
38037
38461
  }) : new Elysia45,
38038
38462
  passwordless ? passwordlessRoutes({
38039
38463
  ...passwordless,
@@ -38280,8 +38704,9 @@ export {
38280
38704
  createPostgresScimTokenStore,
38281
38705
  createNodeSamlAdapter,
38282
38706
  createAuthContext,
38283
- auth2 as auth
38707
+ auth2 as auth,
38708
+ VerificationProviderError
38284
38709
  };
38285
38710
 
38286
- //# debugId=8E94D3B40F6913D564756E2164756E21
38711
+ //# debugId=82A372899BEA4F4564756E2164756E21
38287
38712
  //# sourceMappingURL=server.js.map