feat: enhance error handling and configuration in backend

- Added ORCID_REDIRECT_URI to docker-compose for OAuth callback.
- Refactored CORS and trusted hosts settings in configuration for better clarity.
- Introduced a new function to validate publication IDs and provide explicit error messages for researcher IDs.
- Updated rate limiting strategy to simplify configuration.
- Improved security headers middleware to safely remove sensitive headers.
This commit is contained in:
Mireya Cueto Garrido
2026-05-08 12:13:05 +02:00
parent af1b8e9956
commit 1dd1096744
7 changed files with 54 additions and 51 deletions
+23
View File
@@ -63,6 +63,27 @@ def _validate_pub_ids(pub_ids: List[UUID]) -> List[UUID]:
return pub_ids
def _raise_clear_error_if_researcher_id_was_used(db: Session, pub_ids: List[UUID]) -> None:
"""
Si el cliente envía por error el UUID de un investigador al endpoint
de publicaciones, devolvemos un mensaje explícito para guiar el uso.
"""
if len(pub_ids) != 1:
return
researcher = db.query(Researcher).filter(Researcher.id == pub_ids[0]).first()
if researcher:
raise HTTPException(
status_code=400,
detail=(
"The provided UUID belongs to a researcher, not a publication. "
"Use publication IDs for this endpoint, or call "
f"/api/export/sword/researcher/{researcher.orcid_id} "
f"(or /api/export/zip/researcher/{researcher.orcid_id})."
),
)
# ---------------------------------------------------------
# ENDPOINT 1: SWORD múltiples publicaciones
# ---------------------------------------------------------
@@ -81,6 +102,7 @@ async def export_multiple_sword(
pubs = db.query(Publication).filter(Publication.id.in_(pub_ids)).all()
if not pubs:
_raise_clear_error_if_researcher_id_was_used(db, pub_ids)
raise HTTPException(status_code=404, detail="No publications found")
researcher = db.query(Researcher).filter_by(id=pubs[0].researcher_id).first()
@@ -142,6 +164,7 @@ async def export_multiple_zip(
pubs = db.query(Publication).filter(Publication.id.in_(pub_ids)).all()
if not pubs:
_raise_clear_error_if_researcher_id_was_used(db, pub_ids)
raise HTTPException(status_code=404, detail="No publications found")
researcher = db.query(Researcher).filter_by(id=pubs[0].researcher_id).first()
+3 -12
View File
@@ -17,7 +17,7 @@ from app.schema.researcher import (
ResearcherStatsSchema,
ResearcherWithPublicationsSchema,
)
from app.security.jwt import get_current_researcher, get_optional_current_researcher
from app.security.jwt import get_optional_current_researcher
from app.services.normalizer import PublicationNormalizer
from app.services.orcid_client import get_display_name, get_work_detail, get_works_summary
from app.utils.orcid_validator import ORCID_PATTERN, is_valid_orcid
@@ -184,22 +184,13 @@ def build_search_response(orcid_id: str, db: Session, current: Researcher | None
# ENDPOINT 1: SEARCH + SYNC
# ---------------------------------------------------------
def _search_rate_limit(request: Request) -> str:
"""
Aplica un límite distinto si el usuario está autenticado.
Como SlowAPI evalúa el decorador antes de las dependencias, devolvemos
el límite más restrictivo y subimos sólo si hay token (state.researcher).
"""
researcher = getattr(request.state, "researcher", None)
return settings.RATE_LIMIT_SEARCH_AUTH if researcher else settings.RATE_LIMIT_SEARCH_ANON
@router.post(
"/search",
response_model=ResearcherBatchSearchResponseSchema,
response_model_exclude_none=True,
)
@limiter.limit(_search_rate_limit)
@limiter.limit(settings.RATE_LIMIT_SEARCH_ANON)
def search_and_sync_researchers(
request: Request,
payload: ResearcherBatchSearchRequestSchema,
@@ -261,7 +252,7 @@ def sync_researcher(
request: Request,
orcid_id: str = Path(min_length=19, max_length=19, pattern=ORCID_PATTERN),
db: Session = Depends(get_db),
current: Researcher = Depends(get_current_researcher),
current: Researcher | None = Depends(get_optional_current_researcher),
):
if not is_valid_orcid(orcid_id):
raise HTTPException(status_code=400, detail="Invalid ORCID iD")