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:
144
docs/superpowers/specs/2026-04-03-login-page-oauth-design.md
Normal file
144
docs/superpowers/specs/2026-04-03-login-page-oauth-design.md
Normal 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".
|
||||
Reference in New Issue
Block a user