Signing in through the primary address, when the instance's mail works #153
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.
Depends on
#152 A route that cannot deliver is not a way back in
Postulo/postulo
Reference
Postulo/postulo#153
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
The library already does this, and it is switched off. allauth 65.19 is installed and
carries the whole feature:
None of them is set in
config/settings/base.py. So this is mostly a decision, a setting anda page — not an implementation.
The surrounding conditions are already right.
ACCOUNT_EMAIL_VERIFICATION = "mandatory",so a primary address is verified by construction;
ACCOUNT_MAX_EMAIL_ADDRESSES = 5;ACCOUNT_LOGIN_METHODS = {"username", "email"}. Rate limiting already leans on allauth's owncache-backed limits, which #112 deliberately reused rather than replacing.
And there is a precedent for exactly the hard question this raises.
SiteSettings.sso_is_second_factor— "single sign-on counts as the second factor" — is anullable boolean an administrator sets, with a help text explaining what saying yes means.
Somebody arriving by a link in their inbox raises the same question, and it should be
answered the same way rather than by default.
What this asks for
An administrator switch, available when the instance's mail works, that lets people sign in
through their primary address instead of their password.
Worth being careful about
A code is safer than a link, and the reason is not theoretical. allauth's implementation
sends a code the person types back into the session that asked for it. A link is a bearer
credential that works from anywhere, and mail security scanners follow links — corporate
filters, Defender, Proofpoint and their kind fetch every URL in a message, which consumes a
single-use link before the recipient has read the mail. Postulo's users are job applicants
corresponding with corporate recruiters; some of them have exactly that kind of mail. A code
typed back into the browser that asked for it cannot be burned by a scanner, cannot be
forwarded usefully, and cannot be clicked from a device that is not the one signing in. The
suggestion says link; the recommendation is code, and this is the paragraph to argue with.
Does it satisfy the second factor? This is the question that decides whether the feature
is safe or a hole. If somebody with TOTP configured can sign in by a code from their inbox
and skip the second factor, the feature has removed their second factor by adding a first
one. allauth applies MFA after authentication, so the answer is probably no bypass — but
"probably" is not the standard here. It needs a test that says so explicitly, in
tests/security/, of the same kind that already covers the other boundaries.And whether it counts as a factor is a separate question with an existing answer.
sso_is_second_factorshows the shape: a nullable boolean onSiteSettings, a help textthat says what it means, and no default that quietly decides for the operator. This wants the
same, and probably wants to say no.
It makes email more load-bearing, not less. Today email resets a password; with this it
signs somebody in. Anybody who holds the mailbox holds the account, without needing to
complete a reset. That is not an argument against it — it is already true of password
reset — but it is an argument for the instance being honest about it on the settings page,
and for #104's lock reasoning getting stronger rather than weaker.
"Configured and working" is #152 and cannot be assumed.
recovery_routes()counts atransport that is selected, not one that delivers. Offering sign-in by email on an
instance whose SMTP is misconfigured produces a sign-in page that promises something it
cannot do, to somebody who may have no other way in. The switch should be offered only where
mail is known to work, which is exactly what that issue is for.
The primary address, and only a verified one. Allauth allows up to five addresses; a code
goes to the primary. If the primary changes, the route changes with it, silently — worth
saying on the addresses page rather than leaving somebody to discover it.
Enumeration. The response to "send me a code" must not differ between an address that
exists and one that does not, and the message must say so in a way that is honest rather than
evasive — the same care #90's uniqueness message took.
Rate limits and resend.
LOGIN_BY_CODE_MAX_ATTEMPTSandLOGIN_BY_CODE_MAX_RESEND_COUNTexist; the numbers are a decision, and the failure has to be a refusal that says when to try
again, like the 429 with
Retry-Afterthat #112 established.One more route to explain on a page that already explains several. Settings → Account
already says how sign-in works, with passkeys, TOTP and SSO each described. A fourth way in
needs its sentence there, and the strings are 39 translations.
A code, as the issue recommended and against what the request said. The paragraph
asking to be argued with was right: a link is a bearer credential that works from anywhere,
corporate mail scanners follow links, and Postulo's people correspond with recruiters whose
mail has exactly those scanners.
ACCOUNT_LOGIN_BY_CODE_ENABLED, and the reasoning is in thesettings file where the next person to wonder will find it.
Does it satisfy the second factor? No — asserted, not assumed.
tests/security/test_email_sign_in.pysigns somebody with an authenticator through the wholeflow and checks they are not in the session, with a companion test doing the same for
somebody without one and checking they are. Without the second test the first would pass on
a broken path.
Whether it counts as a factor: no, and deliberately no setting. This is where I departed
from the issue, which suggested following
sso_is_second_factor's shape. The asymmetry isthe reason: that setting exists because an identity provider may itself have checked identity
carefully and Postulo cannot see how — so there is a real judgement for an operator to make. A
code out of an inbox has no provider behind it to trust. There is nothing to decide, and a
switch that should always be off is a switch that will one day be on. A test asserts no such
field exists, so adding one is a deliberate act. Worth pushing back on if you disagree.
"Configured and working" is honoured through #152.
site.email_sign_in()is theadministrator's yes and mail actually delivering, and both the switch and the page read it;
the door closes again by itself if the relay starts failing.
Enumeration — one test asserts the response to a known and an unknown address is the same
page with the same status.
Rate limits and resend — three minutes, three attempts, three resends, and
LOGIN_BY_CODE_TRUST_ENABLED = Falsebecause a one-off code must not become a standingcredential on a machine that may not be theirs next week. All three are environment variables.
The primary address changing is now said on Settings → Account beside the addresses,
rather than left to be discovered, and the fourth way in has its sentence there.
One implementation note. allauth decides whether to offer a code from a Django setting read at
import, because that is when it builds its URLs — so a database switch cannot add or remove
the route. The door is therefore always built, and Postulo shadows both paths with its own:
a
LoginViewsubclass that corrects one fact in the context, and a guard onlogin/code/that 404s where the instance does not offer it. A subclass rather than a copy of their sign-in
template, or every allauth release becomes a merge here.
14 tests in
tests/security/, both new pages walked by the browser suite under axe-core, awiki section, and five strings in all 39 European catalogues.
Shipped in
810458don0.3.0, withmainkept level.