From 7f1f85950f54e2812343979ef17f79a477146fc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 18:54:23 +0000 Subject: [PATCH 1/3] Initial plan From ec2da45526d26a5eb0c7a2edc763825e05a30536 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 18:59:37 +0000 Subject: [PATCH 2/3] Add reCAPTCHA validation to forgot-password flow Agent-Logs-Url: https://github.com/crackmesone/crackmesone_python/sessions/59812f80-da94-45b1-8fd9-e15132e8b109 Co-authored-by: AsyncFor <52682610+AsyncFor@users.noreply.github.com> --- app/controllers/password_reset.py | 6 +++++ templates/password_reset/forgot.html | 4 +++ tests/test_password_reset_captcha.py | 40 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 tests/test_password_reset_captcha.py diff --git a/app/controllers/password_reset.py b/app/controllers/password_reset.py index 022a7c6..8eef69f 100644 --- a/app/controllers/password_reset.py +++ b/app/controllers/password_reset.py @@ -13,6 +13,7 @@ from app.services.passhash import hash_string from app.services.email import send_email, is_configured as email_is_configured from app.services.discord import notify_password_reset_request, notify_password_reset_complete +from app.services.recaptcha import verify as verify_recaptcha from app.controllers.decorators import anonymous_required password_reset_bp = Blueprint('password_reset', __name__) @@ -45,6 +46,11 @@ def forgot_password_post(): flash('Please enter your email address', FLASH_ERROR) return render_template('password_reset/forgot.html') + # Validate reCAPTCHA + if not verify_recaptcha(request): + flash('reCAPTCHA invalid!', FLASH_ERROR) + return render_template('password_reset/forgot.html') + # Check if email service is configured if not email_is_configured(): flash('Password reset is currently unavailable. Please contact support.', FLASH_ERROR) diff --git a/templates/password_reset/forgot.html b/templates/password_reset/forgot.html index 0084cfd..5e7b986 100644 --- a/templates/password_reset/forgot.html +++ b/templates/password_reset/forgot.html @@ -18,6 +18,10 @@ + {% if RECAPTCHA_SITEKEY %} +
+



+ {% endif %}
Back to Login diff --git a/tests/test_password_reset_captcha.py b/tests/test_password_reset_captcha.py new file mode 100644 index 0000000..86afa8e --- /dev/null +++ b/tests/test_password_reset_captcha.py @@ -0,0 +1,40 @@ +""" +Tests for forgot-password reCAPTCHA handling. +""" + +from flask import Flask +from unittest.mock import patch + +from app.models.errors import ErrNoResult +from app.controllers.password_reset import forgot_password_post + + +class TestPasswordResetCaptcha: + """Focused tests for forgot-password captcha flow.""" + + def setup_method(self): + self.app = Flask(__name__) + self.app.secret_key = 'test-secret' + + def test_forgot_password_rejects_invalid_recaptcha(self): + """Forgot-password should reject invalid reCAPTCHA.""" + with self.app.test_request_context('/forgot-password', method='POST', data={'email': 'test@example.com'}): + with patch('app.controllers.password_reset.quota_exceeded', return_value=False): + with patch('app.controllers.password_reset.verify_recaptcha', return_value=False): + with patch('app.controllers.password_reset.email_is_configured') as email_configured: + with patch('app.controllers.password_reset.render_template', return_value='password_reset/forgot.html'): + response = forgot_password_post() + assert response == 'password_reset/forgot.html' + email_configured.assert_not_called() + + def test_forgot_password_accepts_valid_recaptcha(self): + """Forgot-password should continue flow when reCAPTCHA is valid.""" + with self.app.test_request_context('/forgot-password', method='POST', data={'email': 'test@example.com'}): + with patch('app.controllers.password_reset.quota_exceeded', return_value=False): + with patch('app.controllers.password_reset.verify_recaptcha', return_value=True): + with patch('app.controllers.password_reset.email_is_configured', return_value=True): + with patch('app.controllers.password_reset.email_quota_exceeded', return_value=False): + with patch('app.controllers.password_reset.user_by_mail', side_effect=ErrNoResult('not found')): + with patch('app.controllers.password_reset.render_template', return_value='password_reset/email_sent.html'): + response = forgot_password_post() + assert response == 'password_reset/email_sent.html' From a3ca4189985c47d04e025faa1a15966e4405811e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:04:17 +0000 Subject: [PATCH 3/3] Polish forgot-password captcha UX and accessibility Agent-Logs-Url: https://github.com/crackmesone/crackmesone_python/sessions/59812f80-da94-45b1-8fd9-e15132e8b109 Co-authored-by: AsyncFor <52682610+AsyncFor@users.noreply.github.com> --- app/controllers/password_reset.py | 2 +- templates/password_reset/forgot.html | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/controllers/password_reset.py b/app/controllers/password_reset.py index 8eef69f..c92521c 100644 --- a/app/controllers/password_reset.py +++ b/app/controllers/password_reset.py @@ -48,7 +48,7 @@ def forgot_password_post(): # Validate reCAPTCHA if not verify_recaptcha(request): - flash('reCAPTCHA invalid!', FLASH_ERROR) + flash('Verification failed. Please complete the challenge and try again.', FLASH_ERROR) return render_template('password_reset/forgot.html') # Check if email service is configured diff --git a/templates/password_reset/forgot.html b/templates/password_reset/forgot.html index 5e7b986..d890e41 100644 --- a/templates/password_reset/forgot.html +++ b/templates/password_reset/forgot.html @@ -19,8 +19,11 @@
{% if RECAPTCHA_SITEKEY %} -
-



+ +
+
+
+
{% endif %}
Back to Login