feat(settings): eigene Unterseite für User-Einstellungen + Passwort-Wechsel

Header: Profil- und Modell-Dropdowns rausgenommen, dafür ein
"⚙ Einstellungen"-Link. Klick öffnet eine eigene Settings-View
(toggled die main-View aus) mit drei Sektionen:

- Standard-Profil (sofort speichernd)
- Standard-LLM-Modell (sofort speichernd, leer = Worker-Default)
- Passwort ändern (aktuelles + neues + Bestätigung)

Neuer Endpoint POST /api/auth/me/password — verlangt zwingend das
aktuelle Passwort (verhindert Übernahme via offene Browser-Session).
Admin-Reset über /api/admin/users/<id>/password bleibt unberührt.

Layout: Settings-View ist eine Card mit max-width 640px, jede
Sektion durch Trennlinie abgegrenzt. Inline-Status-Meldungen
("Standard-Profil ist jetzt: …") direkt unter den Controls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-05-16 20:40:28 +00:00
co-authored by Claude Opus 4.7
parent f03d46afb5
commit 7679efa7e4
4 changed files with 231 additions and 43 deletions
+27
View File
@@ -71,6 +71,33 @@ def update_me(payload: UpdateMeIn, user: User = Depends(current_user), session:
return _user_dict(user)
class ChangePasswordIn(BaseModel):
current_password: str = Field(min_length=1, max_length=200)
new_password: str = Field(min_length=8, max_length=200)
@router.post("/me/password")
def change_password(
payload: ChangePasswordIn,
user: User = Depends(current_user),
session: Session = Depends(get_session),
):
"""User ändert eigenes Passwort.
Aktuelles Passwort muss korrekt sein — verhindert Übernahme, wenn jemand
eine offene Browser-Session findet. Admin-Reset läuft weiter über
/api/admin/users/<id>/password (ohne current_password).
"""
if not verify_password(payload.current_password, user.password_hash):
log.warning("Password-Change abgelehnt für user=%s (aktuelles Passwort falsch)", user.username)
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Aktuelles Passwort falsch")
user.password_hash = hash_password(payload.new_password)
session.add(user)
session.commit()
log.info("Password geändert für user=%s (id=%d)", user.username, user.id)
return {"ok": True}
@router.get("/needs-setup")
def needs_setup(session: Session = Depends(get_session)):
"""Vor Login prüfen ob noch kein User existiert (erstes Deployment)."""