fix(auth): make self-service "Resend Verification Email" work#2793
Merged
Conversation
better-auth's stock POST /send-verification-email requires `{ email }` in the
body, but the sys_user `resend_verification_email` action — the record-header
button, the "email unverified" record alert, and the record-section quick
action — fires with an EMPTY body (there is no dialog collecting an email, and
the record-alert `action` reference cannot carry params). The request therefore
reached better-auth with no email and bounced with:
[body.email] Invalid input: expected string, received undefined
making every resend affordance permanently broken.
Add a thin wrapper route that shadows the native /send-verification-email
(registered before the catch-all): when the body omits `email`, it defaults to
the authenticated caller's own session email (resolved via /get-session), then
re-dispatches through the real route via handleRequest (which bypasses the
wrapper — no recursion) so token generation, the sendVerificationEmail
callback, and rate limiting all still run. An explicitly-supplied `email`
(admin / verify-screen path) passes through untouched, so no existing caller
changes behaviour and no new enumeration surface is introduced.
The logic lives in a shared, exported helper (mirroring runSetInitialPassword /
runRegisterSsoProviderFromForm) so the cloud AuthProxyPlugin mount point can
adopt it and stay in lockstep. Covered by 7 unit tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BtooAsE7ebn5Tabwijtsg8
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 10, 2026 14:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Clicking 重新发送邮件 / Resend Verification Email on a
sys_userrecord (e.g./_console/apps/com.objectstack.setup/sys_user/record/…) failed with:better-auth's stock
POST /send-verification-emailrequires{ email }in the request body. But thesys_userresend_verification_emailaction fires with an empty body:params: [], andaction: { actionName }reference that cannot carry params at all.So the request reached better-auth with no email and bounced — every resend affordance was permanently broken.
Fix
Add a thin wrapper route that shadows the native
/send-verification-email(registered before the catch-all inAuthPlugin):email, default it to the authenticated caller's own session email (resolved via a/get-sessionre-dispatch), then re-dispatch through the real route viahandleRequest(which bypasses the wrapper — no recursion), so token generation, thesendVerificationEmailcallback, and rate limiting all still run;email(admin / verify-screen path) passes through untouched — no existing caller changes behaviour and no new user-enumeration surface is introduced.The logic lives in a shared, exported helper
runResendVerificationEmail(mirroringrunSetInitialPassword/runRegisterSsoProviderFromForm) so the cloudAuthProxyPluginmount point can adopt it and stay in lockstep.Changes
packages/plugins/plugin-auth/src/send-verification-email.ts— new shared helper.packages/plugins/plugin-auth/src/send-verification-email.test.ts— 7 unit tests (session default, explicit-email passthrough,callbackURLforwarding, no-session 400, non-JSON body, native-error passthrough, blank-email handling).packages/plugins/plugin-auth/src/auth-plugin.ts— register the wrapper route before the wildcard catch-all.packages/plugins/plugin-auth/src/index.ts— re-export the helper..changeset/fix-resend-verification-email.md— patch changeset.Testing
pnpm vitest runinplugin-auth: 368 passed (incl. the 7 new tests).pnpm turbo run build --filter=@objectstack/plugin-auth: build + DTS succeed.eslintclean on new/modified files.🤖 Generated with Claude Code
https://claude.ai/code/session_01BtooAsE7ebn5Tabwijtsg8
Generated by Claude Code