Skip to main content

@c-a-f/permission

Framework-agnostic permission checking interfaces and adapters for CAF. Works with role-based, policy-based, CASL, resource-based, hierarchical, time-based, or custom strategies.

Installation

npm install @c-a-f/permission

For CASL integration (optional):

npm install @c-a-f/permission @casl/ability

Features

FeatureDescription
IPermissionCheckerInterface for any permission adapter: check(permission), checkAny(permissions), checkAll(permissions).
PermissionManagerConvenience API: hasPermission, hasAnyPermission, hasAllPermissions, requirePermission (throws if denied).
RoleBasedPermissionCheckerMap roles to permissions; check by user roles. (@c-a-f/permission/role-based)
PolicyBasedPermissionCheckerPolicy functions per permission with user context. (@c-a-f/permission/policy-based)
SimplePermissionCheckerFlat list of permission strings. (@c-a-f/permission/simple)
CaslPermissionCheckerUse CASL Ability for action/subject permissions. (@c-a-f/permission/casl)
ResourceBasedPermissionCheckerMap resource + action to roles. (@c-a-f/permission/resource-based)
HierarchicalPermissionCheckerWildcards (e.g. admin.*, user.edit.*). (@c-a-f/permission/hierarchical)
TimeBasedPermissionCheckerTime windows, allowed days/hours. (@c-a-f/permission/time-based)

Core interfaces

import { IPermissionChecker, PermissionResult, PermissionManager } from '@c-a-f/permission';

// IPermissionChecker: check, checkAny, checkAll
const permissionManager = new PermissionManager(myChecker);

const canEdit = await permissionManager.hasPermission('user.edit');
const canAny = await permissionManager.hasAnyPermission(['user.delete', 'admin.delete']);
const canAll = await permissionManager.hasAllPermissions(['user.edit', 'user.delete']);

await permissionManager.requirePermission('user.delete'); // throws if denied

Role-based permissions

import { RoleBasedPermissionChecker } from '@c-a-f/permission/role-based';
import { PermissionManager } from '@c-a-f/permission';

const rolePermissions = {
admin: ['user.create', 'user.edit', 'user.delete', 'admin.dashboard'],
editor: ['user.edit', 'post.create', 'post.edit'],
viewer: ['user.view', 'post.view'],
};

const checker = new RoleBasedPermissionChecker(['admin', 'editor'], rolePermissions);
const permissionManager = new PermissionManager(checker);
const canEdit = await permissionManager.hasPermission('user.edit'); // true

Policy-based permissions

import { PolicyBasedPermissionChecker } from '@c-a-f/permission/policy-based';

const policies = {
'user.edit': (context) => context.userId === context.targetUserId || context.roles.includes('admin'),
'admin.dashboard': (context) => context.roles.includes('admin'),
};

const checker = new PolicyBasedPermissionChecker(policies, { userId: '123', roles: ['user'], targetUserId: '123' });
const permissionManager = new PermissionManager(checker);

Simple permissions

import { SimplePermissionChecker } from '@c-a-f/permission/simple';

const checker = new SimplePermissionChecker(['user.view', 'user.edit', 'post.create']);
const permissionManager = new PermissionManager(checker);

CASL integration

import { AbilityBuilder, Ability } from '@casl/ability';
import { CaslPermissionChecker } from '@c-a-f/permission/casl';

const { can, build } = new AbilityBuilder(Ability);
can('read', 'Post');
can('delete', 'Post', { authorId: '123' });
const ability = build();

const checker = new CaslPermissionChecker(ability);
const permissionManager = new PermissionManager(checker);
const canRead = await permissionManager.hasPermission('read:Post');

Resource-based permissions

import { ResourceBasedPermissionChecker } from '@c-a-f/permission/resource-based';

const resourcePermissions = {
user: { create: ['admin'], read: ['admin', 'user'], update: ['admin', 'user'], delete: ['admin'] },
post: { create: ['admin', 'editor'], read: ['admin', 'editor', 'viewer'], update: ['admin', 'editor'], delete: ['admin'] },
};

const checker = new ResourceBasedPermissionChecker(['admin', 'editor'], resourcePermissions);
const permissionManager = new PermissionManager(checker);
const canCreate = await permissionManager.hasPermission('create:user');

Hierarchical permissions

import { HierarchicalPermissionChecker } from '@c-a-f/permission/hierarchical';

const userPermissions = ['admin.*', 'user.edit.*', 'post.create'];
const checker = new HierarchicalPermissionChecker(userPermissions);
const permissionManager = new PermissionManager(checker);
const canAdmin = await permissionManager.hasPermission('admin.dashboard'); // true (admin.*)

Time-based permissions

import { TimeBasedPermissionChecker } from '@c-a-f/permission/time-based';
import { SimplePermissionChecker } from '@c-a-f/permission/simple';

const timePermissions = {
'user.edit': {
allowed: true,
startTime: new Date('2024-01-01'),
endTime: new Date('2024-12-31'),
allowedDays: [1, 2, 3, 4, 5],
allowedHours: { start: 9, end: 17 },
},
};

const baseChecker = new SimplePermissionChecker(['user.edit']);
const checker = new TimeBasedPermissionChecker(baseChecker, timePermissions);
const permissionManager = new PermissionManager(checker);

Custom checker

Implement IPermissionChecker with your own logic and use it with PermissionManager.

Exports

  • Main: IPermissionChecker, PermissionResult, PermissionManager, PermissionDeniedError
  • Subpaths: /role-based, /policy-based, /simple, /casl, /resource-based, /hierarchical, /time-based

Dependencies

  • @c-a-f/core — Core primitives
  • Optional peer: @casl/ability for CASL adapter