mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-20 07:01:03 +00:00
feat(app): add plugin system with auth plugin and static assets
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>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""LangGraph auth adapter for the auth plugin."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import secrets
|
||||
from types import SimpleNamespace
|
||||
|
||||
from langgraph_sdk import Auth
|
||||
|
||||
from app.plugins.auth.security.dependencies import get_current_user_from_request
|
||||
|
||||
auth = Auth()
|
||||
|
||||
_CSRF_METHODS = frozenset({"POST", "PUT", "DELETE", "PATCH"})
|
||||
|
||||
|
||||
def _check_csrf(request) -> None:
|
||||
method = getattr(request, "method", "") or ""
|
||||
if method.upper() not in _CSRF_METHODS:
|
||||
return
|
||||
|
||||
cookie_token = request.cookies.get("csrf_token")
|
||||
header_token = request.headers.get("x-csrf-token")
|
||||
|
||||
if not cookie_token or not header_token:
|
||||
raise Auth.exceptions.HTTPException(
|
||||
status_code=403,
|
||||
detail="CSRF token missing. Include X-CSRF-Token header.",
|
||||
)
|
||||
|
||||
if not secrets.compare_digest(cookie_token, header_token):
|
||||
raise Auth.exceptions.HTTPException(status_code=403, detail="CSRF token mismatch.")
|
||||
|
||||
|
||||
@auth.authenticate
|
||||
async def authenticate(request):
|
||||
_check_csrf(request)
|
||||
resolver_request = SimpleNamespace(
|
||||
cookies=getattr(request, "cookies", {}),
|
||||
state=SimpleNamespace(_auth_session=getattr(request, "_auth_session", None)),
|
||||
app=SimpleNamespace(state=SimpleNamespace(persistence=getattr(request, "_persistence", None))),
|
||||
)
|
||||
|
||||
try:
|
||||
user = await get_current_user_from_request(resolver_request)
|
||||
except Exception as exc:
|
||||
status_code = getattr(exc, "status_code", None)
|
||||
if status_code is None:
|
||||
raise
|
||||
detail = getattr(exc, "detail", "Not authenticated")
|
||||
message = detail.get("message") if isinstance(detail, dict) else str(detail)
|
||||
raise Auth.exceptions.HTTPException(status_code=status_code, detail=message) from exc
|
||||
|
||||
return user.id
|
||||
|
||||
|
||||
@auth.on
|
||||
async def add_owner_filter(ctx: Auth.types.AuthContext, value: dict):
|
||||
metadata = value.setdefault("metadata", {})
|
||||
metadata["user_id"] = ctx.user.identity
|
||||
return {"user_id": ctx.user.identity}
|
||||
|
||||
|
||||
__all__ = ["add_owner_filter", "auth", "authenticate"]
|
||||
Reference in New Issue
Block a user