mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-04-27 12:18:14 +00:00
Add new application structure: - app/main.py - application entry point - app/plugins/ - plugin system with auth plugin: - api/ - REST API endpoints and schemas - authorization/ - auth policies, providers, hooks - domain/ - business logic (service, models, jwt, password) - injection/ - route injection and guards - ops/ - operational utilities - runtime/ - runtime configuration - security/ - middleware, CSRF, dependencies - storage/ - user repositories and models - app/static/ - static assets (scalar.js for API docs) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
79 lines
2.8 KiB
Python
79 lines
2.8 KiB
Python
"""Global authentication middleware for the auth plugin."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
|
|
from fastapi import HTTPException, Request, Response
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from starlette.responses import JSONResponse
|
|
from starlette.types import ASGIApp
|
|
|
|
from app.plugins.auth.authorization import _ALL_PERMISSIONS, AuthContext
|
|
from app.plugins.auth.domain.errors import AuthErrorCode, AuthErrorResponse
|
|
from app.plugins.auth.injection.registry_loader import RoutePolicyRegistry
|
|
from app.plugins.auth.security.dependencies import get_current_user_from_request
|
|
from deerflow.runtime.actor_context import ActorContext, bind_actor_context, reset_actor_context
|
|
|
|
_PUBLIC_PATH_PREFIXES: tuple[str, ...] = ("/health", "/docs", "/redoc", "/openapi.json")
|
|
|
|
_PUBLIC_EXACT_PATHS: frozenset[str] = frozenset(
|
|
{
|
|
"/api/v1/auth/login/local",
|
|
"/api/v1/auth/register",
|
|
"/api/v1/auth/logout",
|
|
"/api/v1/auth/setup-status",
|
|
"/api/v1/auth/initialize",
|
|
}
|
|
)
|
|
|
|
|
|
def _is_public(path: str) -> bool:
|
|
stripped = path.rstrip("/")
|
|
if stripped in _PUBLIC_EXACT_PATHS:
|
|
return True
|
|
return any(path.startswith(prefix) for prefix in _PUBLIC_PATH_PREFIXES)
|
|
|
|
|
|
class AuthMiddleware(BaseHTTPMiddleware):
|
|
def __init__(self, app: ASGIApp) -> None:
|
|
super().__init__(app)
|
|
|
|
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
registry = getattr(request.app.state, "auth_route_policy_registry", None)
|
|
is_public = False
|
|
if isinstance(registry, RoutePolicyRegistry):
|
|
is_public = registry.is_public_request(request.method, request.url.path)
|
|
if is_public or _is_public(request.url.path):
|
|
return await call_next(request)
|
|
|
|
if not request.cookies.get("access_token"):
|
|
return JSONResponse(
|
|
status_code=401,
|
|
content={
|
|
"detail": AuthErrorResponse(
|
|
code=AuthErrorCode.NOT_AUTHENTICATED,
|
|
message="Authentication required",
|
|
).model_dump()
|
|
},
|
|
)
|
|
|
|
try:
|
|
user = await get_current_user_from_request(request)
|
|
except HTTPException as exc:
|
|
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})
|
|
|
|
auth_context = AuthContext(user=user, permissions=_ALL_PERMISSIONS)
|
|
request.scope["user"] = user
|
|
request.scope["auth"] = auth_context
|
|
request.state.user = user
|
|
request.state.auth = auth_context
|
|
token = bind_actor_context(ActorContext(user_id=str(user.id)))
|
|
try:
|
|
return await call_next(request)
|
|
finally:
|
|
reset_actor_context(token)
|
|
|
|
|
|
__all__ = ["AuthMiddleware", "_is_public"]
|