diff --git a/hypha/apply/users/templates/users/login.html b/hypha/apply/users/templates/users/login.html index e216303222..45cbbe7e7f 100644 --- a/hypha/apply/users/templates/users/login.html +++ b/hypha/apply/users/templates/users/login.html @@ -5,136 +5,134 @@ {% block content %}
- -
-
- {% if wizard.steps.current == 'token' %} - {% if device.method == 'call' %} -

{% blocktrans trimmed %}We are calling your phone right now, please enter the - digits you hear.{% endblocktrans %}

- {% elif device.method == 'sms' %} -

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we - sent.{% endblocktrans %}

- {% else %} -

{% trans "Two factor verification" %}

-

{% blocktrans trimmed %}Please enter the 6-digit verification code generated by your Authenticator App.{% endblocktrans %}

- {% endif %} - {% elif wizard.steps.current == 'backup' %} -

{% trans "Two factor verification" %}

-

- {% blocktrans trimmed %}Please enter one of the backup codes to log in to your account.{% endblocktrans %} -

-

- {% blocktrans trimmed %}Those codes were generated for you during 2FA setup to print or keep safe in a password manager.{% endblocktrans %} -

+
+ {% if wizard.steps.current == 'token' %} + {% if device.method == 'call' %} +

{% blocktrans trimmed %}We are calling your phone right now, please enter the + digits you hear.{% endblocktrans %}

+ {% elif device.method == 'sms' %} +

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we + sent.{% endblocktrans %}

+ {% else %} +

{% trans "Two factor verification" %}

+

{% blocktrans trimmed %}Please enter the 6-digit verification code generated by your Authenticator App.{% endblocktrans %}

{% endif %} + {% elif wizard.steps.current == 'backup' %} +

{% trans "Two factor verification" %}

+

+ {% blocktrans trimmed %}Please enter one of the backup codes to log in to your account.{% endblocktrans %} +

+

+ {% blocktrans trimmed %}Those codes were generated for you during 2FA setup to print or keep safe in a password manager.{% endblocktrans %} +

+ {% endif %} + + -
+ {% endif %} + +
+{% endblock %} +{% block extra_js %} + {{ block.super }} {# Fix copy of dynamic fields label #} - - {% endblock %} diff --git a/hypha/apply/users/views.py b/hypha/apply/users/views.py index c5a821859e..7a8c4fc9d4 100644 --- a/hypha/apply/users/views.py +++ b/hypha/apply/users/views.py @@ -37,6 +37,7 @@ from django_htmx.http import HttpResponseClientRedirect from django_otp import devices_for_user from django_ratelimit.decorators import ratelimit +from formtools.wizard.forms import ManagementForm as WizardManagementForm from hijack.views import AcquireUserView from social_django.utils import psa from social_django.views import complete @@ -673,10 +674,15 @@ def get(self, request, uidb64, token, *args, **kwargs): return render(request, "users/activation/invalid.html") def post(self, request, uidb64, token, *args, **kwargs): - # If storage already has an authenticated user we are in the MFA step - # (user confirmed the link, now submitting their OTP). Delegate to the - # parent WizardView which handles the OTP form. - if self.storage.authenticated_user: + # If the wizard management form is present in POST data, we are in the + # MFA step (user confirmed the link and is now submitting their OTP). + # Delegate to the parent WizardView which handles the OTP form. + # We check for the management form rather than just self.storage.authenticated_user + # because stale session data could have authenticated_user set from a previous + # incomplete MFA flow, causing a SuspiciousOperation crash when the user + # submits a fresh confirmation POST (which has no management form). + management_form = WizardManagementForm(request.POST, prefix=self.prefix) + if management_form.is_valid() and self.storage.authenticated_user: return super().post(request, *args, **kwargs) # Initial confirmation POST — validate token and log the user in.