From bf4baf91e83a6cf31c8a61577b20cb81234d22d9 Mon Sep 17 00:00:00 2001 From: JSC Date: Mon, 30 Jun 2025 13:39:28 +0200 Subject: [PATCH] Add CLAUDE.md for project documentation and examples of multi-provider OAuth usage --- CLAUDE.md | 327 ++++++++++++++++++++++++++++++++++++++++ examples/oauth_usage.py | 89 +++++++++++ 2 files changed, 416 insertions(+) create mode 100644 CLAUDE.md create mode 100644 examples/oauth_usage.py diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b5d02ac --- /dev/null +++ b/CLAUDE.md @@ -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/` - Initiate OAuth login for specified provider (google, github) +- `GET /api/auth/callback/` - 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/` - Link additional OAuth provider to current user (requires JWT) +- `GET /api/auth/link/callback/` - Handle OAuth callback for linking provider +- `DELETE /api/auth/unlink/` - 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/` - 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/` 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/` 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/` to link additional OAuth providers +3. Users can sign in with password or any linked OAuth provider +4. Call `DELETE /api/auth/unlink/` 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" +} +``` \ No newline at end of file diff --git a/examples/oauth_usage.py b/examples/oauth_usage.py new file mode 100644 index 0000000..917bdb4 --- /dev/null +++ b/examples/oauth_usage.py @@ -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.") \ No newline at end of file