mirror of
https://github.com/pretix/pretix.git
synced 2026-08-10 10:47:00 +00:00
Validation of user email addresses
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.23 on 2025-09-04 12:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0288_invoice_transmission"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="verified_email",
|
||||
field=models.EmailField(max_length=190, null=True),
|
||||
),
|
||||
]
|
||||
@@ -44,6 +44,7 @@ from django.contrib.auth.models import (
|
||||
)
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.cache import cache
|
||||
from django.db import IntegrityError, models, transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.crypto import get_random_string, salted_hmac
|
||||
@@ -242,6 +243,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)
|
||||
fullname = models.CharField(max_length=255, blank=True, null=True,
|
||||
verbose_name=_('Full name'))
|
||||
is_active = models.BooleanField(default=True,
|
||||
@@ -593,6 +595,11 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
self.session_token = generate_session_token()
|
||||
self.save(update_fields=['session_token'])
|
||||
|
||||
def generate_email_verification_token(self):
|
||||
token = str(self.pk) + '-' + get_random_string(length=32)
|
||||
cache.set("confirm_email_token:" + str(self.pk), token, 3 * 86400)
|
||||
return token
|
||||
|
||||
|
||||
class UserKnownLoginSource(models.Model):
|
||||
user = models.ForeignKey('User', on_delete=models.CASCADE, related_name="known_login_sources")
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Account settings" %}{% endblock %}
|
||||
{% block content %}
|
||||
{% if user.email != user.verified_email %}
|
||||
<div class="alert alert-info">
|
||||
{% trans "Please confirm your email address by clicking on the confirmation link in the email we sent you." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<h1>{% trans "Account settings" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -110,6 +110,7 @@ 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'^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'),
|
||||
|
||||
@@ -37,6 +37,7 @@ import json
|
||||
import logging
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from hmac import compare_digest
|
||||
from urllib.parse import quote
|
||||
|
||||
import webauthn
|
||||
@@ -59,6 +60,7 @@ from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from django_scopes import scopes_disabled
|
||||
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
|
||||
@@ -74,6 +76,7 @@ from pretix.control.permissions import (
|
||||
from pretix.control.views.auth import get_u2f_appid, get_webauthn_rp_id
|
||||
from pretix.helpers.http import redirect_to_url
|
||||
from pretix.helpers.u2f import websafe_encode
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
REAL_DEVICE_TYPES = (TOTPDevice, WebAuthnDevice, U2FDevice)
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -253,9 +256,12 @@ class UserSettings(UpdateView):
|
||||
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)
|
||||
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'])
|
||||
|
||||
sup = super().form_valid(form)
|
||||
self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data=data)
|
||||
@@ -834,3 +840,28 @@ class EditStaffSession(StaffMemberRequiredMixin, UpdateView):
|
||||
return get_object_or_404(StaffSession, pk=self.kwargs['id'])
|
||||
else:
|
||||
return get_object_or_404(StaffSession, pk=self.kwargs['id'], user=self.request.user)
|
||||
|
||||
|
||||
class ConfirmEmailView(View):
|
||||
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user