From 7800c24f9944483c7a96a3aa17a9a8db65ac7a5e Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Mon, 26 Sep 2022 09:40:48 -0400 Subject: [PATCH] Forbid password resets after 15 minutes. --- undercover/db.py | 23 ++++++++++++++--------- undercover/routes.py | 3 ++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/undercover/db.py b/undercover/db.py index 2aa9b64..e1bc625 100644 --- a/undercover/db.py +++ b/undercover/db.py @@ -1,7 +1,8 @@ -from uuid import uuid4, UUID import os from dataclasses import dataclass +from datetime import datetime, timedelta from typing import Optional +from uuid import uuid4, UUID import bcrypt import psycopg @@ -134,17 +135,21 @@ def initiate_password_reset(email: str) -> Optional[UUID]: return reset_id +RESET_TIME = timedelta(minutes=-1 * 15) + + def complete_reset(reset_id: str, new_password: str): with connect() as con: cur = con.cursor() cur.execute("SELECT reset_time, user_id FROM resets WHERE id = %s", (reset_id,)) row = cur.fetchone() - if row: - reset_time, user_id = row - if reset_time: # TODO: And reset_time is not too far in the past - cur.execute("DELETE FROM resets WHERE id = %s", (reset_id,)) - password_hash = __gen_pw_hash(new_password) - cur.execute("UPDATE users SET password = %s WHERE id = %s", (password_hash, user_id)) - con.commit() - return True + if not row or not row[0]: + return False + reset_time, user_id = row + if reset_time > (datetime.utcnow() + RESET_TIME): + cur.execute("DELETE FROM resets WHERE id = %s", (reset_id,)) + password_hash = __gen_pw_hash(new_password) + cur.execute("UPDATE users SET password = %s WHERE id = %s", (password_hash, user_id)) + con.commit() + return True return False diff --git a/undercover/routes.py b/undercover/routes.py index 10fcaae..77742ed 100644 --- a/undercover/routes.py +++ b/undercover/routes.py @@ -166,7 +166,8 @@ def reset_password(): elif existing_reset_id: # TODO: Eventually remove db entry whether or not link is clicked new_password = request.form['password'] - db.complete_reset(existing_reset_id, new_password) + if not db.complete_reset(existing_reset_id, new_password): + return render_index(error="Password reset failed. Your reset link may have expired.", status=500) # TODO: Log in? return redirect('/')