| name | rbac-permissions-builder |
| description | Implements role-based access control with permission matrix, route guards, policy functions, and UI permission hints. Provides middleware/guards, helper utilities, test suggestions, and permission checking patterns. Use when building "RBAC", "permissions", "access control", or "authorization". |
RBAC/Permissions Builder
Implement flexible role-based access control systems.
Permission Matrix
// Define permissions
export enum Permission {
USER_READ = "user:read",
USER_WRITE = "user:write",
USER_DELETE = "user:delete",
POST_READ = "post:read",
POST_WRITE = "post:write",
ADMIN_ACCESS = "admin:access",
}
// Define roles
export const ROLE_PERMISSIONS = {
user: [Permission.USER_READ, Permission.POST_READ, Permission.POST_WRITE],
moderator: [...userPermissions, Permission.POST_DELETE],
admin: Object.values(Permission), // All permissions
};
// Check permission
export const hasPermission = (user: User, permission: Permission): boolean => {
return ROLE_PERMISSIONS[user.role]?.includes(permission) ?? false;
};
Route Guards (Express)
export const requirePermission = (...permissions: Permission[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: "Unauthorized" });
}
const hasAllPermissions = permissions.every((p) =>
hasPermission(req.user, p)
);
if (!hasAllPermissions) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
};
// Usage
router.delete(
"/users/:id",
authenticate,
requirePermission(Permission.USER_DELETE),
controller.delete
);
Policy Pattern
// policies/user.policy.ts
export class UserPolicy {
static canUpdate(currentUser: User, targetUser: User): boolean {
// Users can update themselves
if (currentUser.id === targetUser.id) return true;
// Admins can update anyone
if (hasPermission(currentUser, Permission.USER_WRITE)) return true;
return false;
}
static canDelete(currentUser: User, targetUser: User): boolean {
// Can't delete yourself
if (currentUser.id === targetUser.id) return false;
// Only admins can delete
return hasPermission(currentUser, Permission.USER_DELETE);
}
}
// Usage in controller
if (!UserPolicy.canUpdate(req.user, targetUser)) {
return res.status(403).json({ error: "Cannot update this user" });
}
Resource Ownership
export const requireOwnership = (
getResourceUserId: (req: Request) => Promise<string>
) => {
return async (req: Request, res: Response, next: NextFunction) => {
const resourceUserId = await getResourceUserId(req);
// Owner can access
if (req.user.id === resourceUserId) {
return next();
}
// Admin can access anything
if (hasPermission(req.user, Permission.ADMIN_ACCESS)) {
return next();
}
return res.status(403).json({ error: "Forbidden" });
};
};
UI Permission Hints
// Return permissions with user
GET /api/me
{
"user": { ... },
"permissions": ["user:read", "post:write"]
}
// Frontend helper
export const usePermission = (permission: Permission): boolean => {
const { user } = useAuth();
return user?.permissions?.includes(permission) ?? false;
};
// Usage
{usePermission('user:delete') && <DeleteButton />}
Best Practices
- Define permissions granularly (resource:action)
- Check at multiple layers (route, controller, UI)
- Use policies for complex rules
- Cache permission checks
- Log permission denials
- Test all permission paths
Output Checklist
- Permission enum/constants
- Role-to-permission mapping
- Route guard middleware
- Policy classes for complex rules
- Ownership checking utilities
- Permission checking helpers
- UI permission hints endpoint
- Test cases for all permission paths