[PR #8583] [CLOSED] fix(phone-number): deduplicate verification records on repeated OTP requests #24979

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

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/8583
Author: @thefotios
Created: 3/12/2026
Status: Closed

Base: canaryHead: 03-12-fix_phone-number_deduplicate_verification_records_on_repeated_otp_requests


📝 Commits (10+)

  • 545a73d ci: continue on error
  • 0ff021d chore: release v1.5.0
  • d567e1d feat(blog): add draft support for blog posts and update image URLs
  • cdcdc3d chore(blog): update image URLs to use absolute paths for consistency
  • 5224a3f chore: remove draft
  • c14c404 fix: update sign-in link to use absolute URL for better accessibility
  • d760222 docs(expo): update Metro config for Expo SDK 53+ (#8220)
  • 7c8bece docs: fix release og path
  • 848e5f8 refactor: replace Link components with anchor tags for sign-in links
  • fd37e60 chore: improve readme description

📊 Changes

288 files changed (+37973 additions, -10256 deletions)

View changed files

📝 .cspell/auth-terms.txt (+1 -0)
📝 .cspell/custom-words.txt (+3 -1)
📝 .cspell/names.txt (+2 -0)
📝 .cspell/tech-terms.txt (+5 -2)
📝 .cspell/third-party.txt (+2 -0)
.github/workflows/demo.yml (+56 -0)
.github/workflows/npm-dist-tag.yml (+35 -0)
📝 .github/workflows/release.yml (+1 -0)
📝 .remarkignore (+1 -0)
📝 CLAUDE.md (+13 -2)
📝 CONTRIBUTING.md (+1 -1)
📝 biome.json (+2 -1)
📝 demo/electron/package.json (+10 -10)
demo/electron/pnpm-lock.yaml (+4725 -0)
demo/electron/pnpm-workspace.yaml (+4 -0)
demo/expo/.claude/agents/cherry-pick-release.md (+184 -0)
📝 demo/expo/app.config.ts (+0 -4)
📝 demo/expo/metro.config.js (+1 -5)
📝 demo/expo/package.json (+24 -23)
demo/expo/pnpm-lock.yaml (+8625 -0)

...and 80 more files

📄 Description

Summary

  • Root cause: createVerificationValue inserts without dedup — when a user requests a second OTP before the first expires, a duplicate verification record is created. On databases with unique constraints (Postgres, MySQL), subsequent calls to updateVerificationByIdentifier or deleteVerificationByIdentifier fail with 500 errors because they assume one record per identifier.
  • Fix: Add a createOrReplaceVerification helper that deletes any existing verification for the identifier before creating a new one. Applied to all 3 createVerificationValue call sites in the phone-number plugin:
    • signInPhoneNumber (requireVerification flow)
    • sendPhoneNumberOTP
    • requestPasswordResetPhoneNumber
  • This matches the .catch() delete-and-retry pattern already used in the email-otp plugin (email-otp/routes.ts:117-134), but uses a slightly cleaner delete-first approach that works on all databases (including SQLite which doesn't enforce unique constraints).

Test plan

  • Added test: repeated sendOtp calls produce exactly 1 verification record (not 3)
  • Added test: wrong code → re-send OTP → correct latest code succeeds
  • Added test: repeated requestPasswordReset calls produce exactly 1 verification record (not 2)
  • All 29 tests pass (3 new + 26 existing, zero regressions)
  • No type errors introduced (tsc --noEmit clean for changed file)

Future improvements

  • Add unique: true to identifier in get-tables.ts (breaking change for existing DBs with duplicates — should be bundled with a minor version bump). Users who want to add it early can do so safely after this fix lands, and npx auth migrate won't overwrite it.
  • Extract createOrReplaceVerificationValue into InternalAdapter for all plugins
  • Fix the same latent bug in email-otp (6 of 7 calls missing dedup) and other plugins

Summary by cubic

Prevents duplicate verification records when a user requests multiple OTPs for the same phone number before the first expires. Ensures only the latest code is valid and avoids 500s across Postgres/MySQL/SQLite.

  • Bug Fixes
    • Added createOrReplaceVerification to delete any existing verification before creating a new one.
    • Applied to all phone-number flows: signInPhoneNumber, sendPhoneNumberOTP, and requestPasswordResetPhoneNumber.
    • Matches the cleanup pattern used in email-otp, using a delete-first approach that works on all databases.
    • Fixed downstream errors in updateVerificationByIdentifier/deleteVerificationByIdentifier that assumed a single record.
    • Added tests to ensure repeated OTP requests keep exactly one verification and that only the latest code works.

Written for commit 31c1361b47. Summary will update on new commits.


🔄 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/8583 **Author:** [@thefotios](https://github.com/thefotios) **Created:** 3/12/2026 **Status:** ❌ Closed **Base:** `canary` ← **Head:** `03-12-fix_phone-number_deduplicate_verification_records_on_repeated_otp_requests` --- ### 📝 Commits (10+) - [`545a73d`](https://github.com/better-auth/better-auth/commit/545a73d10c58978d1324e7839e263e3d9aeb36fa) ci: continue on error - [`0ff021d`](https://github.com/better-auth/better-auth/commit/0ff021d0a4f4bd387a9674061e7e97a00c24949c) chore: release v1.5.0 - [`d567e1d`](https://github.com/better-auth/better-auth/commit/d567e1dee4a084315bd316865a20dae71456c0d0) feat(blog): add draft support for blog posts and update image URLs - [`cdcdc3d`](https://github.com/better-auth/better-auth/commit/cdcdc3d05dfd35fe52ef259498d5d614a229bef8) chore(blog): update image URLs to use absolute paths for consistency - [`5224a3f`](https://github.com/better-auth/better-auth/commit/5224a3fe2095b97fe69a6c1486bc9bf6eac403c9) chore: remove draft - [`c14c404`](https://github.com/better-auth/better-auth/commit/c14c40433dd3871ef63c80bcc640f99d9fd54801) fix: update sign-in link to use absolute URL for better accessibility - [`d760222`](https://github.com/better-auth/better-auth/commit/d76022210fba065140d3f7401507945ae815c340) docs(expo): update Metro config for Expo SDK 53+ (#8220) - [`7c8bece`](https://github.com/better-auth/better-auth/commit/7c8bece3fac6f950b46f2ad8d4fe46e75f9ce1a2) docs: fix release og path - [`848e5f8`](https://github.com/better-auth/better-auth/commit/848e5f8a67029fedaf929f2257851c89ef04aff8) refactor: replace Link components with anchor tags for sign-in links - [`fd37e60`](https://github.com/better-auth/better-auth/commit/fd37e60c7047a1b18acb6c29e4bfec55fbb5375d) chore: improve readme description ### 📊 Changes **288 files changed** (+37973 additions, -10256 deletions) <details> <summary>View changed files</summary> 📝 `.cspell/auth-terms.txt` (+1 -0) 📝 `.cspell/custom-words.txt` (+3 -1) 📝 `.cspell/names.txt` (+2 -0) 📝 `.cspell/tech-terms.txt` (+5 -2) 📝 `.cspell/third-party.txt` (+2 -0) ➕ `.github/workflows/demo.yml` (+56 -0) ➕ `.github/workflows/npm-dist-tag.yml` (+35 -0) 📝 `.github/workflows/release.yml` (+1 -0) 📝 `.remarkignore` (+1 -0) 📝 `CLAUDE.md` (+13 -2) 📝 `CONTRIBUTING.md` (+1 -1) 📝 `biome.json` (+2 -1) 📝 `demo/electron/package.json` (+10 -10) ➕ `demo/electron/pnpm-lock.yaml` (+4725 -0) ➕ `demo/electron/pnpm-workspace.yaml` (+4 -0) ➕ `demo/expo/.claude/agents/cherry-pick-release.md` (+184 -0) 📝 `demo/expo/app.config.ts` (+0 -4) 📝 `demo/expo/metro.config.js` (+1 -5) 📝 `demo/expo/package.json` (+24 -23) ➕ `demo/expo/pnpm-lock.yaml` (+8625 -0) _...and 80 more files_ </details> ### 📄 Description ## Summary - **Root cause:** `createVerificationValue` inserts without dedup — when a user requests a second OTP before the first expires, a duplicate verification record is created. On databases with unique constraints (Postgres, MySQL), subsequent calls to `updateVerificationByIdentifier` or `deleteVerificationByIdentifier` fail with 500 errors because they assume one record per identifier. - **Fix:** Add a `createOrReplaceVerification` helper that deletes any existing verification for the identifier before creating a new one. Applied to all 3 `createVerificationValue` call sites in the phone-number plugin: - `signInPhoneNumber` (requireVerification flow) - `sendPhoneNumberOTP` - `requestPasswordResetPhoneNumber` - This matches the `.catch()` delete-and-retry pattern already used in the email-otp plugin (`email-otp/routes.ts:117-134`), but uses a slightly cleaner delete-first approach that works on all databases (including SQLite which doesn't enforce unique constraints). ## Test plan - [x] Added test: repeated `sendOtp` calls produce exactly 1 verification record (not 3) - [x] Added test: wrong code → re-send OTP → correct latest code succeeds - [x] Added test: repeated `requestPasswordReset` calls produce exactly 1 verification record (not 2) - [x] All 29 tests pass (3 new + 26 existing, zero regressions) - [x] No type errors introduced (`tsc --noEmit` clean for changed file) ## Future improvements - Add `unique: true` to `identifier` in `get-tables.ts` (breaking change for existing DBs with duplicates — should be bundled with a minor version bump). Users who want to add it early can do so safely after this fix lands, and `npx auth migrate` won't overwrite it. - Extract `createOrReplaceVerificationValue` into `InternalAdapter` for all plugins - Fix the same latent bug in email-otp (6 of 7 calls missing dedup) and other plugins <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Prevents duplicate verification records when a user requests multiple OTPs for the same phone number before the first expires. Ensures only the latest code is valid and avoids 500s across Postgres/MySQL/SQLite. - **Bug Fixes** - Added `createOrReplaceVerification` to delete any existing verification before creating a new one. - Applied to all `phone-number` flows: `signInPhoneNumber`, `sendPhoneNumberOTP`, and `requestPasswordResetPhoneNumber`. - Matches the cleanup pattern used in `email-otp`, using a delete-first approach that works on all databases. - Fixed downstream errors in `updateVerificationByIdentifier`/`deleteVerificationByIdentifier` that assumed a single record. - Added tests to ensure repeated OTP requests keep exactly one verification and that only the latest code works. <sup>Written for commit 31c1361b47c6bac1c48eedb6d6cb1b795ae36113. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --- <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:40:08 -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#24979