Admin: allow changing your own quota

The self-protection guard refused every change to the admin's own account.
It now only refuses blocking and deleting yourself; a quota change is fine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 15:39:33 +01:00
co-authored by Claude Opus 5.5
parent a4581ab199
commit 77e9c25353
2 changed files with 25 additions and 6 deletions
+11 -6
View File
@@ -3,8 +3,9 @@
The backend seam for the admin panel: the registration gate — the first account
on a fresh install becomes the administrator and closes signup behind itself,
and this is how that gets reopened — the default quota, and managing users
(search, per-user quota, block, delete). An administrator cannot block or delete
their own account, so the instance can never lock out its last admin that way.
(search, per-user quota, block, delete). An administrator can change their own
quota but cannot block or delete their own account, so the instance can never
lock out its last admin that way.
"""
from __future__ import annotations
@@ -280,12 +281,15 @@ def _user_out(user: User, used_bytes: int, accounts: list[str]) -> AdminUserOut:
)
async def _target(db: AsyncSession, admin: User, user_id: uuid.UUID) -> User:
"""The user an admin action is aimed at; never the admin themselves."""
async def _target(
db: AsyncSession, admin: User, user_id: uuid.UUID, *, allow_self: bool = False
) -> User:
"""The user an admin action is aimed at. Blocking and deleting are never
allowed on the admin themselves; a quota change is (``allow_self``)."""
user = await db.scalar(select(User).where(User.id == user_id))
if user is None:
raise_api_error(404, "not_found", "user not found")
if user.id == admin.id:
if user.id == admin.id and not allow_self:
raise_api_error(409, "cannot_modify_self", "you cannot do this to your own account")
return user
@@ -303,7 +307,8 @@ async def patch_user(
user_id: uuid.UUID, body: AdminUserPatch, admin: CurrentAdmin, db: DbSession
) -> AdminUserOut:
"""Change a user's quota and/or block or unblock them."""
user = await _target(db, admin, user_id)
# Your own quota is fine to change; blocking yourself is not.
user = await _target(db, admin, user_id, allow_self=body.blocked is None)
if body.quota_bytes is not None:
user.quota_bytes = body.quota_bytes
if body.blocked is not None:
+14
View File
@@ -168,6 +168,20 @@ async def test_admin_cannot_block_or_delete_themselves(world: World) -> None:
assert (await c.delete(f"/api/admin/users/{admin.id}")).status_code == 409
async def test_admin_can_change_their_own_quota(world: World) -> None:
admin = await world.user(admin=True)
async with await world.client(admin) as c:
resp = await c.patch(f"/api/admin/users/{admin.id}", json={"quota_bytes": 5 * 2**30})
assert resp.status_code == 200
assert resp.json()["quota_bytes"] == 5 * 2**30
# Quota together with a block is still refused: the block is the problem.
assert (
await c.patch(
f"/api/admin/users/{admin.id}", json={"quota_bytes": 2**30, "blocked": True}
)
).status_code == 409
async def test_delete_removes_the_user_and_their_data(world: World, bus: _Bus) -> None:
admin = await world.user(admin=True)
target = await world.user()