Skip to content

Commit

Permalink
fix: move reset password email (#2281)
Browse files Browse the repository at this point in the history
* fix: move verification emails to Dotdigital

* Merge branch 'master' into move-verification-emails-to-dotdigital

* fix: fix dotdigital email tests

* fix: fix parent email test

* fix: add github secret binding

* fix: add chrome to dev container

* fix: attempt to fix under 13 test

* fix: attempt to fix patching

* fix: continue work on updating tests

* fix: continue to update tests

* fix: fix package names

* fix: fix test assert

* fix: fix assert

* fix: update tests

* fix: rearrange decorators

* fix: continue work on tests

* fix: fix typo

* fix: testing something out

* fix: testing something out

* fix: undo chrome changes

* fix: adding additional mocks

* fix: debug tests

* fix: debug tests

* fix: debug tests

* fix: debug tests

* fix: debug tests

* fix: debug tests

* fix: debug tests

* fix: debug tests

* fix: debug tests

* fix: add forgotten import

* fix: update dotdigital auth

* fix: fix environmental variable

* fix: address PR comments

* fix: address PR comments

* fix: debug tests

* fix: debug tests

* fix: tidy code, replace verification reminder emails

* fix: fix verification reminder email tests

* fix: debug tests

* fix: debug tests

* fix: fix patch mock

* fix: move reset password email

* fix: tidy code

* fix: remove old code

* fix: debug tests

* fix: debug tests

* fix: debug tests

* Update ci.yml

* Update ci.yml

* Update ci.yml

* Update ci.yml

* Merge branch 'master' into move-reset-password-email

* fix: tidy code

* fix: tidy code

* Merge branch 'master' into move-reset-password-email

* fix: address PR comments

* Merge branch 'master' into move-reset-password-email

Co-Authored-By: Florian Aucomte <[email protected]>
  • Loading branch information
evemartin and faucomte97 authored Apr 11, 2024
1 parent fec326b commit e00604f
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 62 deletions.
15 changes: 1 addition & 14 deletions cfl_common/common/email_messages.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
from django.urls import reverse, reverse_lazy


def resetEmailPasswordMessage(request, domain, uid, token, protocol):
password_reset_uri = reverse_lazy("password_reset_check_and_confirm", kwargs={"uidb64": uid, "token": token})
url = f"{protocol}://{domain}{password_reset_uri}"
return {
"subject": f"Password reset request",
"message": (
f"You are receiving this email because you requested "
f"a password reset for your Code For Life user account.\n\n"
f"Please go to the following page and choose a new password: {url}"
),
}
from django.urls import reverse


def userAlreadyRegisteredEmail(request, email, is_independent_student=False):
Expand Down
1 change: 1 addition & 0 deletions cfl_common/common/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
campaign_ids = {
"email_change_notification": 1551600,
"email_change_verification": 1551594,
"reset_password": 1557153,
"verify_new_user": 1551577,
"verify_new_user_first_reminder": 1557170,
"verify_new_user_second_reminder": 1557173,
Expand Down
5 changes: 1 addition & 4 deletions cfl_common/common/tests/utils/email.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import re
from builtins import str


Expand Down Expand Up @@ -35,9 +34,7 @@ def _follow_duplicate_account_email_link(page, email):
page.browser.get(message[i:j])


def follow_reset_email_link(browser, email):
message = str(email.body)
link = re.search("http.+/", message).group(0)[:-1]
def follow_reset_email_link(browser, link):
browser.get(link)

from portal.tests.pageObjects.portal.password_reset_form_page import (
Expand Down
37 changes: 16 additions & 21 deletions portal/forms/registration.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from captcha.fields import ReCaptchaField
from captcha.widgets import ReCaptchaV2Invisible
from common.email_messages import resetEmailPasswordMessage
from common.helpers.emails import NOTIFICATION_EMAIL, send_email
from common.mail import campaign_ids, send_dotdigital_email
from common.models import Student, Teacher
from django import forms
from django.contrib.auth import forms as django_auth_forms
from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.urls import reverse_lazy
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode

from portal.helpers.password import PasswordStrength, form_clean_password


Expand Down Expand Up @@ -74,30 +75,24 @@ def save(
continue
if not domain_override:
current_site = get_current_site(request)
site_name = current_site.name
domain = current_site.domain
else:
site_name = domain = domain_override
context = {
"email": user.email,
"domain": domain,
"site_name": site_name,
"uid": urlsafe_base64_encode(force_bytes(user.pk)),
"user": user,
"token": token_generator.make_token(user),
"protocol": self._compute_protocol(use_https),
}

email_subject_content = resetEmailPasswordMessage(
request, domain, context["uid"], context["token"], context["protocol"]
domain = domain_override

reset_password_uri = reverse_lazy(
"password_reset_check_and_confirm",
kwargs={
"uidb64": urlsafe_base64_encode(force_bytes(user.pk)),
"token": token_generator.make_token(user),
},
)
protocol = self._compute_protocol(use_https)
reset_password_url = f"{protocol}://{domain}{reset_password_uri}"

send_email(
NOTIFICATION_EMAIL,
send_dotdigital_email(
campaign_ids["reset_password"],
[user.email],
email_subject_content["subject"],
email_subject_content["message"],
email_subject_content["subject"],
personalization_values={"RESET_PASSWORD_LINK": reset_password_url},
)

def _compute_protocol(self, use_https):
Expand Down
18 changes: 11 additions & 7 deletions portal/tests/test_independent_student.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,17 +284,22 @@ def test_login_not_verified(self, mock_send_dotdigital_email):

assert self.is_dashboard(page)

def test_reset_password(self):
@patch("portal.forms.registration.send_dotdigital_email")
def test_reset_password(self, mock_send_dotdigital_email: Mock):
page = self.go_to_homepage()

page, name, username, _, _ = create_independent_student(page)
page = self.get_to_forgotten_password_page()

page.reset_email_submit(username)

self.wait_for_email()
mock_send_dotdigital_email.assert_called_with(campaign_ids["reset_password"], ANY, personalization_values=ANY)

page = email_utils.follow_reset_email_link(self.selenium, mail.outbox[0])
reset_password_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"][
"RESET_PASSWORD_LINK"
]

page = email_utils.follow_reset_email_link(self.selenium, reset_password_url)

new_password = "AnotherPassword12"

Expand All @@ -312,14 +317,13 @@ def test_reset_password(self):
page = page.go_to_account_page()
assert page.check_account_details({"name": name})

def test_reset_password_fail(self):
@patch("portal.forms.registration.send_dotdigital_email")
def test_reset_password_fail(self, mock_send_dotdigital_email: Mock):
page = self.get_to_forgotten_password_page()
fake_email = "[email protected]"
page.reset_email_submit(fake_email)

time.sleep(5)

assert len(mail.outbox) == 0
mock_send_dotdigital_email.assert_not_called()

def test_update_name_success(self):
homepage = self.go_to_homepage()
Expand Down
15 changes: 9 additions & 6 deletions portal/tests/test_ratelimit.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@ def test_successful_request_resets_cache(self):

assert get_ratelimit_count_for_user(email) == 1

def test_teacher_reset_password_unblocks_user(self):
@patch("portal.forms.registration.send_dotdigital_email")
def test_teacher_reset_password_unblocks_user(self, mock_send_dotdigital_email: Mock):
"""
Given a blocked teacher,
When they reset they password,
Expand All @@ -344,15 +345,17 @@ def test_teacher_reset_password_unblocks_user(self):
# Ask for reset password link
self._reset_password_request(email)

assert len(mail.outbox) == 1
mock_send_dotdigital_email.assert_called_once_with(
campaign_ids["reset_password"], ANY, personalization_values=ANY
)

# Get reset link from email
message = str(mail.outbox[0].body)
url = re.search("http.+/", message).group(0)
reset_password_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"][
"RESET_PASSWORD_LINK"
]

new_password = "AnotherPassword12!"

self._reset_password(url, new_password)
self._reset_password(reset_password_url, new_password)

login_response = self._teacher_login(email, new_password)

Expand Down
29 changes: 19 additions & 10 deletions portal/tests/test_teacher.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,8 @@ def test_change_password(self):

assert self.is_dashboard_page(page)

def test_reset_password(self):
@patch("portal.forms.registration.send_dotdigital_email")
def test_reset_password(self, mock_send_dotdigital_email: Mock):
email, _ = signup_teacher_directly()
create_organisation_directly(email)
_, _, access_code = create_class_directly(email)
Expand All @@ -638,9 +639,13 @@ def test_reset_password(self):

page.reset_email_submit(email)

self.wait_for_email()
mock_send_dotdigital_email.assert_called_with(campaign_ids["reset_password"], ANY, personalization_values=ANY)

page = email_utils.follow_reset_email_link(self.selenium, mail.outbox[0])
reset_password_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"][
"RESET_PASSWORD_LINK"
]

page = email_utils.follow_reset_email_link(self.selenium, reset_password_url)

new_password = "AnotherPassword12!"

Expand All @@ -650,7 +655,8 @@ def test_reset_password(self):
page = HomePage(self.selenium).go_to_teacher_login_page().login(email, new_password)
assert self.is_dashboard_page(page)

def test_reset_with_same_password(self):
@patch("portal.forms.registration.send_dotdigital_email")
def test_reset_with_same_password(self, mock_send_dotdigital_email: Mock):
email, password = signup_teacher_directly()
create_organisation_directly(email)
_, _, access_code = create_class_directly(email)
Expand All @@ -660,23 +666,26 @@ def test_reset_with_same_password(self):

page.reset_email_submit(email)

self.wait_for_email()
mock_send_dotdigital_email.assert_called_with(campaign_ids["reset_password"], ANY, personalization_values=ANY)

reset_password_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"][
"RESET_PASSWORD_LINK"
]

page = email_utils.follow_reset_email_link(self.selenium, mail.outbox[0])
page = email_utils.follow_reset_email_link(self.selenium, reset_password_url)

page.reset_password_fail(password)

message = page.browser.find_element(By.CLASS_NAME, "errorlist")
assert "Please choose a password that you haven't used before" in message.text

def test_reset_password_fail(self):
@patch("portal.forms.registration.send_dotdigital_email")
def test_reset_password_fail(self, mock_send_dotdigital_email: Mock):
page = self.get_to_forgotten_password_page()
fake_email = "[email protected]"
page.reset_email_submit(fake_email)

time.sleep(5)

assert len(mail.outbox) == 0
mock_send_dotdigital_email.assert_not_called()

def test_admin_sees_all_school_classes(self):
email, password = signup_teacher_directly()
Expand Down

0 comments on commit e00604f

Please sign in to comment.