fix(auth): becoming admin through ADMIN_EMAILS drops the password, on any sign-in

The promotion revoke guessed from today's SMTP state whether the password was trustworthy, and ran
only when the account was adopted. Both guesses failed: a row verified while SMTP was off stays
"verified" after SMTP is configured, and a later sign-in matched by Google identity promoted the
account with no revoke at all. Either way a stranger's password and session could become admin.

Now one rule, where the promotion happens: when ADMIN_EMAILS turns a non-admin account into an
admin and it holds a password, the password goes and every session ends; the owner, signed in
through Google at that moment, sets a new one in Settings. An account that already is admin keeps
its password. Tests: the activated squat, the SMTP case, the later sign-in (all three red without
the rule) and an admin's own sign-in.
This commit is contained in:
2026-10-05 01:13:06 +02:00
parent c4298b0a73
commit 513dd8175a
2 changed files with 61 additions and 32 deletions
+35 -7
View File
@@ -59,7 +59,7 @@ def test_a_no_smtp_squat_of_an_admin_address_loses_its_password(client, db, goog
# does. Without SMTP the row is marked verified, and an env-allowed address gets no invite row
# to gate it — so the owner's first Google sign-in activates THIS row as admin. The squatter's
# password must not survive that.
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda: False)
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda db=None: False)
monkeypatch.setattr(auth_routes, "notify_admins_of_request", lambda *_a, **_k: None)
monkeypatch.setattr(settings, "admin_emails", "bob@gmail.com")
auth_routes._register_account("b.ob@gmail.com", "the squatter's own")
@@ -87,7 +87,7 @@ def test_an_activated_squat_loses_its_password_when_the_sign_in_makes_it_admin(
# The same squat after an admin clicked Activate on it: activation vouches for access, not for
# who chose the password, and without SMTP nothing else ever proved the mailbox. ADMIN_EMAILS
# must not raise the squatter's live session to admin.
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda: False)
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda db=None: False)
monkeypatch.setattr(settings, "admin_emails", "bob@gmail.com")
squat = make_user(email="b.ob@gmail.com", password="the squatter's own", is_active=True)
squatter = client.__class__(client.app)
@@ -107,18 +107,46 @@ def test_an_activated_squat_loses_its_password_when_the_sign_in_makes_it_admin(
assert squatter.get("/api/me").json() == {"authenticated": False}
def test_with_smtp_a_verified_account_made_admin_keeps_its_password(
def test_with_smtp_too_an_account_made_admin_loses_its_password(
client, db, google, make_user, monkeypatch
):
# With SMTP the verified flag is a proven mailbox, so the promotion takes nothing away.
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda: True)
# SMTP configured now says nothing about a row verified before it was: the rule does not ask.
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda db=None: True)
monkeypatch.setattr(settings, "admin_emails", "own@example.com")
user = make_user(email="own@example.com", password=_PW, is_active=True)
_sign_in(client, google, sub="sub-own", email="own@example.com")
db.refresh(user)
assert user.role == "admin" and user.password_hash is not None
assert user.role == "admin" and user.password_hash is None
# …and the owner, signed in through Google, is not thrown out by the epoch bump.
assert client.get("/api/me").json()["id"] == user.id
def test_a_later_sign_in_that_makes_the_account_admin_drops_its_password(
client, db, google, make_user, monkeypatch
):
# Adopted while the address was only allowed (the password kept: nothing promoted it), then
# added to ADMIN_EMAILS: the promotion happens on a sign-in matched by Google identity.
user = make_user(email="later@example.com", password=_PW, is_active=True, google_sub="sub-l")
monkeypatch.setattr(settings, "admin_emails", "later@example.com")
_sign_in(client, google, sub="sub-l", email="later@example.com")
db.refresh(user)
assert user.role == "admin"
assert user.password_hash is None and user.password_revoked_at is not None
def test_an_admin_sign_in_keeps_an_admin_password(client, db, google, make_user, monkeypatch):
# Only the promotion drops it: an account that is already admin signs in as before.
user = make_user(email="boss@example.com", password=_PW, role="admin", google_sub="sub-b")
monkeypatch.setattr(settings, "admin_emails", "boss@example.com")
_sign_in(client, google, sub="sub-b", email="boss@example.com")
db.refresh(user)
assert user.password_hash is not None
def test_adopting_an_account_already_let_in_keeps_its_password(
@@ -126,7 +154,7 @@ def test_adopting_an_account_already_let_in_keeps_its_password(
):
# The revoke is for passwords nobody vouched for; an active, verified account that this sign-in
# does not make admin keeps its own.
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda: False)
monkeypatch.setattr(auth_routes.email_mod, "email_enabled", lambda db=None: False)
user = make_user(email="kept@example.com", password=_PW, is_active=True)
monkeypatch.setattr(settings, "allowed_emails", "kept@example.com")