ai-agent / src /ai_agent /api /deps.py
katospiegel's picture
Deploy develop: FastAPI+React frontend, multi-stage Docker (ai_agent serve)
07c2476 verified
Raw
History Blame Contribute Delete
3.29 kB
"""Shared FastAPI dependencies.
- ``require_auth``: simple cookie-based gate using ``APP_PASSWORD`` env var.
If ``APP_PASSWORD`` is empty/unset, auth is disabled entirely (dev mode).
- ``get_pipeline``: returns the singleton ``RAGImagingPipeline`` (lazy-init,
shared with the Gradio path via ``ai_agent.core.pipeline_registry``).
- ``get_doc_index``: name -> SoftwareDoc, derived from the pipeline.
"""
from __future__ import annotations
import hashlib
import hmac
import logging
import os
from typing import Dict, Optional
from fastapi import Cookie, Depends, HTTPException, status
from ai_agent.api.pipeline import RAGImagingPipeline
from ai_agent.core.pipeline_registry import get_pipeline as _shared_pipeline
from ai_agent.retriever.software_doc import SoftwareDoc
log = logging.getLogger("api.deps")
AUTH_COOKIE_NAME = "ai_agent_auth"
_AUTH_VERSION = "v1"
def _expected_cookie() -> Optional[str]:
"""The cookie value clients must present, or None if auth is disabled."""
pw = os.getenv("APP_PASSWORD") or ""
if not pw:
return None
return hmac.new(
pw.encode("utf-8"),
_AUTH_VERSION.encode("utf-8"),
hashlib.sha256,
).hexdigest()
def make_cookie_value(password: str) -> str:
"""Compute the cookie value for a successful login."""
return hmac.new(
password.encode("utf-8"),
_AUTH_VERSION.encode("utf-8"),
hashlib.sha256,
).hexdigest()
def verify_password(password: str) -> bool:
expected = os.getenv("APP_PASSWORD") or ""
if not expected:
# Auth disabled — accept any password (login is then effectively a noop).
return True
return hmac.compare_digest(password, expected)
def auth_disabled() -> bool:
return not (os.getenv("APP_PASSWORD") or "")
async def require_auth(
ai_agent_auth: Optional[str] = Cookie(default=None, alias=AUTH_COOKIE_NAME),
) -> None:
"""FastAPI dependency: 401 if cookie missing or wrong (when auth on)."""
expected = _expected_cookie()
if expected is None:
return # auth disabled
if not ai_agent_auth or not hmac.compare_digest(ai_agent_auth, expected):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="not_authenticated"
)
# ---------------------------------------------------------------------------
# Pipeline / doc index providers (re-export so routers don't import internals).
# ---------------------------------------------------------------------------
def get_pipeline() -> RAGImagingPipeline:
index_dir = os.getenv("RAG_INDEX_DIR", "artifacts/rag_index")
return _shared_pipeline(index_dir=index_dir)
def get_doc_index(
pipe: RAGImagingPipeline = Depends(get_pipeline),
) -> Dict[str, SoftwareDoc]:
try:
docs = list(pipe.index.docs.values())
return {d.name: d for d in docs if getattr(d, "name", None)}
except Exception:
log.exception("Failed to build doc_index from pipeline")
return {}
__all__ = [
"AUTH_COOKIE_NAME",
"auth_disabled",
"get_doc_index",
"get_pipeline",
"make_cookie_value",
"require_auth",
"verify_password",
]