Role-based access control with numeric role levels, permission matrix, and scope clamping. Zero dependencies.
npm install @arraypress/rbacimport { Role, createPermissions, hasPermission, requirePerm, clampScopes } from '@arraypress/rbac';
// Define your permissions (permission → minimum role level)
const permissions = createPermissions({
'products:read': Role.VIEWER, // 10
'products:write': Role.EDITOR, // 30
'orders:read': Role.SUPPORT, // 20
'settings:write': Role.OWNER, // 50
});
// Check permissions
const user = { id: '1', role: 30 }; // Editor
hasPermission(user, 'products:write', permissions); // true
hasPermission(user, 'settings:write', permissions); // false
// Middleware pattern
const denied = requirePerm(user, 'settings:write', permissions);
if (denied) return c.json(denied, denied.status);
// denied = { error: 'FORBIDDEN', status: 403 }Default role levels: VIEWER (10), SUPPORT (20), EDITOR (30), MANAGER (40), OWNER (50).
Create a frozen permission map. Keys are permission names, values are minimum role levels.
Check if a user's role meets the minimum for a permission. Returns boolean.
Ownership check — allows "edit own" vs "edit any" patterns. If user.id === ownerId, checks ownPermission; otherwise checks anyPermission.
canActOnOwn(user, post.authorId, 'content:edit_own', 'content:edit_any', perms);Returns null if authorized, or { error: 'UNAUTHORIZED', status: 401 } / { error: 'FORBIDDEN', status: 403 }.
Filter requested scopes to only those the role level allows. Prevents privilege escalation when issuing API tokens.
const scopeRoles = { 'content:read': 10, 'settings:write': 50 };
clampScopes(['content:read', 'settings:write'], 30, scopeRoles);
// => ['content:read']Get all scopes available to a role level.
Check if a scopes array includes the required scope. The 'admin' scope grants access to everything.
MIT