From 49bd1b303694cd1fa206bce2ea63695093191f18 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Mon, 20 Apr 2026 22:51:40 +0200 Subject: [PATCH] src/auth/routes.py : route /auth/change-password (#73) Co-Authored-By: Claude Sonnet 4.6 --- src/auth/routes.py | 22 ++++++++++- tests/auth/test_account.py | 80 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 tests/auth/test_account.py diff --git a/src/auth/routes.py b/src/auth/routes.py index 9af6e10..e763df8 100644 --- a/src/auth/routes.py +++ b/src/auth/routes.py @@ -1,6 +1,6 @@ from email_validator import EmailNotValidError, validate_email from flask import Blueprint, redirect, request -from flask_login import login_user, logout_user +from flask_login import current_user, login_required, login_user, logout_user from werkzeug.security import check_password_hash, generate_password_hash from src.auth import db, mailer, tokens @@ -145,3 +145,23 @@ def reset_password(): db.update_password_hash(consumed, generate_password_hash(password)) return redirect("/connexion?password_changed=1") + + +@auth_bp.route("/change-password", methods=["POST"]) +@login_required +def change_password(): + current_pw = request.form.get("current_password") or "" + password = request.form.get("password") or "" + password_confirm = request.form.get("password_confirm") or "" + + row = db.get_user_by_id(current_user.id) + if not check_password_hash(row["password_hash"], current_pw): + return _redirect_with_error("/compte", "invalid_current_password") + + if len(password) < MIN_PASSWORD_LENGTH: + return _redirect_with_error("/compte", "password_too_short") + if password != password_confirm: + return _redirect_with_error("/compte", "password_mismatch") + + db.update_password_hash(current_user.id, generate_password_hash(password)) + return redirect("/compte?password_changed=1") diff --git a/tests/auth/test_account.py b/tests/auth/test_account.py new file mode 100644 index 0000000..d1ec760 --- /dev/null +++ b/tests/auth/test_account.py @@ -0,0 +1,80 @@ +from werkzeug.security import check_password_hash, generate_password_hash + +from src.auth import db + + +def _login(client, email="a@b.c", password="old-password12"): + db.init_schema() + uid = db.create_user(email, generate_password_hash(password)) + db.set_email_verified(uid) + client.post("/auth/login", data={"email": email, "password": password}) + return uid + + +def test_change_password_requires_login(client, users_db_path): + resp = client.post( + "/auth/change-password", + data={ + "current_password": "whatever", + "password": "new-password12", + "password_confirm": "new-password12", + }, + ) + assert resp.status_code in (302, 401) + + +def test_change_password_success(client, users_db_path): + uid = _login(client) + resp = client.post( + "/auth/change-password", + data={ + "current_password": "old-password12", + "password": "new-password12", + "password_confirm": "new-password12", + }, + ) + assert resp.status_code == 302 + assert "password_changed=1" in resp.headers["Location"] + row = db.get_user_by_id(uid) + assert check_password_hash(row["password_hash"], "new-password12") + + +def test_change_password_wrong_current(client, users_db_path): + uid = _login(client) + resp = client.post( + "/auth/change-password", + data={ + "current_password": "wrong", + "password": "new-password12", + "password_confirm": "new-password12", + }, + ) + assert "error=invalid_current_password" in resp.headers["Location"] + row = db.get_user_by_id(uid) + assert check_password_hash(row["password_hash"], "old-password12") + + +def test_change_password_short(client, users_db_path): + _login(client) + resp = client.post( + "/auth/change-password", + data={ + "current_password": "old-password12", + "password": "short", + "password_confirm": "short", + }, + ) + assert "error=password_too_short" in resp.headers["Location"] + + +def test_change_password_mismatch(client, users_db_path): + _login(client) + resp = client.post( + "/auth/change-password", + data={ + "current_password": "old-password12", + "password": "new-password12", + "password_confirm": "autre-password12", + }, + ) + assert "error=password_mismatch" in resp.headers["Location"]