├── src ├── __init__.py ├── api │ ├── __init__.py │ ├── endpoints │ │ ├── __init__.py │ │ └── auth.py │ ├── router.py │ ├── api.py │ └── dependencies.py ├── core │ ├── __init__.py │ ├── supabase_client.py │ ├── config.py │ ├── constants.py │ ├── messages.py │ └── secrets.py ├── models │ ├── __init__.py │ └── auth.py ├── tests │ ├── __init__.py │ └── oauth │ │ ├── README.md │ │ └── oauth_test.html └── services │ ├── __init__.py │ └── auth_service.py ├── requirements-dev.txt ├── requirements.txt ├── pyproject.toml ├── lore ├── config.md ├── secrets.md ├── api_setup.md ├── core_constants.md ├── dependencies.md ├── auth_models.md ├── auth_service.md └── auth_endpoints.md ├── .pre-commit-config.yaml ├── .gitignore └── README.md /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | cursor-dungeon-master==0.3.1 2 | mypy>=1.7.0 3 | pre-commit>=3.5.0 4 | types-python-jose>=3.5.0 5 | types-requests>=2.31.0 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | email-validator==2.2.0 2 | fastapi==0.115.12 3 | google-cloud-secret-manager==2.23.3 4 | httpx==0.27.2 5 | pydantic==2.11.5 6 | pydantic-settings==2.9.1 7 | python-dotenv==1.1.0 8 | python-jose==3.5.0 9 | supabase==2.15.2 10 | uvicorn==0.34.0 11 | -------------------------------------------------------------------------------- /src/api/router.py: -------------------------------------------------------------------------------- 1 | # @track_context("api_setup.md") 2 | 3 | from fastapi import APIRouter 4 | 5 | from src.api.endpoints import auth 6 | 7 | api_router = APIRouter() 8 | 9 | # Include all endpoint routers 10 | api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "supabase-api-scaffolding" 7 | version = "0.1.0" 8 | description = "FastAPI backend with Supabase integration" 9 | requires-python = ">=3.10" 10 | 11 | [tool.mypy] 12 | python_version = "3.10" 13 | strict = true 14 | files = ["src"] 15 | exclude = ["src/tests"] 16 | 17 | [[tool.mypy.overrides]] 18 | module = ["google.*", "supabase.*"] 19 | ignore_missing_imports = true 20 | -------------------------------------------------------------------------------- /src/core/supabase_client.py: -------------------------------------------------------------------------------- 1 | # @track_context("config.md") 2 | 3 | from supabase import Client, create_client 4 | 5 | from src.core.config import settings 6 | 7 | # Global client instance to preserve PKCE state 8 | _supabase_client: Client | None = None 9 | 10 | 11 | def get_supabase_client() -> Client: 12 | """Get Supabase client instance (singleton to preserve PKCE state)""" 13 | global _supabase_client 14 | if _supabase_client is None: 15 | _supabase_client = create_client(settings.SUPABASE_URL, settings.SUPABASE_KEY) 16 | return _supabase_client 17 | -------------------------------------------------------------------------------- /src/api/api.py: -------------------------------------------------------------------------------- 1 | # @track_context("api_setup.md") 2 | 3 | import logging 4 | 5 | from fastapi import FastAPI 6 | from fastapi.middleware.cors import CORSMiddleware 7 | 8 | from src.api.router import api_router 9 | from src.core.config import settings 10 | 11 | # Configure logging 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | app = FastAPI( 15 | title="Supabase Auth API", 16 | description="Production-ready authentication API with Supabase", 17 | version="0.1.0", 18 | ) 19 | 20 | # CORS middleware 21 | app.add_middleware( 22 | CORSMiddleware, 23 | allow_origins=settings.CORS_ORIGINS, 24 | allow_credentials=True, 25 | allow_methods=["*"], 26 | allow_headers=["*"], 27 | ) 28 | 29 | # Add API routes 30 | app.include_router(api_router, prefix="/api/v1") 31 | 32 | 33 | @app.get("/health") 34 | async def health_check() -> dict[str, str]: 35 | """Simple health check endpoint""" 36 | return {"status": "healthy", "version": "0.1.0"} 37 | -------------------------------------------------------------------------------- /src/core/config.py: -------------------------------------------------------------------------------- 1 | # @track_context("config.md") 2 | 3 | import logging 4 | import os 5 | 6 | from pydantic_settings import BaseSettings 7 | 8 | from src.core.constants import Defaults, EnvVars 9 | from src.core.secrets import load_secrets_from_gsm, should_use_gsm 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class Settings(BaseSettings): 15 | """Application settings""" 16 | 17 | # Base 18 | APP_NAME: str = Defaults.APP_NAME 19 | API_V1_STR: str = Defaults.API_V1_STR 20 | 21 | # App metadata 22 | APP_VERSION: str = Defaults.APP_VERSION 23 | APP_DESCRIPTION: str = Defaults.APP_DESCRIPTION 24 | 25 | # CORS settings 26 | CORS_ORIGINS: list[str] = Defaults.CORS_ORIGINS 27 | 28 | # Supabase - make these optional with defaults 29 | SUPABASE_URL: str = "" 30 | SUPABASE_KEY: str = "" 31 | SUPABASE_JWT_SECRET: str = "" 32 | 33 | # Application settings 34 | DEBUG: bool = Defaults.DEBUG 35 | TESTING: bool = Defaults.TESTING 36 | LOG_LEVEL: str = Defaults.LOG_LEVEL 37 | USE_GSM: bool = Defaults.USE_GSM 38 | 39 | model_config = {"env_file": ".env", "case_sensitive": True} 40 | 41 | 42 | def get_settings() -> Settings: 43 | """Get application settings with optional Google Secret Manager integration""" 44 | # Load secrets from GSM if enabled (automatically falls back to env variables) 45 | if should_use_gsm(): 46 | load_secrets_from_gsm() 47 | 48 | return Settings() 49 | 50 | 51 | def should_use_testing() -> bool: 52 | return os.environ.get(EnvVars.TESTING, "").lower() in ("true", "1", "t") 53 | 54 | 55 | # Load settings using the function 56 | settings = get_settings() 57 | 58 | # Set log level 59 | LOG_LEVEL = getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO) 60 | -------------------------------------------------------------------------------- /src/models/auth.py: -------------------------------------------------------------------------------- 1 | # @track_context("auth_models.md") 2 | 3 | from datetime import datetime 4 | from enum import Enum 5 | 6 | from pydantic import BaseModel, EmailStr, Field 7 | 8 | from src.core.constants import OAuth, Validation 9 | 10 | 11 | class UserBase(BaseModel): 12 | """Base user schema""" 13 | 14 | email: EmailStr 15 | 16 | 17 | class UserCreate(UserBase): 18 | """User creation schema""" 19 | 20 | password: str = Field(..., min_length=Validation.MIN_PASSWORD_LENGTH) 21 | full_name: str = Field(..., min_length=1) 22 | 23 | 24 | class UserLogin(UserBase): 25 | """User login schema""" 26 | 27 | password: str 28 | 29 | 30 | class UserResponse(UserBase): 31 | """User response schema""" 32 | 33 | id: str 34 | full_name: str 35 | created_at: datetime | None = None 36 | 37 | 38 | class TokenResponse(BaseModel): 39 | """Token response schema""" 40 | 41 | access_token: str = "" 42 | refresh_token: str = "" 43 | token_type: str = "bearer" 44 | 45 | 46 | class AuthResponse(BaseModel): 47 | """Authentication response schema""" 48 | 49 | user: UserResponse 50 | token: TokenResponse 51 | 52 | 53 | class PasswordResetRequest(BaseModel): 54 | """Password reset request schema""" 55 | 56 | email: EmailStr 57 | 58 | 59 | class OAuthProvider(str, Enum): 60 | """OAuth provider options""" 61 | 62 | GOOGLE = OAuth.GOOGLE 63 | 64 | 65 | class OAuthLoginRequest(BaseModel): 66 | """OAuth login initiation request""" 67 | 68 | provider: OAuthProvider 69 | redirect_url: str 70 | 71 | 72 | class OAuthCallbackRequest(BaseModel): 73 | """OAuth callback request""" 74 | 75 | provider: OAuthProvider 76 | code: str 77 | redirect_url: str 78 | 79 | 80 | class OAuthResponse(BaseModel): 81 | """OAuth login response""" 82 | 83 | auth_url: str 84 | -------------------------------------------------------------------------------- /src/core/constants.py: -------------------------------------------------------------------------------- 1 | # @track_context("core_constants.md") 2 | 3 | """ 4 | Core constants for the Supabase Auth API 5 | 6 | This module contains essential configuration constants that are used 7 | across multiple modules in the application. 8 | """ 9 | 10 | 11 | class EnvVars: 12 | """Environment variable names""" 13 | 14 | # Core settings 15 | SUPABASE_URL = "SUPABASE_URL" 16 | SUPABASE_KEY = "SUPABASE_KEY" 17 | SUPABASE_JWT_SECRET = "SUPABASE_JWT_SECRET" 18 | 19 | # Application configuration 20 | DEBUG = "DEBUG" 21 | TESTING = "TESTING" 22 | LOG_LEVEL = "LOG_LEVEL" 23 | USE_GSM = "USE_GSM" 24 | 25 | # Google Cloud 26 | GCP_PROJECT_ID = "GCP_PROJECT_ID" 27 | 28 | 29 | class Defaults: 30 | """Default configuration values""" 31 | 32 | # Application metadata 33 | APP_NAME = "Supabase Auth API" 34 | APP_VERSION = "0.1.0" 35 | APP_DESCRIPTION = "Production-ready authentication API with Supabase" 36 | 37 | # API configuration 38 | API_V1_STR = "/api/v1" 39 | 40 | # CORS 41 | CORS_ORIGINS = ["*"] 42 | 43 | # Logging 44 | LOG_LEVEL = "INFO" 45 | 46 | # Boolean defaults 47 | DEBUG = False 48 | TESTING = False 49 | USE_GSM = False 50 | 51 | 52 | class Validation: 53 | """Validation rules and constraints""" 54 | 55 | # Password requirements 56 | MIN_PASSWORD_LENGTH = 8 57 | 58 | 59 | class Supabase: 60 | """Supabase-specific constants""" 61 | 62 | # Secret names for GSM 63 | REQUIRED_SECRETS = ["SUPABASE_URL", "SUPABASE_KEY", "SUPABASE_JWT_SECRET"] 64 | 65 | # User metadata fields 66 | FULL_NAME_FIELD = "full_name" 67 | 68 | # GSM secret path template 69 | SECRET_PATH_TEMPLATE = "projects/{project_id}/secrets/{secret_name}/versions/latest" 70 | 71 | 72 | class OAuth: 73 | """OAuth provider constants""" 74 | 75 | GOOGLE = "google" 76 | -------------------------------------------------------------------------------- /src/core/messages.py: -------------------------------------------------------------------------------- 1 | # @track_context("core_constants.md") 2 | 3 | """ 4 | User-facing messages for the Supabase Auth API 5 | 6 | This module contains all user-facing text organized by category 7 | for better maintainability and localization support. 8 | """ 9 | 10 | 11 | class ErrorMessages: 12 | """Error messages shown to users""" 13 | 14 | INVALID_CREDENTIALS = "Invalid email or password" 15 | INVALID_TOKEN = "Invalid authentication token" 16 | INVALID_TOKEN_MISSING_USER = "Invalid token: missing user identifier" 17 | 18 | LOGOUT_FAILED = "Failed to log out" 19 | REGISTRATION_FAILED = "Registration failed" 20 | AUTHENTICATION_FAILED = "Authentication failed" 21 | 22 | 23 | class SuccessMessages: 24 | """Success messages shown to users""" 25 | 26 | LOGOUT_SUCCESS = "Successfully logged out" 27 | PASSWORD_RESET_SENT = "Password reset email sent" 28 | 29 | 30 | class LogMessages: 31 | """Internal log messages""" 32 | 33 | USER_CREATED = "User created: {user_id}" 34 | USER_LOGGED_IN = "User logged in: {user_id}" 35 | USER_LOGGED_OUT = "User logged out successfully" 36 | 37 | # Settings loading 38 | LOADING_FROM_ENV = "Loading settings from environment variables (GSM disabled)" 39 | LOADING_FROM_GSM = "Loading secrets from Google Secret Manager" 40 | GSM_ERROR_FALLBACK = "Error loading secrets from GSM: {error}" 41 | GSM_FALLBACK_WARNING = "Falling back to environment variables" 42 | 43 | # Secret management 44 | FETCHING_SECRET_GSM = "Fetching {secret_name} from GSM" 45 | SECRET_RETRIEVED_GSM = "Successfully retrieved secret {secret_name} from GSM" 46 | SECRET_LOADED_GSM = "Loaded {secret_name} from Google Secret Manager" 47 | SECRET_EMPTY_WARNING = "Could not load {secret_name} from GSM, value was empty" 48 | SECRET_ACCESS_ERROR = "Error accessing secret {secret_name}: {error}" 49 | 50 | # JWT validation 51 | JWT_MISSING_SUB = "Token is valid but missing 'sub' claim" 52 | JWT_VALIDATION_FAILED = "JWT validation failed: {error}" 53 | -------------------------------------------------------------------------------- /src/api/dependencies.py: -------------------------------------------------------------------------------- 1 | # @track_context("dependencies.md") 2 | 3 | import logging 4 | from typing import Any 5 | 6 | from fastapi import Depends, HTTPException, status 7 | from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer 8 | from jose import JWTError, jwt 9 | 10 | from src.core.config import settings 11 | from src.core.messages import ErrorMessages, LogMessages 12 | from src.services.auth_service import AuthService 13 | 14 | logger = logging.getLogger(__name__) 15 | security = HTTPBearer() 16 | 17 | 18 | def validate_jwt_token(token: str) -> str: 19 | """ 20 | Extract and validate JWT token, return user_id 21 | 22 | Args: 23 | token: JWT token string 24 | 25 | Returns: 26 | user_id extracted from token 27 | 28 | Raises: 29 | HTTPException: If token is invalid 30 | """ 31 | try: 32 | # Decode and verify token 33 | payload: dict[str, Any] = jwt.decode( 34 | token, 35 | settings.SUPABASE_JWT_SECRET, 36 | algorithms=["HS256"], 37 | options={ 38 | "verify_signature": True, 39 | "verify_exp": True, 40 | "verify_aud": True, 41 | "require_exp": True, 42 | }, 43 | audience="authenticated", 44 | ) 45 | 46 | # Extract user_id from sub claim 47 | user_id: Any = payload.get("sub") 48 | if not user_id: 49 | logger.warning(LogMessages.JWT_MISSING_SUB) 50 | raise HTTPException( 51 | status_code=status.HTTP_401_UNAUTHORIZED, 52 | detail=ErrorMessages.INVALID_TOKEN_MISSING_USER, 53 | ) 54 | 55 | return str(user_id) 56 | 57 | except JWTError as err: 58 | logger.warning(LogMessages.JWT_VALIDATION_FAILED.format(error=err)) 59 | raise HTTPException( 60 | status_code=status.HTTP_401_UNAUTHORIZED, 61 | detail=ErrorMessages.INVALID_TOKEN, 62 | ) from err 63 | 64 | 65 | async def get_current_user( 66 | credentials: HTTPAuthorizationCredentials = Depends(security), 67 | ) -> str: 68 | token = credentials.credentials 69 | user_id = validate_jwt_token(token) 70 | return user_id 71 | 72 | 73 | def get_auth_service() -> AuthService: 74 | return AuthService() 75 | -------------------------------------------------------------------------------- /src/core/secrets.py: -------------------------------------------------------------------------------- 1 | # @track_context("secrets.md") 2 | 3 | import logging 4 | import os 5 | from functools import lru_cache 6 | from typing import Optional 7 | 8 | from google.cloud import secretmanager 9 | 10 | from src.core.constants import EnvVars, Supabase 11 | from src.core.messages import LogMessages 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | @lru_cache 17 | def get_secret(secret_name: str, project_id: Optional[str] = None) -> str: 18 | """ 19 | Retrieve a secret from Google Secret Manager. 20 | 21 | Args: 22 | secret_name: Name of the secret to retrieve 23 | project_id: GCP project ID 24 | 25 | Returns: 26 | The secret value as a string 27 | """ 28 | try: 29 | if not project_id: 30 | project_id = os.environ.get(EnvVars.GCP_PROJECT_ID) 31 | if not project_id: 32 | raise ValueError( 33 | "GCP_PROJECT_ID must be set when using Google Secret Manager" 34 | ) 35 | 36 | logger.debug(f"Attempting to retrieve secret {secret_name} from GSM") 37 | client = secretmanager.SecretManagerServiceClient() 38 | name = Supabase.SECRET_PATH_TEMPLATE.format( 39 | project_id=project_id, secret_name=secret_name 40 | ) 41 | response = client.access_secret_version(request={"name": name}) 42 | logger.info(LogMessages.SECRET_RETRIEVED_GSM.format(secret_name=secret_name)) 43 | return response.payload.data.decode("UTF-8") 44 | except Exception as e: 45 | logger.warning( 46 | LogMessages.SECRET_ACCESS_ERROR.format(secret_name=secret_name, error=e) 47 | ) 48 | return os.environ.get(secret_name, "") 49 | 50 | 51 | def load_secrets_from_gsm() -> None: 52 | """Load critical secrets from Google Secret Manager into environment variables""" 53 | for secret_name in Supabase.REQUIRED_SECRETS: 54 | if secret_name not in os.environ: 55 | logger.info(LogMessages.FETCHING_SECRET_GSM.format(secret_name=secret_name)) 56 | secret_value = get_secret(secret_name) 57 | if secret_value: 58 | os.environ[secret_name] = secret_value 59 | logger.info( 60 | LogMessages.SECRET_LOADED_GSM.format(secret_name=secret_name) 61 | ) 62 | else: 63 | logger.warning( 64 | LogMessages.SECRET_EMPTY_WARNING.format(secret_name=secret_name) 65 | ) 66 | 67 | 68 | def should_use_gsm() -> bool: 69 | return os.environ.get(EnvVars.USE_GSM, "false").lower() in ("true", "1", "t") 70 | -------------------------------------------------------------------------------- /src/tests/oauth/README.md: -------------------------------------------------------------------------------- 1 | # OAuth Testing 2 | 3 | This directory contains tools for testing Google OAuth integration with Supabase. 4 | 5 | ## OAuth PKCE Flow 6 | 7 | This implementation uses **Supabase's built-in PKCE handling** with a backend callback for security. 8 | 9 | ### How It Works 10 | 11 | 1. **Initiate OAuth**: Call `/api/v1/auth/oauth/login` to get Google OAuth URL 12 | 2. **Redirect to Google**: User authenticates with Google 13 | 3. **Google Returns Code**: User returns with authorization code in URL 14 | 4. **Exchange Code**: Frontend calls `/api/v1/auth/oauth/callback` to exchange code for tokens 15 | 5. **Backend Handles PKCE**: Supabase exchanges code for session tokens using stored PKCE verifier 16 | 17 | ### API Endpoints 18 | 19 | - `POST /api/v1/auth/oauth/login` - Get Google OAuth URL 20 | 21 | - Request: `{"provider": "google", "redirect_url": "http://localhost:3000/oauth_test.html"}` 22 | - Response: `{"auth_url": "https://accounts.google.com/oauth/..."}` 23 | 24 | - `POST /api/v1/auth/oauth/callback` - Exchange authorization code for tokens 25 | - Request: `{"provider": "google", "code": "auth_code_here", "redirect_url": "..."}` 26 | - Response: `{"user": {...}, "token": {"access_token": "...", "refresh_token": "..."}}` 27 | 28 | ## Testing 29 | 30 | 1. **Start your FastAPI server**: 31 | 32 | ```bash 33 | cd /path/to/your/project 34 | python -m uvicorn src.main:app --reload --port 8000 35 | ``` 36 | 37 | 2. **Serve the test page**: 38 | 39 | ```bash 40 | cd src/tests/oauth 41 | python -m http.server 3000 42 | ``` 43 | 44 | 3. **Open browser**: http://localhost:3000/oauth_test.html 45 | 46 | 4. **Test OAuth flow**: 47 | - Click "🚀 Start Google OAuth" 48 | - Authenticate with Google 49 | - Get redirected back with success tokens in URL 50 | 51 | ### Success Response 52 | 53 | After successful OAuth, the test page will display tokens received from your backend: 54 | 55 | ```json 56 | { 57 | "user": { 58 | "id": "user_id", 59 | "email": "user@example.com", 60 | "full_name": "User Name" 61 | }, 62 | "token": { 63 | "access_token": "jwt_token_here", 64 | "refresh_token": "refresh_token_here", 65 | "token_type": "bearer" 66 | } 67 | } 68 | ``` 69 | 70 | ## Configuration Required 71 | 72 | ### Google Cloud Console 73 | 74 | 1. Enable Google+ API 75 | 2. Create OAuth 2.0 credentials 76 | 3. Add authorized redirect URI: `https://your-project.supabase.co/auth/v1/callback` 77 | 78 | ### Supabase Dashboard 79 | 80 | 1. Go to Authentication > Providers > Google 81 | 2. Enable Google provider 82 | 3. Add your Google OAuth Client ID and Secret 83 | 84 | ### Environment Variables 85 | 86 | No Google OAuth environment variables needed in your app - Supabase handles the credentials! 87 | 88 | ## Troubleshooting 89 | 90 | **Not getting tokens?** 91 | 92 | - Check Supabase provider configuration in dashboard 93 | - Verify Google Cloud Console redirect URI: `https://your-project.supabase.co/auth/v1/callback` 94 | - Check browser network tab for API errors 95 | - Ensure FastAPI server is running on port 8000 96 | 97 | **Getting authorization code but token exchange fails?** 98 | 99 | - Check that the same Supabase client instance is used (singleton pattern) 100 | - Verify the redirect URL matches exactly between login and callback requests 101 | -------------------------------------------------------------------------------- /lore/config.md: -------------------------------------------------------------------------------- 1 | # config.py & supabase_client.py - Context Documentation 2 | 3 | ## Purpose 4 | 5 | This module group handles application configuration management and Supabase client instantiation. The `config.py` file provides centralized settings management with environment variable loading and optional Google Secret Manager integration, while `supabase_client.py` provides a singleton factory pattern for the Supabase client to maintain PKCE state across authentication flows. Together, they form the foundation for secure configuration management and database connectivity. 6 | 7 | ## Usage Summary 8 | 9 | **File Locations**: 10 | 11 | - `src/core/config.py` - Application settings and configuration 12 | - `src/core/supabase_client.py` - Supabase client factory 13 | 14 | **Primary Use Cases**: 15 | 16 | - Loading application settings from environment variables or Google Secret Manager 17 | - Providing centralized configuration access throughout the application 18 | - Creating and maintaining a singleton Supabase client instance 19 | - Preserving PKCE state during OAuth authentication flows 20 | - Managing CORS, logging, and other application-wide settings 21 | 22 | **Key Dependencies**: 23 | 24 | - `supabase.Client`: Supabase client interface for database and auth operations 25 | - `supabase.create_client`: Factory function to create Supabase client instances 26 | - `src.core.config.settings`: Global settings instance used throughout the application 27 | - `pydantic_settings.BaseSettings`: Provides validation and environment variable loading 28 | - `src.core.secrets.load_secrets_from_gsm`: Google Secret Manager integration for secure secret loading 29 | - `src.core.constants`: Application defaults and configuration constants 30 | 31 | ## Key Functions or Classes 32 | 33 | **Classes:** 34 | 35 | - **Settings**: Pydantic settings class that handles environment variable loading, validation, and Google Secret Manager integration 36 | 37 | **Key Functions:** 38 | 39 | - **get_settings()**: Factory function that loads secrets from GSM if enabled and returns configured Settings instance 40 | - **get_supabase_client()**: Singleton factory that creates and reuses a Supabase client instance to preserve PKCE state 41 | - **should_use_testing()**: Helper function to determine if the application is running in testing mode 42 | - **should_use_gsm()**: Helper function (in secrets.py) to determine if Google Secret Manager should be used 43 | 44 | ## Usage Notes 45 | 46 | - The Settings class uses Pydantic for automatic validation and type conversion of environment variables 47 | - Google Secret Manager integration is optional and controlled by the `USE_GSM` environment variable 48 | - The Supabase client is implemented as a singleton to preserve PKCE state during OAuth flows 49 | - All configuration values have sensible defaults defined in the constants module 50 | - The settings instance is created at module import time for global access 51 | - Log level is automatically configured based on the settings 52 | - CORS origins can be configured but default to allowing all origins for development 53 | - Secret loading fails gracefully and falls back to environment variables if GSM is unavailable 54 | 55 | ## Dependencies & Integration 56 | 57 | This configuration system is used throughout the entire application: 58 | 59 | - **Authentication Service**: Uses Supabase client for auth operations and settings for JWT secrets 60 | - **API Endpoints**: Uses settings for CORS configuration and app metadata 61 | - **Secret Management**: Integrates with Google Secret Manager for production secret loading 62 | - **Logging**: Uses settings to configure application-wide log levels 63 | - **Database Operations**: All database access goes through the singleton Supabase client 64 | - **Testing**: Uses testing mode detection for test-specific configuration 65 | 66 | The singleton pattern for the Supabase client is critical for OAuth flows, as PKCE state must be preserved across requests. 67 | 68 | ## Changelog 69 | 70 | ### [2025-01-19] 71 | 72 | - Context documentation completed 73 | - Documented configuration management and Supabase client factory 74 | - Added details about GSM integration and PKCE state preservation 75 | 76 | --- 77 | 78 | _This document is maintained by Cursor. Last updated: 2025-01-19_ 79 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Pre-commit configuration for Supabase Auth API 2 | # See https://pre-commit.com for more information 3 | 4 | repos: 5 | # General file cleanup and validation 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v4.6.0 8 | hooks: 9 | # File formatting and cleanup 10 | - id: trailing-whitespace 11 | exclude: ^src/tests/oauth/.*\.html$ 12 | - id: end-of-file-fixer 13 | exclude: ^src/tests/oauth/.*\.html$ 14 | - id: check-merge-conflict 15 | - id: check-case-conflict 16 | 17 | # File validation 18 | - id: check-yaml 19 | args: ["--unsafe"] 20 | - id: check-toml 21 | - id: check-json 22 | - id: check-xml 23 | 24 | # Python-specific checks 25 | - id: check-ast 26 | files: \.py$ 27 | - id: debug-statements 28 | files: \.py$ 29 | - id: check-docstring-first 30 | files: \.py$ 31 | 32 | # General security and best practices 33 | - id: detect-private-key 34 | - id: check-added-large-files 35 | args: ["--maxkb=1000"] 36 | 37 | # Requirements and dependencies 38 | - id: requirements-txt-fixer 39 | files: requirements.*\.txt$ 40 | 41 | # Ruff - Modern Python linter and formatter (replaces flake8, black, isort) 42 | - repo: https://github.com/astral-sh/ruff-pre-commit 43 | rev: v0.1.15 44 | hooks: 45 | # Linting with auto-fix 46 | - id: ruff 47 | args: [--fix, --exit-non-zero-on-fix] 48 | files: ^src/.*\.py$ 49 | 50 | # Code formatting 51 | - id: ruff-format 52 | files: ^src/.*\.py$ 53 | 54 | # MyPy - Static type checking 55 | - repo: https://github.com/pre-commit/mirrors-mypy 56 | rev: v1.7.1 57 | hooks: 58 | - id: mypy 59 | files: ^src/.*\.py$ 60 | args: [--config-file, pyproject.toml] 61 | additional_dependencies: 62 | - fastapi 63 | - pydantic 64 | - pydantic-settings 65 | - google-cloud-secret-manager 66 | - types-python-jose 67 | - types-requests 68 | 69 | # Prettier - Format JSON, YAML, Markdown 70 | - repo: https://github.com/pre-commit/mirrors-prettier 71 | rev: v3.1.0 72 | hooks: 73 | - id: prettier 74 | files: \.(json|yaml|yml|md)$ 75 | exclude: ^src/tests/oauth/.*\.html$ 76 | 77 | # Additional Python security checks 78 | - repo: https://github.com/PyCQA/bandit 79 | rev: 1.7.5 80 | hooks: 81 | - id: bandit 82 | files: ^src/.*\.py$ 83 | args: [-r, -ll, --exclude, src/tests] 84 | 85 | # Check for common Python mistakes 86 | - repo: https://github.com/pre-commit/pygrep-hooks 87 | rev: v1.10.0 88 | hooks: 89 | - id: python-check-blanket-noqa 90 | files: ^src/.*\.py$ 91 | - id: python-check-blanket-type-ignore 92 | files: ^src/.*\.py$ 93 | - id: python-no-log-warn 94 | files: ^src/.*\.py$ 95 | - id: python-use-type-annotations 96 | files: ^src/.*\.py$ 97 | 98 | # 🏰 Dungeon Master - Complete context tracking workflow 99 | - repo: local 100 | hooks: 101 | # Step 1: Create missing templates 102 | - id: dungeon-master-update 103 | name: "📜 DM: Create templates" 104 | entry: dm 105 | args: [update] 106 | language: system 107 | pass_filenames: false 108 | always_run: true 109 | 110 | # Step 2: Check significant changes 111 | - id: dungeon-master-review 112 | name: "🔮 DM: Review changes" 113 | entry: dm 114 | args: [review] 115 | language: system 116 | pass_filenames: false 117 | always_run: true 118 | 119 | # Step 3: Final validation 120 | - id: dungeon-master-validate 121 | name: "🛡️ DM: Validate docs" 122 | entry: dm 123 | args: [validate] 124 | language: system 125 | pass_filenames: false 126 | always_run: true 127 | 128 | # Global configuration 129 | default_language_version: 130 | python: python3 131 | 132 | exclude: | 133 | (?x)^( 134 | \.env.*| 135 | \.venv/| 136 | __pycache__/| 137 | \.git/| 138 | \.pytest_cache/| 139 | \.mypy_cache/| 140 | \.ruff_cache/| 141 | src/tests/oauth/.*\.html 142 | )$ 143 | -------------------------------------------------------------------------------- /lore/secrets.md: -------------------------------------------------------------------------------- 1 | # secrets.py - Context Documentation 2 | 3 | ## Purpose 4 | 5 | This module provides secure secret management functionality with Google Secret Manager (GSM) integration and graceful fallback to environment variables. It handles the retrieval, caching, and loading of sensitive configuration data like API keys, database credentials, and JWT secrets. The module is designed to work seamlessly in both development (using environment variables) and production (using GSM) environments, providing a unified interface for secret access throughout the application. 6 | 7 | ## Usage Summary 8 | 9 | **File Location**: `src/core/secrets.py` 10 | 11 | **Primary Use Cases**: 12 | 13 | - Retrieving secrets from Google Secret Manager with automatic fallback to environment variables 14 | - Loading critical Supabase secrets (URL, API key, JWT secret) at application startup 15 | - Caching secret values to minimize API calls to GSM 16 | - Determining whether to use GSM or environment variables based on configuration 17 | - Providing secure secret access for authentication and database operations 18 | 19 | **Key Dependencies**: 20 | 21 | - `logging`: Used for tracking secret retrieval operations, errors, and fallback scenarios for security auditing 22 | - `os`: Provides access to environment variables for fallback secret storage and configuration 23 | - `functools.lru_cache`: Implements caching for secret values to reduce GSM API calls and improve performance 24 | - `typing.Optional`: Provides type hints for optional project ID parameter in secret retrieval 25 | - `google.cloud.secretmanager`: Google Cloud client library for accessing Secret Manager API 26 | - `src.core.constants.EnvVars`: Environment variable name constants for consistent configuration 27 | - `src.core.constants.Supabase`: Supabase-specific constants including required secret names and GSM path templates 28 | - `src.core.messages.LogMessages`: Standardized log messages for secret management operations 29 | 30 | ## Key Functions or Classes 31 | 32 | **Key Functions:** 33 | 34 | - **get_secret(secret_name, project_id)**: Retrieves a specific secret from GSM with LRU caching, automatically falls back to environment variables if GSM access fails 35 | - **load_secrets_from_gsm()**: Loads all critical Supabase secrets from GSM into environment variables at application startup, ensuring they're available for the Settings class 36 | - **should_use_gsm()**: Determines whether Google Secret Manager should be used based on the USE_GSM environment variable configuration 37 | 38 | ## Usage Notes 39 | 40 | - The `get_secret` function is decorated with `@lru_cache` to prevent repeated API calls for the same secret 41 | - GSM integration gracefully degrades to environment variables if GSM is unavailable or misconfigured 42 | - All secret access operations are logged for security auditing and debugging purposes 43 | - The module automatically loads required Supabase secrets at startup when GSM is enabled 44 | - Project ID can be provided explicitly or read from the GCP_PROJECT_ID environment variable 45 | - Secret names must match exactly between GSM and the constants defined in the Supabase class 46 | - Empty or missing secrets from GSM trigger warnings but don't crash the application 47 | - The module follows the principle of "secure by default" with comprehensive error handling 48 | 49 | ## Dependencies & Integration 50 | 51 | This module is a critical component of the application's security infrastructure: 52 | 53 | - **Configuration System**: Used by `config.py` to load secrets before Settings initialization 54 | - **Authentication**: Provides JWT secrets and Supabase credentials for auth operations 55 | - **Database Access**: Supplies database connection credentials through Supabase configuration 56 | - **Production Deployment**: Essential for secure secret management in Google Cloud environments 57 | - **Development Workflow**: Allows developers to use environment variables locally while production uses GSM 58 | - **Logging System**: Integrates with the application's logging for security audit trails 59 | 60 | The module is imported and used early in the application lifecycle, typically before any other services are initialized. 61 | 62 | ## Changelog 63 | 64 | ### [2025-01-19] 65 | 66 | - Context documentation completed 67 | - Documented GSM integration and fallback mechanisms 68 | - Added security and caching considerations 69 | 70 | --- 71 | 72 | _This document is maintained by Cursor. Last updated: 2025-01-19_ 73 | -------------------------------------------------------------------------------- /lore/api_setup.md: -------------------------------------------------------------------------------- 1 | # api.py & router.py - Context Documentation 2 | 3 | ## Purpose 4 | 5 | This module group handles the FastAPI application setup and routing configuration for the Supabase Auth API. The `api.py` file creates and configures the main FastAPI application with middleware, CORS settings, and health check endpoints, while `router.py` organizes and includes all API endpoint routers. Together, they form the foundation of the web application, providing the entry point for all HTTP requests and organizing the API structure in a clean, modular way. 6 | 7 | ## Usage Summary 8 | 9 | **File Locations**: 10 | 11 | - `src/api/api.py` - Main FastAPI application setup and configuration 12 | - `src/api/router.py` - API router organization and endpoint inclusion 13 | 14 | **Primary Use Cases**: 15 | 16 | - Creating and configuring the main FastAPI application instance 17 | - Setting up CORS middleware for cross-origin requests 18 | - Organizing API endpoints into logical router groups 19 | - Providing application health check functionality 20 | - Configuring application metadata (title, description, version) 21 | - Including all endpoint routers with appropriate prefixes and tags 22 | 23 | **Key Dependencies**: 24 | 25 | - `fastapi.FastAPI`: Main FastAPI application class for creating the web application 26 | - `fastapi.APIRouter`: Router class for organizing related endpoints 27 | - `fastapi.middleware.cors.CORSMiddleware`: CORS middleware for handling cross-origin requests 28 | - `src.api.router.api_router`: Main API router that includes all endpoint routers 29 | - `src.api.endpoints.auth`: Authentication endpoints router 30 | - `src.core.config.settings`: Application settings for CORS and metadata configuration 31 | 32 | ## Key Functions or Classes 33 | 34 | **Application Objects:** 35 | 36 | - **app**: Main FastAPI application instance configured with metadata and middleware 37 | 38 | **Router Objects:** 39 | 40 | - **api_router**: Main APIRouter that organizes all endpoint routers with appropriate prefixes 41 | 42 | **API Endpoints:** 43 | 44 | - **GET /health**: Simple health check endpoint that returns application status and version 45 | - **All /api/v1/** endpoints\*\*: Versioned API endpoints organized under the main router 46 | 47 | **Middleware Configuration:** 48 | 49 | - **CORSMiddleware**: Configured to handle cross-origin requests with appropriate settings 50 | 51 | ## Usage Notes 52 | 53 | - The FastAPI application is configured with descriptive metadata for automatic documentation 54 | - CORS middleware is configured to allow all origins for development (should be restricted in production) 55 | - All API endpoints are versioned under `/api/v1` prefix for future compatibility 56 | - The health check endpoint is available at the root level for monitoring and load balancer checks 57 | - Router organization follows a modular pattern where each feature has its own router 58 | - Authentication endpoints are grouped under `/auth` prefix with `auth` tags for documentation 59 | - The application uses centralized configuration from the settings module 60 | - Logging is configured at the application level for consistent log formatting 61 | - The router pattern allows for easy addition of new endpoint groups 62 | 63 | ## Dependencies & Integration 64 | 65 | This module group serves as the application foundation and integrates with: 66 | 67 | - **Configuration System**: Uses settings for CORS origins and application metadata 68 | - **Authentication Endpoints**: Includes the auth router for all authentication operations 69 | - **Middleware Stack**: Configures CORS and other middleware for request processing 70 | - **Health Monitoring**: Provides health check endpoint for operational monitoring 71 | - **API Documentation**: FastAPI automatically generates OpenAPI/Swagger documentation 72 | - **Frontend Applications**: Serves as the backend API for client applications 73 | - **Load Balancers**: Health check endpoint supports load balancer health checks 74 | - **Development Tools**: CORS configuration supports local development workflows 75 | 76 | The application serves as the entry point for all HTTP requests and coordinates the entire API ecosystem. 77 | 78 | ## Changelog 79 | 80 | ### [2025-01-19] 81 | 82 | - Context documentation completed 83 | - Documented FastAPI application setup and router organization 84 | - Added details about middleware configuration and health checks 85 | 86 | --- 87 | 88 | _This document is maintained by Cursor. Last updated: 2025-01-19_ 89 | -------------------------------------------------------------------------------- /lore/core_constants.md: -------------------------------------------------------------------------------- 1 | # constants.py & messages.py - Context Documentation 2 | 3 | ## Purpose 4 | 5 | This module group provides centralized constants and user-facing messages for the Supabase Auth API. The `constants.py` file defines application-wide configuration constants, environment variable names, validation rules, and service-specific constants organized into logical classes. The `messages.py` file contains all user-facing text including error messages, success messages, and internal log messages, organized for maintainability and future localization support. Together, they ensure consistent messaging and configuration throughout the application. 6 | 7 | ## Usage Summary 8 | 9 | **File Locations**: 10 | 11 | - `src/core/constants.py` - Application constants and configuration defaults 12 | - `src/core/messages.py` - User-facing and internal messages 13 | 14 | **Primary Use Cases**: 15 | 16 | - Providing centralized configuration constants and defaults 17 | - Defining environment variable names for consistent access 18 | - Setting validation rules and constraints (password length, etc.) 19 | - Storing Supabase-specific constants like metadata field names 20 | - Providing standardized error messages for consistent user experience 21 | - Supplying internal log messages for debugging and audit trails 22 | - Supporting future internationalization through centralized message management 23 | 24 | **Key Dependencies**: 25 | 26 | - No external dependencies - these are standalone modules that provide constants and messages to other parts of the system 27 | 28 | ## Key Functions or Classes 29 | 30 | **Constants Classes:** 31 | 32 | - **EnvVars**: Environment variable names for configuration (SUPABASE_URL, DEBUG, etc.) 33 | - **Defaults**: Default configuration values for app metadata, API paths, CORS, and boolean settings 34 | - **Validation**: Validation rules and constraints like minimum password length requirements 35 | - **Supabase**: Supabase-specific constants including required secrets, metadata field names, and GSM path templates 36 | - **OAuth**: OAuth provider constants for supported authentication providers 37 | 38 | **Message Classes:** 39 | 40 | - **ErrorMessages**: User-facing error messages for authentication failures, invalid tokens, and other error conditions 41 | - **SuccessMessages**: User-facing success messages for completed operations like logout and password reset 42 | - **LogMessages**: Internal log messages for user actions, settings loading, secret management, and JWT validation 43 | 44 | ## Usage Notes 45 | 46 | - All constants are organized into logical classes to prevent naming conflicts and improve discoverability 47 | - Environment variable names are centralized to ensure consistency across the application 48 | - Default values provide sensible fallbacks for all configuration options 49 | - Error messages are designed to be user-friendly while avoiding information disclosure 50 | - Log messages include placeholders for dynamic content using Python string formatting 51 | - The constants support both development and production environments 52 | - Message organization facilitates future localization efforts 53 | - Validation constants ensure consistent rules across different parts of the application 54 | - OAuth constants are designed to be easily extensible for additional providers 55 | 56 | ## Dependencies & Integration 57 | 58 | These modules are foundational and used throughout the entire application: 59 | 60 | - **Configuration System**: Uses constants for defaults and environment variable names 61 | - **Authentication Service**: Uses OAuth constants, validation rules, and all message types 62 | - **API Endpoints**: Uses error and success messages for consistent user communication 63 | - **Secret Management**: Uses Supabase constants for GSM integration and required secrets 64 | - **Settings Management**: Uses environment variable names and default values 65 | - **Logging System**: Uses log messages for consistent internal communication 66 | - **Validation**: Uses validation constants for input validation across the application 67 | 68 | These modules have no dependencies and serve as the foundation for other modules, ensuring consistent configuration and messaging throughout the system. 69 | 70 | ## Changelog 71 | 72 | ### [2025-01-19] 73 | 74 | - Context documentation completed 75 | - Documented constants organization and message management 76 | - Added details about localization support and validation rules 77 | 78 | --- 79 | 80 | _This document is maintained by Cursor. Last updated: 2025-01-19_ 81 | -------------------------------------------------------------------------------- /lore/dependencies.md: -------------------------------------------------------------------------------- 1 | # dependencies.py - Context Documentation 2 | 3 | ## Purpose 4 | 5 | This module provides FastAPI dependency injection functions for authentication and authorization throughout the API. It handles JWT token validation, user authentication, and service instantiation using FastAPI's dependency system. The module serves as the security layer that protects endpoints and provides authenticated user context to route handlers. It implements JWT validation with proper error handling and integrates with the authentication service for consistent security across all protected endpoints. 6 | 7 | ## Usage Summary 8 | 9 | **File Location**: `src/api/dependencies.py` 10 | 11 | **Primary Use Cases**: 12 | 13 | - Validating JWT tokens from Authorization headers in protected endpoints 14 | - Extracting user IDs from validated JWT tokens for authorization 15 | - Providing authenticated user context to route handlers through dependency injection 16 | - Creating AuthService instances for dependency injection in endpoints 17 | - Implementing centralized authentication logic for consistent security 18 | 19 | **Key Dependencies**: 20 | 21 | - `logging`: Used for tracking authentication attempts, JWT validation failures, and security events 22 | - `typing.Any`: Provides type hints for JWT payload which has dynamic structure 23 | - `fastapi.Depends`: FastAPI's dependency injection system for providing authenticated context 24 | - `fastapi.HTTPException`: Used to raise HTTP 401 Unauthorized errors for invalid tokens 25 | - `fastapi.status`: Provides HTTP status code constants for consistent error responses 26 | - `fastapi.security.HTTPAuthorizationCredentials`: Type for Authorization header credentials 27 | - `fastapi.security.HTTPBearer`: Security scheme for extracting Bearer tokens from headers 28 | - `jose.JWTError`: Exception type for JWT validation errors 29 | - `jose.jwt`: JWT library for token decoding and validation 30 | - `src.core.config.settings`: Application settings including JWT secret for token validation 31 | - `src.core.messages.ErrorMessages`: Standardized error messages for authentication failures 32 | - `src.core.messages.LogMessages`: Standardized log messages for security events 33 | - `src.services.auth_service.AuthService`: Authentication service for dependency injection 34 | 35 | ## Key Functions or Classes 36 | 37 | **Key Functions:** 38 | 39 | - **validate_jwt_token(token)**: Core JWT validation function that decodes tokens, verifies signatures, checks expiration, and extracts user ID from the 'sub' claim 40 | - **get_current_user(credentials)**: FastAPI dependency that extracts Bearer token from Authorization header and returns authenticated user ID 41 | - **get_auth_service()**: Factory dependency that creates AuthService instances for injection into route handlers 42 | 43 | **Security Objects:** 44 | 45 | - **security**: HTTPBearer instance configured for extracting Bearer tokens from Authorization headers 46 | 47 | ## Usage Notes 48 | 49 | - JWT validation includes signature verification, expiration checking, and audience validation 50 | - The 'sub' claim in JWT tokens is used as the user identifier throughout the system 51 | - All JWT validation errors are logged for security monitoring and debugging 52 | - The HTTPBearer security scheme automatically extracts tokens from 'Authorization: Bearer ' headers 53 | - Dependencies are designed to be used with FastAPI's `Depends()` function in route definitions 54 | - Authentication failures result in HTTP 401 Unauthorized responses with descriptive error messages 55 | - The module provides centralized authentication logic to ensure consistent security across all endpoints 56 | - JWT audience is validated against 'authenticated' to ensure tokens are intended for this service 57 | - User ID extraction is type-safe and handles missing 'sub' claims gracefully 58 | 59 | ## Dependencies & Integration 60 | 61 | This module is central to the API's security architecture and integrates with: 62 | 63 | - **API Endpoints**: Used as dependencies in protected route handlers to ensure authentication 64 | - **JWT Token System**: Validates tokens issued by Supabase Auth using the configured JWT secret 65 | - **Authentication Service**: Provides AuthService instances to endpoints for auth operations 66 | - **Error Handling**: Integrates with the application's error handling for consistent responses 67 | - **Logging System**: Provides security audit trails for authentication attempts and failures 68 | - **Configuration System**: Uses JWT secrets from settings for token validation 69 | - **FastAPI Framework**: Leverages FastAPI's dependency injection for clean separation of concerns 70 | 71 | The module is imported by route handlers that require authentication and serves as the gateway for all protected API operations. 72 | 73 | ## Changelog 74 | 75 | ### [2025-01-19] 76 | 77 | - Context documentation completed 78 | - Documented JWT validation and dependency injection patterns 79 | - Added security considerations and integration details 80 | 81 | --- 82 | 83 | _This document is maintained by Cursor. Last updated: 2025-01-19_ 84 | -------------------------------------------------------------------------------- /lore/auth_models.md: -------------------------------------------------------------------------------- 1 | # auth.py - Context Documentation 2 | 3 | ## Purpose 4 | 5 | This module defines comprehensive Pydantic data models for authentication operations in the Supabase Auth API. It provides request/response schemas for user registration, login, OAuth flows, password reset, and token management. The models ensure type safety, automatic validation, and consistent data structures throughout the authentication system. They serve as the contract between the API endpoints and the authentication service, providing clear documentation of expected data formats and validation rules. 6 | 7 | ## Usage Summary 8 | 9 | **File Location**: `src/models/auth.py` 10 | 11 | **Primary Use Cases**: 12 | 13 | - Validating user input for registration, login, and password reset requests 14 | - Defining response schemas for authentication operations and user data 15 | - Providing type safety for OAuth provider selection and flow management 16 | - Structuring token response data with access and refresh tokens 17 | - Ensuring consistent data formats between API endpoints and services 18 | - Supporting automatic API documentation generation through Pydantic integration 19 | 20 | **Key Dependencies**: 21 | 22 | - `datetime.datetime`: Used for timestamp fields like user creation dates in response models 23 | - `enum.Enum`: Provides type-safe enumeration for OAuth provider selection 24 | - `pydantic.BaseModel`: Base class for all data models providing validation and serialization 25 | - `pydantic.EmailStr`: Specialized string type for email validation with proper format checking 26 | - `pydantic.Field`: Provides field-level validation constraints like minimum length requirements 27 | - `src.core.constants.OAuth`: OAuth provider constants for enum value validation 28 | - `src.core.constants.Validation`: Validation rules like minimum password length requirements 29 | 30 | ## Key Functions or Classes 31 | 32 | **Base Models:** 33 | 34 | - **UserBase**: Base schema containing common user fields (email) shared across multiple models 35 | 36 | **Request Models:** 37 | 38 | - **UserCreate**: User registration schema with email, password, and full name validation 39 | - **UserLogin**: User login schema with email and password fields 40 | - **PasswordResetRequest**: Password reset request schema with email validation 41 | - **OAuthLoginRequest**: OAuth login initiation request with provider and redirect URL 42 | - **OAuthCallbackRequest**: OAuth callback request with provider, authorization code, and redirect URL 43 | 44 | **Response Models:** 45 | 46 | - **UserResponse**: User data response schema with ID, email, full name, and creation timestamp 47 | - **TokenResponse**: Token response schema with access token, refresh token, and token type 48 | - **AuthResponse**: Complete authentication response combining user data and token information 49 | - **OAuthResponse**: OAuth login response containing the authorization URL 50 | 51 | **Enums:** 52 | 53 | - **OAuthProvider**: Type-safe enumeration of supported OAuth providers (currently Google) 54 | 55 | ## Usage Notes 56 | 57 | - All models inherit from Pydantic BaseModel for automatic validation and serialization 58 | - Email fields use EmailStr for proper email format validation 59 | - Password fields include minimum length validation based on security requirements 60 | - OAuth provider selection is type-safe through enum usage 61 | - Token response includes both access and refresh tokens with default empty values 62 | - User creation timestamps are optional to handle cases where they might not be available 63 | - The models support both traditional email/password and OAuth authentication flows 64 | - Field validation happens automatically when models are instantiated 65 | - Models provide clear separation between request data (input) and response data (output) 66 | - OAuth models support the complete PKCE flow with proper URL and code handling 67 | - All models are designed to work seamlessly with FastAPI's automatic documentation 68 | 69 | ## Dependencies & Integration 70 | 71 | These models are central to the authentication system and integrate with: 72 | 73 | - **API Endpoints**: Used as request/response schemas in FastAPI route definitions 74 | - **Authentication Service**: Validates input data and structures response data 75 | - **FastAPI Framework**: Provides automatic request validation and response serialization 76 | - **Pydantic Validation**: Ensures data integrity and type safety throughout the system 77 | - **OAuth Flows**: Supports both login initiation and callback handling 78 | - **Token Management**: Structures JWT and refresh token responses 79 | - **API Documentation**: Automatically generates OpenAPI/Swagger documentation 80 | - **Frontend Integration**: Provides clear contracts for client applications 81 | 82 | The models serve as the data contract between the API layer and the business logic, ensuring consistent and validated data flow throughout the authentication system. 83 | 84 | ## Changelog 85 | 86 | ### [2025-01-19] 87 | 88 | - Context documentation completed 89 | - Documented all authentication models and their purposes 90 | - Added details about validation rules and OAuth support 91 | 92 | --- 93 | 94 | --- 95 | 96 | _This document is maintained by Cursor. Last updated: 2025-01-19_ 97 | -------------------------------------------------------------------------------- /lore/auth_service.md: -------------------------------------------------------------------------------- 1 | # auth_service.py - Context Documentation 2 | 3 | ## Purpose 4 | 5 | This Python module provides a comprehensive authentication service that handles user registration, login, logout, password reset, and OAuth integration using Supabase Auth. The AuthService class serves as the primary interface for all authentication operations in the system, providing both traditional email/password authentication and OAuth flows (currently supporting Google). It encapsulates Supabase authentication operations and provides standardized error handling and logging. 6 | 7 | ## Usage Summary 8 | 9 | **File Location**: `src/services/auth_service.py` 10 | 11 | **Primary Use Cases**: 12 | 13 | - User registration with email/password authentication 14 | - User login/logout operations 15 | - Password reset functionality 16 | - OAuth authentication flow (Google provider) 17 | - OAuth callback handling 18 | - Building standardized authentication response dictionaries 19 | 20 | **Key Dependencies**: 21 | 22 | - `logging`: Used for tracking authentication events, errors, and user activities for audit and debugging purposes 23 | - `typing.Any`: Provides type hints for Supabase response objects which have dynamic structures 24 | - `src.core.constants.OAuth`: Contains OAuth provider constants (e.g., Google provider identifier) 25 | - `src.core.constants.Supabase`: Contains Supabase-specific constants like user metadata field names 26 | - `src.core.messages.ErrorMessages`: Provides standardized error messages for authentication failures 27 | - `src.core.messages.LogMessages`: Provides standardized log messages for authentication events 28 | - `src.core.supabase_client.get_supabase_client`: Factory function to get the configured Supabase client 29 | - `src.models.auth.UserCreate`: Pydantic model for user registration data validation 30 | - `src.models.auth.UserLogin`: Pydantic model for user login data validation 31 | 32 | ## Key Functions or Classes 33 | 34 | **Classes:** 35 | 36 | - **AuthService**: Main service class that encapsulates all authentication operations using Supabase Auth. Handles user lifecycle operations, OAuth flows, and provides consistent response formatting. 37 | 38 | **Key Functions:** 39 | 40 | - \***\*init**(self)\*\*: Initializes the AuthService with a Supabase client instance obtained from the client factory 41 | - **signup(self, user_data: UserCreate)**: Registers a new user with email, password, and full name, storing user metadata in Supabase 42 | - **login(self, user_data: UserLogin)**: Authenticates existing users with email/password credentials 43 | - **logout(self, token: str)**: Signs out the current user and invalidates their session 44 | - **request_password_reset(self, email: str)**: Initiates password reset flow by sending reset email 45 | - **oauth_login(self, provider: str, redirect_url: str)**: Initiates OAuth login flow using PKCE for secure authentication 46 | - **handle_oauth_callback(self, provider: str, code: str, redirect_url: str)**: Handles OAuth callback and exchanges authorization code for session 47 | - **\_build_auth_dict(self, auth_response)**: Internal helper that standardizes Supabase auth responses into consistent dictionary format 48 | 49 | ## Usage Notes 50 | 51 | - The service handles all Supabase Auth operations asynchronously using async/await patterns 52 | - OAuth implementation uses PKCE (Proof Key for Code Exchange) for enhanced security 53 | - Currently only supports Google OAuth provider, but is designed to be extensible for additional providers 54 | - All operations include comprehensive error handling with standardized error messages 55 | - User metadata (like full_name) is stored in Supabase user_metadata field 56 | - The service provides consistent logging for all authentication events for audit trails 57 | - Response format is standardized with user and session objects for consistent API responses 58 | - Errors are wrapped in ValueError with descriptive messages for proper error propagation 59 | - The service gracefully handles edge cases like missing user metadata or malformed responses 60 | 61 | ## Dependencies & Integration 62 | 63 | This service is a core component of the authentication system and integrates with: 64 | 65 | - **Supabase Auth**: Primary authentication provider for user management and session handling 66 | - **API Endpoints**: Used by authentication routes in `src/api/endpoints/` for handling HTTP requests 67 | - **Models**: Validates input using Pydantic models from `src/models/auth.py` 68 | - **Core Configuration**: Relies on constants and configuration from `src/core/` 69 | - **Middleware**: Authentication responses are used by authorization middleware for protected routes 70 | - **Client Factory**: Uses the Supabase client factory for consistent client configuration 71 | 72 | The service is imported and used by FastAPI route handlers to process authentication requests and is essential for the JWT-based authorization system throughout the application. 73 | 74 | ## Changelog 75 | 76 | ### [2025-01-19] 77 | 78 | - Context documentation completed 79 | - Documented all authentication methods and OAuth flow 80 | - Added comprehensive usage notes and integration details 81 | 82 | --- 83 | 84 | _This document is maintained by Cursor. Last updated: 2025-01-19_ 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be added to the global gitignore or merged into this project gitignore. For a PyCharm 158 | # project, it is generally recommended to exclude files and directories that are 159 | # automatically generated by the IDE. 160 | .idea/ 161 | 162 | # VSCode 163 | .vscode/ 164 | *.code-workspace 165 | 166 | # Sublime Text 167 | *.sublime-project 168 | *.sublime-workspace 169 | 170 | # Vim 171 | *.swp 172 | *.swo 173 | *~ 174 | 175 | # Emacs 176 | *~ 177 | \#*\# 178 | /.emacs.desktop 179 | /.emacs.desktop.lock 180 | *.elc 181 | auto-save-list 182 | tramp 183 | .\#* 184 | 185 | # OS generated files 186 | .DS_Store 187 | .DS_Store? 188 | ._* 189 | .Spotlight-V100 190 | .Trashes 191 | ehthumbs.db 192 | Thumbs.db 193 | 194 | # Database files 195 | *.db 196 | *.sqlite 197 | *.sqlite3 198 | *.db-journal 199 | 200 | # FastAPI specific 201 | *.pid 202 | 203 | # Alembic 204 | alembic.ini 205 | 206 | # Local configuration files 207 | config.ini 208 | settings.ini 209 | local_settings.py 210 | 211 | # Logs 212 | logs/ 213 | *.log 214 | *.log.* 215 | 216 | # Temporary files 217 | temp/ 218 | tmp/ 219 | *.tmp 220 | *.temp 221 | 222 | # Backup files 223 | *.bak 224 | *.backup 225 | *.orig 226 | 227 | # Node.js (if using any frontend tools) 228 | node_modules/ 229 | npm-debug.log* 230 | yarn-debug.log* 231 | yarn-error.log* 232 | 233 | # Docker 234 | .dockerignore 235 | docker-compose.override.yml 236 | 237 | # Terraform 238 | *.tfstate 239 | *.tfstate.* 240 | .terraform/ 241 | 242 | # AWS 243 | .aws/ 244 | 245 | # Secrets and sensitive files 246 | secrets/ 247 | *.pem 248 | *.key 249 | *.crt 250 | *.csr 251 | .env.local 252 | .env.development.local 253 | .env.test.local 254 | .env.production.local 255 | 256 | # API documentation build files 257 | docs/build/ 258 | docs/dist/ 259 | 260 | # Coverage reports 261 | htmlcov/ 262 | .coverage 263 | coverage.xml 264 | *.cover 265 | 266 | # Profiling 267 | .prof 268 | 269 | # Local development 270 | dev/ 271 | local/ 272 | 273 | # Cache directories 274 | .cache/ 275 | cache/ 276 | .ruff_cache/ 277 | lore_cache.json 278 | -------------------------------------------------------------------------------- /lore/auth_endpoints.md: -------------------------------------------------------------------------------- 1 | # auth.py - Context Documentation 2 | 3 | ## Purpose 4 | 5 | This module defines the complete set of authentication API endpoints for the Supabase Auth API. It provides RESTful endpoints for user registration, login, logout, password reset, OAuth flows, and session validation. The module handles HTTP request/response processing, error handling, and data formatting while delegating business logic to the authentication service. It serves as the primary interface between client applications and the authentication system, providing comprehensive auth functionality through well-designed REST endpoints. 6 | 7 | ## Usage Summary 8 | 9 | **File Location**: `src/api/endpoints/auth.py` 10 | 11 | **Primary Use Cases**: 12 | 13 | - Handling user registration and login requests with proper validation 14 | - Managing OAuth authentication flows including Google OAuth with PKCE 15 | - Processing logout requests and session invalidation 16 | - Handling password reset requests and email notifications 17 | - Validating active user sessions and JWT tokens 18 | - Converting service responses to standardized API response formats 19 | - Providing comprehensive error handling with appropriate HTTP status codes 20 | 21 | **Key Dependencies**: 22 | 23 | - `logging`: Used for tracking authentication events, errors, and security-related activities 24 | - `typing.Any`: Provides type hints for dynamic response data from Supabase 25 | - `fastapi.APIRouter`: Creates the authentication router for organizing related endpoints 26 | - `fastapi.Depends`: Enables dependency injection for services and authentication 27 | - `fastapi.HTTPException`: Used for raising HTTP errors with appropriate status codes 28 | - `fastapi.status`: Provides HTTP status code constants for consistent responses 29 | - `fastapi.security.HTTPAuthorizationCredentials`: Type for Authorization header handling 30 | - `src.api.dependencies`: Authentication dependencies and service factories 31 | - `src.core.constants.Supabase`: Supabase-specific constants for metadata handling 32 | - `src.core.messages`: Error and success messages for consistent user communication 33 | - `src.models.auth`: Pydantic models for request/response validation and serialization 34 | - `src.services.auth_service.AuthService`: Business logic service for authentication operations 35 | 36 | ## Key Functions or Classes 37 | 38 | **Router Object:** 39 | 40 | - **router**: FastAPI APIRouter instance that organizes all authentication endpoints 41 | 42 | **Helper Functions:** 43 | 44 | - **handle_auth_error(e)**: Converts authentication service exceptions to appropriate HTTP exceptions with proper status codes 45 | - **format_auth_response(result)**: Standardizes Supabase auth responses into consistent API response format with user and token data 46 | 47 | **API Endpoints:** 48 | 49 | - **POST /signup**: User registration endpoint with email, password, and full name validation 50 | - **POST /login**: User authentication endpoint with email/password credentials 51 | - **POST /logout**: Session termination endpoint that invalidates user tokens 52 | - **POST /reset-password**: Password reset initiation endpoint that sends reset emails 53 | - **GET /session-check**: Session validation endpoint for checking token validity 54 | - **POST /oauth/login**: OAuth flow initiation endpoint for Google authentication 55 | - **POST /oauth/callback**: OAuth callback endpoint for handling authorization code exchange 56 | 57 | ## Usage Notes 58 | 59 | - All endpoints use Pydantic models for automatic request validation and response serialization 60 | - Error handling is centralized through the `handle_auth_error` helper function 61 | - OAuth endpoints support PKCE flow for enhanced security in public clients 62 | - Response formatting is standardized through the `format_auth_response` helper 63 | - Login endpoint returns 401 Unauthorized for invalid credentials to prevent user enumeration 64 | - Session check endpoint requires valid JWT token and returns user ID for authorization 65 | - Password reset endpoint always returns success to prevent email enumeration attacks 66 | - OAuth flow preserves state through Supabase's built-in PKCE implementation 67 | - All endpoints include comprehensive logging for security auditing and debugging 68 | - Error responses include descriptive messages while avoiding information disclosure 69 | - Token responses include both access and refresh tokens for complete session management 70 | 71 | ## Dependencies & Integration 72 | 73 | This module serves as the primary API interface and integrates with: 74 | 75 | - **Authentication Service**: Delegates all business logic to the AuthService for separation of concerns 76 | - **Dependency Injection**: Uses FastAPI dependencies for service instantiation and authentication 77 | - **Pydantic Models**: Validates all request/response data using the auth models 78 | - **Error Handling**: Provides consistent error responses across all authentication operations 79 | - **OAuth Integration**: Implements complete OAuth flow with Supabase's PKCE support 80 | - **Session Management**: Handles JWT token validation and user session lifecycle 81 | - **API Router**: Integrated into the main API router with '/auth' prefix and 'auth' tags 82 | - **Frontend Applications**: Provides the complete authentication API for client applications 83 | - **Security Middleware**: Works with authentication dependencies for protected endpoints 84 | 85 | The module is included in the main API router and serves as the entry point for all authentication-related operations in the system. 86 | 87 | ## Changelog 88 | 89 | ### [2025-01-19] 90 | 91 | - Context documentation completed 92 | - Documented all authentication endpoints and OAuth flows 93 | - Added comprehensive error handling and security considerations 94 | 95 | --- 96 | 97 | _This document is maintained by Cursor. Last updated: 2025-01-19_ 98 | -------------------------------------------------------------------------------- /src/services/auth_service.py: -------------------------------------------------------------------------------- 1 | # @track_context("auth_service.md") 2 | 3 | import logging 4 | from typing import Any 5 | 6 | from src.core.constants import OAuth, Supabase 7 | from src.core.messages import ErrorMessages, LogMessages 8 | from src.core.supabase_client import get_supabase_client 9 | from src.models.auth import UserCreate, UserLogin 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class AuthService: 15 | """Service for authentication operations""" 16 | 17 | def __init__(self) -> None: 18 | self.client = get_supabase_client() 19 | 20 | async def signup(self, user_data: UserCreate) -> dict[str, Any]: 21 | """Register a new user in Supabase Auth""" 22 | try: 23 | auth_response = self.client.auth.sign_up( 24 | { 25 | "email": user_data.email, 26 | "password": user_data.password, 27 | "options": { 28 | "data": { 29 | Supabase.FULL_NAME_FIELD: user_data.full_name, 30 | } 31 | }, 32 | } 33 | ) 34 | 35 | if not hasattr(auth_response, "user") or not auth_response.user: 36 | raise ValueError( 37 | f"{ErrorMessages.REGISTRATION_FAILED}: Could not create user" 38 | ) 39 | 40 | logger.info(LogMessages.USER_CREATED.format(user_id=auth_response.user.id)) 41 | 42 | return self._build_auth_dict(auth_response) 43 | 44 | except Exception as e: 45 | logger.error(f"Signup error: {e!s}") 46 | raise ValueError(f"{ErrorMessages.REGISTRATION_FAILED}: {e!s}") from e 47 | 48 | async def login(self, user_data: UserLogin) -> dict[str, Any]: 49 | """Authenticate a user with email and password""" 50 | try: 51 | auth_response = self.client.auth.sign_in_with_password( 52 | { 53 | "email": user_data.email, 54 | "password": user_data.password, 55 | } 56 | ) 57 | 58 | if ( 59 | not hasattr(auth_response, "user") 60 | or not auth_response.user 61 | or not auth_response.session 62 | ): 63 | raise ValueError( 64 | f"{ErrorMessages.AUTHENTICATION_FAILED}: Invalid credentials" 65 | ) 66 | 67 | logger.info( 68 | LogMessages.USER_LOGGED_IN.format(user_id=auth_response.user.id) 69 | ) 70 | return self._build_auth_dict(auth_response) 71 | 72 | except Exception as e: 73 | logger.error(f"Login error: {e!s}") 74 | raise ValueError(f"{ErrorMessages.AUTHENTICATION_FAILED}: {e!s}") from e 75 | 76 | async def logout(self, token: str) -> bool: 77 | try: 78 | self.client.auth.sign_out() 79 | logger.info(LogMessages.USER_LOGGED_OUT) 80 | return True 81 | except Exception as e: 82 | logger.error(f"Logout error: {e!s}") 83 | return False 84 | 85 | async def request_password_reset(self, email: str) -> bool: 86 | try: 87 | self.client.auth.reset_password_for_email(email) 88 | return True 89 | except Exception as e: 90 | logger.error(f"Password reset error: {e!s}") 91 | return False 92 | 93 | async def oauth_login(self, provider: str, redirect_url: str) -> dict[str, Any]: 94 | """Initiate OAuth login flow - let Supabase handle PKCE""" 95 | if provider != OAuth.GOOGLE: 96 | raise ValueError(f"Unsupported provider: {provider}") 97 | 98 | # Let Supabase handle PKCE internally - just pass the redirect URL 99 | auth_response = self.client.auth.sign_in_with_oauth( 100 | {"provider": "google", "options": {"redirect_to": redirect_url}} 101 | ) 102 | 103 | return {"auth_url": auth_response.url} 104 | 105 | async def handle_oauth_callback( 106 | self, provider: str, code: str, redirect_url: str 107 | ) -> dict[str, Any]: 108 | """Handle OAuth callback - let Supabase handle PKCE code exchange""" 109 | if provider != OAuth.GOOGLE: 110 | raise ValueError(f"Unsupported provider: {provider}") 111 | 112 | try: 113 | # Pass proper CodeExchangeParams format - Supabase will get code_verifier from storage 114 | code_exchange_params = {"auth_code": code, "redirect_to": redirect_url} 115 | 116 | auth_response = self.client.auth.exchange_code_for_session( 117 | code_exchange_params 118 | ) 119 | 120 | if not auth_response.user or not auth_response.session: 121 | raise ValueError("Failed to exchange code for session") 122 | 123 | logger.info( 124 | LogMessages.USER_LOGGED_IN.format(user_id=auth_response.user.id) 125 | ) 126 | return self._build_auth_dict(auth_response) 127 | 128 | except Exception as e: 129 | logger.error(f"OAuth callback error: {e!s}") 130 | raise ValueError(f"OAuth authentication failed: {e!s}") from e 131 | 132 | def _build_auth_dict(self, auth_response: Any) -> dict[str, Any]: 133 | """Build auth dict in format expected by format_auth_response helper""" 134 | user_metadata = auth_response.user.user_metadata 135 | if isinstance(user_metadata, str): 136 | logger.warning(f"User metadata is string instead of dict: {user_metadata}") 137 | user_metadata = {} 138 | elif user_metadata is None: 139 | user_metadata = {} 140 | 141 | full_name = "" 142 | if isinstance(user_metadata, dict): 143 | full_name = user_metadata.get(Supabase.FULL_NAME_FIELD, "") 144 | 145 | user_dict = { 146 | "id": auth_response.user.id, 147 | "email": auth_response.user.email, 148 | "user_metadata": user_metadata, 149 | "full_name": full_name, 150 | "created_at": auth_response.user.created_at, 151 | } 152 | 153 | session_dict = {} 154 | if auth_response.session: 155 | session_obj = auth_response.session 156 | access_token = getattr(session_obj, "access_token", None) 157 | refresh_token = getattr(session_obj, "refresh_token", None) 158 | 159 | if access_token: 160 | session_dict = { 161 | "access_token": access_token, 162 | "refresh_token": refresh_token or "", 163 | } 164 | 165 | return { 166 | "user": user_dict, 167 | "session": session_dict, 168 | } 169 | -------------------------------------------------------------------------------- /src/api/endpoints/auth.py: -------------------------------------------------------------------------------- 1 | # @track_context("auth_endpoints.md") 2 | 3 | import logging 4 | from typing import Any 5 | 6 | from fastapi import APIRouter, Depends, HTTPException, status 7 | from fastapi.security import HTTPAuthorizationCredentials 8 | 9 | from src.api.dependencies import get_auth_service, get_current_user, security 10 | from src.core.constants import Supabase 11 | from src.core.messages import ErrorMessages, SuccessMessages 12 | from src.models.auth import ( 13 | AuthResponse, 14 | OAuthCallbackRequest, 15 | OAuthLoginRequest, 16 | OAuthResponse, 17 | PasswordResetRequest, 18 | TokenResponse, 19 | UserCreate, 20 | UserLogin, 21 | UserResponse, 22 | ) 23 | from src.services.auth_service import AuthService 24 | 25 | router = APIRouter() 26 | logger = logging.getLogger(__name__) 27 | 28 | 29 | def handle_auth_error(e: Exception) -> HTTPException: 30 | """ 31 | Convert auth errors to appropriate HTTP exceptions 32 | 33 | Args: 34 | e: Exception from auth operation 35 | 36 | Returns: 37 | HTTPException with appropriate status code and message 38 | """ 39 | if isinstance(e, ValueError): 40 | return HTTPException( 41 | status_code=status.HTTP_400_BAD_REQUEST, 42 | detail=str(e), 43 | ) 44 | 45 | return HTTPException( 46 | status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 47 | detail=f"Authentication error: {e!s}", 48 | ) 49 | 50 | 51 | def format_auth_response(result: dict[str, Any]) -> AuthResponse: 52 | """ 53 | Format Supabase auth result into standardized response 54 | 55 | Args: 56 | result: Raw result from Supabase auth operation 57 | 58 | Returns: 59 | Formatted auth response with user and token data 60 | """ 61 | # Use pre-extracted full_name if available, otherwise extract safely 62 | full_name = result["user"].get("full_name", "") 63 | if not full_name: 64 | # Fallback: safely handle user_metadata which might be string or dict for OAuth users 65 | user_metadata = result["user"]["user_metadata"] 66 | if isinstance(user_metadata, dict): 67 | full_name = user_metadata.get(Supabase.FULL_NAME_FIELD, "") 68 | 69 | user_response = UserResponse( 70 | id=result["user"]["id"], 71 | email=result["user"]["email"], 72 | full_name=full_name, 73 | created_at=result["user"]["created_at"], 74 | ) 75 | 76 | # Handle optional session data 77 | token_response = TokenResponse( 78 | access_token="", 79 | refresh_token="", 80 | token_type="bearer", 81 | ) 82 | 83 | # Safely handle session data which might be dict or other type 84 | session_data = result.get("session") 85 | if ( 86 | session_data 87 | and isinstance(session_data, dict) 88 | and session_data.get("access_token") 89 | ): 90 | token_response = TokenResponse( 91 | access_token=session_data["access_token"], 92 | refresh_token=session_data["refresh_token"], 93 | token_type="bearer", 94 | ) 95 | 96 | return AuthResponse(user=user_response, token=token_response) 97 | 98 | 99 | @router.post( 100 | "/signup", response_model=AuthResponse, status_code=status.HTTP_201_CREATED 101 | ) 102 | async def signup( 103 | user_data: UserCreate, 104 | auth_service: AuthService = Depends(get_auth_service), 105 | ) -> AuthResponse: 106 | """Register a new user account""" 107 | try: 108 | result = await auth_service.signup(user_data) 109 | return format_auth_response(result) 110 | except Exception as e: 111 | raise handle_auth_error(e) from e 112 | 113 | 114 | @router.post("/login", response_model=AuthResponse) 115 | async def login( 116 | user_data: UserLogin, 117 | auth_service: AuthService = Depends(get_auth_service), 118 | ) -> AuthResponse: 119 | """Log in with email and password""" 120 | try: 121 | result = await auth_service.login(user_data) 122 | return format_auth_response(result) 123 | except ValueError: 124 | raise HTTPException( 125 | status_code=status.HTTP_401_UNAUTHORIZED, 126 | detail=ErrorMessages.INVALID_CREDENTIALS, 127 | ) from None 128 | except Exception as e: 129 | raise handle_auth_error(e) from e 130 | 131 | 132 | @router.post("/logout", status_code=status.HTTP_200_OK) 133 | async def logout( 134 | token: HTTPAuthorizationCredentials = Depends(security), 135 | auth_service: AuthService = Depends(get_auth_service), 136 | ) -> dict[str, str]: 137 | """Log out the current user""" 138 | try: 139 | if not token.credentials: 140 | raise HTTPException( 141 | status_code=status.HTTP_401_UNAUTHORIZED, 142 | detail=ErrorMessages.INVALID_TOKEN, 143 | ) 144 | 145 | success = await auth_service.logout(token.credentials) 146 | if not success: 147 | raise HTTPException( 148 | status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 149 | detail=ErrorMessages.LOGOUT_FAILED, 150 | ) 151 | 152 | return {"message": SuccessMessages.LOGOUT_SUCCESS} 153 | except Exception as e: 154 | raise handle_auth_error(e) from e 155 | 156 | 157 | @router.post("/reset-password", status_code=status.HTTP_200_OK) 158 | async def reset_password( 159 | request: PasswordResetRequest, 160 | auth_service: AuthService = Depends(get_auth_service), 161 | ) -> dict[str, str]: 162 | """Request a password reset""" 163 | try: 164 | await auth_service.request_password_reset(request.email) 165 | return {"message": SuccessMessages.PASSWORD_RESET_SENT} 166 | except Exception as e: 167 | raise handle_auth_error(e) from e 168 | 169 | 170 | @router.get("/session-check", status_code=status.HTTP_200_OK) 171 | async def session_check( 172 | current_user: str = Depends(get_current_user), 173 | ) -> dict[str, Any]: 174 | """Check if the current session is valid""" 175 | return {"valid": True, "user_id": current_user} 176 | 177 | 178 | @router.post("/oauth/login", response_model=OAuthResponse) 179 | async def oauth_login( 180 | request: OAuthLoginRequest, 181 | auth_service: AuthService = Depends(get_auth_service), 182 | ) -> OAuthResponse: 183 | """Initiate Google OAuth login flow""" 184 | try: 185 | result = await auth_service.oauth_login(request.provider, request.redirect_url) 186 | return OAuthResponse(auth_url=result["auth_url"]) 187 | except ValueError as e: 188 | raise HTTPException( 189 | status_code=status.HTTP_400_BAD_REQUEST, 190 | detail=str(e), 191 | ) from e 192 | except Exception as e: 193 | raise handle_auth_error(e) from e 194 | 195 | 196 | @router.post("/oauth/callback", response_model=AuthResponse) 197 | async def oauth_callback( 198 | request: OAuthCallbackRequest, 199 | auth_service: AuthService = Depends(get_auth_service), 200 | ) -> AuthResponse: 201 | """Handle Google OAuth callback and exchange code for tokens""" 202 | try: 203 | result = await auth_service.handle_oauth_callback( 204 | request.provider, request.code, request.redirect_url 205 | ) 206 | return format_auth_response(result) 207 | except ValueError as e: 208 | raise HTTPException( 209 | status_code=status.HTTP_400_BAD_REQUEST, 210 | detail=str(e), 211 | ) from e 212 | except Exception as e: 213 | raise handle_auth_error(e) from e 214 | -------------------------------------------------------------------------------- /src/tests/oauth/oauth_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OAuth Test Page 7 | 77 | 78 | 79 |
80 |

🔐 OAuth Test Page

81 |

Test your FastAPI Google OAuth implementation with Supabase backend

82 | 83 | 84 | 90 | 91 | 92 |
93 |

Step 1: Initiate OAuth Login

94 |

95 | Click the button to start the Google OAuth flow through your backend: 96 |

97 | 100 | 101 |
102 | 103 | 104 |
105 |

Step 2: OAuth Result

106 | 111 |
112 | 113 | 114 |
115 |

🔧 Debug Info

116 |

117 | API Base URL: 118 | http://localhost:8000 119 |

120 |

Current URL:

121 |

122 | OAuth Status: Ready 123 |

124 | 125 |
126 |
127 | 128 | 322 | 323 | 324 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Supabase Auth API 2 | 3 | A production-ready authentication API scaffolding template using FastAPI and Supabase with Google OAuth support and Google Secret Manager integration. 4 | 5 | ## Features 6 | 7 | - 🔐 **Email/Password Authentication** - Complete signup, login, logout, password reset 8 | - 🌟 **Google OAuth** - Social login with Google authentication (PKCE flow) 9 | - 🛡️ **JWT Token Validation** - Production-level security with proper token verification 10 | - 🌐 **Supabase Integration** - Direct integration with Supabase Auth 11 | - 🔑 **Google Secret Manager** - Production secret management support 12 | - ⚡ **FastAPI** - Modern, fast Python web framework with async support 13 | - 📝 **Auto Documentation** - Interactive API docs at `/docs` 14 | - 🔧 **Testing Tools** - OAuth testing interface included 15 | 16 | ## Architecture Overview 17 | 18 | This API follows a clean architecture pattern: 19 | 20 | - **FastAPI** handles HTTP requests and responses 21 | - **Supabase** manages authentication, user sessions, and OAuth flows 22 | - **Google Secret Manager** securely stores production credentials 23 | - **Pydantic** validates all request/response data 24 | - **PKCE OAuth Flow** ensures secure Google authentication 25 | 26 | ## Complete Setup Guide 27 | 28 | ### Prerequisites 29 | 30 | - Python 3.10+ installed 31 | - A Supabase account 32 | - A Google Cloud Platform account (for OAuth and optional GSM) 33 | - Basic knowledge of environment variables 34 | 35 | ### 1. Clone and Install 36 | 37 | ```bash 38 | # Clone the repository 39 | git clone 40 | cd supabase-api-scaffolding-template 41 | 42 | # Create virtual environment 43 | python -m venv .venv 44 | source .venv/bin/activate # On Windows: .venv\Scripts\activate 45 | 46 | # Install dependencies 47 | pip install -r requirements.txt 48 | ``` 49 | 50 | ### 2. Supabase Project Setup 51 | 52 | #### Step 1: Create Supabase Project 53 | 54 | 1. Go to [supabase.com](https://supabase.com) and sign up/login 55 | 2. Click "New Project" 56 | 3. Choose your organization and region 57 | 4. Set a strong database password (save this!) 58 | 5. Wait for project creation (2-3 minutes) 59 | 60 | #### Step 2: Configure Authentication Settings 61 | 62 | 1. In your Supabase dashboard, go to **Authentication → Settings** 63 | 2. Configure the following: 64 | 65 | **Email Authentication:** 66 | 67 | - ✅ Enable email confirmations (recommended) 68 | - ✅ Enable password recovery 69 | - Set "Confirm email" to your preference (recommended: enabled) 70 | 71 | **Security Settings:** 72 | 73 | - Set JWT expiry to `3600` (1 hour) or your preference 74 | - Enable Row Level Security (RLS) on auth tables 75 | 76 | **URL Configuration:** 77 | 78 | - Set Site URL to your production domain (e.g., `https://yourdomain.com`) 79 | - Add Redirect URLs for development: `http://localhost:3000`, `http://localhost:8000` 80 | 81 | #### Step 3: Get Supabase Credentials 82 | 83 | 1. Go to **Settings → API** 84 | 2. Copy the following values (you'll need these for your `.env` file): 85 | 86 | ``` 87 | Project URL: https://your-project-ref.supabase.co 88 | anon/public key: eyJhbGc... (starts with eyJ) 89 | JWT Secret: your-jwt-secret-here (used for token verification) 90 | ``` 91 | 92 | ### 3. Google Cloud Platform Setup (OAuth) 93 | 94 | #### Step 1: Create GCP Project 95 | 96 | 1. Go to [Google Cloud Console](https://console.cloud.google.com) 97 | 2. Create a new project or select existing one 98 | 3. Note your Project ID (you'll need this for GSM) 99 | 100 | #### Step 2: Enable Required APIs 101 | 102 | ```bash 103 | # Using gcloud CLI (recommended) 104 | gcloud services enable secretmanager.googleapis.com 105 | gcloud services enable oauth2.googleapis.com 106 | 107 | # Or enable manually in console: 108 | # Go to APIs & Services → Library → Search for "Secret Manager API" → Enable 109 | # Search for "Google+ API" → Enable (legacy, but required for OAuth) 110 | ``` 111 | 112 | #### Step 3: Create OAuth 2.0 Credentials 113 | 114 | 1. Go to **APIs & Services → Credentials** 115 | 2. Click **+ CREATE CREDENTIALS → OAuth 2.0 Client IDs** 116 | 3. Set Application type to **Web application** 117 | 4. Name it something like "Supabase Auth API" 118 | 119 | **Important URLs to configure:** 120 | 121 | ``` 122 | Authorized JavaScript origins: 123 | - https://your-project-ref.supabase.co (replace with your actual Supabase URL) 124 | - http://localhost:3000 (for testing) 125 | 126 | Authorized redirect URIs: 127 | - https://your-project-ref.supabase.co/auth/v1/callback (CRITICAL - must match exactly) 128 | ``` 129 | 130 | 5. Save and copy your **Client ID** and **Client Secret** 131 | 132 | #### Step 4: Configure Google OAuth in Supabase 133 | 134 | 1. In Supabase dashboard, go to **Authentication → Providers** 135 | 2. Find **Google** and click to configure 136 | 3. Enable the provider 137 | 4. Enter your Google OAuth **Client ID** and **Client Secret** 138 | 5. Save configuration 139 | 140 | ### 4. Environment Configuration 141 | 142 | Create a `.env` file in the project root with these variables: 143 | 144 | ```env 145 | # ===== SUPABASE CONFIGURATION (REQUIRED) ===== 146 | # Your Supabase project URL - get from Supabase Settings → API 147 | SUPABASE_URL=https://your-project-ref.supabase.co 148 | 149 | # Supabase anon/public key - get from Supabase Settings → API 150 | SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... 151 | 152 | # Supabase JWT secret - get from Supabase Settings → API 153 | # Used for verifying JWT tokens on the server side 154 | SUPABASE_JWT_SECRET=your-jwt-secret-here 155 | 156 | # ===== GOOGLE CLOUD CONFIGURATION (PRODUCTION) ===== 157 | # Set to true in production to use Google Secret Manager for secrets 158 | USE_GSM=false 159 | 160 | # Your GCP Project ID - required if USE_GSM=true 161 | GCP_PROJECT_ID=your-gcp-project-id 162 | 163 | # ===== APPLICATION SETTINGS (OPTIONAL) ===== 164 | # Enable debug logging and error details 165 | DEBUG=false 166 | 167 | # Enable testing mode (affects some behavior) 168 | TESTING=false 169 | 170 | # Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL 171 | LOG_LEVEL=INFO 172 | 173 | # CORS origins - domains allowed to make requests to your API 174 | # Use ["*"] for development, specific domains for production 175 | CORS_ORIGINS=["*"] 176 | ``` 177 | 178 | #### Environment Variables Explained 179 | 180 | | Variable | Purpose | Required | Example | 181 | | --------------------- | ------------------------------------- | ------------------ | ---------------------------- | 182 | | `SUPABASE_URL` | Your Supabase project endpoint | ✅ Yes | `https://abc123.supabase.co` | 183 | | `SUPABASE_KEY` | Public API key for Supabase client | ✅ Yes | `eyJhbGc...` | 184 | | `SUPABASE_JWT_SECRET` | Secret for validating JWT tokens | ✅ Yes | `your-jwt-secret` | 185 | | `USE_GSM` | Enable Google Secret Manager | 🔧 Production | `false` (dev), `true` (prod) | 186 | | `GCP_PROJECT_ID` | Google Cloud project identifier | 🔧 If USE_GSM=true | `my-project-123` | 187 | | `DEBUG` | Enable debug mode and verbose logging | ❌ Optional | `false` | 188 | | `TESTING` | Enable testing mode | ❌ Optional | `false` | 189 | | `LOG_LEVEL` | Logging verbosity level | ❌ Optional | `INFO` | 190 | | `CORS_ORIGINS` | Allowed request origins | ❌ Optional | `["https://myapp.com"]` | 191 | 192 | ### 5. Google Secret Manager Setup (Production) 193 | 194 | For production deployments, you can store sensitive credentials in Google Secret Manager instead of environment variables. 195 | 196 | #### Step 1: Create Secrets in GSM 197 | 198 | ```bash 199 | # Authenticate with Google Cloud 200 | gcloud auth login 201 | gcloud config set project YOUR_GCP_PROJECT_ID 202 | 203 | # Create secrets (replace with your actual values) 204 | echo -n "https://your-project-ref.supabase.co" | gcloud secrets create SUPABASE_URL --data-file=- 205 | echo -n "your_supabase_anon_key" | gcloud secrets create SUPABASE_KEY --data-file=- 206 | echo -n "your_supabase_jwt_secret" | gcloud secrets create SUPABASE_JWT_SECRET --data-file=- 207 | ``` 208 | 209 | #### Step 2: Configure Service Account (Production) 210 | 211 | ```bash 212 | # Create service account 213 | gcloud iam service-accounts create supabase-auth-api 214 | 215 | # Grant Secret Manager access 216 | gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ 217 | --member="serviceAccount:supabase-auth-api@YOUR_PROJECT_ID.iam.gserviceaccount.com" \ 218 | --role="roles/secretmanager.secretAccessor" 219 | 220 | # For Cloud Run/GCE, assign this service account to your instance 221 | ``` 222 | 223 | #### Step 3: Production Environment Variables 224 | 225 | ```env 226 | # Production .env file (minimal) 227 | USE_GSM=true 228 | GCP_PROJECT_ID=your-gcp-project-id 229 | DEBUG=false 230 | LOG_LEVEL=WARNING 231 | CORS_ORIGINS=["https://yourdomain.com"] 232 | ``` 233 | 234 | ### 6. Run and Test 235 | 236 | #### Start the Development Server 237 | 238 | ```bash 239 | # Development with auto-reload 240 | uvicorn src.api.api:app --reload --host 0.0.0.0 --port 8000 241 | 242 | # Production 243 | uvicorn src.api.api:app --host 0.0.0.0 --port 8000 --workers 4 244 | ``` 245 | 246 | #### Access the API 247 | 248 | - **API Base URL**: http://localhost:8000 249 | - **Interactive Docs**: http://localhost:8000/docs 250 | - **Health Check**: http://localhost:8000/health 251 | 252 | #### Test OAuth Flow 253 | 254 | 1. Navigate to `src/tests/oauth/oauth_test.html` 255 | 2. Serve it locally: 256 | ```bash 257 | cd src/tests/oauth 258 | python -m http.server 3000 259 | ``` 260 | 3. Open http://localhost:3000/oauth_test.html 261 | 4. Click "Start Google OAuth" to test the flow 262 | 263 | ## API Endpoints Reference 264 | 265 | ### Authentication Endpoints 266 | 267 | | Method | Endpoint | Description | Request Body | 268 | | ------ | ----------------------------- | ------------------------- | ------------------------------ | 269 | | `POST` | `/api/v1/auth/signup` | Register new user | `{email, password, full_name}` | 270 | | `POST` | `/api/v1/auth/login` | Login with email/password | `{email, password}` | 271 | | `POST` | `/api/v1/auth/logout` | Logout current user | Bearer token required | 272 | | `POST` | `/api/v1/auth/reset-password` | Request password reset | `{email}` | 273 | | `GET` | `/api/v1/auth/session-check` | Validate current session | Bearer token required | 274 | 275 | ### OAuth Endpoints 276 | 277 | | Method | Endpoint | Description | Request Body | 278 | | ------ | ----------------------------- | --------------------- | ------------------------------------ | 279 | | `POST` | `/api/v1/auth/oauth/login` | Initiate Google OAuth | `{provider: "google", redirect_url}` | 280 | | `POST` | `/api/v1/auth/oauth/callback` | Handle OAuth callback | `{provider, code, redirect_url}` | 281 | 282 | ### System Endpoints 283 | 284 | | Method | Endpoint | Description | 285 | | ------ | --------- | ------------ | 286 | | `GET` | `/health` | Health check | 287 | 288 | ## Authentication Flow 289 | 290 | ### Email/Password Flow 291 | 292 | 1. **Signup**: `POST /auth/signup` → User created in Supabase 293 | 2. **Login**: `POST /auth/login` → Returns JWT access/refresh tokens 294 | 3. **Protected Requests**: Include `Authorization: Bearer ` header 295 | 4. **Logout**: `POST /auth/logout` → Invalidates session 296 | 297 | ### Google OAuth Flow (PKCE) 298 | 299 | 1. **Initiate**: `POST /auth/oauth/login` → Returns Google OAuth URL 300 | 2. **Redirect**: User authenticates with Google 301 | 3. **Callback**: Google redirects with authorization code 302 | 4. **Exchange**: `POST /auth/oauth/callback` → Returns JWT tokens 303 | 5. **Protected Requests**: Use JWT tokens same as email/password flow 304 | 305 | ## Project Structure 306 | 307 | ``` 308 | supabase-api-scaffolding-template/ 309 | ├── src/ 310 | │ ├── api/ 311 | │ │ ├── endpoints/ 312 | │ │ │ └── auth.py # All authentication endpoints 313 | │ │ ├── dependencies.py # JWT validation, auth dependencies 314 | │ │ ├── router.py # API route configuration 315 | │ │ └── api.py # FastAPI app configuration 316 | │ ├── core/ 317 | │ │ ├── config.py # Settings and configuration 318 | │ │ ├── constants.py # Application constants 319 | │ │ ├── messages.py # User-facing messages 320 | │ │ ├── secrets.py # Google Secret Manager integration 321 | │ │ └── supabase_client.py # Supabase client singleton 322 | │ ├── models/ 323 | │ │ └── auth.py # Pydantic models for requests/responses 324 | │ ├── services/ 325 | │ │ └── auth_service.py # Authentication business logic 326 | │ └── tests/ 327 | │ └── oauth/ 328 | │ ├── oauth_test.html # Interactive OAuth testing 329 | │ └── README.md # OAuth testing guide 330 | ├── .env # Environment variables (create this) 331 | ├── .gitignore # Git ignore rules 332 | ├── requirements.txt # Python dependencies 333 | └── README.md # This file 334 | ``` 335 | 336 | ## Troubleshooting 337 | 338 | ### Common Issues 339 | 340 | **"redirect_uri_mismatch" OAuth error:** 341 | 342 | - Check that your Google Cloud Console redirect URI exactly matches: `https://your-project-ref.supabase.co/auth/v1/callback` 343 | - Ensure no trailing slashes or typos 344 | 345 | **"Invalid JWT" errors:** 346 | 347 | - Verify `SUPABASE_JWT_SECRET` is correct 348 | - Check token expiration settings in Supabase 349 | - Ensure Bearer token format: `Authorization: Bearer ` 350 | 351 | **GSM authentication errors:** 352 | 353 | - Verify service account has `roles/secretmanager.secretAccessor` 354 | - Check `GCP_PROJECT_ID` matches your actual project 355 | - Ensure secrets exist in Secret Manager 356 | 357 | **CORS errors in browser:** 358 | 359 | - Add your frontend domain to `CORS_ORIGINS` 360 | - For development, use `["*"]` temporarily 361 | 362 | ### Debugging Tips 363 | 364 | 1. **Enable debug logging:** 365 | 366 | ```env 367 | DEBUG=true 368 | LOG_LEVEL=DEBUG 369 | ``` 370 | 371 | 2. **Check Supabase logs:** 372 | 373 | - Go to Supabase Dashboard → Logs → Auth logs 374 | 375 | 3. **Test individual endpoints:** 376 | 377 | - Use the interactive docs at `/docs` 378 | - Test with curl or Postman 379 | 380 | 4. **Verify OAuth setup:** 381 | - Use the provided test page at `src/tests/oauth/oauth_test.html` 382 | 383 | ## Production Checklist 384 | 385 | Before deploying to production: 386 | 387 | - [ ] Set `USE_GSM=true` and configure Google Secret Manager 388 | - [ ] Update `CORS_ORIGINS` to specific domains 389 | - [ ] Set `DEBUG=false` and `LOG_LEVEL=WARNING` 390 | - [ ] Configure proper SSL/TLS certificates 391 | - [ ] Set up monitoring and logging 392 | - [ ] Test OAuth flow with production URLs 393 | - [ ] Configure Supabase RLS policies if using database 394 | - [ ] Set up backup/disaster recovery for secrets 395 | 396 | ## Security Considerations 397 | 398 | - **JWT Secrets**: Never commit JWT secrets to version control 399 | - **Environment Variables**: Use `.env` files locally, GSM in production 400 | - **CORS**: Restrict to specific domains in production 401 | - **HTTPS**: Always use HTTPS in production for OAuth 402 | - **Token Expiry**: Configure appropriate JWT expiration times 403 | - **Rate Limiting**: Consider adding rate limiting for auth endpoints 404 | 405 | ## Contributing 406 | 407 | 1. Fork the repository 408 | 2. Create a feature branch 409 | 3. Make your changes 410 | 4. Test thoroughly (especially OAuth flow) 411 | 5. Submit a pull request 412 | 413 | ## Development 414 | 415 | ### Code Quality 416 | 417 | This project uses pre-commit hooks for automatic code formatting and validation: 418 | 419 | ```bash 420 | # Install development dependencies 421 | pip install -r requirements-dev.txt 422 | 423 | # Install pre-commit hooks 424 | pre-commit install 425 | 426 | # Run hooks on all files 427 | pre-commit run --all-files 428 | 429 | # Run individual tools 430 | ruff check src/ # linting 431 | ruff format src/ # formatting 432 | mypy src/ # type checking 433 | pytest src/tests/ # tests 434 | 435 | # Bypass hooks (emergency only) 436 | git commit --no-verify -m "message" 437 | ``` 438 | 439 | ## License 440 | 441 | MIT License 442 | --------------------------------------------------------------------------------