76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
"""Dev-only bypass when ``DEER_FLOW_AUTH_DISABLED`` is set.
|
|
|
|
Never enable in production: all API and LangGraph routes run as an assumed user.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from datetime import UTC
|
|
from uuid import UUID
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.gateway.auth.models import User
|
|
from deerflow.persistence.engine import get_session_factory
|
|
from deerflow.persistence.user.model import UserRow
|
|
|
|
_FALLBACK_ADMIN_ID = UUID("00000000-0000-4000-8000-000000000001")
|
|
# Must satisfy pydantic EmailStr (avoid reserved/special-use domains like .local).
|
|
_FALLBACK_EMAIL = "anonymous@example.com"
|
|
|
|
_cached_assumed_user: User | None = None
|
|
|
|
|
|
def is_auth_disabled() -> bool:
|
|
"""Return True when auth gate should be bypassed (development / local tooling)."""
|
|
return os.environ.get("DEER_FLOW_AUTH_DISABLED", "").strip().lower() in ("1", "true", "yes", "on")
|
|
|
|
|
|
def _row_to_user(row: UserRow) -> User:
|
|
created = row.created_at
|
|
if created.tzinfo is None:
|
|
created = created.replace(tzinfo=UTC)
|
|
return User(
|
|
id=UUID(row.id),
|
|
email=row.email,
|
|
password_hash=row.password_hash,
|
|
system_role=row.system_role, # type: ignore[arg-type]
|
|
created_at=created,
|
|
oauth_provider=row.oauth_provider,
|
|
oauth_id=row.oauth_id,
|
|
needs_setup=row.needs_setup,
|
|
token_version=row.token_version,
|
|
)
|
|
|
|
|
|
async def get_assumed_user_for_disabled_auth() -> User:
|
|
"""Resolve the user assumed for every request when auth is disabled.
|
|
|
|
Prefer the first admin row in the database so thread ownership matches an
|
|
existing account. If the DB is empty or unavailable, use a stable synthetic
|
|
admin (restart the gateway after creating a real admin to pick them up).
|
|
"""
|
|
global _cached_assumed_user
|
|
if _cached_assumed_user is not None:
|
|
return _cached_assumed_user
|
|
|
|
sf = get_session_factory()
|
|
if sf is not None:
|
|
async with sf() as session:
|
|
stmt = select(UserRow).where(UserRow.system_role == "admin").limit(1)
|
|
row = (await session.execute(stmt)).scalar_one_or_none()
|
|
if row is not None:
|
|
_cached_assumed_user = _row_to_user(row)
|
|
return _cached_assumed_user
|
|
|
|
_cached_assumed_user = User(
|
|
id=_FALLBACK_ADMIN_ID,
|
|
email=_FALLBACK_EMAIL,
|
|
password_hash=None,
|
|
system_role="admin",
|
|
needs_setup=False,
|
|
token_version=0,
|
|
)
|
|
return _cached_assumed_user
|