ceeccabc98
Move the users table into the shared persistence engine so auth matches the pattern of threads_meta, runs, run_events, and feedback — one engine, one session factory, one schema init codepath. New files --------- - persistence/user/__init__.py, persistence/user/model.py: UserRow ORM class with partial unique index on (oauth_provider, oauth_id) - Registered in persistence/models/__init__.py so Base.metadata.create_all() picks it up Modified -------- - auth/repositories/sqlite.py: rewritten as async SQLAlchemy, identical constructor pattern to the other four repositories (def __init__(self, session_factory) + self._sf = session_factory) - auth/config.py: drop users_db_path field — storage is configured through config.database like every other table - deps.py/get_local_provider: construct SQLiteUserRepository with the shared session factory, fail fast if engine is not initialised - tests/test_auth.py: rewrite test_sqlite_round_trip_new_fields to use the shared engine (init_engine + close_engine in a tempdir) - tests/test_auth_type_system.py: add per-test autouse fixture that spins up a scratch engine and resets deps._cached_* singletons
58 lines
1.9 KiB
Python
58 lines
1.9 KiB
Python
"""Authentication configuration for DeerFlow."""
|
|
|
|
import logging
|
|
import os
|
|
import secrets
|
|
|
|
from dotenv import load_dotenv
|
|
from pydantic import BaseModel, Field
|
|
|
|
load_dotenv()
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AuthConfig(BaseModel):
|
|
"""JWT and auth-related configuration. Parsed once at startup.
|
|
|
|
Note: the ``users`` table now lives in the shared persistence
|
|
database managed by ``deerflow.persistence.engine``. The old
|
|
``users_db_path`` config key has been removed — user storage is
|
|
configured through ``config.database`` like every other table.
|
|
"""
|
|
|
|
jwt_secret: str = Field(
|
|
...,
|
|
description="Secret key for JWT signing. MUST be set via AUTH_JWT_SECRET.",
|
|
)
|
|
token_expiry_days: int = Field(default=7, ge=1, le=30)
|
|
oauth_github_client_id: str | None = Field(default=None)
|
|
oauth_github_client_secret: str | None = Field(default=None)
|
|
|
|
|
|
_auth_config: AuthConfig | None = None
|
|
|
|
|
|
def get_auth_config() -> AuthConfig:
|
|
"""Get the global AuthConfig instance. Parses from env on first call."""
|
|
global _auth_config
|
|
if _auth_config is None:
|
|
jwt_secret = os.environ.get("AUTH_JWT_SECRET")
|
|
if not jwt_secret:
|
|
jwt_secret = secrets.token_urlsafe(32)
|
|
os.environ["AUTH_JWT_SECRET"] = jwt_secret
|
|
logger.warning(
|
|
"⚠ AUTH_JWT_SECRET is not set — using an auto-generated ephemeral secret. "
|
|
"Sessions will be invalidated on restart. "
|
|
"For production, add AUTH_JWT_SECRET to your .env file: "
|
|
'python -c "import secrets; print(secrets.token_urlsafe(32))"'
|
|
)
|
|
_auth_config = AuthConfig(jwt_secret=jwt_secret)
|
|
return _auth_config
|
|
|
|
|
|
def set_auth_config(config: AuthConfig) -> None:
|
|
"""Set the global AuthConfig instance (for testing)."""
|
|
global _auth_config
|
|
_auth_config = config
|