Add CLAUDE.md for project documentation and examples of multi-provider OAuth usage

This commit is contained in:
JSC
2025-06-30 13:39:28 +02:00
commit bf4baf91e8
2 changed files with 416 additions and 0 deletions

327
CLAUDE.md Normal file
View File

@@ -0,0 +1,327 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Structure
This is a full-stack application with separate backend (Flask) and frontend (Next.js) components:
- **Backend** (`backend/`): Flask application with clean architecture
- Entry point: `main.py`
- App structure: `app/` with separate `routes/` and `services/` directories
- Business logic in services, routes handle HTTP concerns only
- Uses modern Python tooling (Black, Ruff, pytest)
- CORS enabled for frontend integration
- Python 3.12+ required
- **Frontend** (`frontend/`): React application with Vite
- Uses React 19, TypeScript, React Router v7, and Tailwind CSS v4
- Shadcn/ui components with consistent sidebar layout for authenticated users
- Built with Vite for fast development and production builds
## Development Commands
### Backend (Flask)
```bash
cd backend
uv run python main.py # Run the Flask development server (localhost:5000)
uv run pytest # Run tests
uv run pytest tests/ # Run specific test directory
uv run ruff check # Lint code
uv run ruff format # Format code
uv run black . # Format code (alternative to ruff format)
# Database commands
uv run python migrate_db.py init-db # Initialize database tables and seed data
uv run python migrate_db.py reset-db # Reset database (drop and recreate all tables with seed data)
uv run flask --app app db init # Initialize migrations (first time only)
uv run flask --app app db migrate # Create new migration
uv run flask --app app db upgrade # Apply migrations
```
#### Environment Variables
```bash
# Required for sessions and JWT
export SECRET_KEY="your_secret_key_for_sessions"
export JWT_SECRET_KEY="your_jwt_secret_key"
# OAuth Providers (configure as needed)
# Google OAuth
export GOOGLE_CLIENT_ID="your_google_client_id"
export GOOGLE_CLIENT_SECRET="your_google_client_secret"
# GitHub OAuth
export GITHUB_CLIENT_ID="your_github_client_id"
export GITHUB_CLIENT_SECRET="your_github_client_secret"
```
### Frontend (Next.js)
```bash
cd frontend
bun dev # Start development server (preferred)
# Alternative package managers:
npm run dev / yarn dev / pnpm dev
bun run build # Build for production
bun run start # Start production server
bun run lint # Run ESLint
```
## Code Style
### Backend
- Line length: 80 characters
- Uses Ruff with comprehensive rule set (ALL rules enabled with specific ignores)
- Black formatting enforced
- Ignores: D100 (module docstrings), D104 (package docstrings)
### Frontend
- TypeScript with strict configuration
- ESLint with Next.js configuration
- Tailwind CSS for styling
- Uses Geist font family (sans and mono variants)
## Backend Architecture
The Flask backend follows a clean architecture pattern:
- **Routes** (`app/routes/`): Handle HTTP requests/responses, delegate to services
- **Services** (`app/services/`): Contain all business logic, pure functions when possible
- **Application Factory** (`app/__init__.py`): Creates and configures Flask app
- **Tests** (`tests/`): Comprehensive test coverage for services and routes
Key principles:
- Routes should be thin - only handle HTTP concerns
- Business logic lives in services
- Services are testable in isolation
- Clear separation of concerns
## Authentication
The backend implements multi-provider OAuth authentication (Google, GitHub) with JWT tokens using Flask-JWT-Extended:
### Available Endpoints
**OAuth Authentication:**
- `GET /api/auth/providers` - Get list of available OAuth providers
- `GET /api/auth/login/<provider>` - Initiate OAuth login for specified provider (google, github)
- `GET /api/auth/callback/<provider>` - Handle OAuth callback from specified provider
**Password Authentication:**
- `POST /api/auth/register` - Register new user with email, password, and name
- `POST /api/auth/login` - Login with email and password
**Session Management:**
- `GET /api/auth/logout` - Logout current user (clears cookies)
- `GET /api/auth/me` - Get current user information (requires JWT)
- `POST /api/auth/refresh` - Refresh access token using refresh token
**Multi-Provider Management:**
- `GET /api/auth/link/<provider>` - Link additional OAuth provider to current user (requires JWT)
- `GET /api/auth/link/callback/<provider>` - Handle OAuth callback for linking provider
- `DELETE /api/auth/unlink/<provider>` - Unlink OAuth provider from current user (requires JWT)
**API Token Management:**
- `POST /api/auth/regenerate-api-token` - Generate new API token for current user (requires JWT)
**Example Endpoints:**
- `GET /api/protected` - Example protected endpoint (requires authentication)
- `GET /api/api-protected` - Example endpoint that accepts JWT or API token authentication
- `GET /api/admin` - Example admin-only endpoint (requires admin role)
- `GET /api/use-credits/<amount>` - Example endpoint that costs 5 credits to use
- `GET /api/expensive-operation` - Example endpoint that costs 10 credits to use
### Flask-JWT-Extended Features
- **Access Token**: Short-lived (15 minutes), contains user identity and claims
- **Refresh Token**: Long-lived (7 days), used to generate new access tokens
- **HTTP-only Cookies**: Automatic secure storage with Flask-JWT-Extended
- **Built-in Security**: CSRF protection, secure cookie settings
- **Cookie Paths**: Access tokens work on `/api/`, refresh tokens on `/api/auth/refresh`
### Usage Flow
**Password Authentication:**
1. **Register**: `POST /api/auth/register` with `{"email": "...", "password": "...", "name": "..."}`
2. **Login**: `POST /api/auth/login` with `{"email": "...", "password": "..."}`
3. JWT tokens are automatically set as HTTP-only cookies
4. Access protected endpoints - Flask-JWT-Extended handles token validation
**OAuth Authentication:**
1. Call `/api/auth/providers` to get available OAuth providers
2. Call `/api/auth/login/<provider>` to initiate OAuth flow (e.g., `/api/auth/login/google` or `/api/auth/login/github`)
3. Redirect user to the returned OAuth URL
4. Provider redirects back to `/api/auth/callback/<provider>` with authorization code
5. JWT tokens are automatically set as HTTP-only cookies
6. Access protected endpoints - Flask-JWT-Extended handles token validation
**Multi-Provider Management:**
1. User authenticates with password or OAuth provider
2. Call `/api/auth/link/<provider>` to link additional OAuth providers
3. Users can sign in with password or any linked OAuth provider
4. Call `DELETE /api/auth/unlink/<provider>` to remove OAuth providers (minimum 1 auth method required)
5. User data includes `providers` array showing all available authentication methods
### Authentication Components
- **User Model**: Stores user profile information (email, name, picture, role)
- **UserOAuth Model**: Stores provider-specific authentication data
- **AuthService**: Handles multi-provider OAuth flow and Flask-JWT-Extended integration
- **TokenService**: Simplified token generation using Flask-JWT-Extended
- **OAuthProviderRegistry**: Manages available OAuth providers based on environment variables
- **OAuthProvider**: Abstract base class for OAuth providers (Google, GitHub, etc.)
- **Decorators**: `@require_auth`, `@require_admin`, `@require_role()` for access control
- **Flask-JWT-Extended**: Handles cookie management, validation, and security
### Database Schema
- **users** table: Core user information (id, email, name, picture, password_hash, role, is_active, api_token, api_token_expires_at, timestamps)
- **user_oauth** table: Provider-specific data (user_id, provider, provider_id, email, name, picture)
- **Relationships**: One user can have multiple OAuth providers, enabling flexible authentication
- **Authentication**: Users can authenticate via password OR OAuth providers (or both)
- **User Status**: Users have an `is_active` field (default: true) for account management
- **API Tokens**: Each user automatically gets an API token with no expiration for programmatic access
- **Roles**: First user gets "admin" role, subsequent users get "user" role by default
### Supported Authentication Methods
- **Password**: Traditional email/password authentication with Werkzeug secure hashing
- **Google OAuth**: OpenID Connect with `openid email profile` scopes
- **GitHub OAuth**: OAuth 2.0 with `user:email` scope to access user profile and email
- **API Token**: Long-lived tokens for programmatic access (no expiration by default)
### Role-Based Access Control
- **Admin Role**: First user automatically gets admin privileges
- **User Role**: Default role for all subsequent users
- **Role Assignment**: Automatic during registration (password or OAuth)
- **Access Control**: Use decorators to protect routes by role
- **JWT Integration**: Role included in JWT tokens for stateless authorization
### Available Authentication Decorators
- `@require_auth` - Requires authentication (JWT or API token)
- `@require_role("role_name")` - Requires specific role (chainable with require_auth)
- `@require_credits(amount)` - Requires and deducts specified credits from user
### Multi-Authentication Benefits
- **Flexible Registration**: Register with email/password or OAuth providers
- **Account Linking**: Link multiple authentication methods to one account
- **Flexible Sign-in**: Sign in with password, Google, or GitHub
- **API Access**: Programmatic access via API tokens for scripts and applications
- **Account Recovery**: Multiple authentication options prevent lockout
- **Data Consistency**: Single user profile updated from any authentication method
- **Security**: Werkzeug secure password hashing and OAuth security best practices
- **Role-Based Access**: Fine-grained permission control with automatic admin assignment
### API Token Usage
**Getting Your API Token:**
1. Authenticate via password or OAuth to get JWT tokens
2. Call `POST /api/auth/regenerate-api-token` to get a new API token
3. Use the returned token for programmatic access
**Using API Tokens:**
```bash
# Include API token in Authorization header
curl -H "Authorization: Bearer YOUR_API_TOKEN" http://localhost:5000/api/api-protected
# Alternative format
curl -H "Authorization: Token YOUR_API_TOKEN" http://localhost:5000/api/api-protected
```
**API Token Features:**
- **No Expiration**: Tokens don't expire by default (can be configured per token)
- **Regeneration**: Users can regenerate tokens at any time via `/api/auth/regenerate-api-token`
- **Automatic Creation**: New tokens generated automatically during user registration
- **Role Support**: Tokens inherit user's role for role-based access control
- **Security**: 32-byte URL-safe tokens using `secrets.token_urlsafe()`
## Plan System
The backend includes a subscription plan system that assigns users to different plans with varying credit limits:
### Available Plans
- **Free Plan** (`free`): 25 credits (75 max) - Default plan for new users
- **Premium Plan** (`premium`): 50 credits (150 max) - Enhanced features with increased limits
- **Pro Plan** (`pro`): 100 credits (300 max) - Full access with unlimited usage
### Plan Assignment
- **First User**: Automatically assigned to Pro plan with admin role
- **Subsequent Users**: Automatically assigned to Free plan with user role
- **Plan Information**: Included in all authentication responses (login, register, /me endpoint)
### Database Schema
- **plans** table: id, code, name, description, credits, max_credits
- **users.plan_id**: Foreign key to plans table (required)
- **users.credits**: Current user credits (initialized from plan.credits)
- **Relationship**: Each user belongs to exactly one plan
### Automatic Initialization
The plan system is automatically initialized when the Flask app starts:
- **Database Creation**: `db.create_all()` creates all tables including plans and users
- **Plan Seeding**: Automatically seeds the three default plans if the plans table is empty
- **User Migration**: Automatically assigns plans and credits to existing users who don't have them
- **First User**: Gets Pro plan (100 credits) and admin role automatically
- **Subsequent Users**: Get Free plan (25 credits) and user role automatically
No manual migration scripts are needed - everything happens automatically on app startup.
### Plan Information in API Responses
All user data returned by authentication endpoints includes plan and credits information:
```json
{
"user": {
"id": "1",
"email": "user@example.com",
"name": "User Name",
"role": "user",
"credits": 25,
"plan": {
"id": 1,
"code": "free",
"name": "Free Plan",
"description": "Basic features with limited usage",
"credits": 25,
"max_credits": 75
}
}
}
```
### Credits System
- **User Credits**: Each user has a `credits` field tracking their current available credits
- **Initial Credits**: Set to the plan's `credits` value when user is created
- **Plan Credits**: The default credits amount for the plan (what new users get)
- **Max Credits**: The maximum credits a user on this plan can have
- **Authentication**: Credits are included in JWT tokens and all auth responses
### Credit Usage Decorator
Use the `@require_credits(amount)` decorator to protect endpoints that consume credits:
```python
from app.services.decorators import require_auth, require_credits
@bp.route("/ai-generation")
@require_auth
@require_credits(10) # Costs 10 credits to use
def ai_generation():
"""AI generation endpoint that costs 10 credits."""
return {"result": "AI generated content"}
```
**Features:**
- **Automatic Deduction**: Credits are deducted from user's balance before endpoint execution
- **Insufficient Credits**: Returns HTTP 402 (Payment Required) with clear error message
- **Database Updates**: Credits are updated in real-time in the database
- **Authentication**: Works with both JWT and API token authentication
- **Error Handling**: If endpoint fails, credits are still deducted (transaction-like behavior)
**Example Error Response:**
```json
{
"error": "Insufficient credits. Required: 10, Available: 5"
}
```

89
examples/oauth_usage.py Normal file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""
Example demonstrating the multi-provider OAuth implementation.
This script shows how to use the new generic OAuth system.
"""
# Example usage of the new multi-provider OAuth system
# 1. Environment variables setup:
"""
# Required for sessions and JWT
export SECRET_KEY="your_secret_key_for_sessions"
export JWT_SECRET_KEY="your_jwt_secret_key"
# OAuth Providers (configure as needed)
# Google OAuth
export GOOGLE_CLIENT_ID="your_google_client_id"
export GOOGLE_CLIENT_SECRET="your_google_client_secret"
# GitHub OAuth
export GITHUB_CLIENT_ID="your_github_client_id"
export GITHUB_CLIENT_SECRET="your_github_client_secret"
"""
# 2. Available endpoints:
"""
GET /api/auth/providers
Returns: {"providers": {"google": {"name": "google", "display_name": "Google"}, ...}}
GET /api/auth/login/google
GET /api/auth/login/github
Redirects to OAuth provider login
GET /api/auth/callback/google
GET /api/auth/callback/github
Handles OAuth callback and sets JWT cookies
GET /api/auth/login (backward compatibility - defaults to Google)
GET /api/auth/callback (backward compatibility - defaults to Google)
GET /api/auth/me
Returns current user info including provider field
GET /api/auth/logout
Clears authentication cookies
"""
# 3. Frontend integration example:
"""
// Get available providers
const providersResponse = await fetch('/api/auth/providers');
const { providers } = await providersResponse.json();
// Redirect to OAuth provider
window.location.href = `/api/auth/login/${provider}`; // 'google' or 'github'
// After successful login, user info will include:
{
"id": "user_id",
"email": "user@example.com",
"name": "User Name",
"picture": "https://avatar.url",
"provider": "google" // or "github"
}
"""
# 4. Adding new OAuth providers:
"""
To add a new provider (e.g., Microsoft):
1. Create app/services/oauth_providers/microsoft.py:
- Inherit from OAuthProvider
- Implement required methods (name, display_name, get_client_config, get_user_info)
2. Add to registry.py:
- Add Microsoft provider initialization in _initialize_providers()
- Include environment variable checks
3. Add environment variables:
- MICROSOFT_CLIENT_ID
- MICROSOFT_CLIENT_SECRET
4. The routes automatically support the new provider via:
- GET /api/auth/login/microsoft
- GET /api/auth/callback/microsoft
"""
print("OAuth multi-provider system ready!")
print("Configure environment variables and start the Flask app to use.")