from functools import lru_cache from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import Field class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore" ) APP_NAME: str = "Audit_Firm_v2.0.3.6" ENV: str = "dev" DEBUG: bool = True SECRET_KEY: str = "change-me-to-a-long-random-string" COOKIE_SECURE: bool = False COOKIE_SAMESITE: str = "lax" COOKIE_SESSION_NAME: str = "af2sid" DB_BACKEND: str = Field(default="sqlite", description="sqlite|postgres") SQLITE_COMMON_PATH: str = "./data/common.db" PG_HOST: str = "127.0.0.1" PG_PORT: int = 5432 PG_USER: str = "postgres" PG_PASSWORD: str = "postgres" PG_DB_COMMON: str = "audit_common" DEFAULT_TENANT_CODE: str = "default" DEFAULT_BRANCH_CODE: str = "main" DEFAULT_YEAR_CODE: str = "2025-26" DEFAULT_TIMEZONE: str = "Asia/Kolkata" # Public base URL used for email links such as invite and password reset. # In Coolify production set this to https://your-erp-domain. ERP_PUBLIC_BASE_URL: str = "http://localhost:8000" # Print OTP to server logs only in local/dev troubleshooting. Keep false in UAT/production. DEV_AUTH_OTP_PRINT: bool = False # Context headers are disabled by default for public deployments. # When disabled, browser/client supplied X-Tenant-Code, X-Branch-Code, # and X-Year-Code are ignored. Enable only for trusted internal runners # or reverse proxies that also restrict/strip external request headers. TRUST_CONTEXT_HEADERS: bool = False TRUST_CONTEXT_HEADER_HOSTS: str = "127.0.0.1,localhost,::1" CONTEXT_HEADER_SECRET: str = "" # JWT Configuration JWT_ISSUER: str = "Audit_Firm_v2.0.3.6" JWT_AUDIENCE: str = "audit_firm_clients" JWT_ACCESS_MINUTES: int = 15 JWT_REFRESH_DAYS: int = 30 # Bootstrap Admin BOOTSTRAP_ADMIN_EMAIL: str = "admin@auditfirm.local" BOOTSTRAP_ADMIN_PASSWORD: str = "ChangeMe@123" INVITE_TOKEN_HOURS: int = 72 PASSWORD_RESET_HOURS: int = 2 PASSWORD_MIN_LENGTH: int = 8 @lru_cache def get_settings() -> Settings: s = Settings() # Normalize cookie values s.COOKIE_SAMESITE = (s.COOKIE_SAMESITE or "lax").lower() if s.COOKIE_SAMESITE not in {"lax", "strict", "none"}: s.COOKIE_SAMESITE = "lax" return s