Skip to main content

admin_users

Platform Administrator accounts (separate from Customer Accounts), plus the impersonation flow.

Public surface​

User + role CRUD is gated by the admin_users:manage permission.

Verb + PathPurpose
POST /api/v1/auth/admin/loginAdmin login (2FA challenge if Role requires it)
POST /api/v1/auth/admin/logoutDestroy admin session
GET /api/v1/admin/admin-usersList admin users (deleted rows filtered)
POST /api/v1/admin/admin-usersCreate admin user; rejects dup email with EMAIL_ALREADY_REGISTERED
PATCH /api/v1/admin/admin-users/:idUpdate name / role assignment / status
DELETE /api/v1/admin/admin-users/:idSoft delete (sets deletedAt + status='inactive')
GET /api/v1/admin/admin-rolesList roles with their permission arrays
PUT /api/v1/admin/admin-roles/:codeUpsert role by code; unknown permissions return 400 VALIDATION_FAILED
DELETE /api/v1/admin/admin-roles/:idDelete; refuses with 409 ADMIN_ROLE_IN_USE if any user is still assigned — including a soft-deleted one, whose assignment comes back when the account is restored. The refusal names which population holds the role (details.code is assigned or assigned_to_deleted) and how many there are
GET /api/v1/admin/permissionsCanonical permission catalogue (module / code / label) used by the matrix UI
POST /api/v1/admin/organizations/:id/impersonateBegin impersonation; writes impersonation.start audit row before issuing the cookie
POST /api/v1/admin/impersonation/endRestore the original admin session

Impersonation model​

impersonation-service.ts implements the switch-user pattern: starts a new session keyed to the target Customer + admin shadow id; ends cleanly, writing impersonation.end. Every action taken during an impersonated session carries both actorAdminUserId and impersonatedCustomerAccountId through the audit log.

Entities​

AdminUser (email, passwordHash, two-factor state, single adminRoleId, status, soft-delete deletedAt).

AdminRole (code, display name, JSONB permissions[], requiresTwoFactor). The wildcard * is bootstrap-only and rejected by the upsert route.

Extension points​

  • Custom login challenges — slot before the password verify in admin-auth-service.ts.
  • Impersonation audit consumers — every audit row with action impersonation.start|end is structured the same way; downstream reporting can join on those.