Skip to content

Commit

Permalink
added sending of an email with a token to recover an account
Browse files Browse the repository at this point in the history
  • Loading branch information
cedricbonhomme committed Apr 8, 2020
1 parent 2c82f05 commit dc35748
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 36 deletions.
6 changes: 4 additions & 2 deletions instance/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
# slow database query threshold (in seconds)
DATABASE_QUERY_TIMEOUT = 0.5

MAIL_SERVER = 'localhost'
# Notification
MAIL_SERVER = "localhost"
MAIL_PORT = 25
MAIL_USE_TLS = False
MAIL_USE_SSL = False
MAIL_DEBUG = DEBUG
MAIL_USERNAME = None
MAIL_PASSWORD = None
MAIL_DEFAULT_SENDER = ADMIN_EMAIL
MAIL_DEFAULT_SENDER = "[email protected]"
TOKEN_VALIDITY_PERIOD = 3600
6 changes: 4 additions & 2 deletions instance/production.py.sample
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ ADMIN_EMAIL = '[email protected]'

MAX_CONTENT_LENGTH = 16 * 1024 * 1024

MAIL_SERVER = 'localhost'
# Notification
MAIL_SERVER = "localhost"
MAIL_PORT = 25
MAIL_USE_TLS = False
MAIL_USE_SSL = False
MAIL_DEBUG = DEBUG
MAIL_USERNAME = None
MAIL_PASSWORD = None
MAIL_DEFAULT_SENDER = ADMIN_EMAIL
MAIL_DEFAULT_SENDER = "[email protected]"
TOKEN_VALIDITY_PERIOD = 3600
15 changes: 7 additions & 8 deletions mosp/notifications/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,27 @@

from mosp.bootstrap import application
from mosp.notifications import emails
# from mosp.web.lib.user_utils import generate_confirmation_token
from mosp.web.lib.user_utils import generate_confirmation_token


def new_account_notification(user, email):
def account_recovery(user):
"""Account recovery.
"""
Account creation notification.
"""
token = generate_confirmation_token(user.nickname)
token = generate_confirmation_token(user.login)
expire_time = datetime.datetime.now() + datetime.timedelta(
seconds=application.config["TOKEN_VALIDITY_PERIOD"]
)

plaintext = render_template(
"emails/account_activation.txt",
"emails/account_recovery.txt",
user=user,
platform_url=application.config["PLATFORM_URL"],
platform_url=application.config["INSTANCE_URL"],
token=token,
expire_time=expire_time,
)

emails.send(
to=email, subject="[MOSP] Account creation", plaintext=plaintext,
to=user.email, subject="[MOSP] Account recovery", plaintext=plaintext,
)


Expand Down
9 changes: 0 additions & 9 deletions mosp/templates/emails/account_activation.txt

This file was deleted.

10 changes: 10 additions & 0 deletions mosp/templates/emails/account_recovery.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Dear {{ user.login }},

Your or someone else asked for account recovery.

If it is really you click on the following link in order to confirm it:
{{ instance_url }}/user/recover_account/{{ token }}

The link expires at {{ expire_time.strftime('%Y-%m-%d %H:%M') }}.

See you,
1 change: 1 addition & 0 deletions mosp/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ <h2>{{ _('Log In') }}</h2>
{{ form.submit(class_="btn btn-default") }}
</div>
<div class="col text-right">
<a href="{{ url_for('user_bp.account_recovery') }}">Forgot your password?</a><br />
<a href="{{ url_for('help') }}">Want to join the community?</a>
</div>
</div>
Expand Down
30 changes: 17 additions & 13 deletions mosp/web/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,8 @@ def redirect_target(self):


class SigninForm(RedirectForm):
"""Sign in form.
"""
Sign in form.
"""

login = TextField(
lazy_gettext("Login"),
[
Expand Down Expand Up @@ -93,9 +91,21 @@ def validate(self):
return validated


class AccountRecoveryForm(RedirectForm):
"""Sign in form.
"""
login = TextField(
lazy_gettext("Login"),
[
validators.Length(min=3, max=30),
validators.Required(lazy_gettext("Please enter your login.")),
],
)
submit = SubmitField(lazy_gettext("OK"))


class AddObjectForm(FlaskForm):
"""Form to create and edit JsonObject."""

name = TextField("Name", [validators.Required(lazy_gettext("Please enter a name"))])
description = TextAreaField(
lazy_gettext("Description"),
Expand Down Expand Up @@ -154,10 +164,8 @@ class SchemaForm(FlaskForm):


class UserForm(FlaskForm):
"""Create or edit a user (for the administrator).
"""
Create or edit a user (for the administrator).
"""

login = TextField(
lazy_gettext("Login"),
[
Expand Down Expand Up @@ -189,10 +197,8 @@ def __init__(self, *args, **kwargs):


class OrganizationForm(FlaskForm):
"""Create or edit an organization (for the administrator).
"""
Create or edit an organization (for the administrator).
"""

name = TextField(
lazy_gettext("Name"),
[
Expand All @@ -215,10 +221,8 @@ def __init__(self, *args, **kwargs):


class ProfileForm(FlaskForm):
"""Edit a profile.
"""
Edit a profile.
"""

login = TextField(
lazy_gettext("Login"),
[
Expand Down
21 changes: 21 additions & 0 deletions mosp/web/lib/user_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from itsdangerous import URLSafeTimedSerializer

from mosp.bootstrap import application


def generate_confirmation_token(login):
serializer = URLSafeTimedSerializer(application.config["SECRET_KEY"])
return serializer.dumps(login, salt=application.config["SECURITY_PASSWORD_SALT"])


def confirm_token(token):
serializer = URLSafeTimedSerializer(application.config["SECRET_KEY"])
try:
login = serializer.loads(
token,
salt=application.config["SECURITY_PASSWORD_SALT"],
max_age=application.config["TOKEN_VALIDITY_PERIOD"],
)
except:
return False
return login
34 changes: 32 additions & 2 deletions mosp/web/views/user.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from flask import Blueprint, render_template, redirect, url_for, flash, abort
from flask import Blueprint, render_template, redirect, url_for, flash, abort, request
from flask_login import login_required, current_user
from werkzeug.security import generate_password_hash
from flask_babel import gettext
from flask_paginate import Pagination, get_page_args

from mosp.bootstrap import db
from mosp.models import User, JsonObject
from mosp.web.forms import ProfileForm
from mosp.web.forms import ProfileForm, AccountRecoveryForm
from mosp.notifications import notifications


user_bp = Blueprint("user_bp", __name__, url_prefix="/user")
Expand Down Expand Up @@ -107,3 +108,32 @@ def delete_account():
db.session.commit()
flash(gettext("Account deleted."), "success")
return redirect(url_for("index"))


#
# Account revocery
#

@user_bp.route("/account_recovery", methods=["GET", "POST"])
def account_recovery():
form = AccountRecoveryForm()
if request.method == "GET":
return render_template("account_recovery.html", form=form)
else:
user = User.query.filter(User.login == form.login.data).first()

# Send the recovery email
try:
notifications.account_recovery(user)
except Exception as error:
flash(
gettext(
"Problem while sending activation email: %(error)s", error=error
),
"danger",
)
return redirect(url_for("index"))

flash(gettext("An email has been sent to you with a recover link."), "success")

return redirect(url_for("index"))

0 comments on commit dc35748

Please sign in to comment.