(self, request: Request, call_next)
| 326 | """ |
| 327 | |
| 328 | async def dispatch(self, request: Request, call_next): |
| 329 | path = request.url.path |
| 330 | |
| 331 | # CORS preflight requests do not include custom auth headers. |
| 332 | # Let CORSMiddleware handle them. |
| 333 | if request.method == "OPTIONS": |
| 334 | return await call_next(request) |
| 335 | |
| 336 | if _is_blocked_public_output_path(path): |
| 337 | return Response(status_code=404) |
| 338 | |
| 339 | # Skip excluded paths |
| 340 | if path in EXCLUDED_PATHS: |
| 341 | return await call_next(request) |
| 342 | |
| 343 | # Skip excluded prefixes |
| 344 | if path.startswith(EXCLUDED_PREFIXES): |
| 345 | return await call_next(request) |
| 346 | |
| 347 | client_ip = _extract_client_ip(request) |
| 348 | request.state.client_ip = client_ip |
| 349 | |
| 350 | if ( |
| 351 | settings.SECURITY_RATE_LIMIT_ENABLED |
| 352 | and request.method in _WRITE_METHODS |
| 353 | and _should_check_api_key(path) |
| 354 | ): |
| 355 | if _is_rate_limited(client_ip, _DEFAULT_WRITE_RULE): |
| 356 | return JSONResponse( |
| 357 | status_code=429, |
| 358 | content={"detail": "Too many write requests from this IP. Please retry later."}, |
| 359 | ) |
| 360 | specific_rule = _RATE_LIMIT_RULES.get(path) |
| 361 | if specific_rule and _is_rate_limited(client_ip, specific_rule): |
| 362 | return JSONResponse( |
| 363 | status_code=429, |
| 364 | content={"detail": "Rate limit exceeded for this endpoint. Please retry later."}, |
| 365 | ) |
| 366 | |
| 367 | # Only check API key for /api/* and /paper2video/* routes |
| 368 | if _should_check_api_key(path): |
| 369 | if not API_KEY: |
| 370 | return JSONResponse( |
| 371 | status_code=500, |
| 372 | content={"detail": "BACKEND_API_KEY is not configured"}, |
| 373 | ) |
| 374 | api_key = request.headers.get("X-API-Key") |
| 375 | # EventSource cannot set custom headers, allow query param for rebuttal SSE. |
| 376 | if not api_key and request.method == "GET" and "/paper2rebuttal/progress/" in path: |
| 377 | api_key = request.query_params.get("x_api_key") or request.query_params.get("X-API-Key") |
| 378 | |
| 379 | if not api_key: |
| 380 | return JSONResponse( |
| 381 | status_code=401, |
| 382 | content={"detail": "API key required"}, |
| 383 | ) |
| 384 | |
| 385 | if api_key != API_KEY: |
nothing calls this directly
no test coverage detected