Isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Build complex permission rules using fluent builder pattern or functional DSL approach for flexible ability configuration.
Fluent interface for building abilities with can/cannot methods and rule accumulation.
/**
* Fluent interface for building abilities with rule accumulation
*/
class AbilityBuilder<T extends AnyAbility> {
/** Array of accumulated rules */
rules: RawRuleOf<T>[];
/** Add permission rules (allow actions) */
can: DefineRule<T>;
/** Add restriction rules (deny actions) */
cannot: DefineRule<T>;
/**
* Build the final ability instance with accumulated rules
* @param options - Options for the ability instance
* @returns New ability instance with all accumulated rules
*/
build(options?: AbilityOptionsOf<T>): T;
}
/**
* Rule definition function type used by can/cannot methods
*/
type DefineRule<T extends AnyAbility> = (
action: Parameters<T['can']>[0],
subject?: Parameters<T['can']>[1],
conditionsOrFields?: any,
fields?: string | string[]
) => void;Usage Examples:
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
// Basic builder usage
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Add permission rules
can('read', 'Post');
can('create', 'Post');
can('update', 'Post', { authorId: 'user123' });
can('read', 'User', ['name', 'email']); // Field-level permissions
// Add restriction rules
cannot('delete', 'Post', { published: true });
cannot('update', 'User', { role: 'admin' });
// Build the ability
const ability = build();
console.log(ability.can('read', 'Post')); // true
console.log(ability.can('delete', { __type: 'Post', published: true })); // false
// Multiple builders for different contexts
const adminBuilder = new AbilityBuilder(createMongoAbility);
adminBuilder.can('manage', 'all'); // Admin can do everything
const adminAbility = adminBuilder.build();
const userBuilder = new AbilityBuilder(createMongoAbility);
userBuilder.can('read', 'Post');
userBuilder.can('create', 'Comment');
userBuilder.cannot('delete', 'Comment', { authorId: { $ne: 'user123' } });
const userAbility = userBuilder.build();
// Builder with custom options
const customBuilder = new AbilityBuilder(createMongoAbility);
customBuilder.can('read', 'Document');
const customAbility = customBuilder.build({
detectSubjectType: (subject) => subject?.type || subject?.constructor?.name,
resolveAction: (action) => action === 'manage' ? ['create', 'read', 'update', 'delete'] : action
});Common patterns for defining rules with conditions, fields, and complex logic.
/**
* Raw rule structure for ability definitions
*/
interface RawRule<A extends Abilities, C = AnyObject> {
/** Action or actions this rule applies to */
action: A extends readonly [infer Action, any] ? Action : A;
/** Subject or subjects this rule applies to (optional for claim-based rules) */
subject?: A extends readonly [any, infer S] ? S : string;
/** Conditions that must be met for rule to apply */
conditions?: C;
/** Field restrictions (allow only these fields) */
fields?: string | string[];
/** Whether this is a denial rule */
inverted?: boolean;
/** Reason for denial (for inverted rules) */
reason?: string;
}Usage Examples:
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Basic permissions
can('read', 'Article');
can('create', 'Article');
// Conditional permissions with MongoDB-style queries
can('update', 'Article', { authorId: 'user123' }); // Only own articles
can('publish', 'Article', {
authorId: 'user123',
status: { $in: ['draft', 'review'] }
}); // Complex conditions
// Field-level permissions
can('read', 'User', ['name', 'email', 'avatar']); // Only specific fields
can('update', 'User', { id: 'user123' }, ['name', 'email']); // Conditional field access
// Denial rules with reasons
cannot('delete', 'Article', { published: true }, undefined, 'Published articles cannot be deleted');
cannot('update', 'User', { role: 'admin' }, undefined, 'Admin users cannot be modified');
// Multiple actions
can(['read', 'create'], 'Comment');
cannot(['update', 'delete'], 'Comment', { locked: true });
// Multiple subjects
can('read', ['Article', 'Comment', 'User']);
// Claim-based rules (no subject)
can('login'); // Global permission
cannot('access_admin', undefined, undefined, 'Insufficient privileges');
// Complex nested conditions
can('moderate', 'Comment', {
$or: [
{ 'article.authorId': 'user123' }, // Own article comments
{ 'assignedModerators': { $in: ['user123'] } } // Assigned moderator
]
});
const ability = build();Build rules dynamically based on runtime conditions like user roles or context.
/**
* Helper type for conditional rule building
*/
type ConditionalRuleBuilder<T extends AnyAbility> = {
when: (condition: boolean) => {
can: DefineRule<T>;
cannot: DefineRule<T>;
};
};Usage Examples:
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
// Dynamic rules based on user roles
function buildUserAbility(user: { id: string; roles: string[]; department: string }) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Base permissions for all users
can('read', 'Profile', { userId: user.id });
can('update', 'Profile', { userId: user.id });
// Role-based permissions
if (user.roles.includes('admin')) {
can('manage', 'all'); // Admins can do everything
} else {
// Regular user permissions
can('read', 'Article');
can('create', 'Comment');
can('update', 'Comment', { authorId: user.id });
cannot('delete', 'Comment', { replies: { $gt: 0 } }, undefined, 'Cannot delete comments with replies');
}
if (user.roles.includes('editor')) {
can(['create', 'update', 'publish'], 'Article');
can('moderate', 'Comment');
}
if (user.roles.includes('moderator')) {
can(['update', 'delete'], 'Comment');
can('ban', 'User', { role: { $ne: 'admin' } });
}
// Department-based permissions
switch (user.department) {
case 'engineering':
can('deploy', 'Application');
can('access', 'ServerLogs');
break;
case 'marketing':
can(['create', 'update'], 'Campaign');
can('view', 'Analytics');
break;
case 'hr':
can('manage', 'Employee');
can('view', 'Payroll');
break;
}
return build();
}
// Usage
const adminUser = { id: 'admin1', roles: ['admin'], department: 'engineering' };
const editorUser = { id: 'editor1', roles: ['editor'], department: 'marketing' };
const regularUser = { id: 'user1', roles: ['user'], department: 'engineering' };
const adminAbility = buildUserAbility(adminUser);
const editorAbility = buildUserAbility(editorUser);
const userAbility = buildUserAbility(regularUser);
console.log(adminAbility.can('delete', 'User')); // true (admin can manage all)
console.log(editorAbility.can('publish', 'Article')); // true (editor role)
console.log(userAbility.can('deploy', 'Application')); // true (engineering department)
// Conditional helper function
function createConditionalBuilder<T extends AnyAbility>(builder: AbilityBuilder<T>) {
return {
when: (condition: boolean) => ({
can: condition ? builder.can : () => {},
cannot: condition ? builder.cannot : () => {}
})
};
}
// Usage with conditional helper
function buildFeatureAbility(user: { id: string; features: string[] }) {
const builder = new AbilityBuilder(createMongoAbility);
const { can, cannot } = builder;
const conditional = createConditionalBuilder(builder);
// Always allowed
can('read', 'Profile');
// Feature-based permissions
const hasFeature = (feature: string) => user.features.includes(feature);
conditional.when(hasFeature('beta_editor')).can('use', 'BetaEditor');
conditional.when(hasFeature('advanced_analytics')).can('view', 'AdvancedAnalytics');
conditional.when(hasFeature('team_collaboration')).can('invite', 'TeamMember');
conditional.when(!hasFeature('premium')).cannot('export', 'Report', undefined, 'Premium feature required');
return builder.build();
}Handle rule conflicts and priorities when multiple rules apply to the same action/subject.
/**
* Rule priority is determined by order - later rules override earlier ones
* More specific rules (with conditions/fields) take precedence over general rules
*/
interface RulePriorityBehavior {
/** Later rules in the array have higher priority */
order: 'later-wins';
/** Rules with conditions have higher priority than rules without */
specificity: 'conditions-win';
/** Field-specific rules have higher priority than general rules */
fieldSpecificity: 'field-specific-wins';
}Usage Examples:
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Rule priority by order (later wins)
can('update', 'Article'); // General rule
cannot('update', 'Article', { published: true }); // More specific rule wins
// The specific rule prevents updating published articles
// even though the general rule allows updating articles
// Field-level priority
can('read', 'User'); // Can read all user fields
cannot('read', 'User', undefined, ['password', 'socialSecurityNumber']); // Cannot read sensitive fields
// Specificity example
can('delete', 'Comment'); // General permission
cannot('delete', 'Comment', { hasReplies: true }); // Specific restriction
can('delete', 'Comment', { authorId: 'user123', hasReplies: true }); // Even more specific override
const ability = build();
// Testing priority resolution
const publishedArticle = { __type: 'Article', published: true };
const draftArticle = { __type: 'Article', published: false };
console.log(ability.can('update', draftArticle)); // true (general rule applies)
console.log(ability.can('update', publishedArticle)); // false (specific rule prevents)
const commentWithReplies = {
__type: 'Comment',
authorId: 'user123',
hasReplies: true
};
const otherCommentWithReplies = {
__type: 'Comment',
authorId: 'other',
hasReplies: true
};
console.log(ability.can('delete', commentWithReplies)); // true (most specific rule allows)
console.log(ability.can('delete', otherCommentWithReplies)); // false (middle rule prevents)
// Complex priority example
function buildComplexAbility() {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// Base permissions
can('read', 'Document');
// Department restrictions
cannot('read', 'Document', { department: 'hr' });
cannot('read', 'Document', { department: 'finance' });
// Role-based overrides
can('read', 'Document', { department: 'hr', assignedUsers: { $in: ['user123'] } });
can('read', 'Document', { department: 'finance' }, ['title', 'summary']); // Limited fields
// Security level restrictions
cannot('read', 'Document', { securityLevel: 'confidential' });
can('read', 'Document', {
securityLevel: 'confidential',
clearanceLevel: { $gte: 3 }
});
return build();
}Install with Tessl CLI
npx tessl i tessl/npm-casl--ability