From 152d530fb32aaee4dc9f27944ea038584ff60843 Mon Sep 17 00:00:00 2001 From: Wes Appler Date: Fri, 20 Mar 2026 12:48:32 -0400 Subject: [PATCH 1/2] Don't require the user to have an existing password to reset/set one --- hypha/apply/users/forms.py | 40 +++++++++++++++++++ .../templates/users/password_reset/done.html | 6 +-- hypha/apply/users/views.py | 2 + 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/hypha/apply/users/forms.py b/hypha/apply/users/forms.py index 1b98f022d3..6f59b496b9 100644 --- a/hypha/apply/users/forms.py +++ b/hypha/apply/users/forms.py @@ -1,7 +1,10 @@ +import unicodedata + from django import forms from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.forms import PasswordResetForm as DJPasswordResetForm from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from rolepermissions import roles @@ -302,3 +305,40 @@ def clean_confirmation_text(self): code="confirmation_text_incorrect", ) return text + + +class PasswordResetForm(DJPasswordResetForm): + @staticmethod + def _unicode_ci_compare(s1, s2): + """ + Perform case-insensitive comparison of two identifiers, using the + recommended algorithm from Unicode Technical Report 36, section + 2.11.2(B)(2). + + Pulled directly from django.contrib.auth.forms + """ + return ( + unicodedata.normalize("NFKC", s1).casefold() + == unicodedata.normalize("NFKC", s2).casefold() + ) + + def get_users(self, email): + """Given an email, return matching user(s) who should receive a reset. + + This allows subclasses to more easily customize the default policies + that prevent inactive users and users with unusable passwords from + resetting their password. + """ + UserModel = get_user_model() + email_field_name = UserModel.get_email_field_name() + active_users = UserModel._default_manager.filter( + **{ + "%s__iexact" % email_field_name: email, + "is_active": True, + } + ) + return ( + u + for u in active_users + if self._unicode_ci_compare(email, getattr(u, email_field_name)) + ) diff --git a/hypha/apply/users/templates/users/password_reset/done.html b/hypha/apply/users/templates/users/password_reset/done.html index f2993904f6..6496a66538 100644 --- a/hypha/apply/users/templates/users/password_reset/done.html +++ b/hypha/apply/users/templates/users/password_reset/done.html @@ -8,13 +8,13 @@
-

+

{% trans "Check your inbox for a password reset email!" %}

-

+

{% blocktrans %}We have sent an email to you with a password recovery link, open the link in the email to change your password.{% endblocktrans %}

-

+

{% blocktrans %}Check your "Spam" folder, if you don't find the email in your inbox.{% endblocktrans %}

diff --git a/hypha/apply/users/views.py b/hypha/apply/users/views.py index 87d5c39ffc..16e568b0ae 100644 --- a/hypha/apply/users/views.py +++ b/hypha/apply/users/views.py @@ -61,6 +61,7 @@ CustomAuthenticationForm, Disable2FAConfirmationForm, PasswordlessAuthForm, + PasswordResetForm, ProfileForm, ) from .models import ConfirmAccessToken, PendingSignup @@ -383,6 +384,7 @@ class PasswordResetView(DjPasswordResetView): email_template_name = "users/password_reset/email.txt" template_name = "users/password_reset/form.html" success_url = reverse_lazy("users:password_reset_done") + form_class = PasswordResetForm def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) From fef5b6add6d4d78b9e9331dd8baf6346326b4d61 Mon Sep 17 00:00:00 2001 From: Wes Appler Date: Fri, 20 Mar 2026 12:58:16 -0400 Subject: [PATCH 2/2] Normalizing the password reset UI --- hypha/apply/users/templates/users/password_reset/confirm.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hypha/apply/users/templates/users/password_reset/confirm.html b/hypha/apply/users/templates/users/password_reset/confirm.html index 61b0653931..ff19ea315f 100644 --- a/hypha/apply/users/templates/users/password_reset/confirm.html +++ b/hypha/apply/users/templates/users/password_reset/confirm.html @@ -9,7 +9,7 @@ {% if validlink %}

{% trans "Reset password" %}

-

{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

+

{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

{% csrf_token %} @@ -39,7 +39,7 @@

{% trans "Reset password" %}

{% include "forms/includes/field.html" %} {% endfor %} - +
{% else %}

{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}