Permissions System
The template implements a granular, resource-based permissions system with type-safe permission definitions, logical groupings for UI organization, and utility functions for state management and change detection.
Architecture Overview
Source Files
| File | Purpose |
|---|---|
lib/permissions/definitions.ts | Permission constants, type extraction, default roles |
lib/permissions/groups.ts | UI-oriented permission groupings with metadata |
lib/permissions/utils.ts | State management, diff calculation, and filtering |
Permission Definitions
Permissions follow a resource:action naming convention. The PERMISSIONS object organizes them by resource:
export const PERMISSIONS = {
items: {
read: 'items:read',
create: 'items:create',
update: 'items:update',
delete: 'items:delete',
review: 'items:review',
approve: 'items:approve',
reject: 'items:reject',
},
categories: {
read: 'categories:read',
create: 'categories:create',
update: 'categories:update',
delete: 'categories:delete',
},
tags: {
read: 'tags:read',
create: 'tags:create',
update: 'tags:update',
delete: 'tags:delete',
},
roles: {
read: 'roles:read',
create: 'roles:create',
update: 'roles:update',
delete: 'roles:delete',
},
users: {
read: 'users:read',
create: 'users:create',
update: 'users:update',
delete: 'users:delete',
assignRoles: 'users:assignRoles',
},
analytics: {
read: 'analytics:read',
export: 'analytics:export',
},
system: {
settings: 'system:settings',
},
} as const;
Complete Permission List
| Resource | Actions |
|---|---|
items | read, create, update, delete, review, approve, reject |
categories | read, create, update, delete |
tags | read, create, update, delete |
roles | read, create, update, delete |
users | read, create, update, delete, assignRoles |
analytics | read, export |
system | settings |
Type-Safe Permission Type
The Permission type is extracted from the PERMISSIONS constant using recursive conditional types:
type PermissionValues<T> = T extends Record<string, infer U>
? U extends Record<string, infer V>
? V extends string ? V : never
: never
: never;
export type Permission = PermissionValues<typeof PERMISSIONS>;
// Resolves to: 'items:read' | 'items:create' | ... | 'system:settings'
This ensures compile-time safety: any permission string that does not exist in the PERMISSIONS constant will cause a TypeScript error.
Query Functions
// Get all permissions as a flat array
export function getAllPermissions(): Permission[];
// Get permissions for a specific resource
export function getPermissionsForResource(resource: keyof typeof PERMISSIONS): Permission[];
// Validate whether a string is a valid permission
export function isValidPermission(permission: string): permission is Permission;
Default Roles
Two built-in role definitions provide starting points:
export const DEFAULT_ROLES = {
SUPER_ADMIN: {
id: 'super-admin',
name: 'Super Administrator',
description: 'Full system access with all permissions',
permissions: getAllPermissions(), // Every permission
},
CONTENT_MANAGER: {
id: 'content-manager',
name: 'Content Manager',
description: 'Manage content including items, categories, and tags',
permissions: [
...getPermissionsForResource('items'),
...getPermissionsForResource('categories'),
...getPermissionsForResource('tags'),
],
},
} as const;
Permission Groups
Groups organize permissions for UI display with icons and descriptions:
export interface PermissionGroup {
id: string;
name: string;
description: string;
icon: string; // Lucide icon name
permissions: Permission[];
}
export const PERMISSION_GROUPS: PermissionGroup[] = [
{
id: 'content',
name: 'Content Management',
description: 'Manage items, categories, and tags',
icon: 'FileText',
permissions: [...items, ...categories, ...tags],
},
{
id: 'users',
name: 'User Management',
description: 'Manage users and their roles',
icon: 'Users',
permissions: [...users, ...roles],
},
{
id: 'system',
name: 'System & Analytics',
description: 'System settings and analytics access',
icon: 'Settings',
permissions: [...analytics, ...system],
},
];
Group Query Functions
// Find which group a permission belongs to
export function getPermissionGroup(permission: Permission): PermissionGroup | undefined;
// Get all permissions in a group by group ID
export function getPermissionsByGroup(groupId: string): Permission[];
Permission Display Formatting
// Format for display: "items:approve" -> "Approve Items"
export function formatPermissionName(permission: Permission): string;
// Generate description: "items:approve" -> "Approve submissions items and submissions"
export function formatPermissionDescription(permission: Permission): string;
The description formatter uses lookup tables for both actions and resources:
| Action | Description Prefix |
|---|---|
read | View and access |
create | Create new |
update | Edit existing |
delete | Remove |
review | Review and moderate |
approve | Approve submissions |
reject | Reject submissions |
assignRoles | Assign roles to |
export | Export data from |
settings | Manage settings for |
Permission State Management
The utilities module provides functions for managing permission state in the UI:
Creating State from Permissions
export function createPermissionState(currentPermissions: Permission[]): PermissionState;
// Returns: { 'items:read': true, 'items:create': true, ... }
Extracting Selected Permissions
export function getSelectedPermissions(permissionState: PermissionState): Permission[];
// Filters the state object to return only permissions where value is `true`
Change Detection
export function calculatePermissionChanges(
originalPermissions: Permission[],
newPermissions: Permission[]
): PermissionChanges;
// Returns: { added: Permission[], removed: Permission[] }
Equality Check
export function arePermissionsEqual(
permissions1: Permission[],
permissions2: Permission[]
): boolean;
// Uses Set-based comparison for order-independent equality
Search Filtering
export function filterPermissions(
permissions: Permission[],
searchTerm: string
): Permission[];
// Matches against permission string and space-separated format
// e.g., "assign" matches "users:assignRoles" and "users assignRoles"
Usage Example
import { PERMISSIONS, getAllPermissions } from '@/lib/permissions/definitions';
import { PERMISSION_GROUPS, formatPermissionName } from '@/lib/permissions/groups';
import { createPermissionState, calculatePermissionChanges } from '@/lib/permissions/utils';
// Check a specific permission
if (userPermissions.includes(PERMISSIONS.items.approve)) {
// User can approve items
}
// Build a permission editor UI
const state = createPermissionState(user.permissions);
// After user toggles permissions
const changes = calculatePermissionChanges(user.permissions, newPermissions);
console.log(`Added: ${changes.added.length}, Removed: ${changes.removed.length}`);