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 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e6ae3ccb7
commit
58242117f6
3 files changed
+33
-4
No files matched your search
@@ -51,6 +51,9 @@
|
|||||||
"Note changed successfully!": "Notiz wurde erfolgreich bearbeitet!",
|
"Note changed successfully!": "Notiz wurde erfolgreich bearbeitet!",
|
||||||
"Both passwords must be given": "Beide Passwörter müssen gegeben sein",
|
"Both passwords must be given": "Beide Passwörter müssen gegeben sein",
|
||||||
"Both passwords must be the same": "Beide Passwörter müssen übereinstimmen",
|
"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!",
|
"Password changed successfully!": "Passwort erfolgreich geändert!",
|
||||||
"Admin is not set": "Admin ist nicht gesetzt",
|
"Admin is not set": "Admin ist nicht gesetzt",
|
||||||
"Creation of {email} was successful!": "{email} wurde erfolgreich erstellt!",
|
"Creation of {email} was successful!": "{email} wurde erfolgreich erstellt!",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from flask import flash
|
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.permissions import AllowAdminOrSelf
|
||||||
from max.translations import t
|
from max.translations import t
|
||||||
|
|
||||||
@@ -13,15 +13,34 @@ class UserChangePassword(AllowAdminOrSelf, BackToUsersMixin, FetchUserTemplateGe
|
|||||||
def get_webpagetitle(self):
|
def get_webpagetitle(self):
|
||||||
return t("Change password for {user}", user=self.user.email)
|
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):
|
def post(self):
|
||||||
pw1 = self.request.form.get("password1")
|
pw1 = self.request.form.get("password1")
|
||||||
pw2 = self.request.form.get("password2")
|
pw2 = self.request.form.get("password2")
|
||||||
if not pw1 or not pw2:
|
if not pw1 or not pw2:
|
||||||
flash(t("Both passwords must be given"), category="error")
|
flash(t("Both passwords must be given"), category="error")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(pw1, str) or not isinstance(pw2, str):
|
if not isinstance(pw1, str) or not isinstance(pw2, str):
|
||||||
flash("Invalid data")
|
flash("Invalid data", category="error")
|
||||||
return
|
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:
|
if pw1 != pw2:
|
||||||
flash(t("Both passwords must be the same"), category="error")
|
flash(t("Both passwords must be the same"), category="error")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,14 +5,21 @@
|
|||||||
{% include "alerts.html" %}
|
{% include "alerts.html" %}
|
||||||
|
|
||||||
<form class="pure-form pure-form-aligned" action="{{ url_for('user-change-password', user_id=user.id, return=return_to) }}" method="post">
|
<form class="pure-form pure-form-aligned" action="{{ url_for('user-change-password', user_id=user.id, return=return_to) }}" method="post">
|
||||||
|
{% if is_own_password %}
|
||||||
|
<div class="pure-control-group">
|
||||||
|
<label for="current_password">{{ t("Current password") }}</label>
|
||||||
|
<input id="current_password" type="password" name="current_password" autocomplete="current-password" placeholder="{{ t("Current password") }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="password1">{{ t("Password") }}</label>
|
<label for="password1">{{ t("Password") }}</label>
|
||||||
<input id="password1" type="password" name="password1" placeholder="{{ t("New password") }}">
|
<input id="password1" type="password" name="password1" autocomplete="new-password" placeholder="{{ t("New password") }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="password2">{{ t("Reenter Password") }}</label>
|
<label for="password2">{{ t("Reenter Password") }}</label>
|
||||||
<input id="password2" type="password" name="password2" placeholder="{{ t("New password") }}">
|
<input id="password2" type="password" name="password2" autocomplete="new-password" placeholder="{{ t("New password") }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-controls">
|
<div class="pure-controls">
|
||||||
|
|||||||
Reference in new issue
Block a user