From 2b7b2b64db405fc3dc6082b2c2e95d2a0a19aeb5 Mon Sep 17 00:00:00 2001 From: Bernard Siebens Date: Mon, 13 Jul 2026 22:59:00 +0200 Subject: [PATCH] Fix empty allauth forms, and lay out the 2FA page The 2FA code input was not invisible -- it was absent, along with the fields of every other allauth form except login. Cause: the `fields` element passed `attrs.exclude` straight into a filter. On a page that never sets it, resolving a filter *argument* raises VariableDoesNotExist; Django rescues that for the main variable of an expression but not for a filter argument, and {% if %} then swallows it and reads the condition as false. So every field was skipped. Login was the one page that passes `exclude`, which is exactly why it kept working and hid the damage. `exclude` is now pinned to a real variable first, with tests that render the login, signup and password-reset forms and assert their inputs exist. Two dangling buttons fixed while in here: `elements/form.html` dropped the `id` attribute, so the out-of-band forms allauth generates (webauthn_form, logout-from-stage) had no id for a button's `form` attribute to point at. "Use a security key" submitted nothing. Layout: the code is a daisyUI otp field, Cancel sits beside Sign In as a plain button, both gain icons, and "Use a security key" becomes an accent button. The otp boxes yield once more than six characters are typed. allauth accepts a TOTP code (6) *or* a recovery code (8) in this one field, so hard-boxing it to six would have locked out every recovery code. Co-Authored-By: Claude Opus 4.8 --- authentication/tests.py | 46 ++++++ static/css/app.css | 187 +++++++++++++++++++++++++ templates/allauth/elements/fields.html | 13 +- templates/allauth/elements/form.html | 15 +- templates/allauth/elements/h1.html | 2 +- templates/mfa/authenticate.html | 109 ++++++++++++++ 6 files changed, 366 insertions(+), 6 deletions(-) create mode 100644 templates/mfa/authenticate.html diff --git a/authentication/tests.py b/authentication/tests.py index 871d356..01dfdd5 100644 --- a/authentication/tests.py +++ b/authentication/tests.py @@ -232,3 +232,49 @@ class AdminLoginRoutingTests(TestCase): def test_allauth_login_page_loads(self): self.assertEqual(self.client.get(reverse("account_login")).status_code, 200) + + +class AuthFormRenderingTests(TestCase): + """Every allauth form must actually render its fields. + + Regression: the `fields` element passed `attrs.exclude` straight into a filter. + On a page that never sets it, resolving a filter *argument* raises + VariableDoesNotExist — which Django swallows inside {% if %} and reads as false — + so every field was silently dropped from every form except the login page (the one + page that does pass `exclude`). + """ + + def test_the_login_form_renders_its_fields(self): + self.assertContains(self.client.get(reverse("account_login")), 'name="login"') + + def test_the_password_reset_form_renders_its_fields(self): + self.assertContains(self.client.get(reverse("account_reset_password")), 'name="email"') + + def test_the_signup_form_renders_its_fields(self): + self.assertContains(self.client.get(reverse("account_signup")), 'name="password1"') + + +class TwoFactorPageTests(TestCase): + def setUp(self): + user = User.objects.create_user(email="mfa@example.com", password="pw-secret-123") + enrol_mfa(user) + # Password accepted, second factor still owed: this is the 2FA challenge page. + self.response = self.client.post(reverse("account_login"), {"login": "mfa@example.com", "password": "pw-secret-123"}, follow=True) + + def test_the_code_field_renders_as_an_otp_input(self): + self.assertContains(self.response, 'name="code"') + self.assertContains(self.response, "otp otp-lg") + + def test_cancel_sits_beside_sign_in_and_is_not_primary(self): + self.assertContains(self.response, ' + + + + + {% if "webauthn" in MFA_SUPPORTED_TYPES %} + {% element hr %} + {% endelement %} + {% element h2 %} + {% translate "Alternative options" %} + {% endelement %} + + + +
+ {% csrf_token %} + {{ webauthn_form.credential }} +
+ {{ js_data|json_script:"js_data" }} + {% include "mfa/webauthn/snippets/scripts.html" %} + + {% endif %} + +
+ + {% csrf_token %} +
+ + +{% endblock content %}