diff --git a/routers/organisation.py b/routers/organisation.py
index 0ced976..1891221 100644
--- a/routers/organisation.py
+++ b/routers/organisation.py
@@ -1,3 +1,5 @@
+from types import SimpleNamespace
+
from fastapi import APIRouter, Depends, Form, HTTPException, Request
from fastapi.responses import RedirectResponse
from fastapi_users import exceptions
@@ -17,8 +19,14 @@ from templating import templates
router = APIRouter()
-@router.get("/organisation")
-def organisation_ansicht(request: Request, user=Depends(get_current_user)):
+def _organisation_kontext(user: dict) -> dict:
+ """
+ Gemeinsamer Seitenkontext für /organisation - wird sowohl vom normalen
+ GET als auch von passwort_aendern() bei einem Fehler gebraucht (dort
+ muss die Seite mit allen bestehenden Daten neu gerendert werden, nicht
+ nur mit der Fehlermeldung).
+ """
+
result = anode_request("GET", f"/api/v1/organizations/{user.organization_id}")
if not result.get("success"):
@@ -26,11 +34,12 @@ def organisation_ansicht(request: Request, user=Depends(get_current_user)):
benutzer = fetch_benutzer_fuer_organisation(str(user.organization_id))
- return templates.TemplateResponse(
- request,
- "organisation.html",
- {"user": user, "organization": result["organization"], "benutzer": benutzer},
- )
+ return {"user": user, "organization": result["organization"], "benutzer": benutzer}
+
+
+@router.get("/organisation")
+def organisation_ansicht(request: Request, user=Depends(get_current_user)):
+ return templates.TemplateResponse(request, "organisation.html", _organisation_kontext(user))
@router.post("/organisation")
@@ -118,3 +127,50 @@ async def konto_loeschen(
await user_manager.delete(ziel_user)
return RedirectResponse("/organisation", status_code=303)
+
+
+@router.post("/organisation/passwort")
+async def passwort_aendern(
+ request: Request,
+ aktuelles_passwort: str = Form(...),
+ neues_passwort: str = Form(...),
+ neues_passwort_wiederholung: str = Form(...),
+ user=Depends(get_current_user),
+ user_manager: UserManager = Depends(get_user_manager),
+):
+ """
+ Self-Service-Passwortänderung für den eingeloggten Nutzer selbst -
+ anders als /organisation/konten (fremdes Konto anlegen/löschen, nur für
+ require_org_admin) braucht das hier keine besondere Rolle, jeder
+ eingeloggte Nutzer darf sein eigenes Passwort ändern.
+ """
+
+ fehler = None
+
+ if neues_passwort != neues_passwort_wiederholung:
+ fehler = "Die Wiederholung stimmt nicht mit dem neuen Passwort überein."
+ else:
+ # Gleiches Authenticate()-Muster wie beim Login (auth.py) - prüft
+ # das aktuelle Passwort, ohne eine eigene Vergleichslogik zu bauen.
+ authentifiziert = await user_manager.authenticate(
+ SimpleNamespace(username=user.email, password=aktuelles_passwort)
+ )
+
+ if authentifiziert is None:
+ fehler = "Das aktuelle Passwort ist falsch."
+ else:
+ try:
+ await user_manager.validate_password(neues_passwort, user)
+ except exceptions.InvalidPasswordException as exc:
+ fehler = exc.reason
+ else:
+ await user_manager.user_db.update(
+ user, {"hashed_password": user_manager.password_helper.hash(neues_passwort)}
+ )
+
+ if fehler:
+ kontext = _organisation_kontext(user)
+ kontext["passwort_fehler"] = fehler
+ return templates.TemplateResponse(request, "organisation.html", kontext, status_code=400)
+
+ return RedirectResponse("/organisation", status_code=303)
diff --git a/templates/organisation.html b/templates/organisation.html
index 1ab50c4..8c8cb9a 100644
--- a/templates/organisation.html
+++ b/templates/organisation.html
@@ -25,6 +25,24 @@
+
+ Mein Konto
+ {% if passwort_fehler %}{{ passwort_fehler }}
{% endif %}
+
+
+
Kolleg:innen
{% if not benutzer %}