Self-Service: Passwort ändern (POST /organisation/passwort)
Neue Sektion 'Mein Konto' auf der bestehenden /organisation-Seite. Jeder eingeloggte Nutzer darf sein eigenes Passwort ändern (kein require_org_admin nötig, anders als bei /organisation/konten für fremde Konten). Prüft aktuelles Passwort per user_manager.authenticate() (gleiches Muster wie der Login), validiert das neue Passwort explizit gegen die bestehende Policy (>=12 Zeichen, nicht gleich der E-Mail) aus der Sicherheitshärtung vom 25.08.2026, zeigt Fehler inline auf derselben Seite statt als nackte HTTPException. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fc03a3c3a9
commit
1fdac84220
@ -1,3 +1,5 @@
|
|||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request
|
from fastapi import APIRouter, Depends, Form, HTTPException, Request
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
from fastapi_users import exceptions
|
from fastapi_users import exceptions
|
||||||
@ -17,8 +19,14 @@ from templating import templates
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/organisation")
|
def _organisation_kontext(user: dict) -> dict:
|
||||||
def organisation_ansicht(request: Request, user=Depends(get_current_user)):
|
"""
|
||||||
|
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}")
|
result = anode_request("GET", f"/api/v1/organizations/{user.organization_id}")
|
||||||
|
|
||||||
if not result.get("success"):
|
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))
|
benutzer = fetch_benutzer_fuer_organisation(str(user.organization_id))
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return {"user": user, "organization": result["organization"], "benutzer": benutzer}
|
||||||
request,
|
|
||||||
"organisation.html",
|
|
||||||
{"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")
|
@router.post("/organisation")
|
||||||
@ -118,3 +127,50 @@ async def konto_loeschen(
|
|||||||
await user_manager.delete(ziel_user)
|
await user_manager.delete(ziel_user)
|
||||||
|
|
||||||
return RedirectResponse("/organisation", status_code=303)
|
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)
|
||||||
|
|||||||
@ -25,6 +25,24 @@
|
|||||||
<button type="submit">Speichern</button>
|
<button type="submit">Speichern</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Mein Konto</h3>
|
||||||
|
{% if passwort_fehler %}<p><mark>{{ passwort_fehler }}</mark></p>{% endif %}
|
||||||
|
<form method="post" action="/organisation/passwort">
|
||||||
|
<label for="aktuelles_passwort">Aktuelles Passwort</label>
|
||||||
|
<input type="password" id="aktuelles_passwort" name="aktuelles_passwort" required>
|
||||||
|
|
||||||
|
<label for="neues_passwort">Neues Passwort</label>
|
||||||
|
<input type="password" id="neues_passwort" name="neues_passwort" required minlength="12">
|
||||||
|
<small>Mindestens 12 Zeichen, darf nicht der E-Mail-Adresse entsprechen.</small>
|
||||||
|
|
||||||
|
<label for="neues_passwort_wiederholung">Neues Passwort (Wiederholung)</label>
|
||||||
|
<input type="password" id="neues_passwort_wiederholung" name="neues_passwort_wiederholung" required minlength="12">
|
||||||
|
|
||||||
|
<button type="submit">Passwort ändern</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h3>Kolleg:innen</h3>
|
<h3>Kolleg:innen</h3>
|
||||||
{% if not benutzer %}
|
{% if not benutzer %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user