Admin Roles
Create and manage admin roles, assign permissions to them, and control which admin users hold each role.
Overview
Admin roles let administrators delegate specific sections of the admin panel to team members without granting full system access. Each role carries a set of granular permissions and can be assigned to any number of admin users.
Accessing the Page
Navigate to Admin > Admin Roles (/admin-roles). Both the sidebar entry and the page itself require admin-roles:read, on top of the admin:access dashboard gate.
The list is a table with Name, Permissions (count), Members (count), Type (System / Custom badge), and Actions.
Permissions
| Permission | Description |
|---|---|
admin-roles:read | View the admin roles list |
admin-roles:create | Create new admin roles |
admin-roles:edit | Edit existing roles (name, description, permissions, members) |
admin-roles:delete | Delete non-system roles |
Creating a Role
- Click Create Role (requires
admin-roles:create). - Enter a name (required, max 100 characters) and an optional description.
- Use the permission picker to select which admin permissions the role grants. Permissions come from the backend registry via
/api/admin-permissions, grouped by the category derived from the part of the permission string before the:, each with a tri-state "select all in category" checkbox. - Click Create Role to save.
Editing a Role
Click any row in the table, or the pencil in the Actions column, to open the edit dialog (requires admin-roles:edit).
All fields - name, description, and permissions - are editable for all roles, including system roles. Save Changes stays disabled until something actually changed and sends only the changed fields.
admin:access Permission
Every role automatically includes the admin:access permission. The picker skips it - it cannot be selected or deselected. It is required for any admin user to access the admin panel at all.
Two-factor authentication required
Admin access requires two-factor authentication. A user whose admin roles grant any admin permission, but who has no 2FA factor (an authenticator app or a passkey), cannot use any of it:
- Admin panel - opening it sends the user to the web app at
/dashboard?admin_mfa=required. The dashboard opens the Admin access needs two-factor dialog with a note that the user came from the admin app. A sign-in on the way drops the query; the dialog then still opens, only without that note. The admin login page keeps its message for/login?error=mfa_required(old links and bookmarks), with a link to the same dashboard address. - Web app - while
adminMfaRequiredis true, every dashboard page load opens the same dialog: why admin access needs a second factor, and Set up two-factor, which opens Settings › Security › Sign-in & 2FA at the two-factor section and starts the choice between a passkey and an authenticator app. Later closes it for the rest of the browser session (sessionStorage); the redirect from the admin panel opens it again. When the settings window closes after the setup started, the dashboard readsmeagain: with a factor the dialog is gone, without one it returns. It never appears in popouts or overlays. - API - every admin resolver and admin REST route refuses the session with
ADMIN_MFA_REQUIRED(REST:403, GraphQL:extensions.code). GraphQL and REST use the same check and the same message. - Everything else keeps working - the same session keeps its channels, account permissions and user-level permissions in the web app.
The check runs in the API's auth middleware on every request of a session that holds admin grants. It is fail-closed: if the factor state cannot be read, the request is answered 503 authorization_unavailable instead of being served with admin rights. It takes effect at once in both directions: turning on 2FA unlocks the admin panel on the next request, and removing the last factor locks it on the next request. me { adminMfaRequired } / GET /v1/users/me admin_mfa_required report the state; a refusal is an authorization decision and writes no audit event. System keys are not affected - they carry no user and are provisioned in the config.
devtools:read Permission
Grants the Dev & Admin menu in the web app (lumio.vision, production included): a wrench icon left of Logout in the dashboard sidebar and the shortcut Ctrl + Alt + Shift + D. Its first tool previews a seasonal look in the admin's own browser only (see Preferences → Seasonal Look). The web app shows the menu only to users whose admin roles give them both admin:access and devtools:read; it is seeded to system_admin and can be added to further admin roles here. Nothing the menu does is sent to the server, so it writes no audit event.
System Roles
Roles with is_system: true (shown with a "System" badge) are protected:
- They are editable - you can change their name, description, and permissions.
- They are not deletable - the Delete Role button in the dialog's Danger Zone is disabled, with "System roles cannot be deleted." shown as both a tooltip and a note underneath. The server refuses the delete as well.
The built-in system_admin role is an example of a system role.
Member Management
The edit dialog includes a Members section:
- Lists all admin users currently holding the role with their name, email, and avatar.
- Use the search box to find users by name or email and add them to the role. The search is debounced and excludes users who already hold the role.
- Click the remove button next to any member to unassign them.
API
| Action | GraphQL | REST |
|---|---|---|
| List roles | adminRoles | GET /v1/admin/admin-roles |
| Get a role | adminRole | GET /v1/admin/admin-roles/\{id\} |
| Create a role | adminCreateRole | POST /v1/admin/admin-roles |
| Update a role | adminUpdateRole | PATCH /v1/admin/admin-roles/\{id\} |
| Delete a role | adminDeleteRole | DELETE /v1/admin/admin-roles/\{id\} |
| List role members | adminRoleMembers | GET /v1/admin/admin-roles/\{id\}/members |
| Assign a member | adminAssignUserRole | PUT /v1/admin/admin-roles/\{id\}/members/\{userId\} |
| Unassign a member | adminUnassignUserRole | DELETE /v1/admin/admin-roles/\{id\}/members/\{userId\} |
| Permission registry for the picker | allAdminPermissions | GET /v1/admin/admin-permissions |
Assigning and unassigning members is gated on admin-roles:edit, not on a dedicated permission.
A role's permission list is validated against the backend registry on create and update, so a wildcard such as accounts:* can never be persisted into an admin role.