fix(auth): widen the 0078 backfill so revoke victims who verified are rescued too
Review found the backfill's fourth predicate `AND email_verified = false` strands exactly the subset that needs it most. The revoke does not invalidate the account's outstanding verification token, so clicking the emailed link is the natural next thing such a user does — and it sets email_verified=True while password_hash stays NULL. That row then has no route back at all: reset ineligible, resend refuses an already-verified address, set-password needs a session the account cannot obtain, and nothing re-stamps it because the revoke itself is guarded on being unverified. The remaining three predicates already pin the victim set exactly: registration and the setup wizard always set a hash, every Google row carries a google_sub (adoption assigns it before revoking), and nothing in the codebase ever clears google_sub — so a local, non-demo, passwordless row can only have come from the revoke. Widening is therefore safe, and it is the argument the docstring was already making while the SQL contradicted it. Also corrects that docstring: set-password is unreachable because the account cannot authenticate at all, not because the epoch bump killed a session — an unverified account is refused at login before a session is ever minted, so it never had one.
This commit is contained in:
@@ -15,10 +15,19 @@ and had it revoked, so the reset flow treats it as eligible again. Setting a pas
|
||||
/auth/set-password) clears the stamp. A Google-only account that never had a password keeps it NULL and
|
||||
stays reset-ineligible by design — its owner adds a password from Settings while signed in.
|
||||
|
||||
Backfill: the pre-existing victims of the lockout can't be identified with certainty, but the signature
|
||||
is exact and narrow — a local (google_sub IS NULL), non-demo account with no password whose email is
|
||||
still unverified could ONLY have got there through the revoke, since registration always sets a hash.
|
||||
Stamp those with the current time so they become recoverable without operator intervention.
|
||||
Backfill: the signature is exact and narrow — a local (google_sub IS NULL), non-demo account with NO
|
||||
password could ONLY have got there through the revoke. Registration always sets a hash, the setup
|
||||
wizard sets one too, every Google row carries a google_sub (the adoption path assigns it BEFORE
|
||||
revoking), and nothing in the codebase ever clears google_sub. Stamp those with the current time so
|
||||
they become recoverable without operator intervention.
|
||||
|
||||
Deliberately NOT also requiring `email_verified = false`, even though the revoke only fires on an
|
||||
unverified account: verifying is exactly what such a victim does NEXT. The revoke does not invalidate
|
||||
the outstanding verification token, so clicking the emailed link sets email_verified=True while
|
||||
password_hash stays NULL — and that row has no route back at all (reset ineligible, resend refuses an
|
||||
already-verified address, and set-password needs a signed-in session the account can no longer obtain:
|
||||
no password to sign in with, and no Google identity). Those are the victims who most need the stamp,
|
||||
so the predicate must not exclude them.
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
@@ -42,7 +51,6 @@ def upgrade() -> None:
|
||||
"WHERE password_hash IS NULL "
|
||||
"AND google_sub IS NULL "
|
||||
"AND is_demo = false"
|
||||
"AND email_verified = false"
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user