feat: enhance backend security and configuration
- Updated Dockerfile to improve security with a non-root user and added health checks. - Modified docker-compose.yml to set containers as read-only, restrict ports to localhost, and implement health checks. - Enhanced .env.example with additional environment variables for security and configuration. - Improved FastAPI application with middleware for security headers, CORS, and body size limits. - Refactored authentication flow in auth.py to include state validation and improved error handling. - Added rate limiting to various endpoints to prevent abuse. - Updated researcher and publication handling to ensure better validation and error management.
This commit is contained in:
@@ -1,43 +1,52 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
"""
|
||||
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
|
||||
|
||||
# Cargar variables del .env
|
||||
load_dotenv()
|
||||
|
||||
API_KEY_NAME = os.getenv("API_KEY_NAME")
|
||||
API_KEY_VALUE = os.getenv("API_KEY_VALUE")
|
||||
|
||||
if not API_KEY_NAME:
|
||||
raise RuntimeError("ERROR: La variable API_KEY_NAME no está definida en el .env")
|
||||
|
||||
if not API_KEY_VALUE:
|
||||
raise RuntimeError("ERROR: La variable API_KEY_VALUE no está definida en el .env")
|
||||
|
||||
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
def get_api_key(api_key: str = Depends(api_key_header)):
|
||||
if api_key != API_KEY_VALUE:
|
||||
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 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="API key inválida o ausente."
|
||||
detail="Invalid or missing API key",
|
||||
)
|
||||
return api_key
|
||||
return api_key # type: ignore[return-value]
|
||||
|
||||
|
||||
def get_api_key_optional(api_key: str = Depends(api_key_header)) -> str | None:
|
||||
def get_api_key_optional(api_key: str | None = Depends(api_key_header)) -> str | None:
|
||||
"""
|
||||
Devuelve la API key si está presente y es correcta.
|
||||
- Si no está presente: None
|
||||
- Si está presente pero incorrecta: 401
|
||||
- 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 api_key != API_KEY_VALUE:
|
||||
if not _is_valid_key(api_key):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="API key inválida."
|
||||
detail="Invalid API key",
|
||||
)
|
||||
return api_key
|
||||
|
||||
Reference in New Issue
Block a user