feat(19.6.1-01): administrator lifecycle and last-superuser protection

- activate, deactivate and restore as record and bulk actions; delete is
  the soft delete through deleted_at, never a hard delete
- deleted admins listed and opened (unscoped), deleted/deactivated row
  states, Deleted filter (hide / only), preview status hint
- deactivate and delete bump tokens_valid_after; activate and restore do not
- self delete and self deactivate refused; outranking and last active
  superuser guarded on delete, deactivate and bulk rows (whole selection
  rolls back)
This commit is contained in:
Jakub Zych
2026-10-22 11:13:26 +02:00
parent 88401737c9
commit 530b02af9c
9 changed files with 446 additions and 35 deletions

View File

@@ -164,6 +164,15 @@ The server enforces these rules on every save, whatever the client sends:
- On their own record an administrator may change login, email and names. A change of their own role, superuser flag or permission overrides is refused with 403, and so is a password: an administrator changes their own password in My account, which checks the current one.
- A superuser cannot be demoted when it is the last active superuser.
Administrators are deactivated, deleted and restored, never removed:
- **Activate** and **Deactivate** are actions on the preview and on the list selection; they switch `is_activated`. Deactivate is not offered on your own record, and a selection that holds it is refused as a whole.
- **Delete** is a soft delete through `deleted_at`, so personal API tokens, `linked_by` references and attachments keep valid foreign keys. You cannot delete your own account. Deleted administrators stay in the list with a "Deleted" badge (the Deleted filter hides them or shows only them), and **Restore** brings one back with its role and permissions.
- The last active superuser can be neither demoted, deactivated nor deleted, on the form, by an action or in a bulk selection. Every such write takes one database lock and counts the other active superusers inside its transaction, so two superusers removing each other at the same time leave one of them a superuser. The `admin:*` commands never clear the superuser flag.
- Deactivating or deleting an administrator ends their sessions at once (tokens issued before are refused) and stops their personal API tokens, because the guard refuses an inactive or deleted administrator. Activate and Restore do not issue anything new: the administrator signs in again, and their personal API tokens that are not revoked or expired work again.
When a refused row is part of a bulk selection, the whole selection is rolled back and the refusal is shown.
Setting the password of another administrator ends that administrator's sessions at once, as `admin:reset-password` does: tokens issued before the change are refused. Their personal API tokens keep working, exactly as after `admin:reset-password`; revoke them with `admin:token:revoke` when they should stop too.
### Operator commands