rayhpeng 0f82f8a3a2 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>
2026-04-22 11:31:42 +08:00

65 lines
2.0 KiB
Python

"""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"]