mirror of
https://github.com/pretix/pretix.git
synced 2026-08-09 10:37:50 +00:00
Improve email and password change forms, implement is_verified flag
This commit is contained in:
@@ -214,21 +214,36 @@ class PasswordRecoverForm(forms.Form):
|
||||
error_messages = {
|
||||
'pw_mismatch': _("Please enter the same password twice"),
|
||||
}
|
||||
email = forms.EmailField(max_length=255,
|
||||
disabled=True,
|
||||
label=_("Your email address"),
|
||||
widget=forms.EmailInput(
|
||||
attrs={'autocomplete': 'username'},
|
||||
))
|
||||
password = forms.CharField(
|
||||
label=_('Password'),
|
||||
widget=forms.PasswordInput,
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'autocomplete': 'new-password',
|
||||
}),
|
||||
max_length=4096,
|
||||
required=True
|
||||
)
|
||||
password_repeat = forms.CharField(
|
||||
label=_('Repeat password'),
|
||||
widget=forms.PasswordInput,
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'autocomplete': 'new-password',
|
||||
}),
|
||||
max_length=4096,
|
||||
)
|
||||
|
||||
def __init__(self, user_id=None, *args, **kwargs):
|
||||
self.user_id = user_id
|
||||
super().__init__(*args, **kwargs)
|
||||
try:
|
||||
self.user = User.objects.get(id=user_id)
|
||||
except User.DoesNotExist:
|
||||
self.user = None
|
||||
initial = kwargs.pop('initial', {})
|
||||
initial['email'] = self.user.email
|
||||
super().__init__(*args, initial=initial, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
password1 = self.cleaned_data.get('password', '')
|
||||
@@ -243,11 +258,7 @@ class PasswordRecoverForm(forms.Form):
|
||||
|
||||
def clean_password(self):
|
||||
password1 = self.cleaned_data.get('password', '')
|
||||
try:
|
||||
user = User.objects.get(id=self.user_id)
|
||||
except User.DoesNotExist:
|
||||
user = None
|
||||
if validate_password(password1, user=user) is not None:
|
||||
if validate_password(password1, user=self.user) is not None:
|
||||
raise forms.ValidationError(_(password_validators_help_texts()), code='pw_invalid')
|
||||
return password1
|
||||
|
||||
@@ -307,3 +318,11 @@ class ReauthForm(forms.Form):
|
||||
self.error_messages['inactive'],
|
||||
code='inactive',
|
||||
)
|
||||
|
||||
|
||||
class ConfirmationCodeForm(forms.Form):
|
||||
code = forms.IntegerField(
|
||||
label='',
|
||||
widget=forms.NumberInput(attrs={'class': 'confirmation-code-input', 'inputmode': 'numeric', 'type': 'text'}),
|
||||
)
|
||||
|
||||
|
||||
@@ -39,37 +39,17 @@ from django.contrib.auth.password_validation import (
|
||||
password_validators_help_texts, validate_password,
|
||||
)
|
||||
from django.db.models import Q
|
||||
from django.urls.base import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pytz import common_timezones
|
||||
|
||||
from pretix.base.models import User
|
||||
from pretix.control.forms import SingleLanguageWidget
|
||||
from pretix.helpers.format import format_map
|
||||
|
||||
|
||||
class UserSettingsForm(forms.ModelForm):
|
||||
error_messages = {
|
||||
'duplicate_identifier': _("There already is an account associated with this email address. "
|
||||
"Please choose a different one."),
|
||||
'pw_current': _("Please enter your current password if you want to change your email address "
|
||||
"or password."),
|
||||
'pw_current_wrong': _("The current password you entered was not correct."),
|
||||
'pw_mismatch': _("Please enter the same password twice"),
|
||||
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
|
||||
'pw_equal': _("Please choose a password different to your current one.")
|
||||
}
|
||||
|
||||
old_pw = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("Your current password"),
|
||||
widget=forms.PasswordInput())
|
||||
new_pw = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("New password"),
|
||||
widget=forms.PasswordInput())
|
||||
new_pw_repeat = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("Repeat new password"),
|
||||
widget=forms.PasswordInput())
|
||||
timezone = forms.ChoiceField(
|
||||
choices=((a, a) for a in common_timezones),
|
||||
label=_("Default timezone"),
|
||||
@@ -93,11 +73,75 @@ class UserSettingsForm(forms.ModelForm):
|
||||
self.user = kwargs.pop('user')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['email'].required = True
|
||||
if self.user.auth_backend != 'native':
|
||||
del self.fields['old_pw']
|
||||
del self.fields['new_pw']
|
||||
del self.fields['new_pw_repeat']
|
||||
self.fields['email'].disabled = True
|
||||
self.fields['email'].disabled = True
|
||||
self.fields['email'].help_text = format_map('<a href="{link}"><span class="fa fa-edit"></span> {text}</a>', {
|
||||
'text': _("Change email address"),
|
||||
'link': reverse('control:user.settings.email.change')
|
||||
})
|
||||
|
||||
def clean(self):
|
||||
password1 = self.cleaned_data.get('new_pw')
|
||||
old_pw = self.cleaned_data.get('old_pw')
|
||||
|
||||
if password1 and password1 == old_pw:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['pw_equal'],
|
||||
code='pw_equal'
|
||||
)
|
||||
|
||||
if password1:
|
||||
self.instance.set_password(password1)
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class User2FADeviceAddForm(forms.Form):
|
||||
name = forms.CharField(label=_('Device name'), max_length=64)
|
||||
devicetype = forms.ChoiceField(label=_('Device type'), widget=forms.RadioSelect, choices=(
|
||||
('totp', _('Smartphone with the Authenticator application')),
|
||||
('webauthn', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
|
||||
))
|
||||
|
||||
|
||||
class UserPasswordChangeForm(forms.Form):
|
||||
error_messages = {
|
||||
'pw_current': _("Please enter your current password if you want to change your email address "
|
||||
"or password."),
|
||||
'pw_current_wrong': _("The current password you entered was not correct."),
|
||||
'pw_mismatch': _("Please enter the same password twice"),
|
||||
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
|
||||
'pw_equal': _("Please choose a password different to your current one.")
|
||||
}
|
||||
email = forms.EmailField(max_length=255,
|
||||
disabled=True,
|
||||
label=_("Your email address"),
|
||||
widget=forms.EmailInput(
|
||||
attrs={'autocomplete': 'username'},
|
||||
))
|
||||
old_pw = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("Your current password"),
|
||||
widget=forms.PasswordInput(
|
||||
attrs={'autocomplete': 'current-password'},
|
||||
))
|
||||
new_pw = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("New password"),
|
||||
widget=forms.PasswordInput(
|
||||
attrs={'autocomplete': 'new-password'},
|
||||
))
|
||||
new_pw_repeat = forms.CharField(max_length=255,
|
||||
required=False,
|
||||
label=_("Repeat new password"),
|
||||
widget=forms.PasswordInput(
|
||||
attrs={'autocomplete': 'new-password'},
|
||||
))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user')
|
||||
initial = kwargs.pop('initial', {})
|
||||
initial['email'] = self.user.email
|
||||
super().__init__(*args, initial=initial, **kwargs)
|
||||
|
||||
def clean_old_pw(self):
|
||||
old_pw = self.cleaned_data.get('old_pw')
|
||||
@@ -121,15 +165,6 @@ class UserSettingsForm(forms.ModelForm):
|
||||
|
||||
return old_pw
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data['email']
|
||||
if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.instance.pk)).exists():
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['duplicate_identifier'],
|
||||
code='duplicate_identifier',
|
||||
)
|
||||
return email
|
||||
|
||||
def clean_new_pw(self):
|
||||
password1 = self.cleaned_data.get('new_pw', '')
|
||||
if password1 and validate_password(password1, user=self.user) is not None:
|
||||
@@ -148,32 +183,24 @@ class UserSettingsForm(forms.ModelForm):
|
||||
code='pw_mismatch'
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
password1 = self.cleaned_data.get('new_pw')
|
||||
email = self.cleaned_data.get('email')
|
||||
old_pw = self.cleaned_data.get('old_pw')
|
||||
|
||||
if (password1 or email != self.user.email) and not old_pw:
|
||||
|
||||
class UserEmailChangeForm(forms.Form):
|
||||
error_messages = {
|
||||
'duplicate_identifier': _("There already is an account associated with this email address. "
|
||||
"Please choose a different one."),
|
||||
}
|
||||
new_email = forms.EmailField(label=_('New email address'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_new_email(self):
|
||||
email = self.cleaned_data['new_email']
|
||||
if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.user.pk)).exists():
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['pw_current'],
|
||||
code='pw_current'
|
||||
self.error_messages['duplicate_identifier'],
|
||||
code='duplicate_identifier',
|
||||
)
|
||||
|
||||
if password1 and password1 == old_pw:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['pw_equal'],
|
||||
code='pw_equal'
|
||||
)
|
||||
|
||||
if password1:
|
||||
self.instance.set_password(password1)
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class User2FADeviceAddForm(forms.Form):
|
||||
name = forms.CharField(label=_('Device name'), max_length=64)
|
||||
devicetype = forms.ChoiceField(label=_('Device type'), widget=forms.RadioSelect, choices=(
|
||||
('totp', _('Smartphone with the Authenticator application')),
|
||||
('webauthn', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
|
||||
))
|
||||
return email
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.2.23 on 2025-09-04 12:58
|
||||
# Generated by Django 4.2.23 on 2025-09-04 16:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -12,7 +12,7 @@ class Migration(migrations.Migration):
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="verified_email",
|
||||
field=models.EmailField(max_length=190, null=True),
|
||||
name="is_verified",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
@@ -35,6 +35,7 @@
|
||||
import binascii
|
||||
import json
|
||||
import operator
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from functools import reduce
|
||||
|
||||
@@ -243,7 +244,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
|
||||
email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
|
||||
verbose_name=_('Email'), max_length=190)
|
||||
verified_email = models.EmailField(null=True, blank=True, verbose_name=_('Verified Email'), max_length=190)
|
||||
is_verified = models.BooleanField(default=True, verbose_name=_('Verified email address'))
|
||||
fullname = models.CharField(max_length=255, blank=True, null=True,
|
||||
verbose_name=_('Full name'))
|
||||
is_active = models.BooleanField(default=True,
|
||||
@@ -355,6 +356,43 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
except SendMailException:
|
||||
pass # Already logged
|
||||
|
||||
def send_confirmation_code(self, reason, email=None):
|
||||
from pretix.base.services.mail import mail
|
||||
|
||||
with language(self.locale):
|
||||
if reason == 'email_change':
|
||||
msg = str(_('to confirm changing your email address from {old_email}\nto {new_email}, use the following code:').format(
|
||||
old_email=self.email, new_email=email,
|
||||
))
|
||||
else:
|
||||
raise Exception('Invalid confirmation code reason')
|
||||
|
||||
code = "%07d" % random.randint(0, 9999999)
|
||||
cache.set('user_confirmation_code:' + str(self.pk), code + ':' + reason + ':' + str(email), 1800)
|
||||
|
||||
mail(
|
||||
email or self.email,
|
||||
_('pretix confirmation code'),
|
||||
'pretixcontrol/email/confirmation_code.txt',
|
||||
{
|
||||
'user': self,
|
||||
'reason': msg,
|
||||
'code': code,
|
||||
},
|
||||
event=None,
|
||||
user=self,
|
||||
locale=self.locale
|
||||
)
|
||||
|
||||
def check_confirmation_code(self, reason, code):
|
||||
stored = cache.get('user_confirmation_code:' + str(self.pk))
|
||||
if not stored:
|
||||
return None
|
||||
stored_code, stored_reason, email = stored.split(":", maxsplit=2)
|
||||
if int(stored_code) == int(code) and stored_reason == reason:
|
||||
return email
|
||||
|
||||
|
||||
def send_password_reset(self):
|
||||
from pretix.base.services.mail import mail
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{% extends "pretixcontrol/auth/base.html" %}
|
||||
{% load bootstrap3 %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<form class="form-signin" action="" method="post">
|
||||
<h3>{% trans "Enter confirmation code" %}</h3>
|
||||
{% csrf_token %}
|
||||
<p>{{ message }}</p>
|
||||
{% bootstrap_form_errors form type='all' layout='inline' %}
|
||||
{% bootstrap_field form.code %}
|
||||
<div class="form-group buttons">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{% trans "Okay" %}
|
||||
</button>
|
||||
<a href="{{ cancel_url }}" class="btn btn-default">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -7,6 +7,7 @@
|
||||
<h3>{% trans "Set new password" %}</h3>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form type='all' layout='inline' %}
|
||||
{% bootstrap_field form.email %}
|
||||
{% bootstrap_field form.password %}
|
||||
{% bootstrap_field form.password_repeat %}
|
||||
<div class="form-group buttons">
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{% load i18n %}{% blocktrans with url=url|safe messages=messages|safe %}Hello,
|
||||
|
||||
{{ reason }}
|
||||
|
||||
{{ code }}
|
||||
|
||||
Please do never give this code to another person. Our support team will never ask for this code.
|
||||
|
||||
If this code was not requested by you, please contact us immediately.
|
||||
|
||||
Best regards,
|
||||
Your pretix team
|
||||
{% endblocktrans %}
|
||||
@@ -0,0 +1,21 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Change login email address" %}{% endblock %}
|
||||
{% block content %}
|
||||
<form action="" method="post" class="form centered-form">
|
||||
<h1>
|
||||
{% trans "Change login email address" %}
|
||||
</h1>
|
||||
<br>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.new_email %}
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn btn-primary btn-save btn-lg">
|
||||
{% trans "Continue" %}
|
||||
</button>
|
||||
<a href="{% url "control:user.settings" %}" class="btn btn-default btn-lg">{% trans "Cancel" %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Change password" %}{% endblock %}
|
||||
{% block content %}
|
||||
<form action="" method="post" class="form centered-form">
|
||||
<h1>
|
||||
{% trans "Change password" %}
|
||||
</h1>
|
||||
<br>
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.email %}
|
||||
{% bootstrap_field form.old_pw %}
|
||||
{% bootstrap_field form.new_pw %}
|
||||
{% bootstrap_field form.new_pw_repeat %}
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn btn-primary btn-save btn-lg">
|
||||
{% trans "Change password" %}
|
||||
</button>
|
||||
<a href="{% url "control:user.settings" %}" class="btn btn-default btn-lg">{% trans "Cancel" %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -46,8 +46,18 @@
|
||||
{% bootstrap_field form.new_pw layout='horizontal' %}
|
||||
{% bootstrap_field form.new_pw_repeat layout='horizontal' %}
|
||||
{% endif %}
|
||||
{% if user.auth_backend == 'native' %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">{% trans "Password" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
<a href="{% url "control:user.settings.password.change" %}">
|
||||
<span class="fa fa-edit"></span> {% trans "Change password" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_new_pw_repeat">{% trans "Two-factor authentication" %}</label>
|
||||
<label class="col-md-3 control-label">{% trans "Two-factor authentication" %}</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
{% if user.require_2fa %}
|
||||
<span class="label label-success">{% trans "Enabled" %}</span>
|
||||
|
||||
@@ -110,7 +110,9 @@ urlpatterns = [
|
||||
name='user.settings.2fa.confirm.webauthn'),
|
||||
re_path(r'^settings/2fa/(?P<devicetype>[^/]+)/(?P<device>[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(),
|
||||
name='user.settings.2fa.delete'),
|
||||
re_path(r'^confirm/(?P<token>[a-zA-Z0-9-]+)$', user.ConfirmEmailView.as_view(), name='user.email.confirm'),
|
||||
re_path(r'^settings/email/confirm$', user.UserEmailConfirmView.as_view(), name='user.settings.email.confirm'),
|
||||
re_path(r'^settings/email/change$', user.UserEmailChangeView.as_view(), name='user.settings.email.change'),
|
||||
re_path(r'^settings/password/change$', user.UserPasswordChangeView.as_view(), name='user.settings.password.change'),
|
||||
re_path(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'),
|
||||
re_path(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
|
||||
re_path(r'^organizers/select2$', typeahead.organizer_select2, name='organizers.select2'),
|
||||
|
||||
@@ -62,8 +62,8 @@ from webauthn.helpers import generate_challenge, generate_user_handle
|
||||
|
||||
from django.core.cache import cache
|
||||
from pretix.base.auth import get_auth_backends
|
||||
from pretix.base.forms.auth import ReauthForm
|
||||
from pretix.base.forms.user import User2FADeviceAddForm, UserSettingsForm
|
||||
from pretix.base.forms.auth import ConfirmationCodeForm, ReauthForm
|
||||
from pretix.base.forms.user import User2FADeviceAddForm, UserEmailChangeForm, UserPasswordChangeForm, UserSettingsForm
|
||||
from pretix.base.models import (
|
||||
Event, LogEntry, NotificationSetting, U2FDevice, User, WebAuthnDevice,
|
||||
)
|
||||
@@ -240,28 +240,7 @@ class UserSettings(UpdateView):
|
||||
|
||||
data = {}
|
||||
for k in form.changed_data:
|
||||
if k not in ('old_pw', 'new_pw_repeat'):
|
||||
if 'new_pw' == k:
|
||||
data['new_pw'] = True
|
||||
else:
|
||||
data[k] = form.cleaned_data[k]
|
||||
|
||||
msgs = []
|
||||
|
||||
if 'new_pw' in form.changed_data:
|
||||
self.request.user.needs_password_change = False
|
||||
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:
|
||||
if self._old_email != form.cleaned_data['email']:
|
||||
self.request.user.send_security_notice(msgs, email=self._old_email)
|
||||
token = self.request.user.generate_email_verification_token()
|
||||
link = build_absolute_uri(False, 'control:user.email.confirm', kwargs={'token': token})
|
||||
msgs.append(_('Please click the following link to confirm your new email address: {link}').format(link=link))
|
||||
self.request.user.send_security_notice(msgs, email=form.cleaned_data['email'])
|
||||
data[k] = form.cleaned_data[k]
|
||||
|
||||
sup = super().form_valid(form)
|
||||
self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data=data)
|
||||
@@ -842,26 +821,94 @@ class EditStaffSession(StaffMemberRequiredMixin, UpdateView):
|
||||
return get_object_or_404(StaffSession, pk=self.kwargs['id'], user=self.request.user)
|
||||
|
||||
|
||||
class ConfirmEmailView(View):
|
||||
class UserPasswordChangeView(FormView):
|
||||
max_time = 300
|
||||
|
||||
def get(self, request, token, *args, **kwargs):
|
||||
try:
|
||||
uid = int(token.split("-")[0])
|
||||
except ValueError:
|
||||
uid = None
|
||||
if uid and compare_digest(cache.get('confirm_email_token:' + str(uid)), token):
|
||||
user = User.objects.get(pk=uid)
|
||||
with transaction.atomic():
|
||||
if user.email != user.verified_email:
|
||||
user.log_action("user.email.confirmed", data={
|
||||
"old_email": user.verified_email,
|
||||
"new_email": user.email,
|
||||
})
|
||||
user.verified_email = user.email
|
||||
user.save()
|
||||
messages.success(request, _('Your email has been confirmed.'))
|
||||
else:
|
||||
messages.success(request, _('Your email was already confirmed.'))
|
||||
else:
|
||||
messages.error(request, _('Invalid confirmation link. Please try again.'))
|
||||
return redirect("control:user.settings")
|
||||
form_class = UserPasswordChangeForm
|
||||
template_name = 'pretixcontrol/user/change_password.html'
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
"user": self.request.user,
|
||||
}
|
||||
|
||||
def form_valid(self, form):
|
||||
with transaction.atomic():
|
||||
self.request.user.set_password(form.cleaned_data['new_pw'])
|
||||
self.request.user.needs_password_change = False
|
||||
self.request.user.save()
|
||||
msgs = []
|
||||
msgs.append(_('Your password has been changed.'))
|
||||
self.request.user.send_security_notice(msgs)
|
||||
|
||||
self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data={'new_pw': True})
|
||||
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
return redirect(reverse('control:user.settings', kwargs={}))
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class UserEmailChangeView(RecentAuthenticationRequiredMixin, FormView):
|
||||
max_time = 300
|
||||
|
||||
form_class = UserEmailChangeForm
|
||||
template_name = 'pretixcontrol/user/change_email.html'
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
"user": self.request.user,
|
||||
}
|
||||
|
||||
def form_valid(self, form):
|
||||
self.request.user.send_confirmation_code('email_change', form.cleaned_data['new_email'])
|
||||
return redirect(reverse('control:user.settings.email.confirm', kwargs={}))
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class UserEmailConfirmView(FormView):
|
||||
form_class = ConfirmationCodeForm
|
||||
template_name = 'pretixcontrol/auth/confirmation_code.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return {
|
||||
**super().get_context_data(**kwargs),
|
||||
"cancel_url": reverse('control:user.settings', kwargs={}),
|
||||
"message": _("Please enter the confirmation code we sent to your new email address:"),
|
||||
}
|
||||
|
||||
@transaction.atomic()
|
||||
def form_valid(self, form):
|
||||
new_email = self.request.user.check_confirmation_code('email_change', form.cleaned_data['code'])
|
||||
if not new_email:
|
||||
return self.form_invalid(form)
|
||||
|
||||
msgs = []
|
||||
msgs.append(_('Your email address has been changed to {email}.').format(email=new_email))
|
||||
old_email = self.request.user.email
|
||||
self.request.user.send_security_notice(msgs, email=old_email)
|
||||
self.request.user.send_security_notice(msgs, email=new_email)
|
||||
|
||||
self.request.user.email = new_email
|
||||
self.request.user.is_verified = True
|
||||
self.request.user.save()
|
||||
self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data={
|
||||
'old_email': old_email,
|
||||
'email': new_email,
|
||||
'email_verified': True,
|
||||
})
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
|
||||
messages.success(self.request, _('Your email address has been changed successfully.'))
|
||||
return redirect(reverse('control:user.settings', kwargs={}))
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
@@ -263,3 +263,11 @@ svg.svg-icon {
|
||||
@include table-row-variant('warning', var(--pretix-brand-warning-lighten-40), var(--pretix-brand-warning-lighten-35));
|
||||
@include table-row-variant('danger', var(--pretix-brand-danger-lighten-30), var(--pretix-brand-danger-lighten-25));
|
||||
|
||||
.confirmation-code-input {
|
||||
font-size: 200%;
|
||||
font-family: monospace;
|
||||
font-stretch: expanded;
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
@@ -936,3 +936,11 @@ details {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: $screen-lg-min) {
|
||||
.centered-form {
|
||||
margin: 80px auto;
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user