"""Auth-Endpoints: Login, Logout, Me, Erst-Setup (Initial-Admin).""" from __future__ import annotations import logging from fastapi import APIRouter, Depends, HTTPException, Request, status from pydantic import BaseModel, Field from sqlmodel import Session from app.core import migrate from app.core.auth import ( current_user, get_user_by_username, has_any_user, hash_password, verify_password, ) from app.core.db import get_session from app.models.user import User router = APIRouter(prefix="/api/auth", tags=["auth"]) log = logging.getLogger("voice-agent.auth") class LoginIn(BaseModel): username: str = Field(min_length=1, max_length=64) password: str = Field(min_length=1, max_length=200) class SetupIn(BaseModel): username: str = Field(min_length=3, max_length=64, pattern=r"^[A-Za-z0-9._@-]+$") password: str = Field(min_length=8, max_length=200) def _user_dict(u: User) -> dict: return {"id": u.id, "username": u.username, "is_admin": u.is_admin} @router.get("/me") def me(user: User = Depends(current_user)): return _user_dict(user) @router.get("/needs-setup") def needs_setup(session: Session = Depends(get_session)): """Vor Login prüfen ob noch kein User existiert (erstes Deployment).""" return {"needs_setup": not has_any_user(session)} @router.post("/login") def login(payload: LoginIn, request: Request, session: Session = Depends(get_session)): user = get_user_by_username(session, payload.username.strip()) if not user or not verify_password(payload.password, user.password_hash): raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Benutzername oder Passwort falsch") request.session["user_id"] = user.id log.info("Login: user=%s id=%d admin=%s", user.username, user.id, user.is_admin) return _user_dict(user) @router.post("/logout") def logout(request: Request): request.session.clear() return {"ok": True} @router.post("/setup") def setup(payload: SetupIn, request: Request, session: Session = Depends(get_session)): """Erstellt den ersten Admin und ordnet alle Bestand-Jobs ihm zu. Nur möglich, solange kein User existiert.""" if has_any_user(session): raise HTTPException(status.HTTP_409_CONFLICT, "Setup bereits abgeschlossen") user = User( username=payload.username.strip(), password_hash=hash_password(payload.password), is_admin=True, ) session.add(user) session.commit() session.refresh(user) log.info("Initial-Admin angelegt: %s (id=%d)", user.username, user.id) n = migrate.assign_orphan_jobs_to(user.id) request.session["user_id"] = user.id return {**_user_dict(user), "migrated_jobs": n}