hp-studios-ai-content-agent/backend/app/api/auth.py
DJP 72c8a0d0fe Initial import — HP Studios AI Content Agent
Full-stack app that turns HP customer briefs (master asset + regional
supporting docs) into a set of branded Word deliverables via a RAG +
agent pipeline.

Stack
- FastAPI + SQLAlchemy + pgvector + RQ (backend, Python 3.12)
- React + Vite + TypeScript + Tailwind + TanStack Query (frontend)
- Claude Opus 4.7 (generation) + Haiku 4.5 (translation/OCR)
- Voyage voyage-3 or OpenAI text-embedding-3-small (embeddings)
- python-docx (branded Word output, Montserrat + HP blue)
- Docker Compose (5 services)

Features
- 6 built-in deliverable types (leadership themes, regional enrichment,
  LinkedIn posts, webinar spec, infographic specs, ABM enablement)
- Data-driven deliverable types: admins add new types at runtime via
  prompt + JSON schema + template_json — no code, no deploy
- Generic schema-driven review form + generic Word template renderer
- Document ingestion pipeline with translation, chunking, pgvector RAG
- Pluggable auth provider (password now, Entra SSO later); admin/user roles
- Re-roll / retry on every deliverable; cascading delete; brief editing;
  inline document upload; progress hints; router-level ErrorBoundary
- Admin panel with test-render preview for new deliverable types
- Help page at /help with architecture overview and usage guide

82 backend tests passing, 18 skipped (gated live-API tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:11:25 -04:00

88 lines
2.4 KiB
Python

from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Response, status
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from app.core.auth import get_auth_provider
from app.core.deps import get_current_user
from app.core.security import create_access_token
from app.db.models import User
from app.db.session import get_db
router = APIRouter(prefix="/auth", tags=["auth"])
# ---------------------------------------------------------------------------
# Request / response schemas
# ---------------------------------------------------------------------------
class LoginRequest(BaseModel):
email: EmailStr
password: str
class UserOut(BaseModel):
id: str
email: str
name: str
role: str
model_config = {"from_attributes": True}
class LoginResponse(BaseModel):
access_token: str
token_type: str = "bearer"
user: UserOut
# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------
@router.post("/login", response_model=LoginResponse)
def login(
body: LoginRequest,
response: Response,
db: Session = Depends(get_db),
) -> Any:
provider = get_auth_provider()
user: User | None = provider.authenticate(body.email, body.password, db)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
)
token = create_access_token(subject=str(user.id))
# httpOnly cookie — consumed by SSR / same-origin frontend
response.set_cookie(
key="access_token",
value=token,
httponly=True,
samesite="lax",
secure=False, # set True behind HTTPS in production
max_age=60 * 60 * 8,
)
return LoginResponse(
access_token=token,
user=UserOut(id=str(user.id), email=user.email, name=user.name, role=user.role),
)
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
def logout(response: Response) -> None:
response.delete_cookie("access_token")
@router.get("/me", response_model=UserOut)
def me(current_user: User = Depends(get_current_user)) -> Any:
return UserOut(
id=str(current_user.id),
email=current_user.email,
name=current_user.name,
role=current_user.role,
)