From 58242117f64b237a839d18bd9deb022b956a1f8f Mon Sep 17 00:00:00 2001 From: Finn Stutzenstein Date: Wed, 21 Oct 2026 19:38:14 +0000 Subject: [PATCH] Ask for the current password when changing the own password A hijacked session was enough to change the own password, lock out the owner and get permanent access, including the mailbox via Dovecot. Changing the own password now requires the current password, for users and admins alike. Admins changing the password of another user are not asked, since an admin session can take over accounts in other ways anyway. The new password fields are marked as new-password for password managers. Co-Authored-By: Claude Opus 5.5 --- max/private_static/i18n/de.json | 3 +++ max/routes/user/change_password.py | 23 +++++++++++++++++++++-- max/templates/user/change-password.html | 11 +++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/max/private_static/i18n/de.json b/max/private_static/i18n/de.json index 92f693f..2f20b1f 100644 --- a/max/private_static/i18n/de.json +++ b/max/private_static/i18n/de.json @@ -51,6 +51,9 @@ "Note changed successfully!": "Notiz wurde erfolgreich bearbeitet!", "Both passwords must be given": "Beide Passwörter müssen gegeben sein", "Both passwords must be the same": "Beide Passwörter müssen übereinstimmen", + "Current password": "Aktuelles Passwort", + "The current password must be given": "Das aktuelle Passwort muss gegeben sein", + "The current password is wrong": "Das aktuelle Passwort ist falsch", "Password changed successfully!": "Passwort erfolgreich geändert!", "Admin is not set": "Admin ist nicht gesetzt", "Creation of {email} was successful!": "{email} wurde erfolgreich erstellt!", diff --git a/max/routes/user/change_password.py b/max/routes/user/change_password.py index e481a92..90b119a 100644 --- a/max/routes/user/change_password.py +++ b/max/routes/user/change_password.py @@ -1,6 +1,6 @@ from flask import flash -from max.auth import change_password +from max.auth import change_password, check_password from max.permissions import AllowAdminOrSelf from max.translations import t @@ -13,15 +13,34 @@ class UserChangePassword(AllowAdminOrSelf, BackToUsersMixin, FetchUserTemplateGe def get_webpagetitle(self): return t("Change password for {user}", user=self.user.email) + def is_own_password(self): + return self.user.id == self.auth_user.id + + def get_context(self): + context = super().get_context() + context["is_own_password"] = self.is_own_password() + return context + def post(self): pw1 = self.request.form.get("password1") pw2 = self.request.form.get("password2") if not pw1 or not pw2: flash(t("Both passwords must be given"), category="error") return + if not isinstance(pw1, str) or not isinstance(pw2, str): - flash("Invalid data") + flash("Invalid data", category="error") return + + if self.is_own_password(): + current_password = self.request.form.get("current_password") + if not current_password or not isinstance(current_password, str): + flash(t("The current password must be given"), category="error") + return + if not check_password(current_password, self.user.passwordhash): + flash(t("The current password is wrong"), category="error") + return + if pw1 != pw2: flash(t("Both passwords must be the same"), category="error") return diff --git a/max/templates/user/change-password.html b/max/templates/user/change-password.html index 40e70ee..1cdf013 100644 --- a/max/templates/user/change-password.html +++ b/max/templates/user/change-password.html @@ -5,14 +5,21 @@ {% include "alerts.html" %}
+ {% if is_own_password %} +
+ + +
+ + {% endif %}
- +
- +