Skip to content

Role & Access Management

Scope-based access control (RBAC) with role management, permission scopes, and API key administration.


Overview

PropertyValue
Routes/admin-panel/access (Users, Sessions, Roles, API Keys tabs), /role/[id] (role detail)
Feature flagFEATURE_ROLES=true
Pagesadmin-panel/access/_partial/{users,sessions,roles,api-keys}.vue, role/[id]/config/{info,users,scopes}.vue
StoresuseScopeStore, useRoleStore, useApiKeyStore, useProviderStore
Key componentsScopeMatrix, CreateApiKeyModal, ChangeUserRoleModal, ResetUserPasswordModal

Scope-Based Access Control

How Scopes Work

Scopes follow the resource:action format (e.g. questionnaire:write, role:delete). Each user receives scopes derived from their assigned roles. Scopes are bootstrapped at login time from the /me response via scopeStore.setUserScopes().

ScopeStore API

typescript
const scopeStore = useScopeStore();

// Permission checks
scopeStore.hasScope("user:write"); // single scope
scopeStore.hasAnyScope("plugin:read", "webhook:read"); // any match
scopeStore.hasAllScopes("role:read", "role:write"); // all required

// User scopes
scopeStore.setUserScopes(scopes); // called at login

// Role scope management
await scopeStore.fetchAllScopes(); // full scope registry
await scopeStore.fetchRoleScopes(roleId); // scopes for a role
await scopeStore.syncRoleScopes(roleId, scopeKeys); // update role scopes

scopedAction() Utility

Auto-disables table row actions when the current user lacks the required scope:

typescript
scopedAction(
  { label: "Delete", icon: "lucide:trash", onClick: () => deleteItem(id) },
  "item:delete",
);
// Returns action as-is if user has scope, otherwise { ...action, disabled: true }
// Accepts string or array (any-match)

Disabled buttons show a @common.scopes.noPermission tooltip.

Scope Keys

Scope KeyDescription
questionnaire:readView questionnaires
questionnaire:writeCreate/edit questionnaires
questionnaire:deleteDelete questionnaires
questionnaire_submission:readView questionnaire submissions
enrollment:readView enrollments
enrollment:writeCreate/edit enrollments
enrollment:deleteDelete enrollments
folder:readView folders
folder:writeCreate/edit folders
folder:deleteDelete folders
user:readView users
user:writeCreate/edit users
user:deleteDelete users
role:readView roles
role:writeCreate/edit roles
role:deleteDelete roles
user_role:writeAssign roles to users
user_role:deleteUnassign roles from users
permission:writeCreate/edit permissions
permission:deleteDelete permissions
auth:register_deviceRegister devices
auth:api_keyCreate API keys
device:deleteDelete devices
plugin:readView plugins
plugin:writeCreate/edit plugins
plugin:deleteDelete plugins
webhook:readView webhooks
vault:readView vault entries
workflow:readView workflows
config:readView engine config
event_subscription:readView event delivery tasks

Admin Panel Tab Visibility

The tabScopeMap in admin-panel.vue gates each admin tab:

typescript
const tabScopeMap = {
  "admin-panel-access": () => userStore.isAdmin,
  "admin-panel-event-delivery-tasks": () =>
    scopeStore.hasScope("event_subscription:read"),
  "admin-panel-workflows": () =>
    FEATURE_WORKFLOWS && scopeStore.hasScope("workflow:read"),
  "admin-panel-engines": () => scopeStore.hasScope("config:read"),
  "admin-panel-system": () =>
    scopeStore.hasAnyScope("plugin:read", "webhook:read", "vault:read"),
};

resolveAdminPanelPath() in Sidepanel.vue uses the same scope map to resolve the first visible admin tab for the sidebar link. The activePrefix pattern keeps the sidebar highlight active across all admin panel sub-tabs.


ScopeMatrix Component

ScopeMatrix (app/components/ScopeMatrix.vue) renders a resource × action checkbox grid.

Props:

PropTypeDescription
availableScopesScope[]Full list of scope objects (key, name, description)
modelValuestring[]Selected scope keys (v-model)
readonlybooleanDisable all editing
disabledScopesstring[]Scope keys the user cannot toggle

Features:

  • Rows = resources (part before :), columns = actions (part after :)
  • Row/column toggle checkboxes for bulk selection
  • Indeterminate state for partial selections
  • Used in: Role Scopes tab, Create API Key modal

Role Management

Role List

Admin Panel → Access → Roles tab (admin-panel/access/_partial/roles.vue).

Table with name, description, system badge, and row actions (edit, delete) gated by role:write / role:delete scopes.

Role Detail Page

/role/[id] with three config tabs:

Info Tab

Edit role name and description. System roles show a read-only warning.

Assigned Users Tab

Table of users assigned to this role with columns: user link, email, granted at, granted by. Row actions:

  • Unassign — gated by user_role:delete

Header action:

  • Assign User — gated by user_role:write

Scopes Tab

ScopeMatrix for editing the role's scopes. Scopes the current user doesn't hold are passed as disabledScopes — you can't grant permissions you don't have yourself.

Modals

ModalPurpose
CreateRoleModalCreate a new role with name and description
EditRoleModalEdit an existing role's name and description
DeleteRoleModalConfirm role deletion
AssignUserRoleModalAssign a user to this role
UnassignUserRoleModalConfirm removing a user from the role

All modals follow the async-submit pattern (onSubmit: async (data) => { ... }).


API Key Management

API Keys Tab

Admin Panel → Access → API Keys tab (admin-panel/access/_partial/api-keys.vue).

Table columns: name, token hint, scopes (badges), expires at, last used at, status.

Status badges:

  • active — token is valid and not expired
  • expired — past expiration date
  • revoked — manually revoked

Row action: Revoke — gated by auth:api_key scope.

CreateApiKeyModal

Two-step wizard:

  1. Form step — name, description, expiry date, scopes selection via ScopeMatrix
    • delegatableScopes — only scopes the current user holds are available for delegation
  2. Token reveal step — displays the generated token with a copy button and one-time-display warning

API Key Store

typescript
const apiKeyStore = useApiKeyStore();

await apiKeyStore.fetchTokens(); // list all tokens
await apiKeyStore.revokeToken(id); // revoke a token
apiKeyStore.showCreateApiKeyModal(); // open creation wizard
apiKeyStore.showRevokeApiKeyModal(id); // confirm revocation

User Management Enhancements

Role Badges Column

The users table (admin-panel/access/_partial/users.vue) shows a role badges column. A role-based filter dropdown allows filtering users by their assigned roles.

ChangeUserRoleModal

Multi-role assignment dropdown — select multiple roles to assign to a user. Triggered via providerStore.showChangeUserRoleModal(user).

ResetUserPasswordModal

Manual password entry with a Generate button that creates a random 16-character password using crypto.getRandomValues and auto-copies to clipboard. Triggered via providerStore.showResetUserPasswordModal(user).

Force Password Change

Toggle the force-password-change flag for a user. Triggered via providerStore.showForcePasswordChangeModal(user).

Back Navigation

Role detail and user detail pages use parentPath / parentTab query params for back-navigation to the correct sub-tab in the admin panel.


i18n Keys

NamespaceContent
@pages.adminPanel.tabs.access.roles.*Roles table headers, actions, empty
@pages.adminPanel.tabs.access.apiKeys.*API keys table, status labels
@pages.role.*Role detail page (tabs, sections)
@modals.role.*Create / edit / assign role modals
@modals.adminPanel.*API key, password, role change modals
@toasts.role.*Role success / error toasts
@toasts.scope.*Scope sync success / error toasts
@toasts.apiKey.*API key success / error toasts
@common.scopes.*Scope labels, no-permission tooltip

See Also