summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/api/admin.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/admin.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/admin.py38
1 files changed, 34 insertions, 4 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/admin.py b/packages/meshbay-hub/src/meshbay_hub/api/admin.py
index 4960674..219e8a9 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/admin.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/admin.py
@@ -269,13 +269,26 @@ async def admin_delete_user(
db: AsyncSession = Depends(get_db),
):
"""
- Erase an account. Same erasure a user performs on themselves.
+ Erase an account, and every group it owns.
+
+ The same erasure a user performs on themselves, with one difference: a user
+ is asked to hand their groups over first, an administrator is not. This is
+ the route an erasure ordered by an authority goes through, and it cannot
+ wait on the person it is about.
+
+ Then a signed revocation goes to every connected node, for the account and
+ for each group deleted with it. The hub's records are gone at that point,
+ but an access token already issued stays valid on a node until it expires;
+ the revocation is what makes the nodes refuse the account and close the
+ groups' sessions now. A node that is offline misses it — the hub cannot
+ reach a machine it does not command.
Admin rather than moderator: suspension is reversible and is the moderation
tool; this is not. Refused for one's own account — an administrator locking
themselves out is a support incident, and there is `DELETE /v1/users/me` for
someone who means it.
"""
+ from meshbay_hub.api import revocation
from meshbay_hub.api.users import erase_account
user = await db.get(User, user_id)
@@ -288,9 +301,26 @@ async def admin_delete_user(
if user.status == "deleted":
raise HTTPException(status_code=410, detail="Account already deleted")
- result = await erase_account(db, user)
- log.info("Account %s erased by admin %s", result["username"], current_user.username)
- return result
+ groups = (await db.execute(
+ select(Group.name).where(Group.admin_id == user.id))).scalars().all()
+ db.add(IPLog(
+ user_id=current_user.id,
+ event="admin_user_delete",
+ ip_address="admin",
+ detail=f"{user.username} ({user.id}); groups deleted: {', '.join(groups) or 'none'}"[:256],
+ ))
+ result = await erase_account(db, user, owned_groups="delete")
+
+ reason = "account deleted by an administrator"
+ sent = await revocation.broadcast_revocation(
+ revocation._sign_revocation("user", result["user_id"], reason))
+ for g in result["groups_deleted"]:
+ await revocation.broadcast_revocation(
+ revocation._sign_revocation("group", g["id"], reason))
+ log.warning("Account %s erased by admin %s, %d owned group(s) deleted, "
+ "revocations sent to %d node(s)", result["username"],
+ current_user.username, len(result["groups_deleted"]), sent)
+ return {**result, "nodes_notified": sent}
@router.get("/groups")