104 lines
2.9 KiB
Python
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
|