feat(admin): add non-punitive member deactivation and reactivation
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
Add a non-punitive administrative member-deactivation state for organizational offboarding, temporary suspension, and external identity-lifecycle synchronization.
Buzz already has two related but semantically different mechanisms:
- moderation bans/timeouts for abuse and safety enforcement;
- NIP-IA identity archival for hiding retired identities while preserving history.
The `users` table also already contains `deactivated_at`. On current `main`, code search shows that field being read for active-user usage metrics, but no production path appears to set it or enforce it across authentication, active sessions, media, Git, or other authenticated surfaces.
This proposal turns that existing lifecycle field into an explicit, audited access-control state rather than representing employee or contractor offboarding as a moderation violation.
Related umbrella discussion: #2754.
## Motivation
Organizational and managed deployments need to disable access when:
- an employee leaves the organization;
- a contractor's engagement ends;
- an upstream identity provider disables an account;
- an account is temporarily suspended during a security or HR process;
- an identity or device is being replaced;
- an operator needs a reversible access freeze without declaring the member abusive.
The operation should:
- stop current access immediately;
- prevent new access consistently across all authenticated entry points;
- preserve historical message attribution and audit records;
- be reversible;
- remain distinct from moderation, banning, and global reputation.
## Current state
The schema already includes lifecycle-related fields such as:
- `users.deactivated_at`;
- `users.okta_user_id`.
However, `deactivated_at` currently appears to contribute only to usage-metrics counting. It is not yet a complete administrative lifecycle contract.
The existing moderation system already demonstrates useful enforcement machinery—durable state, audit entries, user notices, and cluster-wide session disconnection—but deactivation should not reuse moderation semantics or public-facing ban reasons.
## Proposed semantics
A deactivated member:
- cannot establish a new WebSocket session;
- cannot authenticate HTTP requests through NIP-98 or other supported paths;
- cannot read or write through media, Git, push, workflow, or other authenticated relay surfaces;
- cannot use pubkey-bound API credentials;
- is disconnected from active sessions across relay replicas;
- remains represented in historical events;
- may be marked inactive or hidden from active member-selection surfaces;
- can later be reactivated without rewriting historical events.
The exact relationship with NIP-43 membership and NIP-IA archival should be decided during design. One possible separation is:
- **deactivation** controls reversible organizational access;
- **NIP-43** expresses relay membership and roles;
- **NIP-IA** controls active UI visibility for retired identities;
- **moderation bans/timeouts** remain abuse and safety mechanisms.
A deactivation may optionally trigger NIP-43 removal or NIP-IA archival as an operator-selected policy, but those side effects should not be implicit until their interoperability and reactivation semantics are agreed.
## Administrative operations
Provide signed or operator-authorized operations for:
- deactivate member;
- reactivate member;
- inspect current lifecycle state;
- optionally record a machine-readable reason code;
- optionally record an external lifecycle reference, such as an IdP or HR-system event identifier.
Possible reason codes:
- `left-organization`;
- `idp-disabled`;
- `contract-ended`;
- `security-review`;
- `identity-replacement`;
- `manual-admin`.
Human-readable private notes must not be exposed to ordinary clients or inserted into public event content.
## Enforcement boundary
Deactivation must be checked at the common principal/admission boundary wherever possible, rather than independently and inconsistently in each feature.
At minimum, tests should cover:
- WebSocket/NIP-42 authentication;
- NIP-98 REST authentication;
- media upload and download authorization;
- Git fetch/push authorization;
- API tokens or other pubkey-bound credentials;
- workflow/admin/moderation commands;
- already-authenticated sessions on every relay replica.
A deactivated owner or administrator must not retain an administrative path that can reactivate itself unless the design explicitly allows and audits a break-glass recovery mechanism.
## Security requirements
- Only authorized owners or administrators can change another member's lifecycle state.
- Deactivation must take effect across all relay replicas immediately.
- Every authenticated entry point must enforce the same durable state.
- Switching from WebSocket authentication to NIP-98 HTTP authentication must not bypass deactivation.
- A deactivated administrator must not retain NIP-43, moderation, Git, media, token-minting, or workflow control.
- Reactivation must be explicit and audited.
- All reads and writes must remain scoped to the host-bound community.
- The operation must preserve historical signed events and authorship.
- Private administrative reasons must not leak to ordinary members.
## Acceptance criteria
- [ ] An authorized operator can deactivate an active member.
- [ ] Existing sessions are closed cluster-wide.
- [ ] New WebSocket and HTTP authentication attempts are rejected.
- [ ] Media, Git, token, workflow, and administrative surfaces enforce the same decision.
- [ ] Historical events remain intact and attributed to the original pubkey.
- [ ] An authorized operator can reactivate the member.
- [ ] Deactivation and reactivation are represented in the audit log.
- [ ] Lifecycle state is visible through an appropriate operator query or admin command.
- [ ] Multi-community tests prove that deactivation in one community does not affect the same pubkey in another.
- [ ] Active-user usage metrics continue to treat deactivated members consistently.
- [ ] The relationship to NIP-43 removal and NIP-IA archival is documented.
## Non-goals
- Implementing Keycloak, Okta, or a SCIM server directly in Buzz.
- Recovering, escrow-holding, or taking custody of private keys.
- Replacing moderation bans or timeouts.
- Deleting the member's historical content.
- Defining global identity reputation.
- Exposing confidential HR or security-review notes to ordinary users.
## Contribution
We are interested in contributing the lifecycle state, cross-surface enforcement, cluster-wide disconnection, tests, and documentation after the expected relationship with NIP-43 and NIP-IA is agreed.
Contributor guide
Assessment
This issue has not been assessed yet.