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.ts — useGetAdminsListQuery, 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¶
- Onboard a new operator. SuperAdmin opens
/admins, clicks Create, fills email + initial roles. Backend creates aUserrow, sends invite email with first-login token. New operator must enable MFA on first login (admin-fe middleware enforcesIS_2FA_ENABLEDcookie before allowing any other route). - Promote a CS rep to risk role. SuperAdmin opens the operator's profile from
/admins, clicks Edit Roles, addsRiskrole. OTP prompt, confirms. BackendPUT /admin/user/:id/roleswith the new role set — note: the endpoint replaces, not appends. To append, use theadd-single-roleendpoint. - Grant a single permission. SuperAdmin opens the operator's profile, Permissions tab, ticks
withdrawals.approve, saves. BackendPUT /admin/user/:id/permissionswith the full list (delete + insert pattern). - Revoke an operator's access (resignation). Open
/admins, find the user. SuperAdmin clears all roles viaPUT /admin/user/:id/roles { roles: [] }and bans the user. Or, faster: ban + the auth middleware will deny everything regardless of role. - 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 byurl contains /admin/payments/withdraw/approve. See admin-logs.md.
Edge cases / gotchas¶
PUT /admin/user/:id/rolesis a replace, not a merge. If you forget to include existing roles, you'll silently revoke them. Useadd-single-role/revoke-single-rolefor incremental changes.- Permissions are also a replace. Same story for
PUT /admin/user/:id/permissions. - Creating an admin without
mfaSecretis a no-op for entry. The user can authenticate but everyPermissionGuard-protected admin endpoint will reject becausepermission.guard.ts:40-43requiresmfaSecret. Tell new admins to enroll MFA on first login. - SuperAdmin bypasses all checks.
roles.guard.ts:33-35andpermission.guard.ts:24-26short-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_idpartitioning; if you need it, that's an architectural change, not a config. add-single-rolematches bytwitchUsername, notuserId. Useful for promoting a known streamer; awkward for non-streamer admins. Seeadmin.user.controller.ts:207-216.- Admin-audit filter does not support free-text search on
requestBody. AuditrequestBodyis JSON; only top-level fields are indexed.
Cross-links¶
- 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:
Roleenum inlibs/_prisma/src/schema/api.prisma— search forenum 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