Skip to content

Commit

Permalink
Add password change confirmation email (#971)
Browse files Browse the repository at this point in the history
## Fixes issue
#947

## Description of Changes
Added an email action that lets the user know that their password has
changed and added validation that emails were being sent in the tests.

## Notes for Deployment
- The `OO_ADMIN_EMAIL` environment variable needs to be added to the
production `.env` file.

## Screenshots (if appropriate)
<img width="1074" alt="Screenshot 2023-07-13 at 1 44 06 PM"
src="https://github.com/lucyparsons/OpenOversight/assets/5885605/1d0195db-b8fb-4e96-8f20-945a8b0633e7">

## Tests and linting
 - [x] This branch is up-to-date with the `develop` branch.
 - [x] `pytest` passes on my local development environment.
 - [x] `pre-commit` passes on my local development environment.
  • Loading branch information
michplunkett authored Jul 17, 2023
1 parent b462294 commit 3606e67
Show file tree
Hide file tree
Showing 12 changed files with 82 additions and 11 deletions.
8 changes: 8 additions & 0 deletions CONTRIB.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ Example `.env` variable:
OO_SERVICE_EMAIL="[email protected]"
```

In addition to needing a service account email, you also need an admin email address so that users have someone to reach out to if an action is taken on their account that needs to be reversed or addressed.
For production, save the email address associated with your admin account to a variable named `OO_HELP_EMAIL` in a `.env` file in the base directory of this repository. For development and testing, update the `OO_HELP_EMAIL` variable in the `docker-compose.yml` file.

Example `.env` variable:
```bash
OO_HELP_EMAIL="[email protected]"
```

## Testing S3 Functionality
We use an S3 bucket for image uploads. If you are working on functionality involving image uploads,
then you should follow the "S3 Image Hosting" section in [DEPLOY.md](/DEPLOY.md) to make a test S3 bucket
Expand Down
4 changes: 4 additions & 0 deletions OpenOversight/app/auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from OpenOversight.app.models.emails import (
AdministratorApprovalEmail,
ChangeEmailAddressEmail,
ChangePasswordEmail,
ConfirmAccountEmail,
ConfirmedUserEmail,
ResetPasswordEmail,
Expand Down Expand Up @@ -175,6 +176,9 @@ def change_password():
db.session.add(current_user)
db.session.commit()
flash("Your password has been updated.")
EmailClient.send_email(
ChangePasswordEmail(current_user.email, user=current_user)
)
return redirect(url_for("main.index"))
else:
flash("Invalid password.")
Expand Down
3 changes: 3 additions & 0 deletions OpenOversight/app/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def __init__(self):
"OO_MAIL_SUBJECT_PREFIX", "[OpenOversight]"
)
self.OO_SERVICE_EMAIL = os.environ.get("OO_SERVICE_EMAIL")
# TODO: Remove the default once we are able to update the production .env file
# TODO: Once that is done, we can re-alpha sort these variables.
self.OO_HELP_EMAIL = os.environ.get("OO_HELP_EMAIL", self.OO_SERVICE_EMAIL)

# AWS Settings
self.AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
Expand Down
13 changes: 13 additions & 0 deletions OpenOversight/app/models/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ def __init__(self, receiver: str, user, token: str):
super().__init__(body, subject, receiver)


class ChangePasswordEmail(Email):
def __init__(self, receiver: str, user):
subject = (
f"{current_app.config['OO_MAIL_SUBJECT_PREFIX']} Your Password Has Changed"
)
body = render_template(
"auth/email/change_password.html",
user=user,
help_email=current_app.config["OO_HELP_EMAIL"],
)
super().__init__(body, subject, receiver)


class ConfirmAccountEmail(Email):
def __init__(self, receiver: str, user, token: str):
subject = f"{current_app.config['OO_MAIL_SUBJECT_PREFIX']} Confirm Your Account"
Expand Down
2 changes: 1 addition & 1 deletion OpenOversight/app/templates/auth/email/change_email.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
<p>Sincerely,</p>
<p>The OpenOversight Team</p>
<p>
<small>Note: replies to this email address are not monitored.</small>
<small>Please note that we may not monitor replies to this email address.</small>
</p>
11 changes: 11 additions & 0 deletions OpenOversight/app/templates/auth/email/change_password.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<p>Dear {{ user.username }},</p>
<p>Your password has just been changed.</p>
<p>If you initiated this change to your password, you can ignore this email.</p>
<p>
If you did not reset your password, please contact <a href="mailto:{{ help_email }}">the OpenOversight help account</a>; they will help you address this issue.
</p>
<p>Sincerely,</p>
<p>The OpenOversight Team</p>
<p>
<small>Please note that we may not monitor replies to this email address.</small>
</p>
2 changes: 1 addition & 1 deletion OpenOversight/app/templates/auth/email/confirm.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
<p>Sincerely,</p>
<p>The OpenOversight Team</p>
<p>
<small>Note: replies to this email address are not monitored.</small>
<small>Please note that we may not monitor replies to this email address.</small>
</p>
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
<p>Sincerely,</p>
<p>The OpenOversight Team</p>
<p>
<small>Note: replies to this email address are not monitored.</small>
<small>Please note that we may not monitor replies to this email address.</small>
</p>
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
<p>Sincerely,</p>
<p>The OpenOversight Team</p>
<p>
<small>Note: replies to this email address are not monitored.</small>
<small>Please note that we may not monitor replies to this email address.</small>
</p>
2 changes: 1 addition & 1 deletion OpenOversight/app/templates/auth/email/reset_password.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
<p>Sincerely,</p>
<p>The OpenOversight Team</p>
<p>
<small>Note: replies to this email address are not monitored.</small>
<small>Please note that we may not monitor replies to this email address.</small>
</p>
43 changes: 37 additions & 6 deletions OpenOversight/tests/routes/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Routing and view tests
from http import HTTPStatus
from unittest import TestCase
from urllib.parse import urlparse

import pytest
Expand Down Expand Up @@ -141,8 +142,10 @@ def test_user_cannot_register_if_passwords_dont_match(mockdata, client, session)


def test_user_can_register_with_legit_credentials(mockdata, client, session):
with current_app.test_request_context():
diceware_password = "operative hamster perservere verbalize curling"
with current_app.test_request_context(), TestCase.assertLogs(
current_app.logger
) as log:
diceware_password = "operative hamster persevere verbalize curling"
form = RegistrationForm(
email="[email protected]",
username="redshiftzero",
Expand All @@ -154,6 +157,10 @@ def test_user_can_register_with_legit_credentials(mockdata, client, session):
)

assert b"A confirmation email has been sent to you." in rv.data
assert (
f"{current_app.config['OO_MAIL_SUBJECT_PREFIX']} Confirm Your Account"
in str(log.output)
)


def test_user_cannot_register_with_weak_password(mockdata, client, session):
Expand All @@ -172,16 +179,24 @@ def test_user_cannot_register_with_weak_password(mockdata, client, session):


def test_user_can_get_a_confirmation_token_resent(mockdata, client, session):
with current_app.test_request_context():
with current_app.test_request_context(), TestCase.assertLogs(
current_app.logger
) as log:
login_user(client)

rv = client.get(url_for("auth.resend_confirmation"), follow_redirects=True)

assert b"A new confirmation email has been sent to you." in rv.data
assert (
f"{current_app.config['OO_MAIL_SUBJECT_PREFIX']} Confirm Your Account"
in str(log.output)
)


def test_user_can_get_password_reset_token_sent(mockdata, client, session):
with current_app.test_request_context():
with current_app.test_request_context(), TestCase.assertLogs(
current_app.logger
) as log:
form = PasswordResetRequestForm(email="[email protected]")

rv = client.post(
Expand All @@ -191,12 +206,18 @@ def test_user_can_get_password_reset_token_sent(mockdata, client, session):
)

assert b"An email with instructions to reset your password" in rv.data
assert (
f"{current_app.config['OO_MAIL_SUBJECT_PREFIX']} Reset Your Password"
in str(log.output)
)


def test_user_can_get_password_reset_token_sent_with_differently_cased_email(
mockdata, client, session
):
with current_app.test_request_context():
with current_app.test_request_context(), TestCase.assertLogs(
current_app.logger
) as log:
form = PasswordResetRequestForm(email="[email protected]")

rv = client.post(
Expand All @@ -206,6 +227,10 @@ def test_user_can_get_password_reset_token_sent_with_differently_cased_email(
)

assert b"An email with instructions to reset your password" in rv.data
assert (
f"{current_app.config['OO_MAIL_SUBJECT_PREFIX']} Reset Your Password"
in str(log.output)
)


def test_user_can_get_reset_password_with_valid_token(mockdata, client, session):
Expand Down Expand Up @@ -361,7 +386,9 @@ def test_user_can_not_confirm_account_with_invalid_token(mockdata, client, sessi


def test_user_can_change_password_if_they_match(mockdata, client, session):
with current_app.test_request_context():
with current_app.test_request_context(), TestCase.assertLogs(
current_app.logger
) as log:
login_user(client)
form = ChangePasswordForm(
old_password="dog", password="validpasswd", password2="validpasswd"
Expand All @@ -372,6 +399,10 @@ def test_user_can_change_password_if_they_match(mockdata, client, session):
)

assert b"Your password has been updated." in rv.data
assert (
f"{current_app.config['OO_MAIL_SUBJECT_PREFIX']} Your Password Has Changed"
in str(log.output)
)


def test_unconfirmed_user_redirected_to_confirm_account(mockdata, client, session):
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ services:
AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}"
ENV: "${ENV:-development}"
FLASK_APP: OpenOversight.app
OO_HELP_EMAIL: "[email protected]"
OO_SERVICE_EMAIL: "[email protected]"
S3_BUCKET_NAME: "${S3_BUCKET_NAME}"
volumes:
Expand Down

0 comments on commit 3606e67

Please sign in to comment.