Skip to content

Fix admin password field corruption and case-sensitive email login - #167

Merged
lbougan merged 2 commits into
mainfrom
fix/admin-readonly-password-hash
Aug 24, 2026
Merged

Fix admin password field corruption and case-sensitive email login#167
lbougan merged 2 commits into
mainfrom
fix/admin-readonly-password-hash

Conversation

@lbougan

@lbougan lbougan commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two login fixes stemming from the August lockout incident.

1. Admin renders the password as a read-only hash (56497b1)

UserAdminForm extended a plain ModelForm with fields = "__all__", which turned the password column into an editable text input showing the raw hash. If an admin typed a new password into that box, Django stored it as plaintext and the affected user could never log in again (this locked out a real user; the record has since been repaired on the server with set_password). The form now extends django.contrib.auth.forms.UserChangeForm, restoring Django's standard protection: the password renders as a read-only hash summary linking to the dedicated admin password-change form, and submitted values for the field are ignored.

2. Case-insensitive email login (2ef32c2)

EmailBackend.authenticate looked the user up with a case-sensitive get(email=...). Anyone typing their address with different casing than stored was rejected even with a correct password, while password-reset emails still arrived, a confusing failure mode reported by a real user. 14 current accounts have mixed-case stored emails. The lookup is now email__iexact, with a fallback to an exact match in case case-variant duplicate accounts ever exist (the unique constraint on email is case-sensitive, so they are possible; production currently has none).

Test plan

  • New test: UserAdminForm.base_fields["password"] is a ReadOnlyPasswordHashField
  • New test: submitting a plaintext password through the admin form leaves the stored hash untouched (reproduced the incident before the fix)
  • New tests: login succeeds regardless of email casing (both directions), wrong password and unknown email still rejected, case-variant duplicate accounts each log into their own account
  • Full suite passes: 12 passed
  • After deploy: open a user in /admin, confirm the password shows as a hash summary with a working "change password" link
  • After deploy: log in with an intentionally wrong-cased email

UserAdminForm extended plain ModelForm with fields=__all__, which turned the
password column into an editable text input showing the raw hash. Saving any
edit there stored plaintext in the password field, making the account
impossible to log into (this locked out a real user). Extending
django.contrib.auth.forms.UserChangeForm instead renders the password as a
ReadOnlyPasswordHashField with a link to the dedicated admin password form,
so submitted values are ignored in favor of the stored hash.
EmailBackend matched the login email with a case-sensitive get(email=...),
so anyone typing their address with different casing than stored (14 accounts
currently have mixed-case emails) was rejected even with a correct password,
while password-reset emails still arrived. Switch to email__iexact, falling
back to an exact match if case-variant duplicate accounts ever exist (the
unique constraint on email is case-sensitive, so they are possible).
@lbougan lbougan changed the title Fix: render password as read-only hash in the user admin Fix admin password field corruption and case-sensitive email login Aug 24, 2026
@lbougan lbougan self-assigned this Aug 24, 2026
@lbougan
lbougan merged commit 47314ac into main Aug 24, 2026
8 checks passed
@lbougan
lbougan deleted the fix/admin-readonly-password-hash branch August 24, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant