@absolutejs/auth 0.49.3 → 0.50.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.
package/dist/index.js CHANGED
@@ -4354,6 +4354,7 @@ var DEFAULT_SMS_CODE_LENGTH = 6;
4354
4354
  var SMS_CODE_TTL_MINUTES = 5;
4355
4355
  var DEFAULT_SMS_CODE_TTL_MS = SMS_CODE_TTL_MINUTES * SECONDS_IN_A_MINUTE * MILLISECONDS_IN_A_SECOND;
4356
4356
  var DEFAULT_SMS_MAX_ATTEMPTS = 3;
4357
+ var DEFAULT_TOTP_MAX_ATTEMPTS = 5;
4357
4358
 
4358
4359
  // src/mfa/secret.ts
4359
4360
  init_crypto();
@@ -4521,7 +4522,8 @@ var mfaChallenge = ({
4521
4522
  sessionDurationMs = DEFAULT_MFA_SESSION_TTL_MS,
4522
4523
  smsCodeLength = DEFAULT_SMS_CODE_LENGTH,
4523
4524
  smsCodeTtlMs = DEFAULT_SMS_CODE_TTL_MS,
4524
- smsMaxAttempts = DEFAULT_SMS_MAX_ATTEMPTS
4525
+ smsMaxAttempts = DEFAULT_SMS_MAX_ATTEMPTS,
4526
+ totpMaxAttempts = DEFAULT_TOTP_MAX_ATTEMPTS
4525
4527
  }) => new Elysia13().use(sessionStore()).post(challengeRoute, async ({
4526
4528
  body: { action, code, factor },
4527
4529
  cookie: { user_session_id },
@@ -4620,12 +4622,24 @@ var mfaChallenge = ({
4620
4622
  if (code === undefined) {
4621
4623
  return status("Unauthorized", "Invalid MFA code");
4622
4624
  }
4625
+ if ((enrollment.totpFailedAttempts ?? 0) >= totpMaxAttempts) {
4626
+ await onMfaChallengeError?.({
4627
+ error: new Error("mfa_totp_attempts_exceeded"),
4628
+ userId: getUserId(user)
4629
+ });
4630
+ return status("Unauthorized", "Too many attempts");
4631
+ }
4623
4632
  const totpValid = enrollment.totpVerified && enrollment.totpSecretCiphertext ? await verifyTotp({
4624
4633
  secret: await decryptTotpSecret(enrollment.totpSecretCiphertext, encryptionKey),
4625
4634
  token: code
4626
4635
  }) : false;
4627
4636
  const remainingBackupHashes = totpValid ? undefined : await consumeBackupCode(code, enrollment.backupCodeHashes);
4628
4637
  if (!totpValid && remainingBackupHashes === undefined) {
4638
+ await mfaStore.saveEnrollment({
4639
+ ...enrollment,
4640
+ totpFailedAttempts: (enrollment.totpFailedAttempts ?? 0) + 1,
4641
+ updatedAt: Date.now()
4642
+ });
4629
4643
  await onMfaChallengeError?.({
4630
4644
  error: new Error("invalid_mfa_code"),
4631
4645
  userId: getUserId(user)
@@ -4636,6 +4650,7 @@ var mfaChallenge = ({
4636
4650
  ...enrollment,
4637
4651
  backupCodeHashes: remainingBackupHashes ?? enrollment.backupCodeHashes,
4638
4652
  lastUsedAt: Date.now(),
4653
+ totpFailedAttempts: 0,
4639
4654
  updatedAt: Date.now()
4640
4655
  });
4641
4656
  return promote();
@@ -23203,6 +23218,7 @@ var mfaEnrollmentsTable = pgTable("auth_mfa_enrollments", {
23203
23218
  sms_pending_code_hash: text("sms_pending_code_hash"),
23204
23219
  sms_phone: varchar("sms_phone", { length: PHONE_LENGTH }),
23205
23220
  sms_verified: boolean("sms_verified").notNull().default(false),
23221
+ totp_failed_attempts: smallint("totp_failed_attempts").notNull().default(0),
23206
23222
  totp_secret_ciphertext: text("totp_secret_ciphertext"),
23207
23223
  totp_verified: boolean("totp_verified").notNull().default(false),
23208
23224
  updated_at_ms: bigint("updated_at_ms", { mode: "number" }).notNull(),
@@ -23217,6 +23233,7 @@ var toEnrollment = (row) => ({
23217
23233
  smsPendingCodeHash: row.sms_pending_code_hash ?? undefined,
23218
23234
  smsPhone: row.sms_phone ?? undefined,
23219
23235
  smsVerified: row.sms_verified,
23236
+ totpFailedAttempts: row.totp_failed_attempts,
23220
23237
  totpSecretCiphertext: row.totp_secret_ciphertext ?? undefined,
23221
23238
  totpVerified: row.totp_verified,
23222
23239
  updatedAt: row.updated_at_ms,
@@ -23245,6 +23262,7 @@ var createPostgresMfaStore = (db) => ({
23245
23262
  sms_pending_code_hash: enrollment.smsPendingCodeHash ?? null,
23246
23263
  sms_phone: enrollment.smsPhone ?? null,
23247
23264
  sms_verified: enrollment.smsVerified,
23265
+ totp_failed_attempts: enrollment.totpFailedAttempts ?? 0,
23248
23266
  totp_secret_ciphertext: enrollment.totpSecretCiphertext ?? null,
23249
23267
  totp_verified: enrollment.totpVerified,
23250
23268
  updated_at_ms: enrollment.updatedAt,
@@ -26573,6 +26591,10 @@ var mfaSmsColumnsMigration = {
26573
26591
  ].join(`
26574
26592
  `)
26575
26593
  };
26594
+ var mfaTotpLockoutMigration = {
26595
+ id: "0003_totp_lockout",
26596
+ sql: 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "totp_failed_attempts" smallint NOT NULL DEFAULT 0;'
26597
+ };
26576
26598
  var blockMigrations = {
26577
26599
  adaptive: initMigration("adaptive", [knownDevicesTable, loginHistoryTable]),
26578
26600
  apikeys: initMigration("apikeys", [
@@ -26596,7 +26618,8 @@ var blockMigrations = {
26596
26618
  block: "mfa",
26597
26619
  migrations: [
26598
26620
  ...initMigration("mfa", [mfaEnrollmentsTable]).migrations,
26599
- mfaSmsColumnsMigration
26621
+ mfaSmsColumnsMigration,
26622
+ mfaTotpLockoutMigration
26600
26623
  ]
26601
26624
  },
26602
26625
  oidc: initMigration("oidc", [
@@ -27872,6 +27895,7 @@ export {
27872
27895
  DEFAULT_VP_ROUTE,
27873
27896
  DEFAULT_VERIFICATION_TOKEN_TTL_MS,
27874
27897
  DEFAULT_VCI_ROUTE,
27898
+ DEFAULT_TOTP_MAX_ATTEMPTS,
27875
27899
  DEFAULT_TOKEN_ROUTE,
27876
27900
  DEFAULT_STATUS_ROUTE,
27877
27901
  DEFAULT_SSO_SESSION_TTL_MS,
@@ -27903,5 +27927,5 @@ export {
27903
27927
  AuthIdentityConflictError
27904
27928
  };
27905
27929
 
27906
- //# debugId=178CE4DFADBEB75764756E2164756E21
27930
+ //# debugId=47718768D4F1D77064756E2164756E21
27907
27931
  //# sourceMappingURL=index.js.map