Resolve the primary administrator to an account that can authenticate

The primary administrator was the earliest Admin by created_at with no further
condition, so a soft-deleted or deactivated account could hold the role and then
be refused by the api-key path, leaving nobody able to use the database API, the
backup download or cross-owner container management. Rows with no recorded signup
time also sorted ahead of every real account. Scan the earliest admins instead and
take the first that is neither deleted nor deactivated, with missing timestamps
sorted last.

Also stop the projects listing returning 500 when project_type or description is
NULL (the dict default never applies to an existing NULL column), align the issue
test fixture with its unit twin so a combined run cannot collide on a fixed uid,
read the settings value from the database rather than a stale per-process cache,
and give the seeded and fixture admins the is_active and created_at fields that
every real signup writes.
This commit is contained in:
2026-07-26 19:58:18 +02:00
parent 535e9c5dc1
commit ca6c527e32
8 changed files with 132 additions and 8 deletions
+18 -3
View File
@@ -15,6 +15,9 @@ def get_users_by_uids(uids):
_admins_cache = TTLCache(ttl=300, max_size=4)
# The primary administrator must be an account that can actually authenticate, so scan a
# few of the earliest admins and skip any that are soft-deleted or deactivated.
PRIMARY_ADMIN_CANDIDATES = 50
def invalidate_admins_cache() -> None:
@@ -71,6 +74,12 @@ def get_online_users(cutoff_iso: str, limit: int = 30) -> list:
)
def _can_hold_primary_admin(row, tracks_active):
if row.get("deleted_at"):
return False
return not tracks_active or bool(row.get("is_active"))
def get_primary_admin_uid():
sync_local_cache("admins", _admins_cache)
cached = _admins_cache.get("primary")
@@ -80,11 +89,17 @@ def get_primary_admin_uid():
return None
rows = list(
db.query(
"SELECT uid FROM users WHERE role = 'Admin' "
"ORDER BY created_at ASC, id ASC LIMIT 1"
"SELECT * FROM users WHERE role = 'Admin' "
"ORDER BY (created_at IS NULL OR created_at = ''), created_at ASC, id ASC "
"LIMIT :cap",
cap=PRIMARY_ADMIN_CANDIDATES,
)
)
primary = rows[0]["uid"] if rows else None
tracks_active = "is_active" in db["users"].columns
primary = next(
(row["uid"] for row in rows if _can_hold_primary_admin(row, tracks_active)),
None,
)
_admins_cache.set("primary", primary or "")
return primary