| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- from __future__ import annotations
- import re
- from collections.abc import Awaitable, Callable
- from fastapi import Request
- from starlette.concurrency import run_in_threadpool
- from starlette.middleware.base import BaseHTTPMiddleware
- from starlette.responses import JSONResponse, Response
- from api.services.auth import ADMIN_ROLE, SESSION_COOKIE_NAME, resolve_session
- _PUBLIC_PATHS = {
- "/api/auth/login",
- }
- _AUTHENTICATED_USER_PATHS = {
- ("GET", "/api/auth/me"),
- ("POST", "/api/auth/logout"),
- }
- _NORMAL_USER_PATHS = {
- ("GET", "/api/category-tree"),
- ("GET", "/api/demand-grade"),
- ("GET", "/api/video-discovery/demands"),
- }
- _NORMAL_USER_PATTERNS = (
- re.compile(r"^/api/video-discovery/demands/\d+$"),
- re.compile(r"^/api/demand-grade/\d+/videos$"),
- )
- def normal_user_can_access(method: str, path: str) -> bool:
- if (method, path) in _AUTHENTICATED_USER_PATHS:
- return True
- if (method, path) in _NORMAL_USER_PATHS:
- return True
- return method == "GET" and any(pattern.fullmatch(path) for pattern in _NORMAL_USER_PATTERNS)
- def _is_protected_path(path: str) -> bool:
- return (
- path == "/api"
- or path.startswith("/api/")
- or path in {"/docs", "/redoc", "/openapi.json"}
- )
- class AuthenticationMiddleware(BaseHTTPMiddleware):
- """Authenticate API requests and enforce the two fixed application roles."""
- async def dispatch(
- self,
- request: Request,
- call_next: Callable[[Request], Awaitable[Response]],
- ) -> Response:
- path = request.url.path
- method = request.method.upper()
- if method == "OPTIONS" or not _is_protected_path(path) or path in _PUBLIC_PATHS:
- return await call_next(request)
- token = request.cookies.get(SESSION_COOKIE_NAME)
- user = await run_in_threadpool(resolve_session, token) if token else None
- if user is None:
- return JSONResponse(
- status_code=401,
- content={"detail": "authentication required"},
- )
- request.state.current_user = user
- if user["role"] == ADMIN_ROLE or normal_user_can_access(method, path):
- return await call_next(request)
- return JSONResponse(
- status_code=403,
- content={"detail": "permission denied"},
- )
|