#FE: Auth screens (from backend Phase 2)
Frontend spec for every feature of backend Phase 2: Authentication & Security (requirements complete 2026-10-01, not built). V8 requirement Backend endpoint paths are not defined yet, so each screen names the capability it needs. When the backend publishes paths, they're added here (auto-sync). Field rules come from the backend doc. Where the doc doesn't say, the V7 behaviour is used and marked V7 only, or the item is marked Assumption.
i18n namespaces: auth.register.*, auth.verify.*, auth.login.*, auth.oauth.*, auth.forgot.*, auth.reset.*, auth.invite.*, auth.twoFactor.*, auth.ipApproval.*, auth.errors.*. All 8 locales.
#2.1 Register
Route: /:locale/register (query: invite, ref, aff) · Public
| Field | Rule | Source |
|---|---|---|
| Name | required, max 150 | V8 requirement required · max V7 only |
| required, valid email | V8 requirement | |
| Password | required, min 8 (max 64 as V7 login) | V8 requirement min · V7 only max |
| Turnstile | widget token, required | V8 requirement |
| Referral / affiliate / invitation code | optional. Pre-filled from the query string, read-only when pre-filled | V8 requirement "saved now, used in their own phases" |
| Onboarding questions | optional. V7 had company size, industry, management experience, heard about us | V8 requirement optional · question list Open question (step 2 or the same form?) |
States: idle · submitting (button spinner) · 422 field errors · 429 · success → 2.2 check-inbox screen (the account is inactive). Turnstile expiry → re-render the widget. No auto-login after registering (inactive until verified).
#2.2 Email verification
Routes: /:locale/verify-email (check inbox) · /:locale/verify/:token (result) · Public
- Check-inbox: shows the address, "Resend" with a 60 s cooldown Assumption (backend rate-limits resends, 2.11), and "Wrong email? Register again".
- Result: success → "Email verified" + Log in button · expired (> 24 h) → "Link expired" + resend · invalid/used → neutral message + log in.
#2.3 Login
Route: /:locale/login?next= · Public (logged-in users → dashboard)
| Result from backend | Screen behaviour |
|---|---|
| Wrong email or password | One generic message auth.login.invalid |
| Unverified | auth.login.unverified + resend verification |
| Banned | auth.login.banned. No retry |
| 2FA required | → 2.7 step (no token yet) |
| IP not whitelisted | → 2.8 notice |
| Success | Token (BFF cookies) + user + organizations → organization chosen (last used / main / first; none → create organization) → next or dashboard |
| 429 | Countdown from Retry-After |
Fields: email, password (show/hide toggle). Links: forgot password, register, Google, GitHub. Login history is recorded by the backend. Nothing to do on the frontend.
#2.4 Google / GitHub login
Routes: buttons on login/register · callback landing /:locale/oauth/:provider/callback · Public
- New email → account created. Existing email → linked automatically. V8 requirement
- Errors come back to
/:locale/login?error=<code>: provider disabled, cancelled, email missing, state invalid → translated message. Codes to be agreed Missing. - Buttons show only for providers the backend reports as enabled (keys are managed in the admin panel) → needs a public config endpoint Missing (see Runtime configuration).
#2.5 Forgot / reset password
Routes: /:locale/forgot-password · /:locale/reset-password/:token · Public
- Forgot: email field → always "If an account exists, we sent a link" (no enumeration) Assumption.
- Reset: new password + confirm (min 8). Link valid 60 min. Success → "Password changed. All devices were signed out" → login. Expired → back to forgot.
#2.6 Invitation password
Route: /:locale/invitations/:token · Public
- Shows the organization name + inviter (if the backend returns them Missing), a "set your password" form (+ name if unknown), then login → the invited organization becomes the current one.
- Invalid/expired token → "This invitation is no longer valid".
- An existing user who opens the link → log in → accept (depends on D-9).
#2.7 2FA at login
Step inside /:locale/login (not a separate URL, so the pending login state isn't bookmarkable).
| Method | UI |
|---|---|
| Email code | 6-digit input Assumption length, valid 10 min, resend (cooldown), "N tries left" (max 5 wrong) |
| Google Authenticator | 6-digit TOTP input |
| Backup code | text input |
Method switch links under the input. After 5 wrong tries → message + back to login. The token is only stored after 2FA passes.
#2.8 IP whitelist at login
- Login from an unknown IP → screen "We emailed you a link to approve this IP (valid 24 h). Approve it, then log in again."
- Approval link route
/:locale/ip-approval/:key→ approved / expired / invalid result + Log in.
#2.9 Tokens & session
- Access 15 d / refresh 30 d, stored per FE: Foundation D-1.
- Central issues the token and the refresh token itself, after all checks pass (password → 2FA → IP whitelist), for email and Google/GitHub login alike. The exact method is decided in the backend's Phase 2 plan. V8 requirement 2026-10-03 — for the frontend this means one login response carries both tokens, and the OAuth callback (2.4) is handled the same way as a password login.
- Refresh once on 401, then retry the original GET. Never replay a mutation automatically.
- Session expired →
/:locale/login?next=<path>+auth.errors.sessionExpired.
#2.10 Logout
User menu → Log out → BFF revokes the token (backend) → clear cookies + client stores → login. Link not prefetched.
#2.11 Rate limits
Every auth form handles 429 with the wait time and disables submit until then. Resend buttons keep their own cooldown.
#Runtime configuration
Settings managed in the backend admin panel affect the screens: Turnstile site key, which social providers are enabled. The frontend can't hard-code them in env files if admins change them at runtime. Needs a public config endpoint (e.g. turnstile_site_key, oauth_providers: ["google","github"], registration_open) Missing. Until then: Open question.
#Phase 2 items not on these screens
- Organizations aren't auto-created → after the first login, the create-organization screen (Organizations).
- Dropped V7 sign-up checks (throwaway email, Gmail dots, device fingerprint): no UI.
- Migration: existing V7 users log in with the same email/password and keep 2FA, IP whitelist and social logins. No special screen is needed Assumption.