"""Dependances communes aux routes.""" from __future__ import annotations from typing import Annotated from fastapi import Depends, Header, Request from sqlalchemy.orm import Session from app.config import Settings, get_settings from app.db.session import get_db from app.services.client_ip import resolve_client_ip from app.services.errors import RateLimitedError, ShareForbiddenError, SharingDisabledError from app.services.rate_limit import limiter, parse_rate MANAGEMENT_TOKEN_HEADER = "X-Management-Token" DbSession = Annotated[Session, Depends(get_db)] AppSettings = Annotated[Settings, Depends(get_settings)] def require_management_token( x_management_token: Annotated[str | None, Header(alias=MANAGEMENT_TOKEN_HEADER)] = None, ) -> str: """Recupere le secret de gestion depuis l'en-tete (jamais depuis l'URL).""" if not x_management_token: raise ShareForbiddenError("Jeton de gestion manquant.") return x_management_token ManagementToken = Annotated[str, Depends(require_management_token)] def require_sharing_enabled(settings: AppSettings) -> None: """Coupe toute l'API de partage quand l'instance est privee. Les cartes locales continuent de fonctionner : seul le service serveur est indisponible, et il le dit clairement. """ if not settings.sharing_enabled: raise SharingDisabledError() SharingEnabled = Annotated[None, Depends(require_sharing_enabled)] def _rate_limit(bucket: str, spec_name: str): # type: ignore[no-untyped-def] """Fabrique une dependance de limitation pour une famille d'endpoints. La limitation est posee sur les routes concernees, jamais en middleware global : /api/health et /api/ready ne peuvent donc pas etre bloques, ce qui garantit que le healthcheck du conteneur reste fiable sous charge. """ def dependency(request: Request, settings: AppSettings) -> None: rule = parse_rate(getattr(settings, spec_name)) if rule is None: return client = resolve_client_ip( request, trust_proxy_headers=settings.trust_proxy_headers, trusted_networks=settings.trusted_proxy_networks, ) retry_after = limiter.check(f"{bucket}:{client}", rule) if retry_after is not None: raise RateLimitedError(retry_after) return dependency RateLimitCreate = Annotated[None, Depends(_rate_limit("share:create", "share_create_rate_limit"))] RateLimitRead = Annotated[None, Depends(_rate_limit("share:read", "share_read_rate_limit"))] RateLimitWrite = Annotated[None, Depends(_rate_limit("share:write", "share_write_rate_limit"))]