An isAdmin boolean is rarely enough for a growing product. Support agents, organization owners, editors, and billing administrators need different abilities and scopes.
Start with permissions expressed as actions:
type Permission =
"members.read" | "members.invite" | "members.remove" | "billing.manage";
const permissionsByRole: Record<Role, ReadonlySet<Permission>> = {
owner: new Set([
"members.read",
"members.invite",
"members.remove",
"billing.manage",
]),
manager: new Set(["members.read", "members.invite"]),
viewer: new Set(["members.read"]),
};
Roles group permissions. Application code authorizes the specific action and target organization.
Scope every administrative query#
An organization administrator must never search or mutate globally by default:
await members.removeFromOrganization({
memberId: input.memberId,
organizationId: actor.organizationId,
});
The repository filter includes both IDs. Global platform administration, if required, should be a separate explicit capability with stronger controls.
Protect dangerous transitions#
Rules often go beyond a permission set: an owner cannot remove the last owner, a manager cannot promote someone above their own role, and a user cannot approve their own privileged request. Put these invariants in the use case and test them directly.
Record an audit event#
Administrative writes should create an append-only audit record containing actor, organization, action, target, timestamp, correlation ID, and a safe summary of the change. Avoid copying secrets or full sensitive documents into the audit trail.
Audit records answer who changed access and why. Operational logs alone may expire and are not a substitute.
Return purpose-built data#
Do not serialize database documents from admin endpoints. Define response schemas and explicitly choose fields. Administrative access is privileged, but it still follows least privilege.
Test the permission matrix and tenant boundary, including attempts to act on another organization. Secure admin APIs come from explicit actions, scoped persistence, protected transitions, and a durable history of sensitive changes.