apps/server/docs/ai-context/email-auth-resend.md
Status: in progress Last updated: 2026-04-27
apps/server 的统一邮件发送 service。emailVerification.sendVerificationEmail(注册后验证邮箱)emailAndPassword.sendResetPassword(忘记密码)user.changeEmail.sendChangeEmailVerification(改邮箱)magicLink.sendMagicLink(passwordless 登录,启用 plugin)apps/ui-server-auth 加上邮箱注册 / 邮箱密码登录 / 忘记密码 / 重置密码 等界面。只有这两条本期要 ship:
/sign-up → 填邮箱 + 密码 → 提交 → 进 verify-email 提示页 → 收邮件点链接 → verify-email?token=... 落地页提示成功 → 跳 /sign-in。/sign-in 点 "Forgot password" → 进 /forgot-password 输邮箱 → 提交 → 提示已发送 → 用户点邮件链接 → /reset-password?token=... 输新密码 → 跳 /sign-in。/sign-in 输邮箱 + 密码 → 走 OIDC loginPage 流程把用户登入,返回上游 /oauth/authorize。服务端为 magic link / change email 接好回调(避免功能闭包不齐一半),但前端 UI 留待后续。Service 拒绝静默吞错——发送失败要走错误响应让 Better Auth 把错抛回前端。
In:
apps/server/src/services/email.ts:统一 EmailService 接口(sendVerification / sendPasswordReset / sendMagicLink / sendChangeEmail),每个方法对应一个 HTML + plaintext 模板。apps/server/src/libs/auth.ts:装上 4 个 callback;启用 requireEmailVerification: true;加载 magicLink plugin。apps/server/src/libs/env.ts:新增 RESEND_API_KEY(必填)、RESEND_FROM_EMAIL(必填)、RESEND_FROM_NAME(可选)、AUTH_EMAIL_VERIFY_REDIRECT_URL / AUTH_PASSWORD_RESET_REDIRECT_URL(可选,默认根据 API_SERVER_URL 推算 ui-server-auth origin)。apps/server/src/app.ts:把 EmailService 通过 injeca 装配,注入到 auth provider。apps/ui-server-auth/src/pages:扩 sign-in.vue;新增 sign-up.vue、verify-email.vue、forgot-password.vue、reset-password.vue。apps/ui-server-auth/src/modules/sign-in.ts 同级补 email-password.ts 处理 emailPassword sign-in/up + forgot/reset 的真实调用。packages/i18n:新增 auth.signUp / verifyEmail / forgotPassword / resetPassword 字段。Out:
magic-link-sent.vue / sign-in 上的 "Email me a link" 入口)。resend npm 包。错误处理走 errorMessageFrom(@moeru/std);失败时抛 ApiError(502, 'email/send_failed', ...) 让 Better Auth 把错传回前端。apps/server,而是放 apps/ui-server-auth。API_SERVER_URL 是 server 自身(如 https://airi-api.moeru.ai),ui-server-auth 通常是另一域(如 https://auth.airi.moeru.ai);两者要么同源(dev)要么通过 trustedOrigins 已经互信。链接组装规则:
<UI_BASE>/verify-email?token=<token><UI_BASE>/reset-password?token=<token>getAuthTrustedOrigins(request) 第一个匹配的 origin 决定 <UI_BASE>,避免硬编码。requireEmailVerification: true 开启的副作用:现存历史用户(尚未验证)将在下次登录被拦截。社交登录(Google/GitHub)默认 emailVerified=true,不受影响。需要在 sign-up 后端响应中带 requiresEmailVerification 标志,前端据此跳到 verify-email 提示页。loginPage: '/sign-in' 不变:sign-in 加表单后仍然走 oauth/authorize → /sign-in?... → 登录成功 → callbackURL 回 oauth/authorize,不破坏现有流程。resend SDK ESM-only?需在加包后 pnpm typecheck 验证(unverified)。better-auth/plugins/magic-link 可与 oauthProvider 共存(unverified,但插件是独立 endpoint,不冲突)。http://localhost:5173,与 apps/server 不同源。server 已在 getAuthTrustedOrigins 把 dev origin 加进来。每条用户路径要落一份验证记录到 docs/ai/context/verifications/email-auth-<path>.md:
email-auth-signup.md:dev 环境注册一次,列出真实 curl / 浏览器步骤、Resend dashboard 命中、点链接落地页结果。email-auth-forgot.md:忘记密码同上。email-auth-signin-email-password.md:emailPassword sign-in 完整 OIDC 闭环。未跑过这三条 = 默认 unverified,不能声明完成。
request_log 表