mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2024-12-24 18:22:24 +00:00
Validate emails
This commit is contained in:
parent
83d396a6dc
commit
30fa8b7840
18
apps/member/migrations/0003_tfjmuser_email_confirmed.py
Normal file
18
apps/member/migrations/0003_tfjmuser_email_confirmed.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.1 on 2020-09-19 20:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0002_auto_20200919_2015'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tfjmuser',
|
||||
name='email_confirmed',
|
||||
field=models.BooleanField(default=False, verbose_name='email confirmed'),
|
||||
),
|
||||
]
|
@ -2,12 +2,17 @@ import os
|
||||
from datetime import date
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import models
|
||||
from django.template import loader
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from polymorphic.models import PolymorphicModel
|
||||
|
||||
from tournament.models import Team, Tournament
|
||||
|
||||
from .tokens import email_validation_token
|
||||
|
||||
|
||||
class TFJMUser(AbstractUser):
|
||||
"""
|
||||
@ -144,6 +149,11 @@ class TFJMUser(AbstractUser):
|
||||
verbose_name=_("year"),
|
||||
)
|
||||
|
||||
email_confirmed = models.BooleanField(
|
||||
verbose_name=_("email confirmed"),
|
||||
default=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def participates(self):
|
||||
"""
|
||||
@ -179,6 +189,27 @@ class TFJMUser(AbstractUser):
|
||||
def __str__(self):
|
||||
return self.first_name + " " + self.last_name
|
||||
|
||||
def send_email_validation_link(self):
|
||||
subject = "[TFJM²] " + str(_("Activate your Note Kfet account"))
|
||||
token = email_validation_token.make_token(self)
|
||||
uid = urlsafe_base64_encode(force_bytes(self.pk))
|
||||
site = Site.objects.first()
|
||||
message = loader.render_to_string('registration/mails/email_validation_email.txt',
|
||||
{
|
||||
'user': self,
|
||||
'domain': site.domain,
|
||||
'token': token,
|
||||
'uid': uid,
|
||||
})
|
||||
html = loader.render_to_string('registration/mails/email_validation_email.html',
|
||||
{
|
||||
'user': self,
|
||||
'domain': site.domain,
|
||||
'token': token,
|
||||
'uid': uid,
|
||||
})
|
||||
self.email_user(subject, message, html_message=html)
|
||||
|
||||
|
||||
class Document(PolymorphicModel):
|
||||
"""
|
||||
|
26
apps/member/tokens.py
Normal file
26
apps/member/tokens.py
Normal file
@ -0,0 +1,26 @@
|
||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||
|
||||
|
||||
class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
|
||||
"""
|
||||
Create a unique token generator to confirm email addresses.
|
||||
"""
|
||||
def _make_hash_value(self, user, timestamp):
|
||||
"""
|
||||
Hash the user's primary key and some user state that's sure to change
|
||||
after an account validation to produce a token that invalidated when
|
||||
it's used:
|
||||
1. The user.profile.email_confirmed field will change upon an account
|
||||
validation.
|
||||
2. The last_login field will usually be updated very shortly after
|
||||
an account validation.
|
||||
Failing those things, settings.PASSWORD_RESET_TIMEOUT_DAYS eventually
|
||||
invalidates the token.
|
||||
"""
|
||||
# Truncate microseconds so that tokens are consistent even if the
|
||||
# database doesn't support microseconds.
|
||||
login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
|
||||
return str(user.pk) + str(user.email) + str(user.email_confirmed) + str(login_timestamp) + str(timestamp)
|
||||
|
||||
|
||||
email_validation_token = AccountActivationTokenGenerator()
|
@ -1,12 +1,17 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import CreateUserView, MyAccountView, UserDetailView, AddTeamView, JoinTeamView, MyTeamView,\
|
||||
ProfileListView, OrphanedProfileListView, OrganizersListView, ResetAdminView
|
||||
from .views import CreateUserView, MyAccountView, UserDetailView, AddTeamView, JoinTeamView, MyTeamView, \
|
||||
ProfileListView, OrphanedProfileListView, OrganizersListView, ResetAdminView, UserValidationEmailSentView, \
|
||||
UserResendValidationEmailView, UserValidateView
|
||||
|
||||
app_name = "member"
|
||||
|
||||
urlpatterns = [
|
||||
path('signup/', CreateUserView.as_view(), name="signup"),
|
||||
path('validate_email/sent/', UserValidationEmailSentView.as_view(), name='email_validation_sent'),
|
||||
path('validate_email/resend/<int:pk>/', UserResendValidationEmailView.as_view(),
|
||||
name='email_validation_resend'),
|
||||
path('validate_email/<uidb64>/<token>/', UserValidateView.as_view(), name='email_validation'),
|
||||
path("my-account/", MyAccountView.as_view(), name="my_account"),
|
||||
path("information/<int:pk>/", UserDetailView.as_view(), name="information"),
|
||||
path("add-team/", AddTeamView.as_view(), name="add_team"),
|
||||
|
@ -1,18 +1,20 @@
|
||||
import random
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.db.models import Q
|
||||
from django.http import FileResponse, Http404
|
||||
from django.shortcuts import redirect
|
||||
from django.shortcuts import redirect, resolve_url
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.http import urlsafe_base64_decode
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic import CreateView, UpdateView, DetailView, FormView
|
||||
from django.views.generic import CreateView, UpdateView, DetailView, FormView, TemplateView
|
||||
from django_tables2 import SingleTableView
|
||||
from tournament.forms import TeamForm, JoinTeam
|
||||
from tournament.models import Team, Tournament, Pool
|
||||
@ -21,6 +23,7 @@ from tournament.views import AdminMixin, TeamMixin, OrgaMixin
|
||||
from .forms import SignUpForm, TFJMUserForm, AdminUserForm, CoachUserForm
|
||||
from .models import TFJMUser, Document, Solution, MotivationLetter, Synthesis
|
||||
from .tables import UserTable
|
||||
from .tokens import email_validation_token
|
||||
|
||||
|
||||
class CreateUserView(CreateView):
|
||||
@ -36,8 +39,87 @@ class CreateUserView(CreateView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.send_email_validation_link()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('index')
|
||||
return reverse_lazy('member:email_validation_sent')
|
||||
|
||||
|
||||
class UserValidateView(TemplateView):
|
||||
"""
|
||||
A view to validate the email address.
|
||||
"""
|
||||
title = _("Email validation")
|
||||
template_name = 'registration/email_validation_complete.html'
|
||||
extra_context = {"title": _("Validate email")}
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
With a given token and user id (in params), validate the email address.
|
||||
"""
|
||||
assert 'uidb64' in kwargs and 'token' in kwargs
|
||||
|
||||
self.validlink = False
|
||||
user = self.get_user(kwargs['uidb64'])
|
||||
token = kwargs['token']
|
||||
|
||||
# Validate the token
|
||||
if user is not None and email_validation_token.check_token(user, token):
|
||||
self.validlink = True
|
||||
user.email_confirmed = True
|
||||
user.save()
|
||||
return self.render_to_response(self.get_context_data(), status=200 if self.validlink else 400)
|
||||
|
||||
def get_user(self, uidb64):
|
||||
"""
|
||||
Get user from the base64-encoded string.
|
||||
"""
|
||||
try:
|
||||
# urlsafe_base64_decode() decodes to bytestring
|
||||
uid = urlsafe_base64_decode(uidb64).decode()
|
||||
user = TFJMUser.objects.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, TFJMUser.DoesNotExist, ValidationError):
|
||||
user = None
|
||||
return user
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['user_object'] = self.get_user(self.kwargs["uidb64"])
|
||||
context['login_url'] = resolve_url(settings.LOGIN_URL)
|
||||
if self.validlink:
|
||||
context['validlink'] = True
|
||||
else:
|
||||
context.update({
|
||||
'title': _('Email validation unsuccessful'),
|
||||
'validlink': False,
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class UserValidationEmailSentView(TemplateView):
|
||||
"""
|
||||
Display the information that the validation link has been sent.
|
||||
"""
|
||||
template_name = 'registration/email_validation_email_sent.html'
|
||||
extra_context = {"title": _('Email validation email sent')}
|
||||
|
||||
|
||||
class UserResendValidationEmailView(LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
Rensend the email validation link.
|
||||
"""
|
||||
model = TFJMUser
|
||||
extra_context = {"title": _("Resend email validation link")}
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user = self.get_object()
|
||||
|
||||
user.profile.send_email_validation_link()
|
||||
|
||||
url = 'member:user_detail' if user.profile.registration_valid else 'member:future_user_detail'
|
||||
return redirect(url, user.id)
|
||||
|
||||
|
||||
class MyAccountView(LoginRequiredMixin, UpdateView):
|
||||
|
@ -917,11 +917,6 @@ msgstr "Votre adresse e-mail a bien été validée."
|
||||
msgid "You can now <a href=\"%(login_url)s\">log in</a>."
|
||||
msgstr "Vous pouvez désormais <a href=\"%(login_url)s\">vous connecter</a>"
|
||||
|
||||
#: templates/registration/email_validation_complete.html:10
|
||||
msgid ""
|
||||
"You must pay now your membership in the Kfet to complete your registration."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/email_validation_complete.html:13
|
||||
msgid ""
|
||||
"The link was invalid. The token may have expired. Please send us an email to "
|
||||
@ -956,32 +951,16 @@ msgstr "Mot de passe oublié ?"
|
||||
msgid "Hi"
|
||||
msgstr "Bonjour"
|
||||
|
||||
#: templates/registration/mails/email_validation_email.html:5
|
||||
msgid ""
|
||||
"You recently registered on the Note Kfet. Please click on the link below to "
|
||||
"confirm your registration."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/mails/email_validation_email.html:9
|
||||
msgid ""
|
||||
"This link is only valid for a couple of days, after that you will need to "
|
||||
"contact us to validate your email."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/mails/email_validation_email.html:11
|
||||
msgid ""
|
||||
"After that, you'll have to wait that someone validates your account before "
|
||||
"you can log in. You will need to pay your membership in the Kfet."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/mails/email_validation_email.html:13
|
||||
msgid "Thanks"
|
||||
msgstr "Merci"
|
||||
|
||||
#: templates/registration/mails/email_validation_email.html:15
|
||||
msgid "The Note Kfet team."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_change_done.html:8
|
||||
msgid "Your password was changed."
|
||||
msgstr "Votre mot de passe a été changé"
|
||||
|
@ -1,15 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% if validlink %}
|
||||
{% trans "Your email have successfully been validated." %}
|
||||
{% if user_object.profile.registration_valid %}
|
||||
<div class="card bg-light">
|
||||
<h3 class="card-header text-center">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
{% if validlink %}
|
||||
<p>
|
||||
{% trans "Your email have successfully been validated." %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}You can now <a href="{{ login_url }}">log in</a>.{% endblocktrans %}
|
||||
</p>
|
||||
{% else %}
|
||||
{% trans "You must pay now your membership in the Kfet to complete your registration." %}
|
||||
<p>
|
||||
{% trans "The link was invalid. The token may have expired. Please send us an email to activate your account." %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% trans "The link was invalid. The token may have expired. Please send us an email to activate your account." %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,7 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Account Activation</h2>
|
||||
|
||||
An email has been sent. Please click on the link to activate your account.
|
||||
{% endblock %}
|
||||
<div class="card bg-light">
|
||||
<h3 class="card-header text-center">
|
||||
{% trans "Account activation" %}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
{% trans "An email has been sent. Please click on the link to activate your account." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,15 +1,36 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% trans "Hi" %} {{ user.username }},
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% trans "You recently registered on the Note Kfet. Please click on the link below to confirm your registration." %}
|
||||
<p>
|
||||
{% trans "Hi" %} {{ user.username }},
|
||||
</p>
|
||||
|
||||
https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
|
||||
<p>
|
||||
{% trans "You recently registered on the TFJM² platform. Please click on the link below to confirm your registration." %}
|
||||
</p>
|
||||
|
||||
{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
|
||||
<p>
|
||||
<a href="https://{{ domain }}{% url 'member:email_validation' uidb64=uid token=token %}">
|
||||
https://{{ domain }}{% url 'member:email_validation' uidb64=uid token=token %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
{% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership in the Kfet." %}
|
||||
<p>
|
||||
{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
|
||||
</p>
|
||||
|
||||
{% trans "Thanks" %},
|
||||
<p>
|
||||
{% trans "Thanks" %},
|
||||
</p>
|
||||
|
||||
{% trans "The Note Kfet team." %}
|
||||
--
|
||||
<p>
|
||||
{% trans "The CNO." %}<br>
|
||||
</p>
|
||||
|
13
templates/registration/mails/email_validation_email.txt
Normal file
13
templates/registration/mails/email_validation_email.txt
Normal file
@ -0,0 +1,13 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% trans "Hi" %} {{ user.username }},
|
||||
|
||||
{% trans "You recently registered on the TFJM² platform. Please click on the link below to confirm your registration." %}
|
||||
|
||||
https://{{ domain }}{% url 'member:email_validation' uidb64=uid token=token %}
|
||||
|
||||
{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
|
||||
|
||||
{% trans "Thanks" %},
|
||||
|
||||
{% trans "The CNO." %}
|
Loading…
Reference in New Issue
Block a user