Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Gmail client and update email functions #944

Merged
merged 123 commits into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from 120 commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
7bdad86
Update requirements.txt
michplunkett Jun 27, 2023
3e79072
Update requirements.txt
michplunkett Jun 27, 2023
665d2b0
Update requirements.txt
michplunkett Jun 27, 2023
47e0f09
Update requirements.txt
michplunkett Jun 27, 2023
ea734a9
Create mail_client.py
michplunkett Jun 27, 2023
6f98f5f
Update requirements.txt
michplunkett Jun 27, 2023
77e69e0
Update .gitignore
michplunkett Jun 27, 2023
5c7029b
Update mail_client.py
michplunkett Jun 27, 2023
f8521dd
Switch file name
michplunkett Jun 27, 2023
d842413
Update __init__.py
michplunkett Jun 27, 2023
b627ccd
Merge branch 'develop' into email_issue
michplunkett Jun 28, 2023
c008090
Merge branch 'develop' into email_issue
michplunkett Jun 28, 2023
886672d
Update __init__.py
michplunkett Jun 28, 2023
0885ecb
Update __init__.py
michplunkett Jun 28, 2023
7a72ef6
Update gmail_client.py
michplunkett Jun 28, 2023
152fa8c
Update gmail_client.py
michplunkett Jun 28, 2023
b4eede3
Update gmail_client.py
michplunkett Jun 28, 2023
cbd8be7
Update views.py
michplunkett Jun 28, 2023
78d67d4
Update views.py
michplunkett Jun 28, 2023
f4688e9
Update gmail_client.py
michplunkett Jun 28, 2023
2d8c12c
Update gmail_client.py
michplunkett Jun 28, 2023
311dbbe
Update docker-compose.yml
michplunkett Jun 28, 2023
4d1c87f
Update __init__.py
michplunkett Jun 28, 2023
10af5d7
Update __init__.py
michplunkett Jun 28, 2023
79669cd
Update views.py
michplunkett Jun 28, 2023
59723d7
Update gmail_client.py
michplunkett Jun 28, 2023
3b5f6b3
Delete email.py
michplunkett Jun 28, 2023
d7c283f
alpha-sort docker environment variables
michplunkett Jun 29, 2023
a996b6d
Update docker-compose.yml
michplunkett Jun 29, 2023
09a1f88
Remove flask-mail requirement
michplunkett Jun 29, 2023
f72bc53
Update config.py
michplunkett Jun 29, 2023
380f1c7
Update gmail_client.py
michplunkett Jun 29, 2023
09412a6
Remove flask_mail code
michplunkett Jun 29, 2023
da308e8
Update __init__.py
michplunkett Jun 29, 2023
aceff6c
Variable editing
michplunkett Jun 29, 2023
6662d34
Update gmail_client.py
michplunkett Jun 29, 2023
d77a540
Update config.py
michplunkett Jun 29, 2023
7515790
Sorting lines
michplunkett Jun 29, 2023
34c9634
Update __init__.py
michplunkett Jun 29, 2023
f5befa9
Update __init__.py
michplunkett Jun 29, 2023
ae58496
Update config.py
michplunkett Jun 29, 2023
c2f91ff
Add file size constants
michplunkett Jun 29, 2023
961aa62
Use html email templates
michplunkett Jun 29, 2023
25d0871
Remove txt email templates
michplunkett Jun 29, 2023
62355a6
Use config values, not key searching
michplunkett Jun 29, 2023
1640a18
Update views.py
michplunkett Jun 29, 2023
e3a3c31
Update docker-compose.prod-img.yml
michplunkett Jun 30, 2023
5ce1043
Update docker-compose.prod-img.yml
michplunkett Jun 30, 2023
1883628
Update gmail_client.py
michplunkett Jun 30, 2023
eea6308
Update requirements.txt
michplunkett Jun 30, 2023
3a3c9c0
Update requirements.txt
michplunkett Jun 30, 2023
d6fcf4c
Update test_user_api.py
michplunkett Jun 30, 2023
d272952
Update test_user_api.py
michplunkett Jun 30, 2023
352e751
Update test_user_api.py
michplunkett Jun 30, 2023
a3a5a5c
Update test_user_api.py
michplunkett Jun 30, 2023
f4140df
Update requirements.txt
michplunkett Jun 30, 2023
d545435
Update test_user_api.py
michplunkett Jun 30, 2023
75b1588
Update test_user_api.py
michplunkett Jun 30, 2023
daa9d06
Update test_user_api.py
michplunkett Jun 30, 2023
eac3483
Update general.py
michplunkett Jun 30, 2023
fc5a2a9
Update test_user_api.py
michplunkett Jun 30, 2023
27dfdc5
Update gmail_client.py
michplunkett Jun 30, 2023
d329f9f
Update gmail_client.py
michplunkett Jun 30, 2023
3fa934e
Trying again
michplunkett Jun 30, 2023
6077f71
Update __init__.py
michplunkett Jun 30, 2023
10b1073
Fix test and dev docker starts.
abandoned-prototype Jul 2, 2023
215ea41
Merge branch 'develop' into email_issue
abandoned-prototype Jul 2, 2023
4d57a52
Update docker-compose.yml
michplunkett Jul 3, 2023
ca34a9e
Update docker-compose.prod-img.yml
michplunkett Jul 3, 2023
82629b6
Update gmail_client.py
michplunkett Jul 3, 2023
6ff3af9
Update Makefile
michplunkett Jul 3, 2023
c3af59a
Update Makefile
michplunkett Jul 3, 2023
632e14e
Spacing and standardize naming format
michplunkett Jul 3, 2023
0f300ef
Remove OO_SERVICE_EMAIL from docker files
michplunkett Jul 3, 2023
10ba6fc
TRAVIS_PYTHON_VERSION -> MAKE_PYTHON_VERSION
michplunkett Jul 3, 2023
f3e84ec
Update Makefile
michplunkett Jul 3, 2023
8775555
Update Makefile
michplunkett Jul 3, 2023
b4df3bf
Spaces
michplunkett Jul 3, 2023
5936a30
Update test_user_api.py
michplunkett Jul 3, 2023
3477bcf
Update general.py
michplunkett Jul 3, 2023
c3351c4
Update gmail_client.py
michplunkett Jul 3, 2023
ea9d82c
Update gmail_client.py
michplunkett Jul 3, 2023
6189cc7
Update CONTRIB.md
michplunkett Jul 3, 2023
57eadd4
Update CONTRIB.md
michplunkett Jul 3, 2023
bf1885f
Update CONTRIB.md
michplunkett Jul 3, 2023
25bec63
Update CONTRIB.md
michplunkett Jul 3, 2023
6785f62
Update CONTRIB.md
michplunkett Jul 3, 2023
774ef60
Update docker-compose.yml
michplunkett Jul 3, 2023
d7c7205
Update Makefile
michplunkett Jul 3, 2023
1aa8772
Update Makefile
michplunkett Jul 3, 2023
4491206
Update CONTRIB.md
michplunkett Jul 3, 2023
57d4ae4
Add models package and email file
michplunkett Jul 5, 2023
ba12f69
Add email objects
michplunkett Jul 5, 2023
e6ea11b
Remove email objects from mail client
michplunkett Jul 5, 2023
23532db
gmail_client -> email_client
michplunkett Jul 5, 2023
d334b24
GmailClient -> EmailClient
michplunkett Jul 5, 2023
067cd37
Update email_client.py
michplunkett Jul 5, 2023
bc739f1
Delete models package
michplunkett Jul 5, 2023
2b26e94
BaseConfig change
michplunkett Jul 5, 2023
a3e352d
Move Email class
michplunkett Jul 5, 2023
43957f4
Update views.py
michplunkett Jul 5, 2023
013b085
Update email_client.py
michplunkett Jul 5, 2023
15c7836
Update test_functional.py
michplunkett Jul 5, 2023
97129bc
Update views.py
michplunkett Jul 5, 2023
fd44c96
Update utils.py
michplunkett Jul 5, 2023
22a0921
Update models.py
michplunkett Jul 5, 2023
6252755
Update test_functional.py
michplunkett Jul 5, 2023
7ae657c
Update test_functional.py
michplunkett Jul 5, 2023
dfb4db5
Move emails back
michplunkett Jul 5, 2023
6145fb5
Update email_client.py
michplunkett Jul 5, 2023
fcf0d9d
Update email_client.py
michplunkett Jul 5, 2023
9e46d85
Update email_client.py
michplunkett Jul 5, 2023
d4a77fc
Add dev logic to the init file
michplunkett Jul 5, 2023
38347e6
Update CONTRIB.md
michplunkett Jul 5, 2023
ebb0e58
Update docker-compose.yml
michplunkett Jul 5, 2023
e5e1aa2
Update CONTRIB.md
michplunkett Jul 5, 2023
1614924
Update email_client.py
michplunkett Jul 6, 2023
2360fb3
Update .gitignore
michplunkett Jul 6, 2023
6dd2f50
Merge branch 'develop' into email_issue
abandoned-prototype Jul 6, 2023
aea80a2
Merge branch 'develop' into email_issue
abandoned-prototype Jul 6, 2023
57c3726
Update requirements.txt
michplunkett Jul 6, 2023
0843f3d
Update requirements.txt
michplunkett Jul 6, 2023
910ddc1
Update requirements.txt
michplunkett Jul 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions CONTRIB.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Contributing Guide

First, thanks for being interested in helping us out! If you find an issue you're interested in, feel free to make a comment about how you're thinking of approaching implementing it in the issue and we can give you feedback. Please also read our [code of conduct](/CODE_OF_CONDUCT.md) before getting started.

## Submitting a Pull Request (PR)

When you come to implement your new feature, clone the repository and then create a branch off `develop` locally and add commits to implement your feature.

If your git history is not so clean, please do rewrite before you submit your PR - if you're not sure if you need to do this, go ahead and submit and we can let you know when you submit.
Expand All @@ -21,13 +19,11 @@ git config user.name "<your-github-username>"
This will make sure that all commits you make locally are associated with your github account and do not contain any additional identifying information. More detailed information on this topic can be found [here](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address).

### Linting / Style Checks
We use [pre-commit](https://pre-commit.com/) for automated linting and style checks. Be sure to [install pre-commit](https://pre-commit.com/#installation) and run `pre-commit install` in your local version of the repository to install our pre-commit checks. This will make sure your commits are always formatted correctly.

We use [pre-commit](https://pre-commit.com/) for automated linting and style checks. Be sure to [install pre-commit](https://pre-commit.com/#installation) and run `pre-commit install` in your local version of the repository to install our pre-commit checks. This will make sure your commits are always formatted correctly.

You can run `pre-commit run --all-files` or `make lint` to run pre-commit over your local codebase, or `pre-commit run` to run it only over the currently stages files.
You can run `pre-commit run --all-files` or `make lint` to run pre-commit over your local codebase, or `pre-commit run` to run it only over the currently stages files.

## Development Environment

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These spaces don't visually change anything in the document, as the header elements have a built-in bottom margin.

You can use our Docker-compose environment to stand up a development OpenOversight.

You will need to have Docker installed in order to use the Docker development environment.
Expand All @@ -53,8 +49,24 @@ $ docker exec -it openoversight_web_1 /bin/bash

Once you're done, `make stop` and `make clean` to stop and remove the containers respectively.

## Testing S3 Functionality
## Gmail Requirements
**NOTE:** If you are running on dev and do not currently have a `service_account_key.json` file, create one and leave it empty. The email client will then default to an empty object and simulate emails in the logs.

For the application to work properly, you will need a [Google Cloud Platform service account](https://cloud.google.com/iam/docs/service-account-overview) that is attached to a GSuite email address. Here are some general tips for working with service accounts: [Link](https://support.google.com/a/answer/7378726?hl=en).
We would suggest that you do not use a personal email address, but instead one that is used strictly for sending out OpenOversight emails.

You will need to do these two things for the service account to work as a Gmail bot:
1. Enable domain-wide delegation for the service account: [Link](https://support.google.com/a/answer/162106?hl=en)
2. Enable the `https://www.googleapis.com/auth/gmail.send` scope in the Gmail API for your service account: [Link](https://developers.google.com/gmail/api/auth/scopes#scopes)
3. Save the service account key file in OpenOversight's base folder as `service_account_key.json`. The file is in the `.gitignore` file GitHub will not allow you to save it, provided you've named it correctly.
4. For production, save the email address associated with your service account to a variable named `OO_SERVICE_EMAIL` in a `.env` file in the base directory of this repository. For development and testing, update the `OO_SERVICE_EMAIL` variable in the `docker-compose.yml` file.

Example `.env` variable:
```bash
OO_SERVICE_EMAIL="[email protected]"
```
Comment on lines +53 to +67
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be all of the info needed, @abandoned-prototype.


## 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
on Amazon Web Services.
Expand All @@ -72,7 +84,6 @@ Now when you run `make dev` as usual in the same session, you will be able to su
your test bucket.

## Database commands

Running `make dev` will create the database and persist it into your local filesystem.

You can access your PostgreSQL development database via psql using:
Expand All @@ -89,7 +100,6 @@ or
`$ python test_data.py --cleanup` to delete the data

### Migrating the Database

If you e.g. add a new column or table, you'll need to migrate the database using the Flask CLI. First we need to 'stamp' the current version of the database:

```sh
Expand Down Expand Up @@ -152,7 +162,6 @@ pip install -r dev-requirements.txt
```

## OpenOversight Management Interface

In addition to generating database migrations, the Flask CLI can be used to run additional commands:

```sh
Expand Down
37 changes: 26 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ export UID=$(shell id -u)
default: build start create_db populate test stop clean

.PHONY: build
build: ## Build containers
build: ## Build containers
docker-compose build

.PHONY: build_with_version
build_with_version:
docker-compose build --build-arg TRAVIS_PYTHON_VERSION=$(PYTHON_VERSION)
build_with_version: create_empty_secret create_empty_env
docker-compose build --build-arg MAKE_PYTHON_VERSION=$(PYTHON_VERSION)

.PHONY: test_with_version
test_with_version: build_with_version assets
FLASK_ENV=testing docker-compose run --rm web pytest --cov=OpenOversight --cov-report xml:OpenOversight/tests/coverage.xml --doctest-modules -n 4 --dist=loadfile -v OpenOversight/tests/

.PHONY: start
start: build ## Run containers
start: build ## Run containers
docker-compose up -d

.PHONY: create_db
Expand All @@ -36,7 +36,7 @@ assets:
dev: build start create_db populate

.PHONY: populate
populate: create_db ## Build and run containers
populate: create_db ## Build and run containers
@until docker-compose exec postgres psql -h localhost -U openoversight -c '\l' postgres &>/dev/null; do \
echo "Postgres is unavailable - sleeping..."; \
sleep 1; \
Expand All @@ -46,27 +46,27 @@ populate: create_db ## Build and run containers
docker-compose run --rm web python ./test_data.py -p

.PHONY: test
test: start ## Run tests
test: start ## Run tests
if [ -z "$(name)" ]; then \
FLASK_ENV=testing docker-compose run --rm web pytest --cov --doctest-modules -n auto --dist=loadfile -v OpenOversight/tests/; \
else \
FLASK_ENV=testing docker-compose run --rm web pytest --cov --doctest-modules -v OpenOversight/tests/ -k $(name); \
FLASK_ENV=testing docker-compose run --rm web pytest --cov --doctest-modules -v OpenOversight/tests/ -k $(name); \
fi

.PHONY: lint
lint:
pre-commit run --all-files

.PHONY: cleanassets
cleanassets:
.PHONY: clean_assets
clean_assets:
rm -rf ./OpenOversight/app/static/dist/

.PHONY: stop
stop: ## Stop containers
stop: ## Stop containers
docker-compose stop

.PHONY: clean
clean: cleanassets stop ## Remove containers
clean: clean_assets stop ## Remove containers
docker-compose rm -f

.PHONY: clean_all
Expand All @@ -88,3 +88,18 @@ help: ## Print this message and exit
.PHONY: attach
attach:
docker-compose exec postgres psql -h localhost -U openoversight openoversight-dev

# TODO: These two commands are the same with the exception of the file name, this should be addressed at some point
.PHONY: create_empty_secret
create_empty_secret: # This is needed to make sure docker doesn't create an empty directory, or delete that directory first
touch service_account_key.json || \
(echo "Need to delete that empty directory first"; \
sudo rm -d service_account_key.json/; \
touch service_account_key.json)

.PHONY: create_empty_env
create_empty_env: # This is needed to make sure docker doesn't create an empty directory, or delete that directory first
touch .env || \
(echo "Need to delete that empty directory first"; \
sudo rm -d .env/; \
touch .env)
Comment on lines +100 to +105
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created so that the base level docker-compose command doesn't error out.

17 changes: 10 additions & 7 deletions OpenOversight/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_login import LoginManager
from flask_mail import Mail
from flask_migrate import Migrate
from flask_sitemap import Sitemap
from flask_wtf.csrf import CSRFProtect
from markupsafe import Markup

from .config import config
from OpenOversight.app.config import config
from OpenOversight.app.email_client import EmailClient
from OpenOversight.app.utils.constants import MEGABYTE, SERVICE_ACCOUNT_FILE


bootstrap = Bootstrap()
mail = Mail()

login_manager = LoginManager()
login_manager.session_protection = "strong"
Expand All @@ -43,12 +43,15 @@ def create_app(config_name="default"):
from .models import db

bootstrap.init_app(app)
mail.init_app(app)
csrf.init_app(app)
db.init_app(app)
login_manager.init_app(app)
# This allows the application to run without creating an email client if it is
# in testing or dev mode and the service account file is empty.
service_account_file_size = os.path.getsize(SERVICE_ACCOUNT_FILE)
EmailClient(dev=app.debug and service_account_file_size == 0, testing=app.testing)
limiter.init_app(app)
login_manager.init_app(app)
sitemap.init_app(app)
csrf.init_app(app)

from .main import main as main_blueprint

Expand All @@ -58,7 +61,7 @@ def create_app(config_name="default"):

app.register_blueprint(auth_blueprint, url_prefix="/auth")

max_log_size = 10 * 1024 * 1024 # start new log file after 10 MB
max_log_size = 10 * MEGABYTE # start new log file after 10 MB
num_logs_to_keep = 5
file_handler = RotatingFileHandler(
"/tmp/openoversight.log", "a", max_log_size, num_logs_to_keep
Expand Down
1 change: 1 addition & 0 deletions OpenOversight/app/auth/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from flask_login import current_user


# TODO: Move these functions to the utils package
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
Expand Down
78 changes: 28 additions & 50 deletions OpenOversight/app/auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@
)
from flask_login import current_user, login_required, login_user, logout_user

from OpenOversight.app.email_client import (
AdministratorApprovalEmail,
ChangeEmailAddressEmail,
ConfirmAccountEmail,
ConfirmedUserEmail,
EmailClient,
ResetPasswordEmail,
)
from OpenOversight.app.models import User, db
from OpenOversight.app.utils.constants import HTTP_METHOD_GET, HTTP_METHOD_POST
from OpenOversight.app.utils.forms import set_dynamic_default
from OpenOversight.app.utils.general import validate_redirect_url

from .. import sitemap
from ..email import send_email
from ..models import User, db
from . import auth
from .forms import (
ChangeDefaultDepartmentForm,
Expand Down Expand Up @@ -99,7 +106,7 @@ def logout():
@sitemap_include
@auth.route("/register", methods=[HTTP_METHOD_GET, HTTP_METHOD_POST])
def register():
jsloads = ["js/zxcvbn.js", "js/password.js"]
js_loads = ["js/zxcvbn.js", "js/password.js"]
form = RegistrationForm()
if form.validate_on_submit():
user = User(
Expand All @@ -113,31 +120,23 @@ def register():
if current_app.config["APPROVE_REGISTRATIONS"]:
admins = User.query.filter_by(is_administrator=True).all()
for admin in admins:
send_email(
admin.email,
"New user registered",
"auth/email/new_registration",
user=user,
admin=admin,
EmailClient.send_email(
AdministratorApprovalEmail(admin.email, user=user, admin=admin)
)
flash(
"Once an administrator approves your registration, you will "
"receive a confirmation email to activate your account."
)
else:
token = user.generate_confirmation_token()
send_email(
user.email,
"Confirm Your Account",
"auth/email/confirm",
user=user,
token=token,
EmailClient.send_email(
ConfirmAccountEmail(user.email, user=user, token=token)
)
flash("A confirmation email has been sent to you.")
return redirect(url_for("auth.login"))
else:
current_app.logger.info(form.errors)
return render_template("auth/register.html", form=form, jsloads=jsloads)
return render_template("auth/register.html", form=form, jsloads=js_loads)


@auth.route("/confirm/<token>", methods=[HTTP_METHOD_GET])
Expand All @@ -148,12 +147,8 @@ def confirm(token):
if current_user.confirm(token):
admins = User.query.filter_by(is_administrator=True).all()
for admin in admins:
send_email(
admin.email,
"New user confirmed",
"auth/email/new_confirmation",
user=current_user,
admin=admin,
EmailClient.send_email(
ConfirmedUserEmail(admin.email, user=current_user, admin=admin)
)
flash("You have confirmed your account. Thanks!")
else:
Expand All @@ -165,12 +160,8 @@ def confirm(token):
@login_required
def resend_confirmation():
token = current_user.generate_confirmation_token()
send_email(
current_user.email,
"Confirm Your Account",
"auth/email/confirm",
user=current_user,
token=token,
EmailClient.send_email(
ConfirmAccountEmail(current_user.email, user=current_user, token=token)
)
flash("A new confirmation email has been sent to you.")
return redirect(url_for("main.index"))
Expand All @@ -179,7 +170,7 @@ def resend_confirmation():
@auth.route("/change-password", methods=[HTTP_METHOD_GET, HTTP_METHOD_POST])
@login_required
def change_password():
jsloads = ["js/zxcvbn.js", "js/password.js"]
js_loads = ["js/zxcvbn.js", "js/password.js"]
form = ChangePasswordForm()
if form.validate_on_submit():
if current_user.verify_password(form.old_password.data):
Expand All @@ -192,7 +183,7 @@ def change_password():
flash("Invalid password.")
else:
current_app.logger.info(form.errors)
return render_template("auth/change_password.html", form=form, jsloads=jsloads)
return render_template("auth/change_password.html", form=form, jsloads=js_loads)


@auth.route("/reset", methods=[HTTP_METHOD_GET, HTTP_METHOD_POST])
Expand All @@ -204,12 +195,8 @@ def password_reset_request():
user = User.by_email(form.email.data).first()
if user:
token = user.generate_reset_token()
send_email(
user.email,
"Reset Your Password",
"auth/email/reset_password",
user=user,
token=token,
EmailClient.send_email(
ResetPasswordEmail(user.email, user=user, token=token)
)
flash("An email with instructions to reset your password has been sent to you.")
return redirect(url_for("auth.login"))
Expand Down Expand Up @@ -245,12 +232,8 @@ def change_email_request():
if current_user.verify_password(form.password.data):
new_email = form.email.data
token = current_user.generate_email_change_token(new_email)
send_email(
new_email,
"Confirm your email address",
"auth/email/change_email",
user=current_user,
token=token,
EmailClient.send_email(
ChangeEmailAddressEmail(new_email, user=current_user, token=token)
)
flash(
"An email with instructions to confirm your new email "
Expand Down Expand Up @@ -338,7 +321,8 @@ def edit_user(user_id):
db.session.add(user)
db.session.commit()

# automatically send a confirmation email when approving an unconfirmed user
# automatically send a confirmation email when approving an
# unconfirmed user
if (
current_app.config["APPROVE_REGISTRATIONS"]
and not already_approved
Expand Down Expand Up @@ -376,12 +360,6 @@ def admin_resend_confirmation(user):
flash("User {} is already confirmed.".format(user.username))
else:
token = user.generate_confirmation_token()
send_email(
user.email,
"Confirm Your Account",
"auth/email/confirm",
user=user,
token=token,
)
EmailClient.send_email(ConfirmAccountEmail(user.email, user=user, token=token))
flash("A new confirmation email has been sent to {}.".format(user.email))
return redirect(url_for("auth.get_users"))
Loading