docs: add login page redesign and OAuth support design spec

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 12:55:44 +08:00
parent 4b3a9481c6
commit 3c53bf08bb

View File

@@ -0,0 +1,144 @@
# Login Page Redesign + OAuth Support
## Overview
Redesign the login/register pages with a left-right split layout featuring prominent branding, and add Google/GitHub/Apple OAuth login via standard server-side redirect flow.
## UI Design
### Layout
- **Desktop**: 50/50 left-right split
- **Mobile**: Brand area hidden or collapsed to compact top banner; form area full-width
### Left Panel (Brand Area)
Shared `AuthBranding` component used by both Login and Register pages.
- Dark/gradient background (fox-amber → fox-orange gradient from existing CSS variables)
- Large product icon (~80px SVG fox logo)
- Product name "AgentFox" (large heading font)
- Slogan "API Docs for LLMs, Done Right"
- 3 feature highlights, each with icon + text:
- "Multi-level API retrieval for minimal token usage"
- "Import OpenAPI specs in seconds"
- "Works with any MCP-compatible LLM"
### Right Panel (Form Area)
- Light background, vertically centered
- Title: "Sign in to your account" (login) / "Create your account" (register)
- Email + Password inputs (reuse existing input styles)
- Primary action button
- Divider: "── or continue with ──"
- Three OAuth buttons in a row: Google / GitHub / Apple (each with official SVG icon)
- Footer link: "Don't have an account? Sign up" / "Already have an account? Sign in"
## OAuth Architecture
### Flow (Standard Server-Side Redirect)
```
Browser clicks OAuth button
→ GET /api/auth/oauth/:provider
→ Server builds authorization URL with state param, 302 redirects to Provider
→ User authorizes on Provider's page
→ Provider redirects to GET /api/auth/oauth/:provider/callback?code=xxx&state=yyy
→ Server validates state, exchanges code for access_token
→ Server fetches user info (email, name, avatar)
→ Server finds or creates user (see Account Linking below)
→ Server issues JWT (accessToken + refreshToken)
→ Server 302 redirects to frontend /login/callback?accessToken=xxx&refreshToken=xxx
```
### Account Linking Strategy
On OAuth callback, the server resolves the user in this order:
1. Look up `OAuthAccount` by `(provider, providerAccountId)` → if found, use linked `User`
2. If no OAuthAccount match, look up `User` by `email` → if found, create `OAuthAccount` linking to existing user
3. If no User match, create new `User` (passwordHash=null, name and avatarUrl from provider) + new `OAuthAccount`
### Security
- **CSRF protection**: Generate random `state` parameter per auth request, store in in-memory Map with 10-minute TTL, validate on callback
- **Token delivery**: Tokens passed via URL query params; frontend immediately consumes and clears URL
- **Secrets**: All client secrets stay server-side; no OAuth SDK loaded in frontend
### Provider Configuration
| Provider | Auth URL | Token URL | UserInfo URL | Scopes |
|----------|----------|-----------|--------------|--------|
| Google | accounts.google.com/o/oauth2/v2/auth | oauth2.googleapis.com/token | www.googleapis.com/oauth2/v2/userinfo | email, profile |
| GitHub | github.com/login/oauth/authorize | github.com/login/oauth/access_token | api.github.com/user + /user/emails | user:email |
| Apple | appleid.apple.com/auth/authorize | appleid.apple.com/auth/token | (decoded from id_token) | name, email |
### Environment Variables
```env
# Already in .env.example
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# New
APPLE_CLIENT_ID=
APPLE_TEAM_ID=
APPLE_KEY_ID=
APPLE_PRIVATE_KEY=
OAUTH_CALLBACK_BASE_URL=http://localhost:3000
```
### Frontend Callback Page (`/login/callback`)
- Extracts `accessToken` and `refreshToken` from URL search params
- Stores in localStorage, updates AuthContext
- Redirects to `/dashboard` (or saved redirect target)
- Shows loading spinner during processing
- Shows error message with retry link on failure
## File Changes
### New Files
| File | Purpose |
|------|---------|
| `packages/server/src/routes/oauth.ts` | OAuth routes (/:provider, /:provider/callback) |
| `packages/server/src/lib/oauth-providers.ts` | Provider configs + token exchange + userinfo fetch |
| `packages/web/src/pages/LoginCallback.tsx` | OAuth callback landing page |
| `packages/web/src/components/AuthBranding.tsx` | Shared left-panel brand component |
| `packages/web/src/components/OAuthButtons.tsx` | Third-party login button group |
### Modified Files
| File | Change |
|------|--------|
| `packages/server/src/index.ts` | Register `/auth/oauth` route |
| `packages/web/src/pages/Login.tsx` | Refactor to left-right split layout |
| `packages/web/src/pages/Register.tsx` | Refactor to left-right split layout |
| `packages/web/src/App.tsx` | Add `/login/callback` route |
| `packages/web/src/lib/i18n.tsx` | Add translation keys |
| `.env.example` | Add Apple OAuth env vars |
### No Changes Needed
- `prisma/schema.prisma` — OAuthAccount model already exists
- JWT signing logic — reuse existing `generateAccessToken`/`generateRefreshToken`
- Existing email/password auth — unchanged
### No New Dependencies
- OAuth token exchange: Node native `fetch`
- Apple JWT client_secret signing: Node `crypto` built-in
- No Passport.js, no OAuth libraries
## User Action Required
Before testing OAuth, the developer must register apps on each provider:
- **Google**: Google Cloud Console → OAuth 2.0 Client → redirect URI: `{OAUTH_CALLBACK_BASE_URL}/auth/oauth/google/callback`
- **GitHub**: GitHub Developer Settings → OAuth App → callback URL: `{OAUTH_CALLBACK_BASE_URL}/auth/oauth/github/callback`
- **Apple**: Apple Developer → Services ID + Key → return URL: `{OAUTH_CALLBACK_BASE_URL}/auth/oauth/apple/callback` (requires HTTPS)
Apple Sign In requires a paid Apple Developer account ($99/year) and HTTPS for callbacks. If unavailable, the Apple button can be displayed as "Coming Soon".