From f5fac09833308bbd73c6cad375610eb522ba41a3 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 11 Jul 2026 21:26:39 +0200 Subject: [PATCH] fix(auth): last-admin guard counts only ACTIVE admins (prevents lockout) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set_user_role (demote) and admin_delete_user counted ALL admins incl. suspended ones, so with e.g. 1 active + 1 suspended admin you could demote/delete the only ACTIVE admin — leaving a single suspended admin who can never sign in → permanent admin lockout needing DB surgery. Now both guards mirror the (already-correct) suspend guard: block only when the target is an active admin AND it's the last one (`not target.is_suspended and count_admins(active_only=True) <= 1`) — which also correctly still allows removing a SUSPENDED admin while one active admin remains. --- backend/app/routes/admin.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/app/routes/admin.py b/backend/app/routes/admin.py index 1d86ed0..3edd262 100644 --- a/backend/app/routes/admin.py +++ b/backend/app/routes/admin.py @@ -166,7 +166,12 @@ def set_user_role( raise HTTPException(status_code=400, detail="The demo account's role can't be changed.") if target.id == admin.id: raise HTTPException(status_code=400, detail="You can't change your own role.") - if target.role == "admin" and role == "user" and count_admins(db) <= 1: + if ( + target.role == "admin" + and role == "user" + and not target.is_suspended + and count_admins(db, active_only=True) <= 1 + ): raise HTTPException(status_code=400, detail="Can't remove the last admin.") changed = target.role != role target.role = role @@ -233,7 +238,7 @@ def admin_delete_user( raise HTTPException( status_code=400, detail="Delete your own account from Settings → Account." ) - if target.role == "admin" and count_admins(db) <= 1: + if target.role == "admin" and not target.is_suspended and count_admins(db, active_only=True) <= 1: raise HTTPException(status_code=400, detail="Can't delete the last admin.") purge_user(db, target, background) return {"deleted": user_id}