Kansho/backend/routers/auth.py

104 lines
2.9 KiB
Python

from __future__ import annotations
import uuid
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from auth import create_session, hash_password, require_auth, verify_password
from db import get_db, row_to_dict
from version import APP_VERSION
router = APIRouter(prefix="/api/auth", tags=["auth"])
class LoginRequest(BaseModel):
email: str
password: str = Field(min_length=4)
class SetupRequest(BaseModel):
email: str
name: str = Field(min_length=1)
password: str = Field(min_length=4)
def _profile_count() -> int:
with get_db() as conn:
row = conn.execute("SELECT COUNT(*) AS n FROM profiles").fetchone()
return int(row["n"])
@router.get("/status")
def auth_status():
return {
"status": "ok",
"service": "kansho",
"version": APP_VERSION,
"needs_setup": _profile_count() == 0,
}
@router.post("/setup")
def setup(req: SetupRequest):
if _profile_count() > 0:
raise HTTPException(409, "Setup ist bereits abgeschlossen")
profile_id = str(uuid.uuid4())
with get_db() as conn:
conn.execute(
"""
INSERT INTO profiles (id, email, name, password_hash, role)
VALUES (?, ?, ?, ?, 'admin')
""",
(profile_id, req.email.lower().strip(), req.name.strip(), hash_password(req.password)),
)
token, expires = create_session(profile_id)
return {
"token": token,
"profile_id": profile_id,
"name": req.name.strip(),
"role": "admin",
"expires_at": expires.isoformat(),
}
@router.post("/login")
def login(req: LoginRequest):
with get_db() as conn:
prof = row_to_dict(
conn.execute(
"SELECT * FROM profiles WHERE email = ?",
(req.email.lower().strip(),),
).fetchone()
)
if not prof or not verify_password(req.password, prof["password_hash"]):
raise HTTPException(401, "Ungültige Zugangsdaten")
if (prof.get("status") or "active") != "active":
raise HTTPException(401, "Ungültige Zugangsdaten")
token, expires = create_session(prof["id"], prof.get("session_days") or 30)
return {
"token": token,
"profile_id": prof["id"],
"name": prof["name"],
"role": prof["role"],
"expires_at": expires.isoformat(),
}
@router.post("/logout")
def logout(session: dict = Depends(require_auth)):
with get_db() as conn:
conn.execute("DELETE FROM sessions WHERE token = ?", (session["token"],))
return {"ok": True}
@router.get("/me")
def me(session: dict = Depends(require_auth)):
with get_db() as conn:
prof = row_to_dict(
conn.execute("SELECT id, email, name, role, status, tier_id FROM profiles WHERE id = ?", (session["profile_id"],)).fetchone()
)
if not prof:
raise HTTPException(401, "Profil nicht gefunden")
return prof