"""Uwierzytelnianie międzywarstwowe (LOG-32). Warstwa logiczna oddaje treść baz interpretacyjnych, więc samo zalogowanie w prezentacji nie wystarczy — bez tego kontrolera wystarczyłoby uderzyć w logikę z pominięciem UI. Gdy ustawiono INTERNAL_TOKEN, każde żądanie (poza /health) musi go przynieść w nagłówku X-Astrololo-Token. Bez INTERNAL_TOKEN kontrola jest wyłączona (dev / zgodność wstecz) — wtedy przy starcie leci ostrzeżenie. """ from __future__ import annotations import logging import os import secrets from fastapi import Request from fastapi.responses import JSONResponse log = logging.getLogger("astrololo.security") HEADER = "X-Astrololo-Token" PUBLIC_PATHS = frozenset({"/health"}) def token() -> str: """Czytany leniwie — konfiguracja może się zmienić bez importu modułu.""" return os.getenv("INTERNAL_TOKEN", "") def enabled() -> bool: return bool(token()) def install(app, layer: str) -> None: if not enabled(): log.warning( "UWAGA: INTERNAL_TOKEN nie ustawiony — warstwa %s przyjmuje żądania od " "kogokolwiek, kto ma do niej dostęp sieciowy.", layer, ) @app.middleware("http") async def _guard(request: Request, call_next): if request.url.path in PUBLIC_PATHS or not enabled(): return await call_next(request) got = request.headers.get(HEADER, "") if not secrets.compare_digest(got, token()): return JSONResponse({"detail": "Brak lub błędny token międzywarstwowy."}, status_code=401) return await call_next(request)