[PR #8877] feat(email): add sendOldEmailVerification option to skip old email confirmation #25172

Open
opened 2026-04-15 22:45:00 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/8877
Author: @ruban-s
Created: 4/1/2026
Status: 🔄 Open

Base: nextHead: feat/send-old-email-verification


📝 Commits (10+)

  • a37bc86 Remove duplicate export for linkedin
  • ab2e12e Remove duplicate export for './kick'
  • 4e7a498 Merge branch 'better-auth:main' into main
  • 890c438 feat(email): add sendOldEmailVerification option to skip old email confirmation (#3742)
  • c35e840 Update packages/better-auth/src/api/routes/update-user.test.ts
  • 4b55500 test: assert verification email recipient is the new email
  • 341f098 Merge branch 'main' into feat/send-old-email-verification
  • 3e4cf0c test: update OpenAPI snapshot for changeEmail password field
  • 2ad2656 Merge branch 'main' into feat/send-old-email-verification
  • dc34d6b fix: address review feedback for sendOldEmailVerification

📊 Changes

9 files changed (+525 additions, -20 deletions)

View changed files

.changeset/pr-8877.md (+5 -0)
📝 docs/content/docs/concepts/users-accounts.mdx (+67 -0)
📝 docs/content/docs/plugins/cimd.mdx (+18 -18)
📝 packages/better-auth/src/api/routes/email-verification.ts (+16 -1)
📝 packages/better-auth/src/api/routes/update-user.test.ts (+337 -0)
📝 packages/better-auth/src/api/routes/update-user.ts (+47 -1)
📝 packages/better-auth/src/plugins/open-api/__snapshots__/open-api.test.ts.snap (+7 -0)
📝 packages/core/src/error/codes.ts (+2 -0)
📝 packages/core/src/types/init-options.ts (+26 -0)

📄 Description

Closes #3742

Summary

When users change their email, the current flow sends a confirmation to the old email before verifying the new one. Users who lost access to their old email get stuck and have to contact support.

This adds sendOldEmailVerification option under emailVerification config. When set to false, the verification email is sent directly to the new email address, skipping the old email
confirmation step.

  • Password or fresh session is required when old email verification is disabled to compensate for the reduced security
  • Default behavior (sendOldEmailVerification: true) is unchanged
  • Adds afterEmailChange hook and sendChangeEmailNotification handler for post-change lifecycle events

Usage

export const auth = betterAuth({
  user: {
    changeEmail: { enabled: true },
  },
  emailVerification: {
    sendOldEmailVerification: false,
    sendVerificationEmail: async ({ user, url }) => {
      // sends directly to the new email
    },
  },
})

Test plan

- Skip old email with correct password  verification sent to new email
- Wrong password rejected with INVALID_PASSWORD
- Fresh session without password ��� allowed
- Stale session without password  rejected with SESSION_EXPIRED
- Default two-step flow preserved when option is not set
- No email existence leak on duplicate email (timing attack prevention)
- afterEmailChange and sendChangeEmailNotification hooks fire after verification

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds an option to verify the new email directly and skip old email confirmation, unblocking users who lost access to their old address while keeping safeguards in place. Aligns with Linear #3742.

- **New Features**
- `emailVerification.sendOldEmailVerification` (default `true`). When `false`, send verification to the new address and skip the old-email confirmation step (and `sendChangeEmailConfirmation`).
- Security: require a correct `password` or a fresh session when skipping; wrong password  `INVALID_PASSWORD`, stale session  `PASSWORD_OR_FRESH_SESSION_REQUIRED`.
- API: `changeEmail` accepts optional `password`; default two-step flow unchanged; no email-existence leak; OpenAPI updated with the `password` field and its requirement.
- Hooks: `user.changeEmail.afterEmailChange` runs after the new email is verified and applied, with `oldEmail` and `newEmail`.
- Docs: updated with examples and a security callout.

<sup>Written for commit 474e8907e5231ad6a1378b401b8cd5d674ee998d. 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>
## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/8877 **Author:** [@ruban-s](https://github.com/ruban-s) **Created:** 4/1/2026 **Status:** 🔄 Open **Base:** `next` ← **Head:** `feat/send-old-email-verification` --- ### 📝 Commits (10+) - [`a37bc86`](https://github.com/better-auth/better-auth/commit/a37bc86701404481f2a982767629c77f7fe97a1a) Remove duplicate export for linkedin - [`ab2e12e`](https://github.com/better-auth/better-auth/commit/ab2e12e68e13a633195b68bb3ee9c724e18a0b93) Remove duplicate export for './kick' - [`4e7a498`](https://github.com/better-auth/better-auth/commit/4e7a4982725dc5ffdc5ae942a77fcb43f716bf99) Merge branch 'better-auth:main' into main - [`890c438`](https://github.com/better-auth/better-auth/commit/890c438f770c168c408a0f01a704d08206b15160) feat(email): add sendOldEmailVerification option to skip old email confirmation (#3742) - [`c35e840`](https://github.com/better-auth/better-auth/commit/c35e840279976586659843f845d854fee8219349) Update packages/better-auth/src/api/routes/update-user.test.ts - [`4b55500`](https://github.com/better-auth/better-auth/commit/4b55500b4d3212cef93b3d6ae9df66741ca8d7ef) test: assert verification email recipient is the new email - [`341f098`](https://github.com/better-auth/better-auth/commit/341f09856f82f1649321af1573a18fcb3def23e5) Merge branch 'main' into feat/send-old-email-verification - [`3e4cf0c`](https://github.com/better-auth/better-auth/commit/3e4cf0c669dd64e8490b89f7eac3ef28a7479cb9) test: update OpenAPI snapshot for changeEmail password field - [`2ad2656`](https://github.com/better-auth/better-auth/commit/2ad2656de9c8bc53109610a51e215f711eedd132) Merge branch 'main' into feat/send-old-email-verification - [`dc34d6b`](https://github.com/better-auth/better-auth/commit/dc34d6b44aa965d508a3f7814c7c6db496cf3ef4) fix: address review feedback for sendOldEmailVerification ### 📊 Changes **9 files changed** (+525 additions, -20 deletions) <details> <summary>View changed files</summary> ➕ `.changeset/pr-8877.md` (+5 -0) 📝 `docs/content/docs/concepts/users-accounts.mdx` (+67 -0) 📝 `docs/content/docs/plugins/cimd.mdx` (+18 -18) 📝 `packages/better-auth/src/api/routes/email-verification.ts` (+16 -1) 📝 `packages/better-auth/src/api/routes/update-user.test.ts` (+337 -0) 📝 `packages/better-auth/src/api/routes/update-user.ts` (+47 -1) 📝 `packages/better-auth/src/plugins/open-api/__snapshots__/open-api.test.ts.snap` (+7 -0) 📝 `packages/core/src/error/codes.ts` (+2 -0) 📝 `packages/core/src/types/init-options.ts` (+26 -0) </details> ### 📄 Description Closes #3742 ## Summary When users change their email, the current flow sends a confirmation to the old email before verifying the new one. Users who lost access to their old email get stuck and have to contact support. This adds `sendOldEmailVerification` option under `emailVerification` config. When set to `false`, the verification email is sent directly to the new email address, skipping the old email confirmation step. - Password or fresh session is required when old email verification is disabled to compensate for the reduced security - Default behavior (`sendOldEmailVerification: true`) is unchanged - Adds `afterEmailChange` hook and `sendChangeEmailNotification` handler for post-change lifecycle events ## Usage ```ts export const auth = betterAuth({ user: { changeEmail: { enabled: true }, }, emailVerification: { sendOldEmailVerification: false, sendVerificationEmail: async ({ user, url }) => { // sends directly to the new email }, }, }) Test plan - Skip old email with correct password — verification sent to new email - Wrong password rejected with INVALID_PASSWORD - Fresh session without password ��� allowed - Stale session without password — rejected with SESSION_EXPIRED - Default two-step flow preserved when option is not set - No email existence leak on duplicate email (timing attack prevention) - afterEmailChange and sendChangeEmailNotification hooks fire after verification <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds an option to verify the new email directly and skip old email confirmation, unblocking users who lost access to their old address while keeping safeguards in place. Aligns with Linear #3742. - **New Features** - `emailVerification.sendOldEmailVerification` (default `true`). When `false`, send verification to the new address and skip the old-email confirmation step (and `sendChangeEmailConfirmation`). - Security: require a correct `password` or a fresh session when skipping; wrong password → `INVALID_PASSWORD`, stale session → `PASSWORD_OR_FRESH_SESSION_REQUIRED`. - API: `changeEmail` accepts optional `password`; default two-step flow unchanged; no email-existence leak; OpenAPI updated with the `password` field and its requirement. - Hooks: `user.changeEmail.afterEmailChange` runs after the new email is verified and applied, with `oldEmail` and `newEmail`. - Docs: updated with examples and a security callout. <sup>Written for commit 474e8907e5231ad6a1378b401b8cd5d674ee998d. 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:45:00 -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#25172