Fixes iPad Safari authentication issue where async JavaScript is blocked on OAuth callback pages after cross-origin redirects (Cloudflare + Safari ITP). **Problem**: iPad Safari blocks all async operations (Promises, setTimeout, onMounted) on the OAuth callback page, preventing frontend token exchange. **Solution**: Move entire OAuth flow to backend with HttpOnly cookies, eliminating JavaScript dependency on callback page. ## Backend Changes (7 files) ### New Files - app/services/oauth_state.py - Redis-based OAuth state management * CSRF protection with one-time use tokens (10min TTL) * Replaces frontend sessionStorage state validation - app/utils/cookies.py - HttpOnly cookie utilities * Access token: 1 hour, Path=/api * Refresh token: 7 days, Path=/api/auth * Security: HttpOnly, Secure (prod), SameSite=Lax ### Modified Files - app/api/routes/auth.py * NEW: GET /discord/login - Initiate OAuth with state creation * NEW: GET /discord/callback/server - Server-side callback handler * NEW: POST /logout - Clear auth cookies * UPDATED: GET /me - Cookie + header support (backwards compatible) * UPDATED: POST /refresh - Cookie + body support (backwards compatible) * FIXED: exchange_code_for_token() accepts redirect_uri parameter - app/config.py * Added discord_server_redirect_uri config * Added frontend_url config for post-auth redirects - app/websocket/handlers.py * Updated connect handler to parse cookies from environ * Falls back to auth object for backwards compatibility - .env.example * Added DISCORD_SERVER_REDIRECT_URI example * Added FRONTEND_URL example ## Frontend Changes (10 files) ### Core Auth Changes - store/auth.ts - Complete rewrite for cookie-based auth * Removed: token, refreshToken, tokenExpiresAt state (HttpOnly) * Added: checkAuth() - calls /api/auth/me with credentials * Updated: loginWithDiscord() - redirects to backend endpoint * Updated: logout() - calls backend logout endpoint * All $fetch calls use credentials: 'include' - pages/auth/callback.vue - Simplified to error handler * No JavaScript token exchange needed * Displays errors from query params * Verifies auth with checkAuth() on success - plugins/auth.client.ts * Changed from localStorage init to checkAuth() call * Async plugin to ensure auth state before navigation - middleware/auth.ts - Simplified * Removed token validity checks (HttpOnly cookies) * Simple isAuthenticated check ### Cleanup Changes - composables/useWebSocket.ts * Added withCredentials: true * Removed auth object with token * Updated canConnect to use isAuthenticated only - layouts/default.vue, layouts/game.vue, pages/index.vue, pages/games/[id].vue * Removed initializeAuth() calls (handled by plugin) ## Documentation - OAUTH_IPAD_ISSUE.md - Problem analysis and investigation notes - OAUTH_SERVER_SIDE_IMPLEMENTATION.md - Complete implementation guide * Security improvements summary * Discord Developer Portal setup instructions * Testing checklist * OAuth flow diagram ## Security Improvements - Tokens stored in HttpOnly cookies (XSS-safe) - OAuth state in Redis with one-time use (CSRF-safe) - Follows OAuth 2.0 Security Best Current Practice - Backwards compatible with Authorization header auth ## Testing - ✅ Backend OAuth endpoints functional - ✅ Token exchange with correct redirect_uri - ✅ Cookie-based auth working - ✅ WebSocket connection with cookies - ✅ Desktop browser flow verified - ⏳ iPad Safari testing pending Discord redirect URI config ## Next Steps 1. Add Discord redirect URI in Developer Portal: https://gameplay-demo.manticorum.com/api/auth/discord/callback/server 2. Test complete flow on iPad Safari 3. Verify WebSocket auto-reconnection with cookies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .claude | ||
| backend | ||
| frontend-pd | ||
| frontend-sba | ||
| .dockerignore | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| OAUTH_IPAD_ISSUE.md | ||
| OAUTH_SERVER_SIDE_IMPLEMENTATION.md | ||
| prd-web-scorecard-1.1.md | ||
| QUICKSTART.md | ||
| README.md | ||
Paper Dynasty Real-Time Game Engine
Web-based real-time multiplayer baseball simulation platform replacing the legacy Google Sheets system.
Project Structure
strat-gameplay-webapp/
├── backend/ # FastAPI game engine
├── frontend-sba/ # SBA League Nuxt frontend
├── frontend-pd/ # PD League Nuxt frontend
├── .claude/ # Claude AI implementation guides
├── docker-compose.yml # Full stack orchestration
└── README.md # This file
Two Development Workflows
Option 1: Local Development (Recommended for Daily Work)
Best for: Fast hot-reload, quick iteration, debugging
Services:
- ✅ Backend runs locally (Python hot-reload)
- ✅ Frontends run locally (Nuxt hot-reload)
- ✅ Redis in Docker (lightweight)
- ✅ PostgreSQL on your existing server
Setup:
-
Environment Setup
# Copy environment template cp .env.example .env # Edit .env with your database credentials and API keys -
Start Redis (in one terminal)
cd backend docker-compose up -
Start Backend (in another terminal)
cd backend uv run python -m app.main # Or manually activate: source .venv/bin/activate && python -m app.mainBackend will be available at http://localhost:8000
-
Start SBA Frontend (in another terminal)
cd frontend-sba npm run devSBA frontend will be available at http://localhost:3000
-
Start PD Frontend (in another terminal)
cd frontend-pd npm run devPD frontend will be available at http://localhost:3001
Advantages:
- ⚡ Instant hot-reload on code changes
- 🐛 Easy debugging (native debuggers work)
- 💨 Fast startup times
- 🔧 Simple to restart individual services
Option 2: Full Docker Orchestration
Best for: Integration testing, demos, production-like environment
Services:
- ✅ Everything runs in containers
- ✅ Consistent environment
- ✅ One command to start everything
Setup:
-
Environment Setup
# Copy environment template cp .env.example .env # Edit .env with your database credentials and API keys -
Start Everything
# From project root docker-compose upOr run in background:
docker-compose up -d -
View Logs
# All services docker-compose logs -f # Specific service docker-compose logs -f backend docker-compose logs -f frontend-sba -
Stop Everything
docker-compose down
Advantages:
- 🎯 Production-like environment
- 🚀 One-command startup
- 🔄 Easy to share with team
- ✅ CI/CD ready
Development Commands
Backend
cd backend
# Install UV (one-time setup)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install dependencies
uv sync
# Run server
uv run python -m app.main
# Run tests
uv run pytest tests/ -v
# Code formatting
uv run black app/ tests/
# Linting
uv run flake8 app/ tests/
# Type checking
uv run mypy app/
Frontend
cd frontend-sba # or frontend-pd
# Install dependencies
npm install
# Run dev server
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
# Lint
npm run lint
# Type check
npm run type-check
Database Setup
This project uses an existing PostgreSQL server. You need to manually create the database:
-- On your PostgreSQL server
CREATE DATABASE paperdynasty_dev;
CREATE USER paperdynasty WITH PASSWORD 'your-secure-password';
GRANT ALL PRIVILEGES ON DATABASE paperdynasty_dev TO paperdynasty;
Then update DATABASE_URL in .env:
DATABASE_URL=postgresql+asyncpg://paperdynasty:your-password@your-db-server:5432/paperdynasty_dev
Environment Variables
Copy .env.example to .env and configure:
Required
DATABASE_URL- PostgreSQL connection stringSECRET_KEY- Application secret key (at least 32 characters)DISCORD_CLIENT_ID- Discord OAuth client IDDISCORD_CLIENT_SECRET- Discord OAuth secretSBA_API_URL/SBA_API_KEY- SBA League API credentialsPD_API_URL/PD_API_KEY- PD League API credentials
Optional
REDIS_URL- Redis connection (auto-configured in Docker)CORS_ORIGINS- Allowed origins (defaults to localhost:3000,3001)
Available Services
When running, the following services are available:
| Service | URL | Description |
|---|---|---|
| Backend API | http://localhost:8000 | FastAPI REST API |
| Backend Docs | http://localhost:8000/docs | Interactive API documentation |
| SBA Frontend | http://localhost:3000 | SBA League web app |
| PD Frontend | http://localhost:3001 | PD League web app |
| Redis | localhost:6379 | Cache (not exposed via HTTP) |
Health Checks
# Backend health
curl http://localhost:8000/api/health
# Or visit in browser
open http://localhost:8000/api/health
Troubleshooting
Backend won't start
- Check
DATABASE_URLis correct in.env - Verify PostgreSQL database exists
- Ensure Redis is running (
docker-compose upin backend/) - Check logs for specific errors
Frontend won't connect to backend
- Verify backend is running at http://localhost:8000
- Check CORS settings in backend
.env - Clear browser cache and cookies
- Check browser console for errors
Docker containers won't start
- Ensure
.envfile exists with all required variables - Run
docker-compose downthendocker-compose upagain - Check
docker-compose logsfor specific errors - Verify no port conflicts (8000, 3000, 3001, 6379)
Database connection fails
- Verify PostgreSQL server is accessible
- Check firewall rules allow connection
- Confirm database and user exist
- Test connection with
psqldirectly
Documentation
- Full PRD: See
/prd-web-scorecard-1.1.md - Implementation Guide: See
.claude/implementation/00-index.md - Architecture Docs: See
.claude/implementation/directory
Tech Stack
Backend
- Framework: FastAPI (Python 3.13+)
- Package Manager: UV (modern Python package management)
- WebSocket: Socket.io
- Database: PostgreSQL 14+ with SQLAlchemy
- Cache: Redis 7
- Auth: Discord OAuth with JWT
Frontend
- Framework: Vue 3 + Nuxt 3
- Language: TypeScript
- Styling: Tailwind CSS
- State: Pinia
- WebSocket: Socket.io-client
Contributing
See .claude/implementation/ for detailed implementation guides and architecture documentation.
License
Proprietary - Paper Dynasty League System