from functools import lru_cache from pydantic import model_validator from pydantic_settings import BaseSettings, SettingsConfigDict # Valeur de développement uniquement (versionnée dans `.env.example`) : un # secret connu de quiconque lit le dépôt ne protège plus rien. Voir le # contrôle dans `Settings.check_production_secrets`. _DEV_JWT_SECRET = "dev-insecure-secret-change-me-please-32b" _MIN_JWT_SECRET_LENGTH = 32 class Settings(BaseSettings): """Configuration applicative, alimentée par l'environnement.""" model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore" ) environment: str = "development" database_url: str = ( "postgresql+psycopg://benevoles:benevoles@localhost:5432/benevoles" ) test_database_url: str = ( "postgresql+psycopg://benevoles:benevoles@localhost:5432/benevoles_test" ) # Authentification (lien magique + session JWT). Secret >= 32 octets. jwt_secret: str = _DEV_JWT_SECRET magic_link_ttl_minutes: int = 30 session_ttl_hours: int = 12 # URL publique du frontend, pour construire les liens des emails. public_base_url: str = "http://localhost:5173" magic_link_path: str = "/connexion" # Envoi d'emails (SMTP). Ne jamais mettre de vraies valeurs ici : elles # vivent dans .env (non versionné) ou l'environnement. # email_enabled=False ⇒ les envois sont seulement journalisés (dev, tests). email_enabled: bool = False smtp_host: str = "localhost" smtp_port: int = 587 smtp_user: str = "" smtp_password: str = "" smtp_starttls: bool = True smtp_ssl: bool = False # Beaucoup de fournisseurs (OVH…) exigent une From = boîte authentifiée # (ou un alias validé). En cas de rejet, mettre SMTP_FROM = SMTP_USER. smtp_from: str = "Festival des bénévoles " log_level: str = "INFO" cors_origins: list[str] = ["http://localhost:5173"] @property def is_production(self) -> bool: return self.environment == "production" @model_validator(mode="after") def check_production_secrets(self) -> "Settings": """Un secret JWT par défaut, vide ou trop court n'importe pas en dev (confort), mais en production il permettrait de forger un jeton pour n'importe quel compte (y compris COORDINATION) — donc de fabriquer soi-même un jeton valide sans jamais s'authentifier. On refuse de démarrer plutôt que de tourner silencieusement sans protection.""" if not self.is_production: return self if ( not self.jwt_secret or self.jwt_secret == _DEV_JWT_SECRET or len(self.jwt_secret) < _MIN_JWT_SECRET_LENGTH ): raise ValueError( "JWT_SECRET manquant, par défaut ou trop court (>= " f"{_MIN_JWT_SECRET_LENGTH} caractères) en production. " "Générer une vraie valeur : openssl rand -hex 32" ) return self @lru_cache def get_settings() -> Settings: return Settings()