Skip to content

Admins

Purpose

The Admins screen lets a SuperAdmin create, role-edit, and audit other admin / operator accounts. It is also where you assign permissions (granular keys from libs/auth/src/permissions/const.ts). Every action here is OTP-gated and logged.

Audience

SuperAdmin only for write operations. Other admins can view the admin list (for "who approved that withdrawal?" lookups) but cannot modify.

Path in admin-fe

Screen URL Page
Admin list /admins ebit-admin-fe/src/app/(dashboard)/admins/page.tsx
Admin row → opens user profile drawer with admin tab focused /admins (in-page) components/pages/admins/

Backing API endpoints

Endpoint Source
GET /admin/user/get-admins-with-roles apps/api/src/user/admin.user.controller.ts:91
POST /admin/user/admin-user (create admin, SuperAdmin) apps/api/src/user/admin.user.controller.ts:72
PUT /admin/user/:id/roles (SuperAdmin + OTP) apps/api/src/user/admin.user.controller.ts:180
PUT /admin/user/:id/permissions (SuperAdmin) apps/api/src/user/admin.user.controller.ts:194
PUT /admin/user/add-single-role (SuperAdmin + OTP) apps/api/src/user/admin.user.controller.ts:207
PUT /admin/user/revoke-single-role (SuperAdmin + OTP) apps/api/src/user/admin.user.controller.ts:218
GET /admin/user/all-permissions apps/api/src/user/admin.user.controller.ts:82
GET /admin/user/admin-audit apps/api/src/user/admin.user.controller.ts:99

Frontend wiring: ebit-admin-fe/src/queries/users/index.tsuseGetAdminsListQuery, useCreateAdminUserMutation, useUpdateUserPermissionsMutation, useUpdateUserRolesMutation, useAllPermissionsQuery.

Key actions

Action Required role API call DB tables touched Audit-logged?
List admins user.view GET /admin/user/get-admins-with-roles User, UserRole yes
Create admin SuperAdmin POST /admin/user/admin-user User, UserRole, password mailer yes
Replace user roles SuperAdmin + OTP PUT /admin/user/:id/roles UserRole (delete + insert) yes
Add a single role (e.g. promote streamer) SuperAdmin + OTP PUT /admin/user/add-single-role UserRole (insert by twitch username) yes
Revoke a single role SuperAdmin + OTP PUT /admin/user/revoke-single-role UserRole (delete) yes
Replace user permissions SuperAdmin PUT /admin/user/:id/permissions UserPermission (delete + insert) yes
List permission catalog user.view GET /admin/user/all-permissions Permission (60+ keys, see libs/auth/src/permissions/const.ts:3-244) no
View admin audit feed admin-users.view-admin-audit GET /admin/user/admin-audit AdminActionLog n/a

Filters and views

  • Page (cursor) — admin lists are typically small (< 50), but page param is supported.
  • Role filter — Admin / SuperAdmin / Streamer / Affiliate / etc.
  • Audit feed — filtered by userId (target), adminUserId (actor), HTTP method, status code, date range.

Common workflows

  1. Onboard a new operator. SuperAdmin opens /admins, clicks Create, fills email + initial roles. Backend creates a User row, sends invite email with first-login token. New operator must enable MFA on first login (admin-fe middleware enforces IS_2FA_ENABLED cookie before allowing any other route).
  2. Promote a CS rep to risk role. SuperAdmin opens the operator's profile from /admins, clicks Edit Roles, adds Risk role. OTP prompt, confirms. Backend PUT /admin/user/:id/roles with the new role set — note: the endpoint replaces, not appends. To append, use the add-single-role endpoint.
  3. Grant a single permission. SuperAdmin opens the operator's profile, Permissions tab, ticks withdrawals.approve, saves. Backend PUT /admin/user/:id/permissions with the full list (delete + insert pattern).
  4. Revoke an operator's access (resignation). Open /admins, find the user. SuperAdmin clears all roles via PUT /admin/user/:id/roles { roles: [] } and bans the user. Or, faster: ban + the auth middleware will deny everything regardless of role.
  5. Find "who approved withdrawal X". From withdrawals.md, copy the admin user ID off the row. Open /admin-logs?userId=<adminId> (the audit feed). Filter by url contains /admin/payments/withdraw/approve. See admin-logs.md.

Edge cases / gotchas

  • PUT /admin/user/:id/roles is a replace, not a merge. If you forget to include existing roles, you'll silently revoke them. Use add-single-role / revoke-single-role for incremental changes.
  • Permissions are also a replace. Same story for PUT /admin/user/:id/permissions.
  • Creating an admin without mfaSecret is a no-op for entry. The user can authenticate but every PermissionGuard-protected admin endpoint will reject because permission.guard.ts:40-43 requires mfaSecret. Tell new admins to enroll MFA on first login.
  • SuperAdmin bypasses all checks. roles.guard.ts:33-35 and permission.guard.ts:24-26 short-circuit. Treat SuperAdmin like root: minimum number, MFA-only, no shared accounts.
  • No sub-tenant scoping. All admins see all users. There is no customer_id / tenant_id partitioning; if you need it, that's an architectural change, not a config.
  • add-single-role matches by twitchUsername, not userId. Useful for promoting a known streamer; awkward for non-streamer admins. See admin.user.controller.ts:207-216.
  • Admin-audit filter does not support free-text search on requestBody. Audit requestBody is JSON; only top-level fields are indexed.
  • Audit feed: admin-logs.md
  • Permission catalog source: ebit-api/libs/auth/src/permissions/const.ts:3-244
  • User profile (admin records share the same drawer): user-profile.md
  • Role definitions: Role enum in libs/_prisma/src/schema/api.prisma — search for enum Role
  • Onboarding new admin: onboarding/day-one.md

Sequence — granting a single permission

sequenceDiagram
    actor sa as SuperAdmin
    participant admin-fe
    participant api
    participant pg as Postgres
    sa->>admin-fe: open /admins → click operator row
    admin-fe->>api: GET /admin/user/all-permissions
    api-->>admin-fe: catalog (60+ keys)
    admin-fe-->>sa: render permission checkboxes
    sa->>admin-fe: tick withdrawals.approve, save
    admin-fe->>api: PUT /admin/user/789/permissions { permissions: [...all incl new...] }
    api->>api: RolesGuard + Roles(SuperAdmin)
    api->>pg: BEGIN — DELETE UserPermission WHERE userId=789 — INSERT new set — COMMIT
    pg-->>api: ok
    api->>pg: INSERT AdminActionLog (method, url, requestBody, ...)
    api-->>admin-fe: 200 OK
    admin-fe-->>sa: re-renders with new permission checked