import os from pathlib import Path from pydantic_settings import BaseSettings from functools import lru_cache from pydantic import field_validator _backend_dir = str(Path(__file__).resolve().parent.parent) class Settings(BaseSettings): # App app_name: str = "CrowData API" environment: str = "development" debug: bool = False # Default: False por seguridad. Usar ENVIRONMENT=development para debug. # Database database_url: str = "" # Redis redis_url: str = "redis://localhost:6379/0" # Security — MUST be set in .env secret_key: str = "" reset_password_token_secret: str = "" # Separate secret for password reset tokens verification_token_secret: str = "" # Separate secret for email verification tokens # MFA Encryption Key (for encrypting TOTP secrets and backup codes in DB) mfa_encryption_key: str = "" # Fernet key (32 bytes base64) # JWT RS256 (asymmetric) — new keys for production jwt_private_key: str = "" # RS256 private key (PEM) jwt_public_key: str = "" # RS256 public key (PEM) jwt_algorithm: str = "RS256" # New tokens signed with RS256 jwt_key_id: str = "key-1" # Key ID for rotation # Legacy HS256 (symmetric) — for backward compatibility during transition jwt_legacy_secret_key: str = "" # HS256 secret (same as secret_key) jwt_legacy_algorithm: str = "HS256" jwt_legacy_enabled: bool = False # Disabled by default in production access_token_expire_minutes: int = 15 # 15 minutes (short-lived access token) refresh_token_expire_days: int = 7 # 7 days (refresh token in HttpOnly cookie) # Auth transport use_cookie_auth: bool = True # Enable HttpOnly cookie auth cookie_secure: bool = True # Secure cookie (HTTPS only) cookie_samesite: str = "lax" # Lax for cross-site top-level nav, Strict for more security # Cache cache_ttl_seconds: int = 86400 # 24 hours # Database pool db_pool_size: int = 10 db_max_overflow: int = 20 # Scrapers playwright_headless: bool = True scraper_timeout_seconds: int = 30 proxy_url: str | None = None proxy_list: list[str] = [] captcha_api_key: str | None = None # 2Captcha API key nopecha_api_key: str | None = None # NopeCHA API key (reCAPTCHA v3 solver) groq_api_key: str | None = None searchapi_key: str | None = None searchapi_keys: list[str] = [] ai_verification_enabled: bool = True # Payments — default: empty string (must be set in .env for production) mp_access_token: str = "" mp_public_key: str = "" # AFIP afip_cuit_representada: str = "" afip_cert_path: str = "" afip_key_path: str = "" afip_cache_file: str = "" # Email / SMTP smtp_host: str = "localhost" smtp_port: int = 587 smtp_user: str = "" smtp_password: str = "" smtp_use_tls: bool = True from_email: str = "crowsistemas@proton.me" from_name: str = "CrowData" # Allowed Origins (for CORS) allowed_origins: str = "" # ─── Validation ─── @field_validator("secret_key", "reset_password_token_secret", "verification_token_secret", "mfa_encryption_key", "jwt_private_key", "jwt_public_key", mode="before") @classmethod def _require_secrets_in_prod(cls, v, info): # Solo validar en producción if info.data.get("environment") == "production" and not v: raise ValueError(f"{info.field_name} must be set in production") return v class Config: env_file = os.path.join(_backend_dir, ".env") env_file_encoding = "utf-8" extra = "allow" @lru_cache() def get_settings() -> Settings: return Settings()