"""Authentification des appels entrants — miroir inversé de `requireInternalWorker` (`apps/api/src/middlewares/require-internal-worker.ts`) : ici c'est `apps/api` qui appelle *ce* service, donc c'est ce service qui vérifie le secret plutôt que de l'envoyer. Comparaison à temps constant (`hmac.compare_digest`, l'équivalent Python du `timingSafeEqual` de Node utilisé côté `apps/api`) — même raisonnement : un attaquant ne doit rien apprendre de la durée de la comparaison au-delà de ce qu'une différence de longueur révèle déjà. """ import hmac from fastapi import Header, HTTPException, status from .config import settings _SECRET_HEADER_NAME = "x-intent-service-secret" def require_valid_secret( x_intent_service_secret: str | None = Header(default=None, alias=_SECRET_HEADER_NAME), ) -> None: """Dépendance FastAPI montée sur chaque route protégée (`/v1/*`) — pas `GET /health`, sondé par le healthcheck Docker sans configuration d'auth propre. `settings.intent_service_secret` est garanti non vide par `config.py` (pas de valeur par défaut dans `Settings`) — le seul cas à traiter ici est un header manquant ou incorrect côté appelant. """ if x_intent_service_secret is None or not hmac.compare_digest( x_intent_service_secret, settings.intent_service_secret ): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")