Skip to Content
Welcome to the official DBS Documentation! 📚

Role-Based Access Control (RBAC)

DBS features a granular, database-backed Role-Based Access Control system. Roles are collections of permissions. Users are assigned roles. Every API route and UI element is gated by a specific permission string.

The Permission Model

Permissions are defined as constants in src/lib/rbac/permission.ts and stored in the database via seeding:

// src/lib/rbac/permission.ts export const PERMISSIONS = { DASHBOARD_ACCESS: 'dashboard.access', USER_READ: 'user.read', USER_WRITE: 'user.write', USER_UPDATE: 'user.update', USER_DELETE: 'user.delete', ROLES_MANAGE: 'roles.manage', LOG_READ: 'log.read', SETTINGS_MANAGE: 'settings.manage', TEAM_READ: 'team.read', TEAM_WRITE: 'team.write', } as const; export type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS];

Role Hierarchy

Roles follow a strict hierarchy from highest to lowest privilege:

super_admin → admin → manager → user → guest
  • super_admin bypasses all permission checks automatically.
  • Each role can only manage roles below it in the hierarchy. This is enforced by canManageRole() in src/lib/role-hierarchy.ts.
// src/lib/role-hierarchy.ts import { canManageRole } from '@/lib/role-hierarchy'; // Can an admin manage a manager? Yes. canManageRole('admin', 'manager'); // true // Can a manager manage an admin? No. canManageRole('manager', 'admin'); // false

Protecting an API Route

Use apiGuard() from src/lib/api-guard.ts at the top of every route handler:

// Any authenticated user export async function GET() { const guard = await apiGuard(); if (guard.error) return guard.error; // 401 if not logged in const { session } = guard; // session.user.id, session.user.email, etc. }

super_admin users automatically bypass all permission checks. They still go through session validation and rate limiting.

Protecting a Page (Server Component)

Use the RouteGuard server component to protect entire pages. If the user lacks the required permission, they are redirected or shown an error.

// src/app/dashboard/users/page.tsx import { RouteGuard } from '@/lib/rbac/components/RouteGuard'; export default function UsersPage() { return ( <RouteGuard permission="user.read"> <UsersPageContent /> </RouteGuard> ); }

Client-Side Permission Check

Use the usePermission hook to conditionally show/hide UI elements based on the current user’s permissions:

'use client'; import { usePermission } from '@/lib/rbac/hooks/usePermission'; import { PermissionAlert } from '@/components/common/PermissionAlert'; export function CreateUserButton() { const { allowed, isLoading } = usePermission('user.write'); if (isLoading) return <Skeleton />; if (!allowed) return <PermissionAlert />; return <Button>Create User</Button>; }

Client-side permission checks are for UI convenience only — they can be bypassed. Always enforce permissions on the server with apiGuard(). The client-side check should never be the only line of defense.

RBAC API Endpoints

MethodEndpointPermission RequiredDescription
GET/api/access/rolesroles.manageList all roles with their permissions
POST/api/access/rolesroles.manageCreate a new role
PUT/api/access/roles/[id]roles.manageUpdate a role’s permissions
DELETE/api/access/roles/[id]roles.manageDelete a role
GET/api/access/permissionsroles.manageList all available permissions
POST/api/access/syncroles.manageSync permission constants to the database

Adding a New Permission

Add your new permission key to src/lib/rbac/permission.ts:

export const PERMISSIONS = { // ...existing... REPORTS_READ: 'reports.read', REPORTS_WRITE: 'reports.write', } as const;
Last updated on