diff --git a/app/blueprints/users/account.py b/app/blueprints/users/account.py index 110a6eb9..9a27cf4d 100644 --- a/app/blueprints/users/account.py +++ b/app/blueprints/users/account.py @@ -106,7 +106,7 @@ def register(): if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user: - sendEmailRaw([form.email.data], "Email already in use", + sendEmailRaw.delay([form.email.data], "Email already in use", "We were unable to create the account as the email is already in use by {}. Try a different email address.".format(user.display_name)) else: user = User(form.username.data, False, form.email.data, make_flask_login_password(form.password.data)) @@ -129,17 +129,43 @@ def register(): return render_template("users/register.html", form=form, suggested_password=genphrase(entropy=52, wordset="bip39")) -class ForgotPassword(FlaskForm): +class ForgotPasswordForm(FlaskForm): email = StringField("Email", [InputRequired(), Email()]) submit = SubmitField("Reset Password") @bp.route("/user/forgot-password/", methods=["GET", "POST"]) def forgot_password(): - form = ForgotPassword(request.form) + form = ForgotPasswordForm(request.form) if form.validate_on_submit(): - pass + email = form.email.data + user = User.query.filter_by(email=email).first() + if user: + token = randomString(32) - return "Forgot password page" + ver = UserEmailVerification() + ver.user = user + ver.token = token + ver.email = email + ver.is_password_reset = True + db.session.add(ver) + db.session.commit() + + sendVerifyEmail.delay(form.email.data, token) + else: + sendEmailRaw.delay([email], "Unable to find account", """ +

+ We were unable to perform the password reset as we could not find an account + associated with this email. +

+

+ If you weren't expecting to receive this email, then you can safely ignore it. +

+ """) + + flash("Check your email address to continue the reset", "success") + return redirect(url_for("homepage.home")) + + return render_template("users/forgot_password.html", form=form) class SetPasswordForm(FlaskForm): @@ -226,7 +252,7 @@ def set_password(): @bp.route("/user/verify/") def verify_email(): token = request.args.get("token") - ver = UserEmailVerification.query.filter_by(token=token).first() + ver : UserEmailVerification = UserEmailVerification.query.filter_by(token=token).first() if ver is None: flash("Unknown verification token!", "danger") return redirect(url_for("homepage.home")) @@ -237,6 +263,13 @@ def verify_email(): db.session.delete(ver) db.session.commit() + if ver.is_password_reset: + login_user(ver.user) + ver.user.password = None + db.session.commit() + + return redirect(url_for("users.set_password")) + if current_user.is_authenticated: return redirect(url_for("users.profile", username=current_user.username)) elif was_activating: diff --git a/app/models.py b/app/models.py index 0e2decfe..7f731d44 100644 --- a/app/models.py +++ b/app/models.py @@ -259,12 +259,15 @@ class User(db.Model, UserMixin): assert self.id > 0 return self.id == other.id + class UserEmailVerification(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey("user.id")) email = db.Column(db.String(100)) token = db.Column(db.String(32)) user = db.relationship("User", foreign_keys=[user_id]) + is_password_reset = db.Column(db.Boolean, nullable=False, default=False) + class Notification(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/app/templates/users/forgot_password.html b/app/templates/users/forgot_password.html new file mode 100644 index 00000000..852acaeb --- /dev/null +++ b/app/templates/users/forgot_password.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %} +Request Password Reset +{% endblock %} + +{% block content %} +{% from "macros/forms.html" import render_field, render_checkbox_field, render_submit_field %} + +
+

{{ _("Request Password Reset") }}

+ +
+ {{ form.hidden_tag() }} + + {{ render_field(form.email) }} +

+ {{ render_submit_field(form.submit, tabindex=180) }} +

+
+
+ +{% endblock %}