1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

fix(logs): a lost-OTP mail and a wrong old password leave a row

The "Lost OTP?" page writes a row when it mails a user their QR code link,
or fails to, under the anonymous requester and their address. A password
change refused for a wrong old password writes a failed row.
This commit is contained in:
catborise 2026-11-07 14:26:06 +03:00
parent c5c6de6ba8
commit 56a3ca4c99
3 changed files with 21 additions and 1 deletions

View file

@ -25,6 +25,16 @@ class OwnAccountRowTests(TestCase):
self.assertEqual(rows(), [("User bob: change email", True), ("User bob: change password", True)])
self.assertFalse(Logs.objects.filter(message__contains="pass-").exists())
def test_a_wrong_old_password_is_a_failed_row(self):
# someone at an open session guessing the password
self.client.post(reverse("accounts:change_password"), {
"old_password": "guess-2026", "new_password1": "Bob-new-pass-1", "new_password2": "Bob-new-pass-1"})
self.client.get(reverse("accounts:change_password"))
self.client.post(reverse("accounts:change_password"), { # none given: not a guess
"new_password1": "Bob-new-pass-1", "new_password2": "Bob-new-pass-1"})
self.assertEqual(rows(), [("User bob: change password: wrong old password", False)])
self.assertFalse(Logs.objects.filter(message__contains="pass-").exists())
def test_the_first_login_password_change(self):
self.bob.user_permissions.clear()
UserAttributes.objects.update_or_create(user=self.bob, defaults={"must_change_password": True})

View file

@ -10,6 +10,8 @@ from django.core.cache import cache
from django.test import TestCase, override_settings
from django.urls import reverse
from logs.models import Logs
URL = reverse("accounts:email_otp")
@ -35,6 +37,9 @@ class EmailOTPTests(TestCase):
self.enroll()
self.post("OTP.User@Example.com") # addresses match case-insensitively
self.assertEqual([m.to for m in mail.outbox], [["otp.user@example.com"]])
# the requester is anonymous: the row does not make it the user
self.assertEqual(list(Logs.objects.values_list("user", "message", "success", "ip")),
[("", "User otp-user: mail OTP QR code (lost OTP)", True, "127.0.0.1")])
def test_an_unenrolled_user_gets_no_mail_and_no_device(self):
self.post("otp.user@example.com")
@ -63,6 +68,7 @@ class EmailOTPTests(TestCase):
self.enroll()
with self.assertLogs("accounts.views", level="ERROR"):
self.post("otp.user@example.com")
self.assertIs(Logs.objects.get(message__startswith="User otp-user: mail OTP QR code (lost OTP):").success, False)
@override_settings(OTP_ENABLED=False)

View file

@ -222,6 +222,8 @@ def change_password(request):
logging.getLogger(__name__).warning("Could not remove the generated admin password file")
messages.success(request, _("Password Changed"))
return redirect("accounts:profile")
if form.has_error("old_password", "password_incorrect"): # someone at an open session guessing it
failed(request, None, f"User {request.user.username}", "change password", "wrong old password")
return render(request, "accounts/change_password_form.html", {"form": form, "must_change": must_change})
@ -329,7 +331,9 @@ def email_otp(request):
device = get_existing_totp_device(user)
if device is not None:
try:
send_email_with_otp(request, user)
# the row's user is the requester: anonymous
with audit(request, None, f"User {user.username}", "mail OTP QR code (lost OTP)"):
send_email_with_otp(request, user)
except Exception:
logging.getLogger(__name__).exception("Could not mail the OTP QR code to user %s", user.pk)