| """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:
|
|
|
| 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
|
| 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"
|
| )
|
|
|
|
|
|
|
|
|
|
|
| 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",
|
| ]
|
|
|