apps/server/docs/ai-context/verifications/email-auth.md
Status: Path 1 verified, Path 2/3 unverified. Last attempted: 2026-04-28 Owner: [email protected]
Note (2026-04-28): UI base path migrated from the original
/_ui/server-auth/prefix to/auth/(commitd5f215134). All Vite-built asset URLs and SPA routes are now served under/auth/. The verification log below has been rewritten to use the current routes; the prior path remains valid only for historical builds tagged before that commit.
Tested with a live Resend API key, real Outlook inbox.
| Step | Evidence |
|---|---|
POST /api/auth/sign-up/email (raw fetch) | 200 with { token: null, user: { ..., emailVerified: false } } for [email protected] and [email protected] |
| Resend dispatch | server log <-- POST /api/auth/sign-up/email → --> POST /api/auth/sign-up/email 200 5s (Resend API call latency, no errors logged from services:email) |
| Inbox delivery | User confirmed receipt at [email protected] with subject "Verify your email", containing link http://localhost:3000/api/auth/verify-email?token=eyJ...&callbackURL=%2F |
| Click verify link | GET /api/auth/verify-email?token=...&callbackURL=/ → 302 (redirect honored) |
emailVerified flips to true | follow-up POST /api/auth/sign-in/email for the same user → 200 with { redirect: false, token: <session>, user: { ..., emailVerified: true, updatedAt > createdAt } } |
| UI sign-up form submit | navigated http://localhost:5174/auth/sign-up, filled form via chrome-devtools, click Create account → server log POST /api/auth/sign-up/email 200 2s → browser landed on UI's verify-email page |
Two follow-up issues surfaced and were fixed in the same session:
[email protected] parsed as a linked-message reference. Escaped to you{'@'}example.com in packages/i18n/src/locales/en/server/auth.yaml.http://localhost:3000/ (404) when there was no OIDC context, because Better Auth resolves bare / callback against API_SERVER_URL. Fixed in apps/ui-server-auth/src/pages/sign-up.vue and sign-in.vue by passing an absolute UI URL (${origin}/auth/verify-email?verified=true) when no OIDC params are present.GET / and notFound() in apps/server/src/app.ts so stale email links / scanners hit a clear pointer instead of hono's default 404 Not Found HTML.
curl http://localhost:3000/ → 200 {"service":"airi-api",...} and curl http://localhost:3000/some/random/path → 404 {"error":"NOT_FOUND",...}.Tested with [email protected] (live Resend account). The bare [email protected] is on Resend's suppression list and cannot be used for QA — see ~/.claude/projects/<project>/memory/reference_resend.md.
| Step | Evidence |
|---|---|
Sign-up [email protected] | POST /api/auth/sign-up/email 200 2s; UI navigated to /verify-email?email=... |
| Verify email | clicked link from real Outlook inbox; GET /api/auth/verify-email?token=...&callbackURL=http://localhost:5173/auth/verify-email?verified=true → 302 → UI shows "Email verified" |
POST /api/auth/request-password-reset from UI | server log 200 3s; UI shows "If [email protected] matches an account, a reset link is on the way" |
| Resend dashboard | Reset your Project AIRI password to [email protected] → last_event: delivered |
| Click reset link | GET /api/auth/reset-password/<token>?callbackURL=http://localhost:5173/auth/reset-password → 302 → UI form rendered with ?token=<token> |
| Submit new password | POST /api/auth/reset-password?token=... → 200; UI shows "Password updated" |
| Sign in with new password | POST /api/auth/sign-in/email → 200 { token: <session>, user: { emailVerified: true, updatedAt: 2026-04-27T06:57:59.387Z } } |
Two follow-up issues surfaced and were fixed in the same session:
apps/ui-server-auth defaulted to production https://api.airi.build because VITE_SERVER_URL was unset. Fixed by adding apps/ui-server-auth/.env.development.local → VITE_SERVER_URL=http://localhost:3000. Detected via window.fetch patching showing prod hostname; saved to ~/.claude/projects/<project>/memory/project_ui_server_auth_dev_env.md.originCheck rejected http://localhost:5173/... callbackURLs when the request came from a top-level GET (no Origin/Referer that matches dev origins). Fixed by adding localhost:5173 / 5174 / 4173 to ALWAYS_TRUSTED_AUTH_ORIGINS in apps/server/src/utils/origin.ts. Prod-safe: those addresses are unreachable in prod, so the static list does not expand attack surface.POST /api/auth/sign-in/email was exercised directly to confirm emailVerified flips and a session token is issued, but the full UI-driven OIDC handoff (stage app → /oauth2/authorize → ui-server-auth → back to stage app with tokens) has NOT been tested in this session.
/sign-in, click "Forgot password?" → /forgot-password.POST /api/auth/request-password-reset returns 200, an email arrives ("Reset your Project AIRI password").${UI}/auth/reset-password?token=<token>.POST /api/auth/reset-password?token=... returns 200; UI shows "Password updated".apps/stage-web) → triggers OIDC /oauth2/authorize → bounces to ui-server-auth /sign-in?....code → token exchange.Treat the email-auth feature as partially shipped. Sign-up + verify-email is production-quality; password reset and OIDC bridging are code-complete but not load-bearing without an end-to-end run.
request_log.:5174; prod expects ui-server-auth dist under apps/server/public/ui-server-auth).