|
| 1 | +# Google OAuth Gating for Dashboard |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Replace basic auth with Google OAuth, restricting access to @stackone.com email domain only. Uses Arctic OAuth library for edge-compatible implementation on Cloudflare Workers. |
| 6 | + |
| 7 | +## Requirements |
| 8 | + |
| 9 | +- Google OAuth as sole authentication method |
| 10 | +- Only @stackone.com emails allowed |
| 11 | +- 24-hour session duration |
| 12 | +- Remove existing basic auth |
| 13 | + |
| 14 | +## Architecture |
| 15 | + |
| 16 | +``` |
| 17 | +┌─────────────┐ ┌─────────────────┐ ┌─────────────┐ |
| 18 | +│ Browser │────▶│ Cloudflare Worker│────▶│ Google OAuth│ |
| 19 | +│ │◀────│ (Hono + Arctic)│◀────│ Server │ |
| 20 | +└─────────────┘ └─────────────────┘ └─────────────┘ |
| 21 | + │ |
| 22 | + ┌───────┴───────┐ |
| 23 | + │ Signed Cookie │ |
| 24 | + │ (Session) │ |
| 25 | + └───────────────┘ |
| 26 | +``` |
| 27 | + |
| 28 | +**Components:** |
| 29 | +- **Arctic** - handles OAuth 2.0 flow with Google |
| 30 | +- **Hono middleware** - protects routes, redirects unauthenticated users |
| 31 | +- **Signed cookie** - stores session (email + expiry), signed with secret key |
| 32 | +- **No database sessions** - stateless auth via cookie signature verification |
| 33 | + |
| 34 | +**New files:** |
| 35 | +- `src/auth/google.ts` - Arctic Google provider setup |
| 36 | +- `src/auth/session.ts` - Cookie session management |
| 37 | +- `src/auth/middleware.ts` - Auth middleware for route protection |
| 38 | +- `src/auth/routes.ts` - `/auth/login`, `/auth/callback`, `/auth/logout` |
| 39 | + |
| 40 | +## Authentication Flow |
| 41 | + |
| 42 | +**Login (`/auth/login`):** |
| 43 | +1. Generate random `state` parameter (CSRF protection) |
| 44 | +2. Store `state` in short-lived cookie (5 min) |
| 45 | +3. Redirect to Google OAuth consent screen |
| 46 | + |
| 47 | +**Callback (`/auth/callback`):** |
| 48 | +1. Validate `state` matches cookie (prevents CSRF) |
| 49 | +2. Exchange authorization code for tokens via Arctic |
| 50 | +3. Fetch user info from Google (email, name) |
| 51 | +4. Reject if email domain ≠ @stackone.com |
| 52 | +5. Create signed session cookie (24h expiry) |
| 53 | +6. Redirect to dashboard (`/`) |
| 54 | + |
| 55 | +**Logout (`/auth/logout`):** |
| 56 | +1. Clear session cookie |
| 57 | +2. Redirect to `/auth/login` |
| 58 | + |
| 59 | +**Unauthenticated access:** |
| 60 | +- Any protected route → redirect to `/auth/login` |
| 61 | +- After login → redirect back to originally requested URL |
| 62 | + |
| 63 | +## Session Management |
| 64 | + |
| 65 | +**Session cookie structure:** |
| 66 | +```typescript |
| 67 | +{ |
| 68 | + email: "user@stackone.com", |
| 69 | + name: "User Name", |
| 70 | + exp: 1711324800 // Unix timestamp (24h from login) |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +**Cookie configuration:** |
| 75 | +- **Name:** `session` |
| 76 | +- **Signed:** Yes, using `COOKIE_SECRET` env var (HMAC-SHA256) |
| 77 | +- **HttpOnly:** Yes (not accessible via JavaScript) |
| 78 | +- **Secure:** Yes (HTTPS only in production) |
| 79 | +- **SameSite:** Lax (allows redirects from Google) |
| 80 | +- **Max-Age:** 86400 (24 hours) |
| 81 | + |
| 82 | +## Route Protection |
| 83 | + |
| 84 | +**Protected routes (require auth):** |
| 85 | +- `/` - Dashboard |
| 86 | +- `/api/*` - All API endpoints (except webhook) |
| 87 | + |
| 88 | +**Public routes (no auth):** |
| 89 | +- `/auth/login` - Login page/redirect |
| 90 | +- `/auth/callback` - OAuth callback |
| 91 | +- `/auth/logout` - Logout |
| 92 | +- `/health` - Health check |
| 93 | +- `/api/pylon/webhook` - Pylon webhook (has its own HMAC verification) |
| 94 | + |
| 95 | +## Error Handling |
| 96 | + |
| 97 | +**OAuth errors (callback failures):** |
| 98 | +- Invalid state → redirect to `/auth/login?error=invalid_state` |
| 99 | +- Google denies access → redirect to `/auth/login?error=access_denied` |
| 100 | +- Token exchange fails → redirect to `/auth/login?error=token_error` |
| 101 | + |
| 102 | +**Domain rejection:** |
| 103 | +- Non-@stackone.com email → redirect to `/auth/login?error=unauthorized_domain` |
| 104 | + |
| 105 | +**Error messages:** |
| 106 | +| Error | Message | |
| 107 | +|-------|---------| |
| 108 | +| `unauthorized_domain` | "Access restricted to @stackone.com accounts" | |
| 109 | +| `access_denied` | "Google sign-in was cancelled" | |
| 110 | +| `invalid_state` | "Session expired. Please try again." | |
| 111 | +| `token_error` | "Authentication failed. Please try again." | |
| 112 | + |
| 113 | +## Environment Configuration |
| 114 | + |
| 115 | +**New environment variables:** |
| 116 | + |
| 117 | +| Variable | Description | |
| 118 | +|----------|-------------| |
| 119 | +| `GOOGLE_CLIENT_ID` | OAuth client ID from Google Cloud Console | |
| 120 | +| `GOOGLE_CLIENT_SECRET` | OAuth client secret | |
| 121 | +| `COOKIE_SECRET` | Random string for signing cookies (32+ chars) | |
| 122 | +| `AUTH_REDIRECT_URI` | OAuth callback URL | |
| 123 | + |
| 124 | +**Removed:** |
| 125 | +- `DASHBOARD_PASSWORD` - no longer needed |
| 126 | + |
| 127 | +**Google Cloud Console setup required:** |
| 128 | +1. Create OAuth 2.0 credentials (Web application) |
| 129 | +2. Add authorized redirect URI: `{domain}/auth/callback` |
| 130 | +3. Enable Google People API (for user info) |
0 commit comments