From 7c1d4e23ea3a07fe547e10141d19b14b387714a6 Mon Sep 17 00:00:00 2001
From: Dorian Lesbre <dorian.lesbre@gmail.com>
Date: Mon, 8 Mar 2021 18:29:12 +0100
Subject: [PATCH] Added password reset option

---
 .../activation.html}                          |  3 ++
 .../{change_email.html => email/change.html}  |  3 ++
 accounts/templates/email/password_reset.html  | 10 +++++
 accounts/templates/email/password_reset.txt   |  1 +
 accounts/templates/login.html                 |  4 +-
 accounts/templates/password_reset.html        | 15 +++++++
 .../templates/password_reset_confirm.html     | 23 ++++++++++
 accounts/tokens.py                            |  2 +-
 accounts/urls.py                              |  6 +++
 accounts/views.py                             | 44 ++++++++++++++++---
 interludes/settings.py                        |  5 ++-
 11 files changed, 107 insertions(+), 9 deletions(-)
 rename accounts/templates/{activation_email.html => email/activation.html} (90%)
 rename accounts/templates/{change_email.html => email/change.html} (90%)
 create mode 100644 accounts/templates/email/password_reset.html
 create mode 100644 accounts/templates/email/password_reset.txt
 create mode 100644 accounts/templates/password_reset.html
 create mode 100644 accounts/templates/password_reset_confirm.html

diff --git a/accounts/templates/activation_email.html b/accounts/templates/email/activation.html
similarity index 90%
rename from accounts/templates/activation_email.html
rename to accounts/templates/email/activation.html
index 16b2d3a..475f3d9 100644
--- a/accounts/templates/activation_email.html
+++ b/accounts/templates/email/activation.html
@@ -4,4 +4,7 @@ Bonjour {{ user.first_name }} {{ user.last_name }},
 Veuillez suivre le lien ci-dessous pour valider votre compte :
 
 http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}
+
+--
+L'équipe Interludes
 {% endautoescape %}
diff --git a/accounts/templates/change_email.html b/accounts/templates/email/change.html
similarity index 90%
rename from accounts/templates/change_email.html
rename to accounts/templates/email/change.html
index 1e2a2b0..4aa492d 100644
--- a/accounts/templates/change_email.html
+++ b/accounts/templates/email/change.html
@@ -4,4 +4,7 @@ Bonjour {{ user.first_name }} {{ user.last_name }},
 Veuillez suivre le lien ci dessous pour valider le changement d'adresse email :
 
 http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}
+
+--
+L'équipe Interludes
 {% endautoescape %}
diff --git a/accounts/templates/email/password_reset.html b/accounts/templates/email/password_reset.html
new file mode 100644
index 0000000..d6740c2
--- /dev/null
+++ b/accounts/templates/email/password_reset.html
@@ -0,0 +1,10 @@
+{% autoescape off %}
+Bonjour {{ user.first_name }} {{ user.last_name }},
+
+Pour réinitialiser votre mot de passe sur le site interludes, veuillez suivre le lien suivant :
+
+{{ protocol }}://{{ domain }}{% url 'accounts:password_reset_confirm' uidb64=uid token=token %}
+
+--
+L'équipe Interludes
+{% endautoescape %}
\ No newline at end of file
diff --git a/accounts/templates/email/password_reset.txt b/accounts/templates/email/password_reset.txt
new file mode 100644
index 0000000..002315a
--- /dev/null
+++ b/accounts/templates/email/password_reset.txt
@@ -0,0 +1 @@
+Mot de passe oublié site Interludes
\ No newline at end of file
diff --git a/accounts/templates/login.html b/accounts/templates/login.html
index 249ac46..1976409 100644
--- a/accounts/templates/login.html
+++ b/accounts/templates/login.html
@@ -33,8 +33,8 @@
 				<td>{{ form.password }}</td>
 			</tr>
 		</table>
-		<br>
-
+		<a href="{% url 'accounts:password_reset' %}">Mot de passe oublié&nbsp;?</a>
+		<br><br>
 		<div class="flex">
 		<input type="submit" value="Connexion">
 			{% if settings.registrations_open %}
diff --git a/accounts/templates/password_reset.html b/accounts/templates/password_reset.html
new file mode 100644
index 0000000..a1a6ebc
--- /dev/null
+++ b/accounts/templates/password_reset.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+
+{% block "content" %}
+  <h2>Mot de passe oublié ?</h2>
+  <p>Saissisez votre adresse email ci-dessous pour recevoir un lien de réinitialisation du mot de passe.</p>
+
+  <form method="POST">
+    {% csrf_token %}
+    {{ form.as_p }}
+		<div class="flex">
+    	<input type="submit" value="Valider">
+			<a class="button" href="{% url 'accounts:login' %}">Annuler</a>
+		</div>
+  </form>
+{% endblock %}
diff --git a/accounts/templates/password_reset_confirm.html b/accounts/templates/password_reset_confirm.html
new file mode 100644
index 0000000..f7e901e
--- /dev/null
+++ b/accounts/templates/password_reset_confirm.html
@@ -0,0 +1,23 @@
+{% extends 'base.html' %}
+
+{% block "content" %}
+
+{% if validlink %}
+
+<h2>Saissisez un nouveau mot de passe</h2>
+<form method="POST">
+  {% csrf_token %}
+  {{ form.as_p }}
+  <input type="submit" value="Change my password">
+</form>
+
+{% else %}
+
+<h2>Lien invalide</h2>
+
+<p>Le lien de réinitialisation est invalide, peut-être a-t-il déjà été utilisé.
+  Veuillez <a href="{% url 'accounts:password_reset' %}">demander un nouveau lien</a>.
+</p>
+
+{% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/accounts/tokens.py b/accounts/tokens.py
index ce2abb2..4b1f76b 100644
--- a/accounts/tokens.py
+++ b/accounts/tokens.py
@@ -12,7 +12,7 @@ class EmailVerificationTokenGenerator:
 	Strategy object used to generate and check tokens for the email
 	verification mechanism.
 	"""
-	key_salt = "shared.EmailVerificationTokenGenerator"
+	key_salt = "accounts.EmailVerificationTokenGenerator"
 	secret = settings.SECRET_KEY
 
 	def make_token(self, user):
diff --git a/accounts/urls.py b/accounts/urls.py
index fd0184a..518cadb 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -12,4 +12,10 @@ urlpatterns = [
 	path("update/", views.UpdateAccountView.as_view(), name="update"),
 	path("change_password/", views.UpdatePasswordView.as_view(), name="change_password"),
 	path('activate/<uidb64>/<token>/', views.ActivateAccountView.as_view(), name='activate'),
+	path("password_reset/", views.ResetPasswordView.as_view(), name="password_reset"),
+	path(
+		"password_reset/<uidb64>/<token>/",
+		views.ResetPasswordConfirmView.as_view(),
+		name="password_reset_confirm"
+	),
 ]
diff --git a/accounts/views.py b/accounts/views.py
index 6e72ce0..a1aaafa 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,12 +1,12 @@
 from django.contrib import messages
 from django.contrib.auth import login, logout
 from django.contrib.auth.mixins import LoginRequiredMixin
-from django.contrib.auth.views import LoginView as DjangoLoginView
+from django.contrib.auth import views as auth_views
 from django.contrib.sites.shortcuts import get_current_site
 from django.http import Http404
 from django.utils.encoding import force_bytes
 from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
-from django.urls import reverse
+from django.urls import reverse, reverse_lazy
 from django.template.loader import render_to_string
 from django.views.generic import FormView, RedirectView, TemplateView, UpdateView, View
 from django.shortcuts import render, redirect
@@ -28,7 +28,7 @@ def send_validation_email(request, user, subject, template):
 	user.email_user(subject, message)
 
 
-class LoginView(DjangoLoginView):
+class LoginView(auth_views.LoginView):
 	"""Vue pour se connecter"""
 	template_name = "login.html"
 	redirect_authenticated_user = "accounts:profile"
@@ -57,6 +57,7 @@ class CreateAccountView(View):
 	"""Vue pour la creation de compte"""
 	form_class = CreateAccountForm
 	template_name = 'create_account.html'
+	email_template = 'email/activation.html'
 
 	@staticmethod
 	def check_creation_allowed():
@@ -85,7 +86,7 @@ class CreateAccountView(View):
 		user.email_confirmed = False
 		user.save()
 
-		send_validation_email(request, user, "Activer votre compte Interludes", "activation_email.html")
+		send_validation_email(request, user, "Activer votre compte Interludes", self.email_template)
 
 		messages.info(request, 'Un lien vous a été envoyé par mail. Utilisez le pour finaliser la création de compte.')
 
@@ -125,10 +126,16 @@ class ActivateAccountView(RedirectView):
 		return reverse(self.success_pattern_name)
 
 
+# ==============================
+# Update personal info
+# ==============================
+
+
 class UpdateAccountView(LoginRequiredMixin, UpdateView):
 	"""Vue pour la mise à jour des infos personnelles"""
 	template_name = "update.html"
 	form_class = UpdateAccountForm
+	email_template = "email/change.html"
 
 	def get_object(self):
 		return self.request.user
@@ -146,7 +153,7 @@ class UpdateAccountView(LoginRequiredMixin, UpdateView):
 			send_validation_email(
 				self.request, self.request.user,
 				"Valider le changement d'email de votre compte Interludes",
-				"change_email.html"
+				self.email_template
 			)
 
 			messages.info(self.request, 'Un lien vous a été envoyé par mail. Utilisez le pour valider la mise à jour.')
@@ -181,3 +188,30 @@ class UpdatePasswordView(LoginRequiredMixin, FormView):
 		messages.success(self.request, "Mot de passe mis à jour")
 		login(self.request, self.request.user)
 		return redirect("accounts:profile")
+
+
+# ==============================
+# Reset password
+# ==============================
+
+
+class ResetPasswordView(auth_views.PasswordResetView):
+	"""Vue pour le gestion du mot de passe oublié"""
+	email_template_name = 'email/password_reset.html'
+	subject_template_name = 'email/password_reset.txt'
+	success_url = reverse_lazy('accounts:login')
+	template_name = 'password_reset.html'
+
+	def form_valid(self, form):
+		messages.info(self.request, "Un email vous a été envoyé avec un lien de réinitialisation")
+		return super().form_valid(form)
+
+
+class ResetPasswordConfirmView(auth_views.PasswordResetConfirmView):
+	"""Vue demandant de saisir un nouveau mot de passe"""
+	success_url = reverse_lazy('accounts:login')
+	template_name = "password_reset_confirm.html"
+
+	def form_valid(self, form):
+		messages.success(self.request, "Votre mot de passe a été enregistré")
+		return super().form_valid(form)
diff --git a/interludes/settings.py b/interludes/settings.py
index d9009f9..25e9b60 100644
--- a/interludes/settings.py
+++ b/interludes/settings.py
@@ -114,6 +114,8 @@ AUTH_PASSWORD_VALIDATORS = [
 	},
 ]
 
+# Session time in seconds
+SESSION_COOKIE_AGE = 3600
 
 # Internationalization
 # https://docs.djangoproject.com/en/3.0/topics/i18n/
@@ -138,5 +140,6 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static')
 LOGIN_URL = "accounts:login"
 LOGIN_REDIRECT_URL = "accounts:profile"
 
-# This will display email in Console.
+# This will display emails in Console.
+# FIXME: remove in production
 EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
-- 
GitLab