"""Read Authentik / reverse-proxy identity headers in Streamlit.""" from __future__ import annotations import os from typing import Mapping DEFAULT_HEADER_CANDIDATES = ( "X-Forwarded-User", "X-Authentik-Username", "X-Authentik-Uid", "Remote-User", "X-Forwarded-Email", ) def _truthy(value: str | None, default: bool = False) -> bool: if value is None: return default return value.strip().lower() in {"1", "true", "yes", "on"} def auth_required() -> bool: return _truthy(os.environ.get("AUTH_REQUIRED"), default=True) def header_candidates() -> tuple[str, ...]: primary = os.environ.get("AUTH_USERNAME_HEADER", "").strip() extras = [ os.environ.get("AUTH_UID_HEADER", "").strip(), os.environ.get("AUTH_EMAIL_HEADER", "").strip(), ] ordered: list[str] = [] for name in (primary, *extras, *DEFAULT_HEADER_CANDIDATES): if name and name not in ordered: ordered.append(name) return tuple(ordered) def _normalize(value: str | None) -> str | None: if not value: return None cleaned = value.strip() return cleaned or None def username_from_headers(headers: Mapping[str, str]) -> str | None: lowered = {k.lower(): v for k, v in headers.items()} for name in header_candidates(): value = _normalize(lowered.get(name.lower())) if value: return value return None def get_current_user() -> str | None: """Return the authenticated username from proxy-injected headers.""" try: from streamlit.web.server.websocket_headers import _get_websocket_headers headers = _get_websocket_headers() or {} user = username_from_headers(headers) if user: return user except Exception: pass if auth_required(): return None dev_user = os.environ.get("DEV_USER", "").strip() return dev_user or "anonymous"