diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/admin.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/admin.py | 38 |
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") |