57 lines
1.7 KiB
Python
57 lines
1.7 KiB
Python
"""
|
|
Autenticación por API key (uso máquina-a-máquina, p. ej. el scheduler interno).
|
|
|
|
Endurecimiento:
|
|
- Comparación constante en tiempo (`hmac.compare_digest`) para evitar timing attacks.
|
|
- No se loggea el valor de la cabecera bajo ninguna circunstancia.
|
|
- Se separa este mecanismo del JWT de usuario; la API key NO debe usarse como
|
|
prueba de identidad de un investigador.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hmac
|
|
|
|
from fastapi import Depends, HTTPException, status
|
|
from fastapi.security import APIKeyHeader
|
|
|
|
from app.core.config import settings
|
|
|
|
|
|
api_key_header = APIKeyHeader(name=settings.API_KEY_NAME, auto_error=False)
|
|
|
|
|
|
def _is_valid_key(provided: str | None) -> bool:
|
|
if not provided or not settings.API_KEY_VALUE:
|
|
return False
|
|
return hmac.compare_digest(provided.encode("utf-8"), settings.API_KEY_VALUE.encode("utf-8"))
|
|
|
|
|
|
def is_valid_api_key(provided: str | None) -> bool:
|
|
return _is_valid_key(provided)
|
|
|
|
|
|
def get_api_key(api_key: str | None = Depends(api_key_header)) -> str:
|
|
if not _is_valid_key(api_key):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid or missing API key",
|
|
)
|
|
return api_key # type: ignore[return-value]
|
|
|
|
|
|
def get_api_key_optional(api_key: str | None = Depends(api_key_header)) -> str | None:
|
|
"""
|
|
- Si no llega cabecera: None.
|
|
- Si llega y es válida: la devuelve.
|
|
- Si llega pero es inválida: 401.
|
|
"""
|
|
if api_key is None:
|
|
return None
|
|
if not _is_valid_key(api_key):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid API key",
|
|
)
|
|
return api_key
|