"""Versioned, redacted platform settings service. The API key is encrypted with a Fernet key derived from ``SECRET_KEY`` so the plaintext never appears in the record, API responses, or logs. The public API surface only ever sees a masked value and the settings version; a per-job snapshot references the version rather than the secret. """ from __future__ import annotations import base64 import hashlib from datetime import datetime, timezone from typing import Optional from uuid import uuid4 from cryptography.fernet import Fernet from sqlalchemy.orm import Session from ..config import Config from ..models.settings import PlatformSettings MASK = "sk-••••••••" def _utc_now() -> datetime: return datetime.now(timezone.utc) class SettingsService: """Flush-only settings repository; caller owns transactions.""" def __init__(self, session: Session): self.session = session def _fernet(self) -> Fernet: secret = Config.SECRET_KEY if not secret: raise ValueError("settings_secret_key_required") digest = hashlib.sha256(secret.encode("utf-8")).digest() key = base64.urlsafe_b64encode(digest) return Fernet(key) def _encrypt_secret(self, api_key: str) -> str: return self._fernet().encrypt(api_key.encode("utf-8")).decode("utf-8") def _decrypt_secret(self, secret_ref: str) -> Optional[str]: if not secret_ref: return None try: return self._fernet().decrypt(secret_ref.encode("utf-8")).decode("utf-8") except Exception: return None def _latest_row(self) -> Optional[PlatformSettings]: return ( self.session.query(PlatformSettings) .order_by(PlatformSettings.created_at.desc()) .first() ) def save_settings( self, settings: dict, *, api_key: Optional[str] = None, updated_by: Optional[str] = None, ) -> str: """Persist a new version, encrypting the API key if provided.""" version = f"v{uuid4().hex[:12]}" # Clear active on all existing rows, then insert the new active version. for row in self.session.query(PlatformSettings).filter( PlatformSettings.active.is_(True) ): row.active = False record = PlatformSettings( version=version, settings=settings, secret_ref=self._encrypt_secret(api_key) if api_key else None, updated_by_user_id=updated_by, active=True, ) self.session.add(record) self.session.flush() return version def _mask(self, settings: dict) -> dict: out = dict(settings or {}) # A secret value should never be in the public dict; be defensive. for key in list(out.keys()): if "key" in key.lower() or "secret" in key.lower() or "token" in key.lower(): out[key] = MASK return out def active_settings(self) -> dict: row = self._latest_row() if row is None: return {"settings": {}, "version": None, "updated_by": None, "api_key": MASK} masked = self._mask(row.settings if isinstance(row.settings, dict) else {}) return { "settings": masked, "version": row.version, "updated_by": row.updated_by_user_id, "api_key": MASK if row.secret_ref else MASK, } def snapshot_for_job(self) -> dict: row = self._latest_row() if row is None: return {"settings": {}, "version": None, "settings_version": None} return { "settings": self._mask(row.settings if isinstance(row.settings, dict) else {}), "version": row.version, "settings_version": row.version, }