[PR #8711] [MERGED] fix(two-factor): prevent unverified TOTP enrollment from gating sign-in #25064

Closed
opened 2026-04-15 22:42:42 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/8711
Author: @aarmful
Created: 3/20/2026
Status: Merged
Merged: 4/9/2026
Merged by: @gustavovalverde

Base: mainHead: fix/two-factor-totp-verified-enrollment


📝 Commits (10+)

  • 33bf040 fix(two-factor): add verified flag to prevent unverified totp use during
  • 042de98 fix(two-factor): update cli schema snapshots for verified column
  • ff0f5d4 fix(two-factor): default verified column to true to avoid breaking
  • da0d98b fix: format
  • c49cb12 fix(cli): update snapshot default value
  • b09d0c6 Merge branch 'canary' into fix/two-factor-totp-verified-enrollment
  • 6339f0a docs(two-factor): add verified field to schema table
  • ce3a8e0 Merge branch 'fix/two-factor-totp-verified-enrollment' of https://github.com/armfuldev/better-auth into fix/two-factor-totp-verified-enrollment
  • 4ee34eb Merge branch 'main' into fix/two-factor-totp-verified-enrollment
  • 4073199 Merge branch 'main' into fix/two-factor-totp-verified-enrollment

📊 Changes

22 files changed (+388 additions, -59 deletions)

View changed files

.changeset/two-factor-verified-enrollment.md (+13 -0)
📝 docs/content/docs/plugins/2fa.mdx (+1 -0)
📝 packages/better-auth/src/plugins/two-factor/index.ts (+15 -8)
📝 packages/better-auth/src/plugins/two-factor/schema.ts (+8 -0)
📝 packages/better-auth/src/plugins/two-factor/totp/index.ts (+43 -27)
📝 packages/better-auth/src/plugins/two-factor/two-factor.test.ts (+275 -6)
📝 packages/better-auth/src/plugins/two-factor/types.ts (+1 -1)
📝 packages/cli/test/__snapshots__/auth-schema-mysql-number-id.txt (+1 -0)
📝 packages/cli/test/__snapshots__/auth-schema-mysql-uuid.txt (+1 -0)
📝 packages/cli/test/__snapshots__/auth-schema-mysql.txt (+1 -0)
📝 packages/cli/test/__snapshots__/auth-schema-number-id.txt (+1 -0)
📝 packages/cli/test/__snapshots__/auth-schema-pg-uuid.txt (+1 -0)
📝 packages/cli/test/__snapshots__/auth-schema-sqlite-number-id.txt (+1 -0)
📝 packages/cli/test/__snapshots__/auth-schema-sqlite-uuid.txt (+1 -0)
📝 packages/cli/test/__snapshots__/auth-schema-sqlite.txt (+1 -0)
📝 packages/cli/test/__snapshots__/auth-schema.txt (+1 -0)
📝 packages/cli/test/__snapshots__/schema-mongodb.prisma (+3 -2)
📝 packages/cli/test/__snapshots__/schema-mysql-custom.prisma (+5 -4)
📝 packages/cli/test/__snapshots__/schema-mysql.prisma (+5 -4)
📝 packages/cli/test/__snapshots__/schema-numberid.prisma (+3 -2)

...and 2 more files

📄 Description

closes #8627

the totp enrollment flow was using user.twoFactorEnabled to figure out if someone was setting up totp for the first time or signing in. that works fine normally, but if you already have otp enabled (so twoFactorEnabled is already true) and then start totp setup, the code thinks you're signing in and skips the verification step entirely. if you close the tab mid-enrollment, the unverified secret is now active and you're locked out.

the fix adds a verified column to the twoFactor table. new rows start unverified, get marked verified once you enter a valid code, and totp sign-in rejects unverified rows. backup codes and otp are unaffected and work as fallbacks during unfinished enrollment. re-enrollment (calling enableTwoFactor when totp is already verified) preserves verified: true so users are never locked out while rotating their secret.

adds a migration column verified (boolean, default true). existing rows automatically get true which is correct since they were already verified under the old flow. enableTwoFactor explicitly sets new rows to false.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/8711 **Author:** [@aarmful](https://github.com/aarmful) **Created:** 3/20/2026 **Status:** ✅ Merged **Merged:** 4/9/2026 **Merged by:** [@gustavovalverde](https://github.com/gustavovalverde) **Base:** `main` ← **Head:** `fix/two-factor-totp-verified-enrollment` --- ### 📝 Commits (10+) - [`33bf040`](https://github.com/better-auth/better-auth/commit/33bf040fb9c6fde74033c9e01c52c275bfad4ee5) fix(two-factor): add verified flag to prevent unverified totp use during - [`042de98`](https://github.com/better-auth/better-auth/commit/042de9847abbd30fc22b0ed6755691134714adda) fix(two-factor): update cli schema snapshots for verified column - [`ff0f5d4`](https://github.com/better-auth/better-auth/commit/ff0f5d40edbee88699c4a2ef06a3d29064ecf3b8) fix(two-factor): default verified column to true to avoid breaking - [`da0d98b`](https://github.com/better-auth/better-auth/commit/da0d98bc2322f535ec07b499b53fd2f7b36742f5) fix: format - [`c49cb12`](https://github.com/better-auth/better-auth/commit/c49cb1218d550aa4c8c305c3a01066f8e98b37e0) fix(cli): update snapshot default value - [`b09d0c6`](https://github.com/better-auth/better-auth/commit/b09d0c6ba4865e63030f0a41144d39cfc8a929c9) Merge branch 'canary' into fix/two-factor-totp-verified-enrollment - [`6339f0a`](https://github.com/better-auth/better-auth/commit/6339f0a2ae7809ef71fd1c8b257da799d2b4f42d) docs(two-factor): add verified field to schema table - [`ce3a8e0`](https://github.com/better-auth/better-auth/commit/ce3a8e0835c28e0a902d39519f4e19f3162b5a2d) Merge branch 'fix/two-factor-totp-verified-enrollment' of https://github.com/armfuldev/better-auth into fix/two-factor-totp-verified-enrollment - [`4ee34eb`](https://github.com/better-auth/better-auth/commit/4ee34ebe313b8647f7b2dcba6f859de1877639c5) Merge branch 'main' into fix/two-factor-totp-verified-enrollment - [`4073199`](https://github.com/better-auth/better-auth/commit/4073199ba18d22902e430e82b616a4cb5ae88cec) Merge branch 'main' into fix/two-factor-totp-verified-enrollment ### 📊 Changes **22 files changed** (+388 additions, -59 deletions) <details> <summary>View changed files</summary> ➕ `.changeset/two-factor-verified-enrollment.md` (+13 -0) 📝 `docs/content/docs/plugins/2fa.mdx` (+1 -0) 📝 `packages/better-auth/src/plugins/two-factor/index.ts` (+15 -8) 📝 `packages/better-auth/src/plugins/two-factor/schema.ts` (+8 -0) 📝 `packages/better-auth/src/plugins/two-factor/totp/index.ts` (+43 -27) 📝 `packages/better-auth/src/plugins/two-factor/two-factor.test.ts` (+275 -6) 📝 `packages/better-auth/src/plugins/two-factor/types.ts` (+1 -1) 📝 `packages/cli/test/__snapshots__/auth-schema-mysql-number-id.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-mysql-uuid.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-mysql.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-number-id.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-pg-uuid.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-sqlite-number-id.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-sqlite-uuid.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-sqlite.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/auth-schema.txt` (+1 -0) 📝 `packages/cli/test/__snapshots__/schema-mongodb.prisma` (+3 -2) 📝 `packages/cli/test/__snapshots__/schema-mysql-custom.prisma` (+5 -4) 📝 `packages/cli/test/__snapshots__/schema-mysql.prisma` (+5 -4) 📝 `packages/cli/test/__snapshots__/schema-numberid.prisma` (+3 -2) _...and 2 more files_ </details> ### 📄 Description closes #8627 the totp enrollment flow was using `user.twoFactorEnabled` to figure out if someone was setting up totp for the first time or signing in. that works fine normally, but if you already have otp enabled (so `twoFactorEnabled` is already `true`) and then start totp setup, the code thinks you're signing in and skips the verification step entirely. if you close the tab mid-enrollment, the unverified secret is now active and you're locked out. the fix adds a `verified` column to the `twoFactor` table. new rows start unverified, get marked verified once you enter a valid code, and totp sign-in rejects unverified rows. backup codes and otp are unaffected and work as fallbacks during unfinished enrollment. re-enrollment (calling `enableTwoFactor` when totp is already verified) preserves `verified: true` so users are never locked out while rotating their secret. adds a migration column `verified` (boolean, default `true`). existing rows automatically get `true` which is correct since they were already verified under the old flow. `enableTwoFactor` explicitly sets new rows to `false`. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-15 22:42:42 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#25064