### Run Development Server in Next.js Project Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/README.md Commands to start the local development server for the Next.js project. These commands are typically used with package managers like npm, yarn, pnpm, or bun. Ensure you have the project dependencies installed before running these commands. The server will be accessible at http://localhost:3000. ```bash npm run dev # or yarn dev # or pnpm dev # or bun dev ``` -------------------------------- ### Best Practice: Start Restrictive (TypeScript) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/role-based-permissions.mdx Demonstrates the best practice of starting with limited permissions and explicitly adding necessary ones. This approach prevents accidental over-permissioning. The code shows a 'good' example with specific abilities and a 'bad' example starting with broad permissions. ```typescript // ✅ Good: Start with limited permissions const ability = createAbility() .can('read', 'Post', { published: true }) .can('update', 'Post', { authorId: userId }) // ❌ Bad: Starting with broad permissions const ability = createAbility() .can('manage', 'all') .cannot('delete', 'Post') ``` -------------------------------- ### Install Asgardian React Integration Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/index.mdx This command installs the Asgardian React library, which provides hooks and components for easily integrating Asgardian's authorization logic into React applications. Use npm or yarn for installation. ```sh npm i @nordic-ui/asgardian-react ``` ```sh yarn add @nordic-ui/asgardian-react ``` -------------------------------- ### Install Asgardian Authorization Library Source: https://github.com/nordic-ui/asgardian/blob/main/packages/asgardian/README.md Install the Asgardian library using your preferred package manager. This command is compatible with npm, yarn, and pnpm. ```sh npm install @nordic-ui/asgardian yarn add @nordic-ui/asgardian pnpm add @nordic-ui/asgardian ``` -------------------------------- ### Install @nordic-ui/asgardian-react Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/hooks.mdx Installs the necessary React package for Asgardian permissions. This command can be used with either npm or yarn. ```sh npm install @nordic-ui/asgardian-react ``` ```sh yarn add @nordic-ui/asgardian-react ``` -------------------------------- ### React Example: Integrate Asgardian with AbilityProvider and useCan Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/index.mdx This React TypeScript example demonstrates how to set up Asgardian for frontend authorization. It uses AbilityProvider to make the ability instance available throughout the component tree and the useCan hook to conditionally render UI elements based on user permissions. ```tsx import { createAbility } from '@nordic-ui/asgardian'; import { AbilityProvider, useCan } from '@nordic-ui/asgardian-react'; const ability = createAbility() .can('read', 'Article') .can('write', 'Comment') .can('manage', 'Profile'); const App = () => { return ( ); }; const TypedPostActions = () => { const canWrite = useCan('write', 'Article'); const canDelete = useCan('delete', 'Comment'); return (
{canWrite && } {canDelete && }
); }; ``` -------------------------------- ### Quick Start: Create and Check Permissions with Asgardian Source: https://github.com/nordic-ui/asgardian/blob/main/README.md Demonstrates how to create a type-safe ability instance, define permissions using 'can' and 'cannot' methods, and check if actions are allowed for specific resources and conditions using the 'isAllowed' method. ```typescript import { createAbility } from '@nordic-ui/asgardian' // Create a type-safe ability instance const ability = createAbility<'publish', 'Post'>() // Define permissions ability .can('read', 'Post', { published: true }) .can('update', 'Post', { authorId: 123 }) .can('publish', 'Post', { draft: true, authorId: 123 }) .cannot('delete', 'Post') // Check permissions ability.isAllowed('read', 'Post', { published: true }) // true ability.isAllowed('publish', 'Post', { draft: false }) // false ability.isAllowed('delete', 'Post') // false ``` -------------------------------- ### Setup AbilityProvider in React Application Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/hooks.mdx Demonstrates how to set up the `AbilityProvider` at the root of your React application. This provider makes the Asgardian ability instance available to all child components. ```tsx import { createAbility } from '@nordic-ui/asgardian' import { AbilityProvider } from '@nordic-ui/asgardian-react' // Create your ability instance const ability = createAbility() .can('read', 'Post') .can('write', 'Comment') .can('manage', 'Profile') const App = () => { return ( ) } ``` -------------------------------- ### Combine 'can' and 'cannot' Rules in TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/rules-and-conditions.mdx This example shows how to chain multiple 'can' and 'cannot' rules to create complex permission logic. It demonstrates allowing users to read posts while explicitly denying them the ability to delete posts, and verifying these permissions. ```typescript // Allow reading posts and disallow deleting posts ability .can('read', 'Post') .cannot('delete', 'Post'); const canReadPost = ability.isAllowed('read', 'Post'); console.log(canReadPost); // true const canDeletePost = ability.isAllowed('delete', 'Post'); console.log(canDeletePost); // false ``` -------------------------------- ### Setting and Getting Rule Reasons in Asgardian Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/api-reference.mdx Demonstrates how to define rules with specific reasons for denied actions and how to retrieve these reasons using the `getReason` method. It covers cases where a reason is explicitly set, where conditions apply, and where no specific reason is found. ```typescript ability .cannot('delete', 'Post').reason('Deletion not allowed') .cannot('update', 'Post', { archived: true }).reason('Cannot update archived posts') const deleteReason = ability.getReason('delete', 'Post') // Returns: "Deletion not allowed" const updateReason = ability.getReason('update', 'Post', { archived: true }) // Returns: "Cannot update archived posts" const unknownReason = ability.getReason('create', 'Post') // Returns: undefined ``` -------------------------------- ### Best Practice: Be Explicit (TypeScript) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/role-based-permissions.mdx Highlights the importance of being explicit about resource access. The 'good' example clearly defines permissions for 'Post' and 'Comment', while the 'bad' example shows an overly broad permission that could lead to security issues. ```typescript // ✅ Good: Specific resource access const editorAbility = createAbility() .can('manage', 'Post') // Explicitly grant all actions on posts .can('read', 'Comment') // Explicitly grant only read access on comments // ❌ Bad: Over-permissive resource access const editorAbility = createAbility() .can('manage', 'all') // Too broad, grants access to all current and future resources ``` -------------------------------- ### Install Asgardian React Source: https://github.com/nordic-ui/asgardian/blob/main/packages/asgardian-react/README.md Install the Asgardian React package using npm, yarn, or pnpm. Note that `@nordic-ui/asgardian` is a peer dependency and must also be installed. ```sh npm install @nordic-ui/asgardian-react yarn add @nordic-ui/asgardian-react pnpm add @nordic-ui/asgardian-react ``` -------------------------------- ### API Route Protection with Express.js Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/error-handling.mdx Provides an example of protecting an Express.js API route using `throwIfNotAllowed()`. It demonstrates how to integrate permission checks within a route handler to ensure only authorized users can perform actions like deleting a post, and how to return appropriate HTTP responses. ```typescript // Express.js route handler app.delete('/api/posts/:id', async (req, res) => { try { const ability = await getUserAbility(req.user) const post = await getPost(req.params.id) ability.throwIfNotAllowed('delete', 'Post', post) await deletePost(req.params.id) res.json({ success: true }) } catch (error) { if (error instanceof ForbiddenError) { res.status(403).json({ error: 'Permission denied', reason: error.message }) } else { res.status(500).json({ error: 'Internal server error' }) } } }) ``` -------------------------------- ### Implement Rule Precedence Best Practices in TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/rules-and-conditions.mdx This code illustrates effective rule precedence in Asgardian. It contrasts a bad practice of starting with broad permissions and then restricting them, with good practices that start restrictively and add specific allowances, finally showing an explicit approach following the principle of least privilege. ```typescript const ability = createAbility() // ❌ Bad practice: Starting with broad permissions ability .can('manage', 'all') .cannot('delete', 'Comment') // Trying to restrict after the fact // ✅ Good practice: Start restrictive, add specific permissions ability .can('read', 'Post', { published: true }) // Allow reading published posts .can(['create', 'update'], 'Post', { authorId: 123 }) // Allow authors to manage their own posts .cannot('delete', 'Post') // Ensure deletion is never allowed, regardless of other rules // Even better: Be explicit about all permissions const userAbility = createAbility() // Post permissions .can('read', 'Post', { published: true }) .can('create', 'Post') .can('update', 'Post', { authorId: 123 }) // Comment permissions .can('read', 'Comment') .can('create', 'Comment', { authenticated: true }) .cannot('delete', 'Comment') // Extra safety guard ``` -------------------------------- ### Define Rule for All Resources ('all') in TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/rules-and-conditions.mdx This example shows the use of the 'all' keyword to create a rule that applies to every resource managed by Asgardian. This is typically used for super-administrator roles. ```typescript // Allow managing all resources ability.can('manage', 'all'); ``` -------------------------------- ### Chain Multiple Rules for Complex Permissions in TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/rules-and-conditions.mdx This example showcases how to build a complex set of permissions by chaining multiple 'can' and 'cannot' rules. It defines read access for posts, read/create access for comments, and explicitly denies update access for comments, then verifies these permissions. ```typescript const ability = createAbility() .can('read', 'Post') .can(['read', 'create'], 'Comment') .cannot('update', 'Comment') // Check permissions ability.isAllowed('read', 'Post') // true ability.isAllowed('delete', 'Post') // false ability.isAllowed('read', 'Comment') // true ability.isAllowed('create', 'Comment') // true ability.isAllowed('update', 'Comment') // false ``` -------------------------------- ### Install Asgardian Permission System Source: https://github.com/nordic-ui/asgardian/blob/main/README.md Installs the core Asgardian permission system package using npm, yarn, or pnpm. This package is essential for setting up permission rules and checks in your TypeScript application. ```bash # Using npm npm install @nordic-ui/asgardian # Using yarn yarn add @nordic-ui/asgardian # Using pnpm pnpm add @nordic-ui/asgardian ``` -------------------------------- ### Add Reasons to Permission Rules with .reason() - TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/error-handling.mdx Demonstrates how to chain the `.reason()` method after `can()` and `cannot()` rules in Asgardian to provide explanations for permission decisions. This improves user feedback, debugging, and auditing by associating specific reasons with rule outcomes. It shows examples with various conditions and resource types. ```ts import { createAbility } from '@nordic-ui/asgardian' const ability = createAbility<'publish' | 'archive', 'Post' | 'Comment'>() ability // read published posts .can('read', 'Post', { published: true }) .reason('Public posts are readable by everyone') // update own posts .can('update', 'Post', { authorId: 123 }) .reason('Authors can edit their own posts') // delete any posts .cannot('delete', 'Post') .reason('Deletion not allowed for security reasons') // publish non-draft posts .cannot('publish', 'Post', { status: { $ne: 'draft'} }) .reason('Cannot publish non-draft posts') // update locked posts .cannot('update', 'Post', { locked: true }) .reason('Post is locked for editing') ``` -------------------------------- ### Apply Conditional Permissions in Asgardian Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/index.mdx This TypeScript example illustrates how to add conditions to Asgardian rules. It defines a permission to read a 'Post' only if it's published, and then checks this permission against different post objects, demonstrating conditional access control. ```typescript ability.can('read', 'Post', { published: true }); const publishedPost = { published: true }; const draftPost = { published: false }; console.log(ability.isAllowed('read', 'Post', publishedPost)); // true console.log(ability.isAllowed('read', 'Post', draftPost)); // false ``` -------------------------------- ### Check Permissions for Different User Roles (TypeScript) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/chained-api.mdx Provides examples of checking permissions using the `isAllowed` method of an Asgardian ability instance for different user roles ('admin', 'user', 'moderator'). It demonstrates how the defined chained rules are evaluated against user context. Assumes an initialized Asgardian ability instance and defined roles/permissions. ```typescript const adminUser = { id: 123, roles: ['admin'], }; const userUser = { id: 456, roles: ['user'], }; const moderatorUser = { id: 789, roles: ['moderator'], }; // Check permissions for admin console.log(ability.isAllowed('manage', 'Post', null, { user: adminUser })); // true console.log(ability.isAllowed('create', 'Post', null, { user: adminUser })); // true console.log(ability.isAllowed('read', 'Post', null, { user: adminUser })); // true console.log(ability.isAllowed('delete', 'Post', null, { user: adminUser })); // true // Check permissions for user console.log(ability.isAllowed('manage', 'Post', null, { user: userUser })); // false console.log(ability.isAllowed('create', 'Post', null, { user: userUser })); // false console.log(ability.isAllowed('read', 'Post', null, { user: userUser })); // true console.log(ability.isAllowed('delete', 'Post', null, { user: userUser })); // false // Check permissions for moderator console.log(ability.isAllowed('manage', 'Post', null, { user: moderatorUser })); // false console.log(ability.isAllowed('create', 'Post', null, { user: moderatorUser })); // true console.log(ability.isAllowed('read', 'Post', null, { user: moderatorUser })); // true console.log(ability.isAllowed('delete', 'Post', null, { user: moderatorUser })); // false ``` -------------------------------- ### Define Rule with Multiple Actions in TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/rules-and-conditions.mdx This example illustrates how to specify multiple actions within a single rule using an array. This allows for more concise rule definitions when a permission applies to several operations on the same resource. ```typescript // Allow creating, updating, and deleting posts ability.can(['create', 'update', 'delete'], 'Post'); ``` -------------------------------- ### Role-Based Access Control (RBAC) in TypeScript Source: https://context7.com/nordic-ui/asgardian/llms.txt Illustrates how to implement Role-Based Access Control (RBAC) using the Asgardian SDK. It defines permissions based on user roles, including special 'manage' and 'all' permissions. The example shows how to dynamically define abilities based on a user's role, granting different levels of access to resources. ```typescript import { createAbility } from '@nordic-ui/asgardian' type User = { id: number role: 'admin' | 'editor' | 'author' | 'viewer' } type Resources = 'Post' | 'Comment' | 'User' | 'Settings' function defineAbilities(user: User) { const ability = createAbility() if (user.role === 'admin') { // Admins can do everything ability.can('manage', 'all') return ability } if (user.role === 'editor') { // Editors can manage posts and comments ability.can('manage', ['Post', 'Comment']) // But can only read users and settings ability.can('read', ['User', 'Settings']) } if (user.role === 'author') { // Authors can read all posts ability.can('read', 'Post') // But can only manage their own posts ability.can(['create', 'update', 'delete'], 'Post', { authorId: user.id }) // Can create and read comments ability.can(['create', 'read'], 'Comment') } if (user.role === 'viewer') { // Viewers can only read published content ability.can('read', 'Post', { published: true }) ability.can('read', 'Comment') } return ability } // Usage const admin = { id: 1, role: 'admin' as const } const adminAbility = defineAbilities(admin) console.log(adminAbility.isAllowed('delete', 'User')) // true const author = { id: 123, role: 'author' as const } const authorAbility = defineAbilities(author) console.log(authorAbility.isAllowed('update', 'Post', { authorId: 123 })) // true console.log(authorAbility.isAllowed('update', 'Post', { authorId: 456 })) // false const viewer = { id: 789, role: 'viewer' as const } const viewerAbility = defineAbilities(viewer) console.log(viewerAbility.isAllowed('read', 'Post', { published: true })) // true console.log(viewerAbility.isAllowed('read', 'Post', { published: false })) // false console.log(viewerAbility.isAllowed('create', 'Post')) // false ``` -------------------------------- ### Setup AbilityProvider in React Source: https://github.com/nordic-ui/asgardian/blob/main/packages/asgardian-react/README.md Wrap your application with the `AbilityProvider` component from `@nordic-ui/asgardian-react` to make Asgardian permissions available throughout the component tree. This requires creating an `ability` instance using `createAbility` from `@nordic-ui/asgardian`. ```tsx import { createAbility } from '@nordic-ui/asgardian'; import { AbilityProvider } from '@nordic-ui/asgardian-react'; const ability = createAbility(); // Configure your ability with rules... const App = () => { return ( ); } ``` -------------------------------- ### useCan for Navigation Guards (React) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/hooks.mdx Illustrates using the `useCan` hook to implement navigation guards within a React application. This example controls the rendering of navigation links based on whether the user has permissions to 'access' the 'AdminPanel', 'manage' 'User' resources, or 'view' 'Reports'. ```tsx const Navigation = () => { const { can: canViewAdmin } = useCan('access', 'AdminPanel') const { can: canManageUsers } = useCan('manage', 'User') const { can: canViewReports } = useCan('view', 'Reports') return ( ) } ``` -------------------------------- ### Implement Nested Chained Permission Rules (TypeScript) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/chained-api.mdx Illustrates how to create nested or sequential permission rules using Asgardian's chained API. This example defines a read permission with a condition, followed by an update permission with conditional author/admin check, and finally a delete permission with a condition. Assumes an initialized Asgardian ability instance. ```typescript ability .can('read', 'Post', (post) => post.published) .can('update', 'Post', (post, context) => post.authorId === context.userId || context.user.roles.includes('admin')) .cannot('delete', 'Post', (post, context) => post.authorId !== context.userId); ``` -------------------------------- ### Apply Multiple Conditions to a Rule in TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/rules-and-conditions.mdx This snippet demonstrates how to define a rule that requires multiple conditions to be met simultaneously. The example allows 'manage' action on 'Post' only if both `authorId` and `published` properties match the specified criteria. It also shows how `isAllowed` checks all conditions. ```typescript const ability = createAbility() // Allow managing posts only if both conditions are met ability.can('manage', 'Post', { authorId: 123, published: true }) // All conditions must match ability.isAllowed('manage', 'Post', { authorId: 123, published: true }) // true ability.isAllowed('manage', 'Post', { authorId: 123, published: false }) // false ability.isAllowed('manage', 'Post', { authorId: 456, published: true }) // false ``` -------------------------------- ### Use Role-Based Abilities to Check Permissions (TypeScript) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/role-based-permissions.mdx This example demonstrates how to instantiate a role-based ability object using the previously defined `createRoleAbility` function and then use its `isAllowed` method to perform permission checks. It shows checking for read, update, and delete permissions on 'Post' resources with specific conditions. ```typescript // Assuming createRoleAbility is defined as above // Create ability instance based on user's role const user = { id: 123, role: 'author' } const ability = createRoleAbility(user.role, user.id) // Check permissions ability.isAllowed('read', 'Post', { published: true }) // true ability.isAllowed('update', 'Post', { authorId: 123 }) // true ability.isAllowed('update', 'Post', { authorId: 456 }) // false ability.isAllowed('delete', 'Post', { authorId: 456 }) // false ``` -------------------------------- ### useCan and useCannot for Conditional Rendering (React) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/hooks.mdx Demonstrates how to use `useCan` and `useCannot` hooks to conditionally render UI elements based on user permissions. This example shows controlling visibility of edit/delete buttons and comments section based on user's ability to 'edit' a 'Post', 'delete' a 'Post', or 'create' a 'Comment'. ```tsx type BlogPostProps = { post: Post } const BlogPost: FC = ({ post }) => { const { can: canEdit } = useCan('edit', 'Post', { authorId: post.authorId }) const { can: canDelete } = useCan('delete', 'Post', { authorId: post.authorId }) const { cannot: cannotComment } = useCannot('create', 'Comment') return (

{post.title}

{post.content}

{canEdit && ( )} {canDelete && ( )}
{cannotComment ? (

Sign in to leave a comment

) : ( )}
) } ``` -------------------------------- ### Apply Conditions to Rules in TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/rules-and-conditions.mdx This snippet illustrates how to add conditions to Asgardian rules, allowing for more granular permission checks. The example demonstrates allowing the 'read' action on 'Post' only if the post resource has a 'published' property set to true. It also shows how to check permissions against specific resource objects. ```typescript // Allow reading only published posts ability.can('read', 'Post', { published: true }); const publishedPost = { published: true }; const draftPost = { published: false }; console.log(ability.isAllowed('read', 'Post', publishedPost)); // true console.log(ability.isAllowed('read', 'Post', draftPost)); // false ``` -------------------------------- ### Best Practice: Clear, Actionable Reasons Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/error-handling.mdx Illustrates the best practice of providing clear and actionable reasons for permission checks. It contrasts helpful reasons that explain the restriction with vague reasons that offer little insight. ```typescript // ✅ Good - Clear and actionable for both allow and deny ability .can('read', 'Post', { published: true }) .reason('Published content is publicly accessible') .cannot('publish', 'Post', { status: 'draft' }) .reason('Post must be reviewed before publishing') // ❌ Less helpful - Vague ability .can('read', 'Post') .reason('Allowed') .cannot('publish', 'Post', { status: 'draft' }) .reason('Cannot publish') ``` -------------------------------- ### Asgardian: Implement Role-Based Access Control Source: https://github.com/nordic-ui/asgardian/blob/main/README.md Demonstrates how to define permissions based on user roles (e.g., 'admin', 'editor') by conditionally creating and configuring Asgardian ability instances. Admins get 'manage all' permissions, while editors get specific permissions for 'Post'. ```typescript const defineAbilities = (user: User) => { const ability = createAbility() if (user.role === 'admin') { ability.can('manage', 'all') return ability; // Early return because admin has all permissions already } if (user.role === 'editor') { ability.can(['read', 'update'], 'Post') ability.can('publish', 'Post', { draft: true }) } return ability } ``` -------------------------------- ### Best Practice: Use Type Safety (TypeScript) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/role-based-permissions.mdx Emphasizes the use of type safety for roles and resources to ensure predictable behavior and prevent runtime errors. The 'good' example defines specific types for roles and resources, while the 'bad' example uses generic `any` types, lacking type safety. ```typescript // ✅ Good: Type-safe roles and resources type Role = 'admin' | 'editor' | 'author' | 'visitor' type Resources = 'Post' | 'Comment' const ability = createAbility() // ❌ Bad: No type safety const ability = createAbility() ``` -------------------------------- ### Retrieve Permission Reasons with getReason() - TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/error-handling.mdx Shows how to use the `getReason()` method in Asgardian to retrieve the specific explanation associated with a permission rule. This function takes the action, resource, and optional conditions as arguments and returns the reason string for the matching rule, or `undefined` if no rule matches. It demonstrates retrieving reasons for allowed, denied, and conditional actions. ```ts const ability = createAbility() ability .can('read', 'Post').reason('Reading is always allowed') .cannot('delete', 'Post').reason('Deletion not allowed') .cannot('update', 'Post', { archived: true }).reason('Cannot modify archived content') // Get reasons for both allowed and denied actions const readReason = ability.getReason('read', 'Post') console.log(readReason) // "Reading is always allowed" const deleteReason = ability.getReason('delete', 'Post') console.log(deleteReason) // "Deletion not allowed" const updateReason = ability.getReason('update', 'Post', { archived: true }) console.log(updateReason) // "Cannot modify archived content" const unknownReason = ability.getReason('publish', 'Post') console.log(unknownReason) // undefined (no matching rule) ``` -------------------------------- ### Integrate Asgardian with Database for Permissions Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/advanced-usage.mdx Shows how to integrate Asgardian with a database to fetch and apply user roles and resource permissions dynamically. Assumes database helper functions like `fetchUserRoles` and `fetchResourcePermissions` exist. Requires @nordic-ui/asgardian. ```javascript 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 }); ``` -------------------------------- ### User-Friendly Messages for Permissions Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/error-handling.mdx Demonstrates how to define custom, user-friendly reasons for permission checks using the `.reason()` method. This allows for more specific and helpful feedback to users when their actions are restricted. ```typescript const ability = createAbility() ability .can('read', 'Post', { published: true }) .reason('Published posts are available to all users') .cannot('read', 'Post', { published: false }) .reason('This post is not yet published') .can('update', 'Post', { authorId: 123 }) .reason('You have full editing rights to your posts') .cannot('update', 'Post', { authorId: { $ne: 123 } }) .reason('You can only edit your own posts') .cannot('delete', 'Post', { hasComments: true }) .reason('Cannot delete posts with comments. Please delete comments first.') .cannot('publish', 'Post', { status: 'draft', reviewStatus: { $ne: 'approved' } }) .reason('Posts must be reviewed before publishing') ``` -------------------------------- ### Get Denial Reason - TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/api-reference.mdx Retrieves the reason string associated with why a specific action was denied on a resource, optionally considering conditions. Returns the reason string or undefined if the action is allowed or no reason was set. ```typescript getReason( action: Action | Action[], resource: Resource, conditions?: Condition ): string | undefined // Example usage (assuming a denial with a reason was previously set) const reason = ability.getReason('delete', 'Post') if (reason) { console.log('Cannot delete:', reason) } ``` -------------------------------- ### Get Permission Denial Reason - TypeScript Source: https://context7.com/nordic-ui/asgardian/llms.txt Retrieves a custom reason message for denied actions using the `getReason` method. This requires defining rules with `.reason()` beforehand. It returns `undefined` for allowed actions. ```typescript import { createAbility } from '@nordic-ui/asgardian' const ability = createAbility<'publish', 'Post'>() ability .cannot('update', 'Post', { userId: { $ne: 1 } }) .reason('You must be the owner of the post to update it') ability .cannot('publish', 'Post', { status: 'draft' }) .reason('Post must not be in draft status to publish') ability .cannot('delete', 'Post', { published: true }) .reason('Cannot delete published posts') // Check permissions and get reasons console.log(ability.isAllowed('update', 'Post', { userId: 2 })) // false console.log(ability.getReason('update', 'Post', { userId: 2 })) // "You must be the owner of the post to update it" console.log(ability.isAllowed('publish', 'Post', { status: 'draft' })) // false console.log(ability.getReason('publish', 'Post', { status: 'draft' })) // "Post must not be in draft status to publish" console.log(ability.isAllowed('delete', 'Post', { published: true })) // false console.log(ability.getReason('delete', 'Post', { published: true })) // "Cannot delete published posts" // No reason for allowed actions console.log(ability.isAllowed('delete', 'Post', { published: false })) // true console.log(ability.getReason('delete', 'Post', { published: false })) // undefined ``` -------------------------------- ### Permission Methods - getReason Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/api-reference.mdx Retrieves the reason why an action was denied on a resource, optionally with conditions. ```APIDOC ## Permission Methods - getReason ### Description Retrieves the reason why an action was denied on a resource, optionally with conditions. ### Method ```ts getReason( action: Action | Action[], resource: Resource, conditions?: Condition ): string | undefined ``` ### Parameters #### Path Parameters - **action** (Action | Action[]) - Required - Single action or array of actions to check. - **resource** (Resource) - Required - The resource to check against. - **conditions** (Condition) - Optional - Conditions to evaluate. ### Request Example ```ts // Get reason for denial ability.getReason('delete', 'Post') // "Deletion not allowed for security" or undefined // With conditions ability.getReason('update', 'Post', { archived: true }) // "Reason for archived update denial" or undefined ``` ### Response #### Success Response (200) - **string | undefined**: Returns the denial reason string if available, otherwise `undefined`. #### Response Example ```json "Deletion not allowed for security" ``` ``` -------------------------------- ### Define Basic Permission Rules with Chained API (TypeScript) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/chained-api.mdx Demonstrates the fundamental usage of Asgardian's chained API to define basic 'can' rules. It initializes an ability instance and chains 'can' methods to specify allowed actions and resources. Requires the '@nordic-ui/asgardian' package. ```typescript import { createAbility } from '@nordic-ui/asgardian'; const ability = createAbility(); ability .can('read', 'Post') .can('update', 'Post'); ``` -------------------------------- ### Define Dynamic Permission Rules with Asgardian Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/advanced-usage.mdx Demonstrates how to create dynamic permission rules in Asgardian that adapt based on context, such as user properties or resource states. Requires the @nordic-ui/asgardian package. ```typescript 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')); ``` -------------------------------- ### Use Consistent Messaging with Asgardian Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/error-handling.mdx This TypeScript code demonstrates how to define and use consistent messages for permission checks in Asgardian. It shows both good and bad examples of attaching reasons to 'can' and 'cannot' rules, emphasizing clarity and avoiding internal logic exposure. ```typescript const MESSAGES = { PUBLIC_ACCESS: 'This content is publicly accessible', OWNER_ONLY: 'You can only modify your own content', LOCKED_RESOURCE: 'This resource is locked and cannot be modified', INVALID_STATE: (state: string) => `Action not allowed when resource is ${state}`, UPGRADE_REQUIRED: 'Upgrade your account to access this feature' } ability .can('read', 'Post', { published: true }) .reason(MESSAGES.PUBLIC_ACCESS) .cannot('delete', 'Post', { authorId: { $ne: 123 } }) .reason(MESSAGES.OWNER_ONLY) .cannot('update', 'Post', { locked: true }) .reason(MESSAGES.LOCKED_RESOURCE) ``` -------------------------------- ### Create Ability Instance - TypeScript Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/api-reference.mdx Creates a new ability instance for defining and checking permissions. Supports generic parameters for custom actions and resources. Returns an object with methods for permission management. ```typescript function createAbility(): CreateAbility // Basic usage const ability = createAbility() // With custom actions const ability = createAbility<'publish' | 'archive'>() // With custom resources const ability = createAbility() // With both custom actions and resources const ability = createAbility<'publish' | 'archive', 'Post' | 'Comment'>() ``` -------------------------------- ### Conditional Rendering with useAbility in React Source: https://github.com/nordic-ui/asgardian/blob/main/packages/asgardian-react/README.md Demonstrates how to use the `can` method from the `useAbility` hook to conditionally render UI elements based on user permissions. This example shows edit and delete buttons for a post, only visible if the user has the appropriate permissions. ```tsx const PostActions = ({ post }) => { const { can } = useAbility(); return (
{can('edit', 'post', { userId: post.authorId }) && ( )} {can('delete', 'post', { userId: post.authorId }) && ( )}
); } ``` -------------------------------- ### Core Functions - createAbility Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/api-reference.mdx Creates a new ability instance for defining and checking permissions. Allows customization of actions and resources. ```APIDOC ## Core Functions - createAbility ### Description Creates a new ability instance for defining and checking permissions. Allows customization of actions and resources. ### Method ```ts function createAbility(): CreateAbility ``` ### Parameters #### Generic Parameters - **ExtendedActions** (string) - Optional - Custom action types to extend the default actions. - **ExtendedResources** (string) - Optional - Custom resource types to extend the default resources. ### Returns - Returns an object with methods for defining and checking permissions. ### Examples ```ts // Basic usage const ability = createAbility() // With custom actions const ability = createAbility<'publish' | 'archive'>() // With custom resources const ability = createAbility() // With both custom actions and resources const ability = createAbility<'publish' | 'archive', 'Post' | 'Comment'>() ``` ``` -------------------------------- ### Error Handling: useAbility must be within AbilityProvider (React) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/hooks.mdx Highlights the requirement for hooks like `useCan` and `useAbility` to be used within an `AbilityProvider`. Using them outside this context will result in an error, indicating that the ability context is not available. The example shows correct and incorrect usage patterns. ```tsx const ComponentWithoutProvider = () => { const { can: canRead } = useCan('read', 'Post') return
This won't work
} // Error: "useAbilityContext must be used within an AbilityProvider" // ✅ Correct usage const App = () => { const ability = createAbility() return ( ) } ``` -------------------------------- ### Define and Check Permissions with Asgardian Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/index.mdx This TypeScript code demonstrates how to create an ability instance, define rules for allowed and disallowed actions on 'Post' resources, and then check if specific actions are permitted. It showcases the basic fluent API for setting permissions. ```typescript import { createAbility } from '@nordic-ui/asgardian'; const ability = createAbility(); ability.can(['read', 'create'], 'Post'); ability.cannot('delete', 'Post'); const canReadPost = ability.isAllowed('read', 'Post'); console.log(canReadPost); // true const canDeletePost = ability.isAllowed('delete', 'Post'); console.log(canDeletePost); // false ``` -------------------------------- ### Avoid Exposing Sensitive Information in Asgardian Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/error-handling.mdx This TypeScript example illustrates how to prevent sensitive information from being exposed in Asgardian's permission reason messages. It contrasts a good approach, using generic and user-friendly reasons, with a bad approach that reveals internal details like user IDs. ```typescript // ✅ Good - Generic but helpful ability .can('read', 'Post', { public: true }) .reason('Public posts are accessible to all users') .cannot('read', 'Post', { private: true, authorId: { $ne: user.id } }) .reason('This post is private') // ❌ Bad - Exposes internal logic ability.cannot('read', 'Post', { private: true, authorId: { $ne: user.id } }) .reason('Post is private and authorId 456 does not match current user 123') ``` -------------------------------- ### Chain Multiple Actions and Manage All Resources (TypeScript) Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/chained-api.mdx Shows how to chain multiple actions ('create', 'update', 'delete') for a specific resource ('Post') and how to define a broad 'manage all' permission using Asgardian's chained API. Assumes an initialized Asgardian ability instance. ```typescript ability .can(['create', 'update', 'delete'], 'Post') .can('manage', 'all'); ``` -------------------------------- ### Centralized Permission Checking with PermissionService Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/advanced-usage.mdx Demonstrates how to create a `PermissionService` class for centralized permission checking. It initializes user abilities based on roles and provides methods to check and enforce permissions, logging denials with detailed context. Dependencies include `@nordic-ui/asgardian` and a `logger` instance. ```typescript 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() // 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() }) } } ``` -------------------------------- ### Role-Based Permission Grouping with switch statement Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/advanced-usage.mdx Shows how to group permissions effectively using roles and a switch statement. This method simplifies permission management by assigning specific capabilities to different user roles, ensuring easier updates and maintenance. ```typescript 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 } ``` -------------------------------- ### Logging and Auditing Permission Checks Source: https://github.com/nordic-ui/asgardian/blob/main/apps/docs/content/docs/error-handling.mdx Shows how to log permission checks for auditing purposes. The `auditPermissionCheck` function logs whether an action is allowed, the reason for denial, and other relevant details to a logger and potentially a database. ```typescript const auditPermissionCheck = (action: string, resource: string, user: User, data?: any) => { const ability = getUserAbility(user) const isAllowed = ability.isAllowed(action, resource, data) const reason = ability.getReason(action, resource, data) logger.info('Permission check', { userId: user.id, action, resource, isAllowed, reason, timestamp: new Date().toISOString(), data: data ? { id: data.id } : null }) if (!isAllowed) { // Could also store in database for audit trail await saveAuditLog({ userId: user.id, action: 'PERMISSION_DENIED', details: { action, resource, reason } }) } return isAllowed } ```