05/24 Enhance functionalities
This commit is contained in:
+64
-1
@@ -4,8 +4,13 @@ routes/auth.py — Authentication routes: login, logout, change-password.
|
||||
|
||||
import logging
|
||||
from flask import Blueprint, render_template, request, session, redirect, url_for, flash
|
||||
from models import authenticate, check_login_allowed, change_password, log_action
|
||||
from models import (
|
||||
authenticate, check_login_allowed, change_password, log_action,
|
||||
get_user_by_email, create_password_reset_token,
|
||||
get_password_reset_user, consume_password_reset_token,
|
||||
)
|
||||
from utils.decorators import login_required
|
||||
from utils.email import send_email
|
||||
|
||||
logger = logging.getLogger("routes.auth")
|
||||
auth_bp = Blueprint("auth", __name__)
|
||||
@@ -95,3 +100,61 @@ def ping():
|
||||
"""Keep-alive endpoint for the session timeout warning in app.js."""
|
||||
session.modified = True
|
||||
return "", 204
|
||||
|
||||
|
||||
@auth_bp.route("/forgot-password", methods=["GET", "POST"])
|
||||
def forgot_password():
|
||||
if "user" in session:
|
||||
return redirect(url_for("index"))
|
||||
if request.method == "GET":
|
||||
return render_template("forgot_password.html")
|
||||
|
||||
email = request.form.get("email", "").strip().lower()
|
||||
if email:
|
||||
user = get_user_by_email(email)
|
||||
if user and user.get("email"):
|
||||
try:
|
||||
token = create_password_reset_token(user["id"])
|
||||
reset_url = url_for("auth.reset_password", token=token, _external=True)
|
||||
body = (
|
||||
f"Hi {user.get('full_name') or user['username']},\n\n"
|
||||
f"A password reset was requested for your Website Checker account.\n"
|
||||
f"Click the link below to set a new password (valid for 1 hour):\n\n"
|
||||
f"{reset_url}\n\n"
|
||||
f"If you did not request this, you can safely ignore this email.\n"
|
||||
)
|
||||
send_email(user["email"], "Website Checker — Password Reset", body)
|
||||
except Exception as e:
|
||||
logger.error(f"Password reset email error for '{email}': {e}")
|
||||
|
||||
# Always show the same message to prevent user enumeration
|
||||
flash("If that email is registered, a reset link has been sent.", "info")
|
||||
return render_template("forgot_password.html")
|
||||
|
||||
|
||||
@auth_bp.route("/reset-password/<token>", methods=["GET", "POST"])
|
||||
def reset_password(token):
|
||||
user = get_password_reset_user(token)
|
||||
if not user:
|
||||
flash("This reset link is invalid or has expired.", "danger")
|
||||
return redirect(url_for("auth.forgot_password"))
|
||||
|
||||
if request.method == "GET":
|
||||
return render_template("reset_password.html", token=token)
|
||||
|
||||
password = request.form.get("password", "")
|
||||
confirm = request.form.get("confirm_password", "")
|
||||
|
||||
if len(password) < 8:
|
||||
flash("Password must be at least 8 characters.", "danger")
|
||||
return render_template("reset_password.html", token=token)
|
||||
if password != confirm:
|
||||
flash("Passwords do not match.", "danger")
|
||||
return render_template("reset_password.html", token=token)
|
||||
|
||||
if consume_password_reset_token(token, password):
|
||||
logger.info(f"Password reset successfully for user id={user['id']}.")
|
||||
flash("Password reset successfully. Please log in.", "success")
|
||||
return redirect(url_for("auth.login"))
|
||||
flash("Reset link expired or already used. Please request a new one.", "danger")
|
||||
return redirect(url_for("auth.forgot_password"))
|
||||
|
||||
Reference in New Issue
Block a user