Skip to Content
DocumentationAdvanced Usage

Advanced Usage

In this section, we will explore advanced usage patterns and best practices for using Asgardian to define and manage permissions. This includes handling dynamic rules, integrating with databases, optimizing performance, and advanced error handling.

Handling Dynamic Rules

Sometimes, permission rules need to be dynamic and can change based on various factors such as user properties, resource states, or external conditions.

Example of Dynamic Rules

import { createAbility } from '@nordic-ui/asgardian'; const ability = createAbility(); // Dynamic rule based on user properties ability .can('read', 'Post', (post, context) => post.published || context.user.roles.includes('admin')); // Dynamic rule based on resource state ability .can('update', 'Post', (post, context) => post.authorId === context.userId || context.user.roles.includes('moderator'));

Integrating with Databases

For applications that require permission rules to be stored and managed in a database, Asgardian can be integrated with various database systems.

Example with Database Integration

Assume you have a database that stores roles and permissions. You can fetch these rules and apply them dynamically.

import { createAbility } from '@nordic-ui/asgardian'; import { fetchUserRoles, fetchResourcePermissions } from './database'; const ability = createAbility(); // Fetch roles and permissions from the database async function setupAbility(userId) { const userRoles = await fetchUserRoles(userId); const resourcePermissions = await fetchResourcePermissions(); resourcePermissions.forEach(({ action, resource, condition }) => { ability.can(action, resource, (resource, context) => { return userRoles.some(role => condition.includes(role)); }); }); } // Example usage setupAbility(123).then(() => { const user = { id: 123, roles: ['admin'] }; console.log(ability.isAllowed('manage', 'all', null, { user })); // true });

Optimizing Performance

Performance is crucial when dealing with complex permission rules, especially in large applications. Here are some tips to optimize performance.

Caching Rules

Caching permission rules can significantly improve performance, especially if the rules do not change frequently.

import { createAbility } from '@nordic-ui/asgardian'; import { fetchUserRoles, fetchResourcePermissions } from './database'; import { LRUCache } from 'lru-cache'; const cache = new LRUCache({ max: 1000 }); const ability = createAbility(); async function setupAbility(userId) { let userRoles = cache.get(`userRoles:${userId}`); if (!userRoles) { userRoles = await fetchUserRoles(userId); cache.set(`userRoles:${userId}`, userRoles); } let resourcePermissions = cache.get('resourcePermissions'); if (!resourcePermissions) { resourcePermissions = await fetchResourcePermissions(); cache.set('resourcePermissions', resourcePermissions); } resourcePermissions.forEach(({ action, resource, condition }) => { ability.can(action, resource, (resource, context) => { return userRoles.some(role => condition.includes(role)); }); }); } // Example usage setupAbility(123).then(() => { const user = { id: 123, roles: ['admin'] }; console.log(ability.isAllowed('manage', 'all', null, { user })); // true });

Batch Checking

Instead of checking permissions one by one, batch checking can reduce the number of function calls and improve performance.

const actions = ['read', 'update', 'delete']; const resources = ['Post', 'Comment']; const isAllowed = actions.reduce((acc, action) => { resources.forEach(resource => { acc[`${action}-${resource}`] = ability.isAllowed(action, resource, null, { user }); }); return acc; }, {}); console.log(isAllowed);

Error Handling Strategies

Centralized Permission Checking

Create centralized functions for common permission patterns:

import { createAbility, ForbiddenError } from '@nordic-ui/asgardian' class PermissionService { private ability: CreateAbility constructor(user: User) { this.ability = this.createUserAbility(user) } private createUserAbility(user: User) { const ability = createAbility<AppActions, AppResources>() // Base permissions ability .can('read', 'Post', { published: true }) .cannot('read', 'Post', { private: true, authorId: { $ne: user.id } }) .reason('This post is private') // Role-based permissions if (user.role === 'admin') { ability.can('manage', 'all') } else if (user.role === 'moderator') { ability .can(['update', 'delete'], 'Post') .cannot('delete', 'Post', { hasComments: true }) .reason('Cannot delete posts with comments') } return ability } // Centralized checking with detailed error context checkPermission(action: string, resource: string, data?: any) { const isAllowed = this.ability.isAllowed(action, resource, data) if (!isAllowed) { const reason = this.ability.getReason(action, resource, data) // Log the denial for auditing this.logPermissionDenial(action, resource, reason, data) return { allowed: false, reason } } return { allowed: true } } // Throw with enhanced context enforcePermission(action: string, resource: string, data?: any) { const result = this.checkPermission(action, resource, data) if (!result.allowed) { throw new ForbiddenError(result.reason || 'Access denied') } } private logPermissionDenial(action: string, resource: string, reason?: string, data?: any) { logger.warn('Permission denied', { action, resource, reason, userId: this.user.id, resourceId: data?.id, timestamp: new Date().toISOString() }) } }

Best Practices

Here are some best practices to follow when using Asgardian for permission management.

Keep Rules DRY

Avoid duplicating rules. Use conditions and role-based permissions to keep your rules DRY (Don’t Repeat Yourself).

// Define reusable reason templates const PERMISSION_MESSAGES = { OWNERSHIP_REQUIRED: 'You can only perform this action on your own content', INSUFFICIENT_ROLE: (requiredRole: string) => `This action requires ${requiredRole} role`, RESOURCE_LOCKED: 'This resource is locked and cannot be modified', BUSINESS_HOURS_ONLY: 'This action is only available during business hours', UPGRADE_REQUIRED: (feature: string) => `Upgrade your account to access ${feature}`, } as const ability .cannot('update', 'Post', { authorId: { $ne: user.id } }) .reason(PERMISSION_MESSAGES.OWNERSHIP_REQUIRED) .cannot('delete', 'User', { role: 'admin' }) .reason(PERMISSION_MESSAGES.INSUFFICIENT_ROLE('super-admin')) .cannot('publish', 'Post', { subscription: 'free' }) .reason(PERMISSION_MESSAGES.UPGRADE_REQUIRED('content publishing'))

Use Roles for Grouping

Use roles to group permissions. This makes it easier to manage and update permissions.

const createRoleBasedAbility = (user: User) => { const ability = createAbility() switch (user.role) { case 'admin': ability.can('manage', 'all') break case 'moderator': ability .can(['read', 'update'], 'Post') .can(['read', 'delete'], 'Comment') .cannot('delete', 'Post', { published: true }) .reason('Moderators cannot delete published posts') .cannot('update', 'User', { role: 'admin' }) .reason('Moderators cannot modify admin accounts') break case 'user': ability .can('read', 'Post', { published: true }) .can(['create', 'update'], 'Post', { authorId: user.id }) .cannot('delete', 'Post') .reason('Regular users cannot delete posts') .cannot('update', 'Post', { published: true }) .reason('Cannot edit published posts') break default: ability .can('read', 'Post', { published: true }) .cannot('create', 'Post') .reason('Please sign in to create posts') } return ability }

Log and Monitor Permissions

Logging and monitoring permissions can help you debug and audit your permission rules.

import { createAbility, logger } from '@nordic-ui/asgardian'; const ability = createAbility(); ability .can('read', 'Post', (post, context) => { const allowed = post.published || context.user.roles.includes('admin'); if (!allowed) { logger.warn(`User ${context.user.id} attempted to read unpublished post ${post.id}`); } return allowed; }) .can('update', 'Post', (post, context) => { const allowed = post.authorId === context.userId || context.user.roles.includes('moderator'); if (!allowed) { logger.warn(`User ${context.user.id} attempted to update post ${post.id}`); } return allowed; });

Summary

In this section, we explored advanced usage patterns and best practices for using Asgardian to define and manage permissions. This includes handling dynamic rules, integrating with databases, and optimizing performance.

💡
Tip

For more information and examples, refer to the API Reference and FAQ sections.

Last updated on