diff --git a/src/pretix/base/models/auth.py b/src/pretix/base/models/auth.py index ee487057ff..cbda18363c 100644 --- a/src/pretix/base/models/auth.py +++ b/src/pretix/base/models/auth.py @@ -5,6 +5,8 @@ from django.contrib.auth.models import ( from django.db import models from django.utils.translation import ugettext_lazy as _ from django_otp.models import Device +from pretix.base.i18n import language +from pretix.helpers.urls import build_absolute_uri from .base import LoggingMixin @@ -128,6 +130,28 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin): else: return self.email + def send_security_notice(self, messages, email=None): + from pretix.base.services.mail import mail, SendMailException + + try: + with language(self.locale): + msg = '- ' + '\n- '.join(str(m) for m in messages) + + mail( + email or self.email, + _('Account information changed'), + 'pretixcontrol/email/security_notice.txt', + { + 'user': self, + 'messages': msg, + 'url': build_absolute_uri('control:user.settings') + }, + event=None, + locale=self.locale + ) + except SendMailException: + pass # Already logged + class U2FDevice(Device): json_data = models.TextField() diff --git a/src/pretix/control/templates/pretixcontrol/email/security_notice.txt b/src/pretix/control/templates/pretixcontrol/email/security_notice.txt new file mode 100644 index 0000000000..b0f7639fcc --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/email/security_notice.txt @@ -0,0 +1,16 @@ +{% load i18n %}{% blocktrans with url=url|safe %}Hello, + +this is to inform you that the account information of your pretix account has been +changed. In particular, the following changes have been performed: + +{{ messages }} + +If this change was not performed by you, please contact us immediately. + +You can review and change your account settings here: + +{{ url }} + +Best regards, +Your pretix team +{% endblocktrans %} diff --git a/src/pretix/control/views/user.py b/src/pretix/control/views/user.py index 3fc2d01354..331c2c033d 100644 --- a/src/pretix/control/views/user.py +++ b/src/pretix/control/views/user.py @@ -31,6 +31,7 @@ class UserSettings(UpdateView): template_name = 'pretixcontrol/user/settings.html' def get_object(self, queryset=None): + self._old_email = self.request.user.email return self.request.user def get_form_kwargs(self): @@ -44,7 +45,6 @@ class UserSettings(UpdateView): def form_valid(self, form): messages.success(self.request, _('Your changes have been saved.')) - sup = super().form_valid(form) data = {} for k in form.changed_data: @@ -53,6 +53,21 @@ class UserSettings(UpdateView): data['new_pw'] = True else: data[k] = form.cleaned_data[k] + + msgs = [] + + if 'new_pw' in form.changed_data: + msgs.append(_('Your password has been changed.')) + + if 'email' in form.changed_data: + msgs.append(_('Your email address has been changed to {email}.').format(email=form.cleaned_data['email'])) + + if msgs: + self.request.user.send_security_notice(msgs, email=form.cleaned_data['email']) + if self._old_email != form.cleaned_data['email']: + self.request.user.send_security_notice(msgs, email=self._old_email) + + sup = super().form_valid(form) self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data=data) update_session_auth_hash(self.request, self.request.user) @@ -126,11 +141,16 @@ class User2FADeviceDeleteView(TemplateView): 'id': self.device.pk }) self.device.delete() + msgs = [ + _('A two-factor authentication device has been removed from your account.') + ] if not any(dt.objects.filter(user=self.request.user, confirmed=True) for dt in REAL_DEVICE_TYPES): self.request.user.require_2fa = False self.request.user.save() self.request.user.log_action('pretix.user.settings.2fa.disabled', user=self.request.user) + msgs.append(_('Two-factor authentication has been disabled.')) + self.request.user.send_security_notice(msgs) messages.success(request, _('The device has been removed.')) return redirect(reverse('control:user.settings.2fa')) @@ -170,6 +190,9 @@ class User2FADeviceConfirmU2FView(TemplateView): 'id': self.device.pk, 'devicetype': 'u2f' }) + self.request.user.send_security_notice([ + _('A new two-factor authentication device has been added to your account.') + ]) messages.success(request, _('The device has been verified and can now be used.')) return redirect(reverse('control:user.settings.2fa')) @@ -209,6 +232,9 @@ class User2FADeviceConfirmTOTPView(TemplateView): 'id': self.device.pk, 'devicetype': 'totp' }) + self.request.user.send_security_notice([ + _('A new two-factor authentication device has been added to your account.') + ]) messages.success(request, _('The device has been verified and can now be used.')) return redirect(reverse('control:user.settings.2fa')) @@ -235,6 +261,9 @@ class User2FAEnableView(TemplateView): self.request.user.save() self.request.user.log_action('pretix.user.settings.2fa.enabled', user=self.request.user) messages.success(request, _('Two-factor authentication is now enabled for your account.')) + self.request.user.send_security_notice([ + _('Two-factor authentication has been enabled.') + ]) return redirect(reverse('control:user.settings.2fa')) @@ -246,6 +275,9 @@ class User2FADisableView(TemplateView): self.request.user.save() self.request.user.log_action('pretix.user.settings.2fa.disabled', user=self.request.user) messages.success(request, _('Two-factor authentication is now disabled for your account.')) + self.request.user.send_security_notice([ + _('Two-factor authentication has been disabled.') + ]) return redirect(reverse('control:user.settings.2fa')) @@ -258,6 +290,9 @@ class User2FARegenerateEmergencyView(TemplateView): for i in range(10): d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890')) self.request.user.log_action('pretix.user.settings.2fa.regenemergency', user=self.request.user) + self.request.user.send_security_notice([ + _('Your two-factor emergency codes have been regenerated.') + ]) messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe ' 'place in case you lose access to your devices.')) return redirect(reverse('control:user.settings.2fa'))