about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn_auth/auth/authorisation/users/views.py72
-rw-r--r--gn_auth/templates/emails/forgot-password.html30
-rw-r--r--gn_auth/templates/emails/forgot-password.txt10
-rw-r--r--gn_auth/templates/users/forgot-password-token-send-success.html21
-rw-r--r--gn_auth/templates/users/forgot-password.html35
5 files changed, 168 insertions, 0 deletions
diff --git a/gn_auth/auth/authorisation/users/views.py b/gn_auth/auth/authorisation/users/views.py
index 4b56c3d..84d7ef0 100644
--- a/gn_auth/auth/authorisation/users/views.py
+++ b/gn_auth/auth/authorisation/users/views.py
@@ -366,3 +366,75 @@ def send_verification_code():
     })
     resp.code = 400
     return resp
+
+
+def send_forgot_password_email(conn, user: User):
+    """Send the 'forgot-password' email."""
+    subject="GeneNetwork: Change Your Password"
+    token = secrets.token_urlsafe(64)
+    generated = datetime.now()
+    expiration_minutes = 15
+    def __render__(template):
+        return render_template(template,
+                               subject=subject,
+                               forgot_password_uri=urljoin(
+                                   request.url,
+                                   url_for("oauth2.users.change_password",
+                                           forgot_password_token=token)),
+                               expiration_minutes=expiration_minutes)
+
+    with db.cursor(conn) as cursor:
+        cursor.execute(
+            ("INSERT INTO "
+             "forgot_password_tokens(user_id, token, generated, expires) "
+             "VALUES (:user_id, :token, :generated, :expires) "
+             "ON CONFLICT (user_id) REPLACE"),
+            {
+                "user_id": str(user.user_id),
+                "token": token,
+                "generated": int(generated.timestamp()),
+                "expires": int(
+                    (generated +
+                     timedelta(
+                         minutes=expiration_minutes)).timestamp())
+            })
+        send_message(smtp_user=current_app.config["SMTP_USER"],
+                     smtp_passwd=current_app.config["SMTP_PASSWORD"],
+                     message=build_email_message(
+                         from_address=current_app.config["EMAIL_ADDRESS"],
+                         to_addresses=(user_address(user),),
+                         subject=subject,
+                         txtmessage=__render__("emails/forgot-password.txt"),
+                         htmlmessage=__render__("emails/forgot-password.html")),
+                     host=current_app.config["SMTP_HOST"],
+                     port=current_app.config["SMTP_PORT"])
+
+
+@users.route("/forgot-password", methods=["GET", "POST"])
+def forgot_password():
+    """Enable user to request password change."""
+    if request.method == "GET":
+        return render_template("users/forgot-password.html")
+
+    form = request.form
+    email = form.get("email", "").strip()
+    if not bool(email):
+        flash("You MUST provide an email.", "alert-danger")
+        return redirect(url_for("oauth2.users.forgot_password"))
+
+    with (db.connection(current_app.config["AUTH_DB"]) as conn,
+          db.cursor(conn) as cursor):
+        user = user_by_email(conn, form["email"])
+        if not bool(user):
+            flash("We could not find an account with that email.",
+                  "alert-danger")
+            return redirect(url_for("oauth2.users.forgot_password"))
+
+        send_forgot_password_email(conn, user)
+        return render_template("users/forgot-password-token-send-success.html")
+
+
+@users.route("/change-password/<forgot_password_token>", methods=["GET", "POST"])
+def change_password(forgot_password_token):
+    """Enable user to perform password change."""
+    return "Would change password..."
diff --git a/gn_auth/templates/emails/forgot-password.html b/gn_auth/templates/emails/forgot-password.html
new file mode 100644
index 0000000..18321d5
--- /dev/null
+++ b/gn_auth/templates/emails/forgot-password.html
@@ -0,0 +1,30 @@
+<html>
+  <head>
+    <meta charset="UTF-8" />
+    <title>{{subject}}</title>
+  </head>
+  <body>
+    <p>
+      You (or someone pretending to be you) made a request to change your
+      password. Please follow the link below to change it.
+    </p>
+
+    <p>
+      Click the button below to change your password
+      <a href="{{forgot_password_uri}}"
+         style="display: block;text-align: center;vertical-align: center;cursor: pointer;border-radius: 4px;background-color: #336699;border-color: #357ebd;color: white;text-decoration: none;font-size: large;width: 9em;text-transform: capitalize;margin: 1em 0 0 3em;box-shadow: 2px 2px rgba(0, 0, 0, 0.3);">Change my Password</a>.</p>
+
+    <p>
+      Or copy the link below onto your browser's address bar:<br /><br />
+      <span style="font-weight: bolder;">{{forgot_password_uri}}</span>
+    </p>
+
+    <p>
+      If you did not request to change your password, simply ignore this email.
+    </p>
+
+    <p style="font-weight: bold;color: #ee55ee;">
+      The link will expire in <strong>{{expiration_minutes}}</strong> minutes.
+    </p>
+  </body>
+</html>
diff --git a/gn_auth/templates/emails/forgot-password.txt b/gn_auth/templates/emails/forgot-password.txt
new file mode 100644
index 0000000..7eda908
--- /dev/null
+++ b/gn_auth/templates/emails/forgot-password.txt
@@ -0,0 +1,10 @@
+{{subject}}
+===============
+
+You (or someone pretending to be you) made a request to change your password. Please copy the link below onto your browser to change your password:
+
+{{forgot_password_uri}}
+
+If you did not request to change your password, simply ignore this email.
+
+The link will expire {{expiration_minutes}} minutes.
diff --git a/gn_auth/templates/users/forgot-password-token-send-success.html b/gn_auth/templates/users/forgot-password-token-send-success.html
new file mode 100644
index 0000000..ab8a741
--- /dev/null
+++ b/gn_auth/templates/users/forgot-password-token-send-success.html
@@ -0,0 +1,21 @@
+{%extends "base.html"%}
+
+{%block title%}gn-auth: Forgot Password{%endblock%}
+
+{%block pagetitle%}Forgot Password{%endblock%}
+
+{%block content%}
+{{flash_messages()}}
+
+<div class="container-fluid">
+  <div class="row"><h1>Forgot Password</h1></div>
+
+  <div class="row">
+    <p>
+      We have sent an email to '{{email}}'. Please log in to your email and
+      click the URL to change your password.
+    </p>
+  </div>
+
+</div>
+{%endblock%}
diff --git a/gn_auth/templates/users/forgot-password.html b/gn_auth/templates/users/forgot-password.html
new file mode 100644
index 0000000..94fcc68
--- /dev/null
+++ b/gn_auth/templates/users/forgot-password.html
@@ -0,0 +1,35 @@
+{%extends "base.html"%}
+
+{%block title%}gn-auth: Forgot Password{%endblock%}
+
+{%block pagetitle%}Forgot Password{%endblock%}
+
+{%block content%}
+{{flash_messages()}}
+
+<div class="container-fluid">
+  <div class="row"><h1>Forgot Password</h1></div>
+
+  <div class="row">
+    <form method="POST"
+          action="{{url_for('oauth2.users.forgot_password')}}">
+      <div class="form-group">
+        <span>
+          Provide you email below, and we will send you a link you can use to
+          change your password.
+        </span>
+      </div>
+
+      <div class="form-group">
+        <label for="txt-email" class="form-label">Email</label>
+        <input type="email" name="email" id="txt-email" class="form-control" />
+      </div>
+
+      <div class="form-group">
+        <input type="submit" class="btn btn-primary" value="Send Link" />
+      </div>
+    </form>
+  </div>
+
+</div>
+{%endblock%}