""" Environment Configuration Module for Todo Backend This module loads and validates all environment variables required for the FastAPI application to connect to Neon PostgreSQL. Security Rules: - NEVER commit .env file to version control - Database URL contains sensitive credentials - JWT secret must be cryptographically secure - All sensitive values must come from environment variables """ from typing import List import os from pydantic import Field, validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """ Application settings loaded from environment variables. All fields are required at runtime. Use .env file for local development. """ # API Configuration api_host: str = Field( default="0.0.0.0", description="Host address for the FastAPI server" ) api_port: int = Field( default=8000, description="Port number for the FastAPI server" ) environment: str = Field( default="development", description="Environment name (development, staging, production)" ) # Database Configuration (Neon PostgreSQL) database_url: str = Field( ..., description="Neon PostgreSQL connection URL (postgresql://...)" ) # JWT Configuration jwt_secret_key: str = Field( ..., description="Secret key for JWT token signing (must be cryptographically secure)" ) jwt_algorithm: str = Field( default="HS256", description="Algorithm for JWT token signing" ) access_token_expire_minutes: int = Field( default=15, description="Access token expiration time in minutes" ) refresh_token_expire_days: int = Field( default=7, description="Refresh token expiration time in days" ) # CORS Configuration cors_origins: str = Field( default="http://localhost:3000,http://localhost:5173", description="Comma-separated list of allowed CORS origins" ) # Application Metadata app_name: str = "Todo API" app_version: str = "2.0.0" debug_mode: bool = False @property def cors_origins_list(self) -> List[str]: """ Parse cors_origins string into a list. Returns: List[str]: Parsed list of CORS origins """ if isinstance(self.cors_origins, str): return [origin.strip() for origin in self.cors_origins.split(",") if origin.strip()] return self.cors_origins if isinstance(self.cors_origins, list) else [] @validator("environment") def validate_environment(cls, v): """Validate that environment is one of the allowed values.""" allowed = ["development", "staging", "production"] if v not in allowed: raise ValueError(f"environment must be one of {allowed}, got '{v}'") return v @validator("database_url") def validate_database_url(cls, v): """Validate Neon database URL format (supports async drivers).""" valid_prefixes = [ "postgresql://", "postgres://", "postgresql+asyncpg://", "postgres+asyncpg://" ] if not any(v.startswith(prefix) for prefix in valid_prefixes): raise ValueError( f"database_url must start with 'postgresql://', 'postgres://', or async driver (postgresql+asyncpg://), got: {v[:20]}..." ) return v @validator("jwt_secret_key") def validate_jwt_secret(cls, v): """Validate JWT secret key is sufficiently long.""" if len(v) < 32: raise ValueError( f"jwt_secret_key must be at least 32 characters for security, got {len(v)} characters" ) return v @validator("debug_mode", pre=True, always=True) def set_debug_mode(cls, v, values): """Automatically set debug mode based on environment.""" if "environment" in values: return values["environment"] == "development" return v model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore" ) # Global settings instance (lazy-loaded) _settings: Settings | None = None def get_settings() -> Settings: """ Get the global settings instance (singleton pattern). Creates the settings instance on first call and reuses it. Returns: Settings: Application settings loaded from environment Example: >>> settings = get_settings() >>> print(settings.database_url) 'postgresql://user:pass@ep-xyz.aws.neon.tech/neondb?sslmode=require' """ global _settings if _settings is None: _settings = Settings() return _settings # ============================================================================ # SECURITY DOCUMENTATION # ============================================================================ """ SECURITY RULES FOR NEON POSTGRESQL & JWT ========================================= 1. DATABASE URL - Purpose: Connection string to Neon PostgreSQL - Contains: Username, password, host, port, database name - Security: MUST NEVER be exposed in frontend or committed to git - Format: postgresql://user:password@host:port/database?sslmode=require 2. JWT SECRET KEY - Purpose: Cryptographic key for signing JWT tokens - Requirements: At least 32 characters, cryptographically random - Security: MUST NEVER be exposed to frontend or committed to git - Generation: Use `openssl rand -hex 32` or similar 3. ACCESS TOKEN (15 minutes) - Purpose: Short-lived token for API authentication - Storage: HTTP-only cookies or secure localStorage - Contains: user_id, email, role, exp - Security: Validated on every protected route 4. REFRESH TOKEN (7 days) - Purpose: Long-lived token for getting new access tokens - Storage: HTTP-only cookies or secure database - Contains: user_id, token_id, exp - Security: Stored in database, revocable DATABASE SECURITY ================= 1. SSL Mode - Always use `?sslmode=require` in database URL - Neon enforces SSL by default - Never disable SSL for production 2. Connection Pooling - SQLAlchemy manages connection pooling - Pool size should match your concurrent load - Use `pool_pre_ping=True` for health checks 3. Row Level Security (Future) - Can be enabled in PostgreSQL for additional security - Use application-level filtering for now - User isolation enforced in service layer JWT TOKEN VALIDATION FLOW =========================== 1. Frontend sends request with Authorization header: Authorization: Bearer 2. Backend extracts token from header 3. Backend validates token using: - Verify signature using jwt_secret_key - Check expiration time (exp claim) - Verify issuer (iss claim) if set 4. If valid, extract user_id from token 5. Use user_id to: - Filter database queries (enforce user isolation) - Load user-specific data - Enforce authorization rules TOKEN REFRESH FLOW ================== 1. Access token expires (after 15 minutes) 2. Frontend sends refresh request: POST /api/v1/auth/refresh Body: { "refresh_token": "" } 3. Backend validates refresh token: - Check token exists in database - Verify not revoked - Check not expired 4. If valid, generate new tokens: - New access_token (15 min) - New refresh_token (7 days) - Rotate old refresh token (invalidate old one) 5. Return new tokens to frontend KEY HANDLING RULES =================== 1. Environment Variables Only - NEVER hardcode secrets in source code - ALWAYS load from environment variables or .env file - Use python-dotenv for .env loading 2. Git Safety - .env file MUST be in .gitignore - .env.example file SHOULD be committed (with placeholder values) - Verify: `git status` should never show .env file 3. Production Environment - Use platform-specific secret management: * Vercel: Environment Variables dashboard * Railway: Config vars * AWS: Secrets Manager * Neon: Supports connection pooling - Rotate secrets if accidentally exposed 4. Local Development - Create .env file from .env.example - Generate strong JWT secret with: `openssl rand -hex 32` - Get Neon database URL from Neon console - Never share .env file via screenshots or chat SECURITY CHECKLIST =================== Before deploying: - [ ] .env is in .gitignore - [ ] No secrets in source code (grep for passwords, keys) - [ ] .env.example has placeholder values only - [ ] JWT secret is at least 32 characters - [ ] Database URL uses sslmode=require - [ ] CORS origins restricted to real domains - [ ] Environment variables set in production platform - [ ] Secrets rotated if accidentally exposed NEON POSTGRESQL SETUP ====================== 1. Create Neon Project - Go to https://neon.tech - Create new project - Select region closest to your users - Copy connection string 2. Connection String Format postgresql://username:password@ep-cool-name.aws.neon.tech/neondb?sslmode=require 3. Environment Variable DATABASE_URL=postgresql://username:password@ep-xxx.aws.neon.tech/neondb?sslmode=require 4. Connection Pooling (Optional) - Neon supports PgBouncer for connection pooling - Use for production to reduce connection overhead - Add ?pgbouncer=true to connection string if needed FOR MORE INFORMATION ==================== Neon Docs: https://neon.tech/docs SQLAlchemy Async: https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html FastAPI Security: https://fastapi.tiangolo.com/tutorial/security/ JWT Best Practices: https://tools.ietf.org/html/rfc8725 """