diff --git a/docker-compose.yml b/docker-compose.yml index 19e38809a..d33e029b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,7 +77,7 @@ services: /usr/bin/mc anonymous set download minio_docker/$AWS_STORAGE_BUCKET_NAME; else echo 'MINIO_ACCESS_KEY, MINIO_SECRET_KEY, or MINIO_PORT are not defined. Skipping buckets creation.'; - fi + fi; exit 0; " diff --git a/src/apps/profiles/urls_accounts.py b/src/apps/profiles/urls_accounts.py index 6266d5216..86321d24e 100644 --- a/src/apps/profiles/urls_accounts.py +++ b/src/apps/profiles/urls_accounts.py @@ -1,7 +1,7 @@ from django.conf.urls import url from django.urls import path - from . import views +from django.contrib.auth import views as auth_views app_name = "accounts" @@ -12,4 +12,8 @@ path('login/', views.LoginView.as_view(), name='login'), # path('logout/', auth_views.LogoutView.as_view(), name='logout'), path('logout/', views.LogoutView.as_view(), name='logout'), + path('password_reset/', views.CustomPasswordResetView.as_view(), name='password_reset'), + path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'), + path('reset///', views.CustomPasswordResetConfirmView.as_view(), name='password_reset_confirm'), + path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'), ] diff --git a/src/apps/profiles/views.py b/src/apps/profiles/views.py index 50e5cf66a..fe02bdee9 100644 --- a/src/apps/profiles/views.py +++ b/src/apps/profiles/views.py @@ -1,13 +1,15 @@ import json +import django from django.conf import settings from django.contrib import messages from django.contrib.auth import authenticate from django.contrib.sites.shortcuts import get_current_site -from django.core.mail import EmailMessage +from django.core.mail import EmailMessage, EmailMultiAlternatives from django.http import Http404 from django.shortcuts import render, redirect from django.contrib.auth import views as auth_views +from django.contrib.auth import forms as auth_forms from django.contrib.auth.mixins import LoginRequiredMixin from django.template.loader import render_to_string from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode @@ -126,6 +128,77 @@ def sign_up(request): return render(request, 'registration/signup.html', context) +# Password Reset views/forms below +# auth_forms +class CustomPasswordResetForm(auth_forms.PasswordResetForm): + """ + Subclassed auth_forms.PasswordResetForm in order to add a print statement + to see the email in the logs. + Source: https://github.com/django/django/blob/8b1ff0da4b162e87edebd94e61f2cd153e9e159d/django/contrib/auth/forms.py#L287 + """ + def send_mail( + self, + subject_template_name, + email_template_name, + context, + from_email, + to_email, + html_email_template_name=None, + ): + """ + Send a django.core.mail.EmailMultiAlternatives to `to_email`. + """ + subject = render_to_string(subject_template_name, context) + # Email subject *must not* contain newlines + subject = "".join(subject.splitlines()) + body = render_to_string(email_template_name, context) + + email_message = EmailMultiAlternatives(subject, body, from_email, [to_email]) + print(email_message.message()) + if html_email_template_name is not None: + html_email = render_to_string(html_email_template_name, context) + email_message.attach_alternative(html_email, "text/html") + + email_message.send() + + +# auth_views +# https://devdocs.io/django~2.2/topics/auth/default#django.contrib.auth.views.PasswordChangeView # Search for PasswordResetView +class CustomPasswordResetView(auth_views.PasswordResetView): + """ + 1. form_class: subclassing auth_views.PasswordResetView to use a custom form "CustomPasswordResetForm" above + 2. success_url: Our src/apps/profiles/urls_accounts.py has become an "app" with the use of "app_name". + We have to use app:view_name syntax in templates like " {% url 'accounts:password_reset_confirm'%} " + Therefore we need to tell this view to find the right success_url with that syntax or django won't be + able to find the view. + 3. from_email: We want to set the from_email to info@codalab.org - may eventually put in .env file. + # The other commented sections are the defaults for other attributes in auth_views.PasswordResetView. + They are in here in case someone wants to customize in the future. All attributes show up in the order + shown in the docs. + """ + # template_name = 'registration/password_reset_form.html' + form_class = CustomPasswordResetForm # auth_forms.PasswordResetForm + # email_template_name = '' # Defaults to registration/password_reset_email.html if not supplied. + # subject_template_name = '' # Defaults to registration/password_reset_subject.txt if not supplied. + # token_generator = '' # This will default to default_token_generator, it’s an instance of django.contrib.auth.tokens.PasswordResetTokenGenerator. + success_url = django.urls.reverse_lazy("accounts:password_reset_done") + from_email = "info@codalab.org" + + +class CustomPasswordResetConfirmView(auth_views.PasswordResetConfirmView): + """ + 1. success_url: Our src/apps/profiles/urls_accounts.py has become an "app" with the use of "app_name". + We have to use app:view_name syntax in templates like " {% url 'accounts:password_reset_confirm'%} " + Therefore we need to tell this view to find the right success_url with that syntax or django won't be + able to find the view. + """ + # template_name = '' # Default value is registration/password_reset_confirm.html. + # form_class = '' # Defaults to django.contrib.auth.forms.SetPasswordForm. + # token_generator = '' # This will default to default_token_generator, it’s an instance of django.contrib.auth.tokens.PasswordResetTokenGenerator. + # post_reset_login = '' # Defaults to False. + success_url = django.urls.reverse_lazy("accounts:password_reset_complete") + + class UserNotificationEdit(LoginRequiredMixin, DetailView): queryset = User.objects.all() template_name = 'profiles/user_notifications.html' diff --git a/src/templates/base.html b/src/templates/base.html index 127c812c8..5e96e8e1b 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -145,6 +145,10 @@ Notifications + + + Change Password + Logout diff --git a/src/templates/registration/login.html b/src/templates/registration/login.html index 4ffd5fcb3..f4b802cb2 100644 --- a/src/templates/registration/login.html +++ b/src/templates/registration/login.html @@ -53,6 +53,7 @@

New to us? Sign Up

+

Forgot your password?

diff --git a/src/templates/registration/password_reset_complete.html b/src/templates/registration/password_reset_complete.html new file mode 100644 index 000000000..e69a6e197 --- /dev/null +++ b/src/templates/registration/password_reset_complete.html @@ -0,0 +1,10 @@ +{% extends 'base.html' %} + +{% block content %} +
+

+ Password Reset Complete +

+

Your password has been successfully reset.

+
+{% endblock %} diff --git a/src/templates/registration/password_reset_confirm.html b/src/templates/registration/password_reset_confirm.html new file mode 100644 index 000000000..873f819bb --- /dev/null +++ b/src/templates/registration/password_reset_confirm.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block content %} +
+

+ Change Password +

+
+ {% csrf_token %} + {{ form.as_p }} + +
+
+{% endblock %} diff --git a/src/templates/registration/password_reset_done.html b/src/templates/registration/password_reset_done.html new file mode 100644 index 000000000..c36f7c225 --- /dev/null +++ b/src/templates/registration/password_reset_done.html @@ -0,0 +1,18 @@ +{% extends "registration/registration_base.html" %} + +{% block title %}Password reset{% endblock %} + +{% block content %} +
+

+ Password Reset Complete +

+

+ We have sent you an email with a link to reset your password. Please check + your email and click the link to continue. +

+
+{% endblock %} + + +{# This is used by django.contrib.auth #} \ No newline at end of file diff --git a/src/templates/registration/password_reset_email.html b/src/templates/registration/password_reset_email.html new file mode 100644 index 000000000..249023bb2 --- /dev/null +++ b/src/templates/registration/password_reset_email.html @@ -0,0 +1,2 @@ +Someone asked for password reset for email {{ email }}. Follow the link below: +{{ protocol}}://{{ domain }}{% url 'accounts:password_reset_confirm' uidb64=uid token=token %} diff --git a/src/templates/registration/password_reset_form.html b/src/templates/registration/password_reset_form.html new file mode 100644 index 000000000..5d225765d --- /dev/null +++ b/src/templates/registration/password_reset_form.html @@ -0,0 +1,24 @@ +{% extends "registration/registration_base.html" %} + + +{% block title %}Reset password{% endblock %} + +{% block content %} +
+

+ Reset password +

+ {% if user.is_authenticated %} +

Note: you are already logged in as {{ user.username }}.

+ {% endif %} +

Forgot your password? Enter your email in the form below and we'll send you instructions for creating a new one.

+
+ {% csrf_token %} + {{ form.as_p }} + +
+
+{% endblock %} + + +{# This is used by django.contrib.auth #} \ No newline at end of file diff --git a/src/templates/registration/registration_base.html b/src/templates/registration/registration_base.html new file mode 100644 index 000000000..63913c188 --- /dev/null +++ b/src/templates/registration/registration_base.html @@ -0,0 +1 @@ +{% extends "base.html" %} \ No newline at end of file