import secrets from typing import Optional from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Application settings from environment variables.""" model_config = SettingsConfigDict(env_file=".env", extra="ignore") jwt_secret_key: str = Field( default_factory=lambda: secrets.token_urlsafe(32), validation_alias="JWT_SECRET_KEY", ) password_salt: str = Field( default="default-salt", validation_alias="PASSWORD_SALT" ) master_password_hash: Optional[str] = Field( default=None, validation_alias="MASTER_PASSWORD_HASH" ) # ⚠️ WARNING: DEVELOPMENT ONLY - NEVER USE IN PRODUCTION ⚠️ # This field allows setting a plaintext master password via environment # variable for development/testing purposes only. In production # deployments, use MASTER_PASSWORD_HASH instead and NEVER set this field. master_password: Optional[str] = Field( default=None, validation_alias="MASTER_PASSWORD", description=( "**DEVELOPMENT ONLY** - Plaintext master password. " "NEVER enable in production. Use MASTER_PASSWORD_HASH instead." ), ) token_expiry_hours: int = Field( default=24, validation_alias="SESSION_TIMEOUT_HOURS" ) anime_directory: str = Field( default="", validation_alias="ANIME_DIRECTORY" ) log_level: str = Field( default="INFO", validation_alias="LOG_LEVEL" ) # Additional settings from .env database_url: str = Field( default="sqlite:///./data/aniworld.db", validation_alias="DATABASE_URL" ) cors_origins: str = Field( default="http://localhost:3000", validation_alias="CORS_ORIGINS", ) api_rate_limit: int = Field( default=100, validation_alias="API_RATE_LIMIT" ) default_provider: str = Field( default="aniworld.to", validation_alias="DEFAULT_PROVIDER" ) provider_timeout: int = Field( default=30, validation_alias="PROVIDER_TIMEOUT" ) retry_attempts: int = Field( default=3, validation_alias="RETRY_ATTEMPTS" ) @property def allowed_origins(self) -> list[str]: """Return the list of allowed CORS origins. The environment variable should contain a comma-separated list. When ``*`` is provided we fall back to a safe local development default instead of allowing every origin in production. """ raw = (self.cors_origins or "").strip() if not raw: return [] if raw == "*": return [ "http://localhost:3000", "http://localhost:8000", ] return [origin.strip() for origin in raw.split(",") if origin.strip()] settings = Settings()