mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Add email verification flow for existing users
This commit is contained in:
@@ -380,6 +380,10 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
msg = str(_('to confirm changing your email address from {old_email}\nto {new_email}, use the following code:').format(
|
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,
|
old_email=self.email, new_email=email,
|
||||||
))
|
))
|
||||||
|
elif reason == 'email_verify':
|
||||||
|
msg = str(_('to confirm that your email address {email} belongs to your pretix account, use the following code:').format(
|
||||||
|
email=self.email,
|
||||||
|
))
|
||||||
else:
|
else:
|
||||||
raise Exception('Invalid confirmation code reason')
|
raise Exception('Invalid confirmation code reason')
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,20 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% if not user.is_verified %}
|
{% if not user.is_verified %}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
{% trans "Please confirm your email address by clicking on the confirmation link in the email we sent you." %}
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Your email address is not confirmed yet. To secure your account, please confirm your email address by
|
||||||
|
clicking a confirmation link we send to your email address.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<form action="{% url "control:user.settings.email.send_verification_code" %}" method="post" class="form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
{% trans "Send confirmation email" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h1>{% trans "Account settings" %}</h1>
|
<h1>{% trans "Account settings" %}</h1>
|
||||||
@@ -73,7 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-3 control-label" for="">{% trans "Authorized applications" %}</label>
|
<label class="col-md-3 control-label">{% trans "Authorized applications" %}</label>
|
||||||
<div class="col-md-9 static-form-row">
|
<div class="col-md-9 static-form-row">
|
||||||
<a href="{% url "control:user.settings.oauth.list" %}">
|
<a href="{% url "control:user.settings.oauth.list" %}">
|
||||||
<span class="fa fa-plug"></span>
|
<span class="fa fa-plug"></span>
|
||||||
@@ -82,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-3 control-label" for="">{% trans "Account history" %}</label>
|
<label class="col-md-3 control-label">{% trans "Account history" %}</label>
|
||||||
<div class="col-md-9 static-form-row">
|
<div class="col-md-9 static-form-row">
|
||||||
<a href="{% url "control:user.settings.history" %}">
|
<a href="{% url "control:user.settings.history" %}">
|
||||||
<span class="fa fa-history"></span>
|
<span class="fa fa-history"></span>
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ urlpatterns = [
|
|||||||
name='user.settings.2fa.delete'),
|
name='user.settings.2fa.delete'),
|
||||||
re_path(r'^settings/email/confirm$', user.UserEmailConfirmView.as_view(), name='user.settings.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/email/change$', user.UserEmailChangeView.as_view(), name='user.settings.email.change'),
|
||||||
|
re_path(r'^settings/email/verify', user.UserEmailVerifyView.as_view(), name='user.settings.email.send_verification_code'),
|
||||||
re_path(r'^settings/password/change$', user.UserPasswordChangeView.as_view(), name='user.settings.password.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/$', organizer.OrganizerList.as_view(), name='organizers'),
|
||||||
re_path(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
|
re_path(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ from django.shortcuts import get_object_or_404, redirect
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.html import format_html
|
||||||
from django.utils.http import url_has_allowed_host_and_scheme
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -890,13 +891,30 @@ class UserEmailChangeView(RecentAuthenticationRequiredMixin, FormView):
|
|||||||
email=form.cleaned_data['new_email'],
|
email=form.cleaned_data['new_email'],
|
||||||
state=form.cleaned_data['new_email'],
|
state=form.cleaned_data['new_email'],
|
||||||
)
|
)
|
||||||
return redirect(reverse('control:user.settings.email.confirm', kwargs={}))
|
self.request.session['email_confirmation_destination'] = form.cleaned_data['new_email']
|
||||||
|
return redirect(reverse('control:user.settings.email.confirm', kwargs={}) + '?reason=email_change')
|
||||||
|
|
||||||
def form_invalid(self, form):
|
def form_invalid(self, form):
|
||||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class UserEmailVerifyView(View):
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if self.request.user.is_verified:
|
||||||
|
messages.success(self.request, _('Your email address was already verified.'))
|
||||||
|
return redirect(reverse('control:user.settings', kwargs={}))
|
||||||
|
|
||||||
|
self.request.user.send_confirmation_code(
|
||||||
|
session=self.request.session,
|
||||||
|
reason='email_verify',
|
||||||
|
email=self.request.user.email,
|
||||||
|
state=self.request.user.email,
|
||||||
|
)
|
||||||
|
self.request.session['email_confirmation_destination'] = self.request.user.email
|
||||||
|
return redirect(reverse('control:user.settings.email.confirm', kwargs={}) + '?reason=email_verify')
|
||||||
|
|
||||||
|
|
||||||
class UserEmailConfirmView(FormView):
|
class UserEmailConfirmView(FormView):
|
||||||
form_class = ConfirmationCodeForm
|
form_class = ConfirmationCodeForm
|
||||||
template_name = 'pretixcontrol/user/confirmation_code_dialog.html'
|
template_name = 'pretixcontrol/user/confirmation_code_dialog.html'
|
||||||
@@ -905,43 +923,52 @@ class UserEmailConfirmView(FormView):
|
|||||||
return {
|
return {
|
||||||
**super().get_context_data(**kwargs),
|
**super().get_context_data(**kwargs),
|
||||||
"cancel_url": reverse('control:user.settings', kwargs={}),
|
"cancel_url": reverse('control:user.settings', kwargs={}),
|
||||||
"message": _("Please enter the confirmation code we sent to your new email address."),
|
"message": format_html(
|
||||||
|
_("Please enter the confirmation code we sent to your email address <strong>{email}</strong>."),
|
||||||
|
email=self.request.session.get('email_confirmation_destination', ''),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
reason = self.request.GET['reason']
|
||||||
|
if reason not in ('email_change', 'email_verify'):
|
||||||
|
raise PermissionDenied
|
||||||
try:
|
try:
|
||||||
new_email = self.request.user.check_confirmation_code(
|
new_email = self.request.user.check_confirmation_code(
|
||||||
session=self.request.session,
|
session=self.request.session,
|
||||||
reason='email_change',
|
reason=reason,
|
||||||
code=form.cleaned_data['code'],
|
code=form.cleaned_data['code'],
|
||||||
)
|
)
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
except BadRequest:
|
except BadRequest:
|
||||||
messages.error(self.request, _(
|
messages.error(self.request, _(
|
||||||
'Your email address could not be changed, because we were unable to '
|
'We were unable to verify your confirmation code. Please try again.'
|
||||||
'verify your confirmation code. Please try again.'
|
|
||||||
))
|
))
|
||||||
return redirect(reverse('control:user.settings', kwargs={}))
|
return redirect(reverse('control:user.settings', kwargs={}))
|
||||||
|
|
||||||
msgs = []
|
log_data = {
|
||||||
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': new_email,
|
||||||
'email_verified': True,
|
'email_verified': True,
|
||||||
})
|
}
|
||||||
|
if reason == 'email_change':
|
||||||
|
msgs = []
|
||||||
|
msgs.append(_('Your email address has been changed to {email}.').format(email=new_email))
|
||||||
|
log_data['old_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=log_data)
|
||||||
update_session_auth_hash(self.request, self.request.user)
|
update_session_auth_hash(self.request, self.request.user)
|
||||||
|
|
||||||
messages.success(self.request, _('Your email address has been changed successfully.'))
|
if reason == 'email_change':
|
||||||
|
messages.success(self.request, _('Your email address has been changed successfully.'))
|
||||||
|
else:
|
||||||
|
messages.success(self.request, _('Your email address has been confirmed successfully.'))
|
||||||
return redirect(reverse('control:user.settings', kwargs={}))
|
return redirect(reverse('control:user.settings', kwargs={}))
|
||||||
|
|
||||||
def form_invalid(self, form):
|
def form_invalid(self, form):
|
||||||
|
|||||||
Reference in New Issue
Block a user