A password strength meter wherever a password is chosen #26
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#26
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
What exists today
config/settings/base.py, line 116): similarity to the person's own attributes, minimum length (Django's default of 8), the common-passwords list, and not-all-numeric. A rejected password comes back as form errors after the round trip; there is no feedback while typing.account/base_*.htmloverrides intemplates/account/. No project template contains a password field of its own, so the meter has to attach to allauth's rendered forms rather than to markup Postulo writes.script-src 'self', andbase.htmlhas no block for page-specific scripts (onlytitle,chromeandcontent), so anything loaded for password pages alone needs ascriptsblock added first.Shape
1. Where. Every place a password is chosen: sign-up (when registration is open), invitation acceptance, password change, password set (an SSO account adding a password, #6), password reset from a link. Never on the sign-in form: a meter there tells an attacker nothing useful but it is noise.
2. Client-side estimation with zxcvbn, vendored.
@zxcvbn-ts/corewith@zxcvbn-ts/language-commonandlanguage-en(MIT), served fromstatic/js/vendor/like htmx, loaded only on password pages through the new block. Rationale over a server-side check on every keystroke: the password never leaves the browser until the person submits it, there is no new endpoint to rate-limit, and the estimate — dictionary words, keyboard walks, dates, repeats — is far better than the length-and-variety heuristics that make "P@ssw0rd1" look strong. FeeduserInputswith the email's local part and the person's name so a password built from them scores low, which is what Django's similarity validator will say on submit anyway. Language packs for French exist and for Portuguese partially (language-pt-br); load them with the interface language once translations arrive.3. What it shows. A four-segment bar with a word — weak, fair, good, strong — and zxcvbn's own suggestion ("add another word or two; uncommon words are better") when the score is low. Under it, Django's rules as a checklist that the meter cannot contradict, from
password_validators_help_texts: the meter says how good, the list says what is required. Colour never carries the meaning alone; the word does.4. Behaviour. Delegated from
app.jsoninputevents for[data-password-meter], which the password templates add to allauth'spassword1field by overriding the form's widget attributes inaccounts/forms.py(allauth'sACCOUNT_FORMSsetting). With scripts off: no bar, the checklist stays; nothing is required from the meter to submit.5. Announce it. The meter's text is a live region so a screen-reader user hears "fair" change to "strong" as they type.
6. While in there. Raise the minimum length to 12. Length is the one factor that reliably matters; NIST's guidance is a minimum of 8 with a blocklist and no composition rules, and 12 costs nothing with a meter beside it. It applies only to new passwords, so nobody is locked out.
Classification
Enhancement. Not breaking: existing passwords are untouched; a validator change applies to new passwords only.
Open questions