banyaro/backend/routes/auth.py
rene b58789373c Sprint 12: UI-Vereinheitlichung + Läufigkeits-Tracker
- by-tabs/by-tab: einheitliche Tab/Pill-Navigation in allen Seiten
- by-section-label, by-toolbar: einheitliche Section-Labels und Toolbars
- Design-Tokens: fehlende --c-amber, --c-primary-soft ergänzt, Fallback-Werte entfernt
- sitting.js: sitting-layout für konsistentes flush-Layout (wie walks)
- Läufigkeits-Tracker: neuer Health-Tab für Hündinnen mit Zyklusvorhersage,
  Timeline vergangener Läufigkeiten, Erinnerungen und auto-berechnetem Nächst-Datum
- emptyState-Bug: icon-Parameter muss SVG sein, nicht Icon-Name (dog/bell/warning gefixt)
- SW-Cache: by-v103, APP_VER: 79
2026-04-16 22:31:33 +02:00

101 lines
3.1 KiB
Python

"""BAN YARO — Auth Routes"""
from fastapi import APIRouter, HTTPException, Response, Depends
from pydantic import BaseModel, EmailStr
from database import db
from auth import (
hash_password, verify_password, create_token,
get_current_user
)
router = APIRouter()
COOKIE_NAME = "by_token"
class LoginRequest(BaseModel):
email: EmailStr
password: str
class RegisterRequest(BaseModel):
email: EmailStr
password: str
name: str
def _set_cookie(response: Response, token: str):
response.set_cookie(
key=COOKIE_NAME, value=token,
httponly=True, secure=True, samesite="lax",
max_age=30 * 24 * 3600
)
@router.post("/register")
async def register(data: RegisterRequest, response: Response):
name = data.name.strip()
if len(name) < 2:
raise HTTPException(400, "Name muss mindestens 2 Zeichen lang sein.")
if len(name) > 40:
raise HTTPException(400, "Name darf maximal 40 Zeichen lang sein.")
with db() as conn:
if conn.execute("SELECT 1 FROM users WHERE email=?", (data.email,)).fetchone():
raise HTTPException(400, "E-Mail bereits registriert.")
if conn.execute(
"SELECT 1 FROM users WHERE name=? COLLATE NOCASE", (name,)
).fetchone():
raise HTTPException(400, "Dieser Name ist bereits vergeben. Bitte wähle einen anderen.")
try:
conn.execute(
"INSERT INTO users (email, pw_hash, name) VALUES (?,?,?)",
(data.email, hash_password(data.password), name)
)
except Exception:
# Fallback falls UNIQUE-Index greift (Race Condition)
raise HTTPException(400, "Dieser Name ist bereits vergeben. Bitte wähle einen anderen.")
user = conn.execute(
"SELECT id, rolle FROM users WHERE email=?", (data.email,)
).fetchone()
token = create_token(user["id"], user["rolle"])
_set_cookie(response, token)
return {"token": token, "name": name}
@router.post("/login")
async def login(data: LoginRequest, response: Response):
with db() as conn:
user = conn.execute(
"SELECT id, pw_hash, name, rolle, is_premium FROM users WHERE email=?",
(data.email,)
).fetchone()
if not user or not verify_password(data.password, user["pw_hash"]):
raise HTTPException(401, "E-Mail oder Passwort falsch.")
token = create_token(user["id"], user["rolle"])
_set_cookie(response, token)
with db() as conn:
conn.execute(
"UPDATE users SET last_login=datetime('now') WHERE id=?", (user["id"],)
)
return {"token": token, "name": user["name"], "is_premium": bool(user["is_premium"])}
@router.post("/logout")
async def logout(response: Response):
response.delete_cookie(COOKIE_NAME)
return {"ok": True}
@router.get("/me")
async def me(user=Depends(get_current_user)):
return {
"id": user["id"],
"name": user["name"],
"email": user["email"],
"rolle": user["rolle"],
"is_premium": bool(user["is_premium"]),
}