fix(auth): commit the account on registration so a pre-approved sign-up isn't lost

_register_account() flushed the new User but relied on someone else to commit:
upsert_pending_invite() commits only when it actually writes an invite row, and
_issue_token() runs only when SMTP is enabled. For an email an admin had ALREADY
approved, on an instance where email is unavailable, neither fired — the session
closed and the account was rolled back. The would-be user got the uniform "ok"
and no account, could not sign in, and re-registering was a silent no-op.

Commit explicitly after the invite upsert (a no-op when upsert already committed
the same transaction). Three DB-lane regression tests cover the pre-approved
no-SMTP path (fails without the fix), the unknown-email path that always worked,
and the pre-approved path with SMTP on.
This commit is contained in:
2026-09-01 16:25:05 +02:00
parent 602f62967a
commit d954e71a77
4 changed files with 91 additions and 2 deletions
+8 -1
View File
@@ -636,7 +636,14 @@ def _register_account(email: str, password: str) -> None:
# no-op as the pre-check hit — roll back and stop.
db.rollback()
return
upsert_pending_invite(db, email) # admin-approval gate (commits)
upsert_pending_invite(db, email) # admin-approval gate (commits only when it writes a row)
# Persist the account ourselves. upsert_pending_invite commits only when it actually inserts
# (or revives) an invite row, and _issue_token below only runs with SMTP on — so for an email
# the admin had ALREADY approved, on an instance with no SMTP, nothing along this path
# committed and the flushed user was silently rolled back when the session closed. The
# would-be user got a uniform "ok" and no account. Commit unconditionally; it is a no-op
# when upsert already committed the same transaction.
db.commit()
if email_ok:
raw = _issue_token(db, user, "verify", VERIFY_TTL)
# Fragment (#), not a query token: keeps the token out of proxy/access logs + Referer (SB3).