or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-utilities.mdcore-abilities.mderror-handling.mdindex.mdpermission-checking.mdrule-building.md
tile.json

permission-checking.mddocs/

Permission Checking

Core permission verification functionality with support for conditions, field-level access control, and rule matching.

Capabilities

Basic Permission Checking

Check if specific actions are allowed or forbidden on subjects.

/**
 * Parameters for permission checking methods
 */
type CanParameters<A extends Abilities> = 
  | [action: ActionFrom<A>]
  | [action: ActionFrom<A>, subject: SubjectFrom<A>]
  | [action: ActionFrom<A>, subject: SubjectFrom<A>, field: string];

/**
 * Check if action is allowed
 * @param action - Action to check (e.g., 'read', 'create', 'update', 'delete')
 * @param subject - Subject to check against (optional for claim-based rules)
 * @param field - Specific field to check (optional for field-level permissions)
 * @returns true if action is allowed, false otherwise
 */
can(...args: CanParameters<A>): boolean;

/**
 * Check if action is forbidden (inverse of can)
 * @param action - Action to check
 * @param subject - Subject to check against (optional)
 * @param field - Specific field to check (optional)
 * @returns true if action is forbidden, false otherwise
 */
cannot(...args: CanParameters<A>): boolean;

Usage Examples:

import { createMongoAbility } from "@casl/ability";

const ability = createMongoAbility([
  { action: 'read', subject: 'Post' },
  { action: 'update', subject: 'Post', conditions: { authorId: 'user123' } },
  { action: 'read', subject: 'User', fields: ['name', 'email'] }
]);

// Basic permission checks
console.log(ability.can('read', 'Post')); // true
console.log(ability.can('delete', 'Post')); // false
console.log(ability.cannot('delete', 'Post')); // true

// Conditional permission checks
const ownPost = { __type: 'Post', id: 1, authorId: 'user123', title: 'My Post' };
const otherPost = { __type: 'Post', id: 2, authorId: 'other', title: 'Other Post' };

console.log(ability.can('update', ownPost)); // true
console.log(ability.can('update', otherPost)); // false

// Field-level permission checks
console.log(ability.can('read', 'User', 'name')); // true
console.log(ability.can('read', 'User', 'password')); // false

// Subject-only checks (claim-based)
const claimAbility = createMongoAbility([
  { action: 'read' }, // No subject specified
  { action: 'create' }
]);

console.log(claimAbility.can('read')); // true
console.log(claimAbility.can('delete')); // false

Rule Inspection

Get detailed information about rules that match specific conditions.

/**
 * Get the most relevant rule for the given parameters
 * @param action - Action to find rule for
 * @param subject - Subject to find rule for (optional)
 * @param field - Field to find rule for (optional)
 * @returns The most relevant rule or null if no matching rule
 */
relevantRuleFor(...args: CanParameters<A>): Rule<A, Conditions> | null;

/**
 * Get all rules that match the given criteria
 * @param action - Action to match
 * @param subjectType - Subject type to match (optional)
 * @param field - Field to match (optional)
 * @returns Array of matching rules
 */
rulesFor(action: string, subjectType?: string, field?: string): Rule<A, Conditions>[];

/**
 * Get all possible rules for an action and subject type (before condition matching)
 * @param action - Action to get rules for
 * @param subjectType - Subject type to get rules for
 * @returns Array of potentially matching rules
 */
possibleRulesFor(action: string, subjectType: string): Rule<A, Conditions>[];

/**
 * Get all actions allowed for a subject type
 * @param subjectType - Subject type to get actions for
 * @returns Array of allowed action strings
 */
actionsFor(subjectType: string): string[];

Usage Examples:

import { createMongoAbility } from "@casl/ability";

const ability = createMongoAbility([
  { action: 'read', subject: 'Post' },
  { action: 'update', subject: 'Post', conditions: { authorId: 'user123' } },
  { action: 'delete', subject: 'Post', inverted: true, conditions: { published: true } },
  { action: 'read', subject: 'User', fields: ['name', 'email'] }
]);

// Get relevant rule
const updateRule = ability.relevantRuleFor('update', { __type: 'Post', authorId: 'user123' });
console.log(updateRule?.conditions); // { authorId: 'user123' }
console.log(updateRule?.inverted); // false

const deleteRule = ability.relevantRuleFor('delete', { __type: 'Post', published: true });
console.log(deleteRule?.inverted); // true (cannot delete)

// Get all matching rules
const postRules = ability.rulesFor('read', 'Post');
console.log(postRules.length); // 1

const userFieldRules = ability.rulesFor('read', 'User');
console.log(userFieldRules[0].fields); // ['name', 'email']

// Get possible rules (before condition matching)
const possibleUpdateRules = ability.possibleRulesFor('update', 'Post');
console.log(possibleUpdateRules.length); // 1

// Get allowed actions
const postActions = ability.actionsFor('Post');
console.log(postActions); // ['read', 'update'] (delete not included due to inverted rule)

const userActions = ability.actionsFor('User');
console.log(userActions); // ['read']

Rule Object Details

Information about individual rule objects returned by rule inspection methods.

/**
 * Compiled rule with matching capabilities
 */
interface Rule<A extends Abilities, Conditions> {
  /** Action(s) this rule applies to */
  readonly action: string | string[];
  
  /** Subject(s) this rule applies to */
  readonly subject?: string | string[];
  
  /** Whether this is a denial rule (cannot) */
  readonly inverted: boolean;
  
  /** Conditions that must be met for rule to apply */
  readonly conditions?: Conditions;
  
  /** Field restrictions for this rule */
  readonly fields?: string[];
  
  /** Reason for denial (for inverted rules) */
  readonly reason?: string;
  
  /** Rule priority for conflict resolution */
  readonly priority: number;
  
  /** Original raw rule this was compiled from */
  readonly origin: RawRule<A, Conditions>;
  
  /** Parsed condition AST (computed property) */
  readonly ast?: Condition;

  /**
   * Check if an object matches this rule's conditions
   * @param object - Object to test against conditions
   * @returns true if object matches the conditions
   */
  matchesConditions(object: any): boolean;

  /**
   * Check if a field matches this rule's field restrictions
   * @param field - Field name to check
   * @returns true if field is allowed by this rule
   */
  matchesField(field: string): boolean;
}

Usage Examples:

import { createMongoAbility } from "@casl/ability";

const ability = createMongoAbility([
  { 
    action: 'update', 
    subject: 'Post', 
    conditions: { authorId: 'user123', status: { $ne: 'published' } },
    fields: ['title', 'content']
  }
]);

const rule = ability.relevantRuleFor('update', 'Post');

if (rule) {
  console.log(rule.action); // 'update'
  console.log(rule.subject); // 'Post'
  console.log(rule.inverted); // false
  console.log(rule.conditions); // { authorId: 'user123', status: { $ne: 'published' } }
  console.log(rule.fields); // ['title', 'content']
  
  // Test condition matching
  const draftPost = { authorId: 'user123', status: 'draft', title: 'Test' };
  const publishedPost = { authorId: 'user123', status: 'published', title: 'Test' };
  
  console.log(rule.matchesConditions(draftPost)); // true
  console.log(rule.matchesConditions(publishedPost)); // false
  
  // Test field matching
  console.log(rule.matchesField('title')); // true
  console.log(rule.matchesField('author')); // false
}

Subject Type Detection

Utilities for working with subject types in permission checks.

/**
 * Detect the type of a subject object
 * @param object - Object to detect type for
 * @returns String representation of the subject type
 */
function detectSubjectType(object?: any): string;

/**
 * Utility type for objects with explicit type annotation
 */
type ForcedSubject<T> = { __type: T };

/**
 * Attach explicit type to subject objects
 * @param type - The type to attach
 * @param object - The object to attach type to  
 * @returns Object with type annotation
 */
function subject<T extends SubjectType, U extends Record<PropertyKey, any>>(
  type: T, 
  object: U
): U & ForcedSubject<T>;

Usage Examples:

import { createMongoAbility, subject, detectSubjectType } from "@casl/ability";

const ability = createMongoAbility([
  { action: 'read', subject: 'Article' },
  { action: 'update', subject: 'BlogPost', conditions: { authorId: 'user123' } }
]);

// Using class constructors for type detection
class Article {
  constructor(public title: string, public content: string) {}
}

class BlogPost {
  constructor(public title: string, public authorId: string) {}
}

const article = new Article('Test Article', 'Content');
const blogPost = new BlogPost('Test Post', 'user123');

console.log(detectSubjectType(article)); // 'Article'
console.log(detectSubjectType(blogPost)); // 'BlogPost'

console.log(ability.can('read', article)); // true
console.log(ability.can('update', blogPost)); // true

// Using explicit type annotation
const plainObject = { title: 'Plain Object', authorId: 'user123' };
const typedObject = subject('BlogPost', plainObject);

console.log(detectSubjectType(plainObject)); // 'Object'
console.log(detectSubjectType(typedObject)); // 'BlogPost'

console.log(ability.can('update', plainObject)); // false (wrong type)
console.log(ability.can('update', typedObject)); // true (correct type and conditions)

// Using __type property directly
const manuallyTyped = { 
  __type: 'BlogPost',
  title: 'Manual Type', 
  authorId: 'user123' 
};

console.log(detectSubjectType(manuallyTyped)); // 'BlogPost'
console.log(ability.can('update', manuallyTyped)); // true