Access Control
The Access Control page (/dashboard/access) provides administrators a visual interface for managing roles and permissions. Instead of editing seed files, admins can create new roles, adjust what permissions are assigned to each role, and sync permission definitions from code to the database — all without redeployment.
This page requires the roles.manage permission. Only super_admin and admin roles can access it by default.
What You Can Do
- View all roles — See every role with its full list of assigned permissions
- Create a custom role — Add a new role (e.g., “Auditor”) with a specific subset of permissions
- Edit role permissions — Toggle individual permissions on/off for any role
- Delete a role — Remove a role entirely (users with that role fall back to default)
- Sync permissions — Push new permission constants from
src/lib/rbac/permission.tsto the database without reseeding
API Endpoints
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/access/roles | roles.manage | List all roles with permissions |
POST | /api/access/roles | roles.manage | Create a new role |
PUT | /api/access/roles/[id] | roles.manage | Update a role’s permissions |
DELETE | /api/access/roles/[id] | roles.manage | Delete a role |
GET | /api/access/permissions | roles.manage | List all permissions in the database |
POST | /api/access/sync | roles.manage | Sync code permission constants → database |
Frontend API Client
Use accessApi from src/services/access/api.ts:
import { accessApi } from '@/services/access/api';
// Get all roles with their permissions
const { data: roles } = await accessApi.getRoles();
// Get all available permissions
const { data: permissions } = await accessApi.getPermissions();
// Create a new role
await accessApi.createRole({
name: 'auditor',
label: 'Auditor',
permissionIds: [3, 7], // IDs of permissions to assign
});
// Update a role's permissions
await accessApi.updateRole(roleId, {
permissionIds: [2, 3, 7, 9],
});
// Delete a role
await accessApi.deleteRole(roleId);
// Sync permission constants to DB
await accessApi.syncPermissions();Fetching Access Data in the UI
'use client';
import { useData } from '@/hooks/use-data';
import { accessApi } from '@/services/access/api';
export function AccessControlPage() {
const { data: roles, isLoading: rolesLoading } = useData(
'roles-list',
() => accessApi.getRoles()
);
const { data: permissions } = useData(
'permissions-list',
() => accessApi.getPermissions()
);
// Render roles with their permission checkboxes
}Syncing New Permissions
When you add a new permission constant in src/lib/rbac/permission.ts, it won’t be visible in the Access Control UI until it’s synced to the database.
Via UI
Click the “Sync Permissions” button in the Access Control page header. This calls POST /api/access/sync and inserts any permission keys that don’t yet exist in the database.
The sync operation is additive only — it inserts missing permissions but never deletes existing ones. This means removing a permission constant from code won’t break existing role assignments; you must manually delete deprecated permissions through the UI or a migration.
Custom Role Example
Here’s how to create a read-only “Auditor” role that can view users and logs but cannot write anything:
// Via API or UI
await accessApi.createRole({
name: 'auditor',
label: 'Auditor',
permissionIds: [
permissions.find(p => p.key === 'dashboard.access')!.id,
permissions.find(p => p.key === 'user.read')!.id,
permissions.find(p => p.key === 'log.read')!.id,
permissions.find(p => p.key === 'team.read')!.id,
],
});Then assign this role to users via the Users management page.