from __future__ import annotations import os from dataclasses import dataclass from datetime import timedelta from functools import lru_cache from typing import Optional from services.security import JWTSettings @dataclass(frozen=True, slots=True) class SessionSettings: """Cookie and header configuration for session token transport.""" access_cookie_name: str refresh_cookie_name: str cookie_secure: bool cookie_domain: Optional[str] cookie_path: str header_name: str header_prefix: str allow_header_fallback: bool @dataclass(frozen=True, slots=True) class Settings: """Application configuration sourced from environment variables.""" jwt_secret_key: str = "change-me" jwt_algorithm: str = "HS256" jwt_access_token_minutes: int = 15 jwt_refresh_token_days: int = 7 session_access_cookie_name: str = "calminer_access_token" session_refresh_cookie_name: str = "calminer_refresh_token" session_cookie_secure: bool = False session_cookie_domain: Optional[str] = None session_cookie_path: str = "/" session_header_name: str = "Authorization" session_header_prefix: str = "Bearer" session_allow_header_fallback: bool = True @classmethod def from_environment(cls) -> "Settings": """Construct settings from environment variables.""" return cls( jwt_secret_key=os.getenv("CALMINER_JWT_SECRET", "change-me"), jwt_algorithm=os.getenv("CALMINER_JWT_ALGORITHM", "HS256"), jwt_access_token_minutes=cls._int_from_env( "CALMINER_JWT_ACCESS_MINUTES", 15 ), jwt_refresh_token_days=cls._int_from_env( "CALMINER_JWT_REFRESH_DAYS", 7 ), session_access_cookie_name=os.getenv( "CALMINER_SESSION_ACCESS_COOKIE", "calminer_access_token" ), session_refresh_cookie_name=os.getenv( "CALMINER_SESSION_REFRESH_COOKIE", "calminer_refresh_token" ), session_cookie_secure=cls._bool_from_env( "CALMINER_SESSION_COOKIE_SECURE", False ), session_cookie_domain=os.getenv("CALMINER_SESSION_COOKIE_DOMAIN"), session_cookie_path=os.getenv("CALMINER_SESSION_COOKIE_PATH", "/"), session_header_name=os.getenv( "CALMINER_SESSION_HEADER_NAME", "Authorization" ), session_header_prefix=os.getenv( "CALMINER_SESSION_HEADER_PREFIX", "Bearer" ), session_allow_header_fallback=cls._bool_from_env( "CALMINER_SESSION_ALLOW_HEADER_FALLBACK", True ), ) @staticmethod def _int_from_env(name: str, default: int) -> int: raw_value = os.getenv(name) if raw_value is None: return default try: return int(raw_value) except ValueError: return default @staticmethod def _bool_from_env(name: str, default: bool) -> bool: raw_value = os.getenv(name) if raw_value is None: return default lowered = raw_value.strip().lower() if lowered in {"1", "true", "yes", "on"}: return True if lowered in {"0", "false", "no", "off"}: return False return default def jwt_settings(self) -> JWTSettings: """Build runtime JWT settings compatible with token helpers.""" return JWTSettings( secret_key=self.jwt_secret_key, algorithm=self.jwt_algorithm, access_token_ttl=timedelta(minutes=self.jwt_access_token_minutes), refresh_token_ttl=timedelta(days=self.jwt_refresh_token_days), ) def session_settings(self) -> SessionSettings: """Provide transport configuration for session tokens.""" return SessionSettings( access_cookie_name=self.session_access_cookie_name, refresh_cookie_name=self.session_refresh_cookie_name, cookie_secure=self.session_cookie_secure, cookie_domain=self.session_cookie_domain, cookie_path=self.session_cookie_path, header_name=self.session_header_name, header_prefix=self.session_header_prefix, allow_header_fallback=self.session_allow_header_fallback, ) @lru_cache(maxsize=1) def get_settings() -> Settings: """Return cached application settings.""" return Settings.from_environment()