fix(security): prevent removing the last active admin

Admins could remove the admin group from, deactivate, or delete the last
active admin, locking the system out of all administration. Add a
count_active_admins() repo method and a _is_last_active_admin() guard, and
block all three operations when they would leave zero active admins.

Refs: porchlight-yq7

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Johan Lundberg 2026-06-05 13:31:39 +02:00
parent e54764cda9
commit aedb451128
No known key found for this signature in database
GPG key ID: A6C152738D03C7D1
5 changed files with 110 additions and 0 deletions

View file

@ -237,3 +237,14 @@ async def test_roundtrip_preserves_all_fields(user_repo: SQLiteUserRepository) -
assert sorted(fetched.groups) == ["admin", "users"]
assert fetched.created_at == user.created_at
assert fetched.updated_at == user.updated_at
async def test_count_active_admins(user_repo: SQLiteUserRepository) -> None:
await user_repo.create(_make_user(userid="a1", username="admin1", groups=["admin", "users"]))
await user_repo.create(_make_user(userid="a2", username="admin2", groups=["admin"]))
# Inactive admin must not count.
await user_repo.create(_make_user(userid="a3", username="admin3", groups=["admin"], active=False))
# Non-admin must not count.
await user_repo.create(_make_user(userid="u1", username="user1", groups=["users"]))
assert await user_repo.count_active_admins() == 2