Skip to content

fix(auth): make self-service "Resend Verification Email" work#2793

Merged
os-zhuang merged 1 commit into
mainfrom
claude/email-resend-validation-bir052
Jul 10, 2026
Merged

fix(auth): make self-service "Resend Verification Email" work#2793
os-zhuang merged 1 commit into
mainfrom
claude/email-resend-validation-bir052

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Problem

Clicking 重新发送邮件 / Resend Verification Email on a sys_user record (e.g. /_console/apps/com.objectstack.setup/sys_user/record/…) failed with:

[body.email] Invalid input: expected string, received undefined

better-auth's stock POST /send-verification-email requires { email } in the request body. But the sys_user resend_verification_email action fires with an empty body:

  • the record-header button and record-section quick action declare params: [], and
  • the "email unverified" record alert invokes the action via an action: { 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 in AuthPlugin):

  • when the body omits email, default it to the authenticated caller's own session email (resolved via a /get-session re-dispatch), then re-dispatch 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 — no existing caller changes behaviour and no new user-enumeration surface is introduced.

The logic lives in a shared, exported helper runResendVerificationEmail (mirroring runSetInitialPassword / runRegisterSsoProviderFromForm) so the cloud AuthProxyPlugin mount 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, callbackURL forwarding, 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 run in plugin-auth: 368 passed (incl. the 7 new tests).
  • pnpm turbo run build --filter=@objectstack/plugin-auth: build + DTS succeed.
  • eslint clean on new/modified files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BtooAsE7ebn5Tabwijtsg8


Generated by Claude Code

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
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 10, 2026 2:10pm

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Jul 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-auth.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-auth)
  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-auth)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/sso.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-auth)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-auth)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-auth)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 10, 2026 14:26
@os-zhuang
os-zhuang merged commit 9887465 into main Jul 10, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/email-resend-validation-bir052 branch July 10, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants