Role Management
The Settings page provides role CRUD, member-role assignment, member removal, and phone assignment. All endpoints enforce ABAC permissions via the Role and Member resources — org owners and super admins bypass checks automatically.
For how permissions work at runtime, see ABAC Authorization. For a walkthrough of role assignment effects, see Assigning Roles & Limiting Permissions.
Database Tables
Section titled “Database Tables”role — stores role definitions with granular permissions:
| Column | Type | Notes |
|---|---|---|
id | UUID | Primary key |
organization_id | UUID | FK → organization, cascade delete |
name | String | Role display name |
description | String? | Optional description |
permissions | JSONB | Array of permission strings |
member_role — junction table linking members to roles:
| Column | Type | Notes |
|---|---|---|
id | UUID | Primary key |
member_id | UUID | FK → member, cascade delete |
role_id | UUID | FK → role, cascade delete |
Unique constraint on (member_id, role_id). Deleting a role cascades to remove all its assignments.
member_phone_number — junction table linking members to phone numbers:
| Column | Type | Notes |
|---|---|---|
id | UUID | Primary key |
member_id | UUID | FK → member, cascade delete |
phone_number_id | UUID | FK → phone_number, cascade delete |
is_default | bool | One phone per member can be default |
created_at | timestamp | Auto-set on insert |
Data Types
Section titled “Data Types”// Role definitionpub struct Role { pub id: Uuid, pub name: String, pub description: Option<String>, pub permissions: Vec<String>,}
// Create/update payloadpub struct RoleData { pub name: String, pub description: Option<String>, pub permissions: Vec<String>,}
// Member with their assigned rolespub struct OrgMemberWithRoles { pub member_id: Uuid, pub user_id: Uuid, pub name: String, pub is_owner: bool, pub roles: Vec<MemberRoleSummary>,}
pub struct MemberRoleSummary { pub role_id: Uuid, pub role_name: String,}
// Replaces ALL role assignments for a memberpub struct AssignRolesData { pub role_ids: Vec<Uuid>,}
// Phone assignment typespub struct AssignPhonesData { pub phone_number_ids: Vec<Uuid>, pub default_phone_number_id: Option<Uuid>,}
pub struct PhoneAssignmentInfo { pub phone_number_id: Uuid, pub number: String, pub friendly_name: Option<String>, pub assigned_to: Vec<PhoneAssignee>,}
pub struct PhoneAssignee { pub member_id: Uuid, pub member_name: String, pub is_default: bool,}
pub struct MemberPhoneSummary { pub phone_number_id: Uuid, pub number: String, pub friendly_name: Option<String>, pub is_default: bool,}API Endpoints
Section titled “API Endpoints”All endpoints enforce ABAC permissions. Super admins and org owners bypass all checks. Other members need the specific Role:* or Member:* permissions assigned via their roles.
Role CRUD
Section titled “Role CRUD”| Method | Route | Permission Required | Body | Returns |
|---|---|---|---|---|
POST | /api/roles | Role:Collection:Create | RoleData | Role |
GET | /api/roles | Role:Collection:List | — | Vec<Role> |
GET | /api/roles/{id} | Role:Instance:View | — | Role |
PUT | /api/roles/{id} | Role:Instance:Update | RoleData | () |
DELETE | /api/roles/{id} | Role:Instance:Delete | — | () |
On create and update, the server validates permission strings against the catalog via validate_permission_strings(). Invalid strings are rejected with an error listing the bad keys.
Member Management
Section titled “Member Management”| Method | Route | Permission Required | Body | Returns |
|---|---|---|---|---|
GET | /api/org/members-with-roles | Member:Collection:List | — | Vec<OrgMemberWithRoles> |
POST | /api/members/{member_id}/roles | Member:Instance:Update | AssignRolesData | () |
DELETE | /api/members/{member_id}/roles/{role_id} | Member:Instance:Update | — | () |
DELETE | /api/members/{member_id} | Member:Instance:Remove | — | () |
The POST /api/members/{member_id}/roles endpoint uses a replace-all strategy: it deletes all existing assignments for that member and inserts the new set. Send the complete list of role IDs the member should have.
The members endpoint sorts owners first in the response. You cannot remove the org owner — the API returns 400.
Phone Assignments
Section titled “Phone Assignments”| Method | Route | Permission Required | Body | Returns |
|---|---|---|---|---|
GET | /api/org/phone-assignments | Member:Collection:List | — | Vec<PhoneAssignmentInfo> |
POST | /api/members/{member_id}/phones | Member:Instance:Update | AssignPhonesData | () |
Phone assignment also uses replace-all. If only one phone is assigned and no default_phone_number_id is provided, that phone becomes the default automatically. The API validates that all phone IDs belong to the current org before assigning.
Permission Catalog
Section titled “Permission Catalog”all_permission_groups() in permission_catalog_type.rs returns the full catalog of available permissions, grouped by resource. Each permission follows the Resource:Level:Variant format.
pub struct PermissionGroup { pub resource: String, // "Contact", "Agent", etc. pub permissions: Vec<PermissionEntry>,}
pub struct PermissionEntry { pub key: String, // "Contact:Instance:View" pub label: String, // "View" pub level: String, // "Instance" or "Collection" pub display_name: String, // "View any contact"}There are 19 resource groups: Contact, Agent, Settings, Role, Member, ContactNote, Analyzer, Call, Dashboard, Knowledge, Message, Phone, TextAgent, Task, Plan, PlanTemplate, Twilio, Notification, Report, and Assistant.
Each permission entry includes a display_name — a human-readable label like "View any contact" or "List all agents". Display names follow these patterns:
| Level | Pattern | Example |
|---|---|---|
| Instance | "<Action> any <resource>" | "View any contact" |
| Assigned | "<Action> assigned <resources>" | "View assigned contacts" |
| Collection | "List all <resources>" or "Create <resources>" | "List all contacts" |
UI Components
Section titled “UI Components”The role management UI lives in two new owner-only tabs on the Settings page: Roles and Members.
PermissionPicker
Section titled “PermissionPicker”A grouped, collapsible permission selector with a sticky search bar. Each resource group shows a header with the resource name and a selected count badge (e.g., “3/9”). Expand a group to see individual permission checkboxes showing human-readable display names (e.g., “View any contact”) instead of raw labels. Each group has a “Select all” toggle.
Search bar — a sticky input at the top of the picker lets you filter permissions by keyword. The search supports multiple terms separated by spaces — all terms must match against either the resource name or the permission display name. Matching groups auto-expand; non-matching groups are hidden. A clear button (X) appears while searching.
// Multi-term filtering logiclet terms: Vec<&str> = raw_query.split_whitespace().collect();terms.iter().all(|term| { resource_lower.contains(term) || display_lower.contains(term)})Example searches:
"contact"→ shows Contact and ContactNote groups"update contact"→ filters to Update/UpdateAssigned/UpdateOwn permissions in the Contact group"view assigned"→ shows ViewAssigned permissions across all resources
RoleForm
Section titled “RoleForm”Dual-mode form for creating or editing a role. Fields: name (required), description, and a PermissionPicker for selecting permissions. Submit is disabled while the name is empty or a save is in progress.
RoleList
Section titled “RoleList”Fetches all roles via GET /api/roles and renders each as a card showing the name, description, and a permission count badge. Click ”+ New Role” to show a create form above the list. Click the pencil icon on a card to edit inline — the card is replaced with RoleForm. Click the trash icon to delete (with confirmation).
MemberRoleManager
Section titled “MemberRoleManager”Fetches all org members with their roles and all available roles. Each member shows a card with their name and role badges. Click “Manage Roles” to expand a checkbox list of available roles — toggling a checkbox immediately calls the assign endpoint with the updated full role list. The owner is always listed first and cannot have roles managed.
MemberPhonePicker
Section titled “MemberPhonePicker”Displays a member’s assigned phone numbers with a picker to add or remove phones. Shows each phone’s number and friendly name, with a radio button to set the default. Uses the PhoneAssignmentInfo data to show which phones are available and who else has them assigned.
ConfirmRemoveMember
Section titled “ConfirmRemoveMember”Confirmation dialog for removing a member from the organization. Requires Member:Instance:Remove permission. Prevents removing the org owner.
Error Handling
Section titled “Error Handling”All mutation components use the Toaster context for error feedback. When a server call fails (permission denied, validation error, network issue), the component calls toaster.mutation_error(&e.to_string()) to display a toast notification instead of failing silently.
// Standard pattern in mutation handlersmatch assign_member_roles_api(member_id.to_string(), data).await { Ok(_) => on_roles_changed.call(()), Err(e) => toaster.mutation_error(&e.to_string()),}This pattern is consistent across the settings, contact, and phone modules.
ABAC Resource Definitions
Section titled “ABAC Resource Definitions”PR #628 added two new resources to the ABAC system for settings module enforcement:
Role resource — controls who can manage role definitions:
| Permission | Description |
|---|---|
Role:Instance:View | View a single role |
Role:Instance:Update | Edit role name, description, permissions |
Role:Instance:Delete | Delete a role (cascades to assignments) |
Role:Collection:List | List all org roles |
Role:Collection:Create | Create new roles |
Member resource — controls who can manage org members:
| Permission | Description |
|---|---|
Member:Instance:View | View member details |
Member:Instance:Update | Assign/unassign roles and phones |
Member:Instance:Remove | Remove a member from the org |
Member:Collection:List | List all org members |
Both resources use org-scoped instance checks via RoleInstance { org_id } and MemberInstance { org_id }.
File Locations
Section titled “File Locations”src/mods/settings/├── types/│ ├── role_type.rs # Role, RoleData│ ├── role_resource.rs # RoleInstance, Resource impl for RoleResource│ ├── member_resource.rs # MemberInstance, Resource impl for MemberResource│ ├── org_member_with_roles_type.rs # OrgMemberWithRoles, AssignRolesData│ ├── member_phone_type.rs # MemberPhoneSummary, AssignPhonesData│ ├── phone_assignment_info_type.rs # PhoneAssignmentInfo, PhoneAssignee│ └── permission_catalog_type.rs # PermissionGroup, all_permission_groups()├── api/│ ├── create_role_api.rs│ ├── get_roles_api.rs│ ├── get_role_api.rs│ ├── update_role_api.rs│ ├── delete_role_api.rs│ ├── get_org_members_api.rs│ ├── get_org_members_with_roles_api.rs│ ├── assign_member_roles_api.rs│ ├── unassign_member_role_api.rs│ ├── remove_member_api.rs│ ├── assign_member_phones_api.rs│ └── get_phone_assignments_api.rs└── components/ ├── permission_picker_component.rs # PermissionPicker ├── role_form_component.rs # RoleForm ├── role_list_component.rs # RoleList ├── member_role_manager_component.rs # MemberRoleManager ├── member_phone_picker_component.rs # MemberPhonePicker ├── member_detail_panel_component.rs # MemberDetailPanel └── confirm_remove_member_component.rs # ConfirmRemoveMember