- AI
- Analytics
- Auth
- CMS
- Collaboration
- Cron Jobs
- Database
- Data Layer
- CRUD System
- Design System
- Transactional Emails
- Feature Flags
- Formatting
- Internationalization
- Observability
- Next.js Config
- Notifications
- Payments
- Security
- SEO
- Storage
- Testing
- Toolbar
- View System
- View Builder
- Trigger
- Trigger Rules
- Webhooks
Permissions System
Access control for CRUD operations
Permissions System
The permissions package in @repo/crud
provides a comprehensive access control system for CRUD operations. It allows you to define who can perform which operations on which resources.
Features
- Fine-Grained Control: Define permissions at the resource and operation level
- Dynamic Permissions: Permissions can be based on the user, resource, and data
- Role-Based Access Control: Support for role-based permissions
- Attribute-Based Access Control: Support for attribute-based permissions
- Flexible API: Easy to integrate with your authentication system
- Caching: Efficient permission checking with caching
- TypeScript Support: Full TypeScript support with generics
Basic Usage
Setting Up Permissions
import { permissions } from '@repo/crud';
import { engine } from '@repo/crud';
import { providers } from '@repo/data';
// Define permissions
const userPermissions = permissions.definePermissions({
users: {
read: true, // Everyone can read users
create: (user) => user.role === 'admin', // Only admins can create users
update: (user, resource) => user.id === resource.id || user.role === 'admin', // Users can update themselves, admins can update anyone
delete: (user) => user.role === 'admin' // Only admins can delete users
},
posts: {
read: true, // Everyone can read posts
create: (user) => !!user, // Any authenticated user can create posts
update: (user, resource) => user.id === resource.userId || user.role === 'admin', // Users can update their own posts, admins can update any
delete: (user, resource) => user.id === resource.userId || user.role === 'admin' // Users can delete their own posts, admins can delete any
}
});
// Create a permissions provider
const permissionsProvider = permissions.createPermissionsProvider({
getPermissions: async (user) => {
return userPermissions;
}
});
// Create a data provider
const dataProvider = providers.supabase.createProvider({
url: process.env.SUPABASE_URL,
key: process.env.SUPABASE_KEY
});
// Create a CRUD engine with permissions
const crudEngine = engine.createCrudEngine({
dataProvider,
enablePermissions: true,
permissionsProvider
});
Setting the Current User
Before performing CRUD operations, you need to set the current user:
import { permissions } from '@repo/crud';
// Set the current user
permissions.setUser({
id: '123',
role: 'admin',
name: 'John Doe'
});
// Now CRUD operations will be checked against this user
const result = await crudEngine.getList({
resource: 'users',
pagination: { page: 1, perPage: 10 }
});
Integration with Authentication
You can integrate the permissions system with your authentication system:
import { permissions } from '@repo/crud';
import { useAuth } from '@repo/auth';
function PermissionsProvider({ children }) {
const { user } = useAuth();
// Set the current user whenever it changes
useEffect(() => {
if (user) {
permissions.setUser(user);
} else {
permissions.clearUser();
}
}, [user]);
return children;
}
function App() {
return (
<AuthProvider>
<PermissionsProvider>
<YourApp />
</PermissionsProvider>
</AuthProvider>
);
}
Permission Types
Boolean Permissions
The simplest form of permissions is a boolean value:
const permissions = {
users: {
read: true, // Everyone can read users
create: false, // No one can create users
update: false, // No one can update users
delete: false // No one can delete users
}
};
Function Permissions
For more complex permissions, you can use functions:
const permissions = {
users: {
read: (user) => !!user, // Only authenticated users can read users
create: (user) => user.role === 'admin', // Only admins can create users
update: (user, resource) => user.id === resource.id || user.role === 'admin', // Users can update themselves, admins can update anyone
delete: (user, resource) => user.role === 'admin' // Only admins can delete users
}
};
Data-Dependent Permissions
You can also define permissions that depend on the data being created or updated:
const permissions = {
posts: {
create: (user, data) => {
// Users can only create posts with their own userId
return data.userId === user.id;
},
update: (user, resource, data) => {
// Users can update their own posts, but can't change the userId
if (user.id === resource.userId) {
return data.userId === user.id;
}
// Admins can update any post
return user.role === 'admin';
}
}
};
Permission Providers
Creating a Permissions Provider
You can create a permissions provider to fetch permissions dynamically:
import { permissions } from '@repo/crud';
// Create a permissions provider
const permissionsProvider = permissions.createPermissionsProvider({
getPermissions: async (user) => {
// Fetch permissions from an API or database
const response = await fetch(`/api/permissions?userId=${user.id}`);
const data = await response.json();
// Transform the data into the permissions format
return {
users: {
read: data.canReadUsers,
create: data.canCreateUsers,
update: data.canUpdateUsers,
delete: data.canDeleteUsers
},
posts: {
read: data.canReadPosts,
create: data.canCreatePosts,
update: data.canUpdatePosts,
delete: data.canDeletePosts
}
};
}
});
Caching Permissions
The permissions provider supports caching to improve performance:
import { permissions } from '@repo/crud';
// Create a permissions provider with caching
const permissionsProvider = permissions.createPermissionsProvider({
getPermissions: async (user) => {
// Fetch permissions from an API or database
const response = await fetch(`/api/permissions?userId=${user.id}`);
const data = await response.json();
// Transform the data into the permissions format
return {
users: {
read: data.canReadUsers,
create: data.canCreateUsers,
update: data.canUpdateUsers,
delete: data.canDeleteUsers
},
posts: {
read: data.canReadPosts,
create: data.canCreatePosts,
update: data.canUpdatePosts,
delete: data.canDeletePosts
}
};
},
cache: {
enabled: true,
ttl: 5 * 60 * 1000 // 5 minutes
}
});
Role-Based Access Control (RBAC)
Defining Role-Based Permissions
You can define permissions based on roles:
import { permissions } from '@repo/crud';
// Define role-based permissions
const rolePermissions = {
admin: {
users: {
read: true,
create: true,
update: true,
delete: true
},
posts: {
read: true,
create: true,
update: true,
delete: true
}
},
editor: {
users: {
read: true,
create: false,
update: false,
delete: false
},
posts: {
read: true,
create: true,
update: true,
delete: true
}
},
user: {
users: {
read: true,
create: false,
update: (user, resource) => user.id === resource.id,
delete: false
},
posts: {
read: true,
create: true,
update: (user, resource) => user.id === resource.userId,
delete: (user, resource) => user.id === resource.userId
}
}
};
// Create a permissions provider
const permissionsProvider = permissions.createPermissionsProvider({
getPermissions: async (user) => {
// Get permissions for the user's role
return rolePermissions[user.role] || rolePermissions.user;
}
});
Multiple Roles
You can support users with multiple roles:
import { permissions } from '@repo/crud';
// Define role-based permissions
const rolePermissions = {
admin: {
users: {
read: true,
create: true,
update: true,
delete: true
}
},
editor: {
posts: {
read: true,
create: true,
update: true,
delete: true
}
},
user: {
users: {
read: true,
create: false,
update: (user, resource) => user.id === resource.id,
delete: false
},
posts: {
read: true,
create: true,
update: (user, resource) => user.id === resource.userId,
delete: (user, resource) => user.id === resource.userId
}
}
};
// Create a permissions provider
const permissionsProvider = permissions.createPermissionsProvider({
getPermissions: async (user) => {
// Merge permissions for all roles
const userPermissions = {};
// Start with the base user permissions
Object.assign(userPermissions, rolePermissions.user);
// Add permissions for each role
for (const role of user.roles) {
if (rolePermissions[role]) {
mergePermissions(userPermissions, rolePermissions[role]);
}
}
return userPermissions;
}
});
// Helper function to merge permissions
function mergePermissions(target, source) {
for (const resource in source) {
if (!target[resource]) {
target[resource] = {};
}
for (const operation in source[resource]) {
// If the source permission is more permissive, use it
if (typeof source[resource][operation] === 'boolean') {
if (source[resource][operation]) {
target[resource][operation] = true;
}
} else {
// For function permissions, we need to combine them
const targetPerm = target[resource][operation];
const sourcePerm = source[resource][operation];
if (typeof targetPerm === 'boolean' && targetPerm) {
// If the target is already true, keep it
} else if (typeof targetPerm === 'function' && typeof sourcePerm === 'function') {
// Combine functions with OR logic
target[resource][operation] = (...args) => targetPerm(...args) || sourcePerm(...args);
} else {
// Use the source permission
target[resource][operation] = sourcePerm;
}
}
}
}
}
Attribute-Based Access Control (ABAC)
Defining Attribute-Based Permissions
You can define permissions based on attributes of the user, resource, and environment:
import { permissions } from '@repo/crud';
// Define attribute-based permissions
const attributePermissions = {
users: {
read: true,
create: (user) => user.role === 'admin',
update: (user, resource) => {
// Users can update themselves
if (user.id === resource.id) {
return true;
}
// Managers can update users in their department
if (user.role === 'manager' && user.department === resource.department) {
return true;
}
// Admins can update anyone
return user.role === 'admin';
},
delete: (user, resource) => {
// Admins can delete anyone
if (user.role === 'admin') {
return true;
}
// Managers can delete users in their department, except other managers
if (user.role === 'manager' && user.department === resource.department) {
return resource.role !== 'manager';
}
return false;
}
}
};
// Create a permissions provider
const permissionsProvider = permissions.createPermissionsProvider({
getPermissions: async (user) => {
return attributePermissions;
}
});
Environment-Based Permissions
You can also include environment factors in your permissions:
import { permissions } from '@repo/crud';
// Define environment-based permissions
const environmentPermissions = {
users: {
read: true,
create: (user, data, env) => {
// Only allow user creation during business hours
const hour = env.currentTime.getHours();
return hour >= 9 && hour < 17;
},
update: (user, resource, data, env) => {
// Only allow user updates during business hours
const hour = env.currentTime.getHours();
return hour >= 9 && hour < 17;
},
delete: (user, resource, env) => {
// Only allow user deletion during business hours
const hour = env.currentTime.getHours();
return hour >= 9 && hour < 17;
}
}
};
// Create a permissions provider
const permissionsProvider = permissions.createPermissionsProvider({
getPermissions: async (user) => {
return environmentPermissions;
},
getEnvironment: () => {
return {
currentTime: new Date()
};
}
});
UI Integration
Conditional Rendering
You can use the permissions system to conditionally render UI elements:
import { permissions } from '@repo/crud';
import { ui } from '@repo/crud';
function UserList() {
const canCreateUser = permissions.check('users', 'create');
return (
<ui.ResourceList
resource="users"
columns={[
{ field: 'id', headerName: 'ID' },
{ field: 'name', headerName: 'Name' },
{ field: 'email', headerName: 'Email' },
{ field: 'role', headerName: 'Role' }
]}
actions={[
...(permissions.check('users', 'update') ? ['edit'] : []),
...(permissions.check('users', 'delete') ? ['delete'] : [])
]}
toolbar={
canCreateUser && (
<button onClick={() => console.log('Create user')}>
Create User
</button>
)
}
/>
);
}
usePermissions Hook
The permissions package provides a hook for checking permissions in components:
import { permissions } from '@repo/crud';
function UserActions({ user }) {
const { check } = permissions.usePermissions();
const canUpdateUser = check('users', 'update', user);
const canDeleteUser = check('users', 'delete', user);
return (
<div>
{canUpdateUser && (
<button onClick={() => console.log('Update user')}>
Edit
</button>
)}
{canDeleteUser && (
<button onClick={() => console.log('Delete user')}>
Delete
</button>
)}
</div>
);
}
Advanced Usage
Custom Permission Checks
You can perform custom permission checks:
import { permissions } from '@repo/crud';
// Check if the current user can perform an operation
const canReadUsers = permissions.check('users', 'read');
const canCreateUser = permissions.check('users', 'create');
const canUpdateUser = permissions.check('users', 'update', { id: '123', name: 'John' });
const canDeleteUser = permissions.check('users', 'delete', { id: '123', name: 'John' });
// Check with custom user
const canAdminReadUsers = permissions.checkWithUser(
{ id: 'admin', role: 'admin' },
'users',
'read'
);
// Check with custom data
const canCreateUserWithRole = permissions.check(
'users',
'create',
null,
{ name: 'John', role: 'admin' }
);
Permission Middleware
You can create middleware for your API routes to check permissions:
import { permissions } from '@repo/crud';
// Express middleware
function checkPermission(resource, operation) {
return (req, res, next) => {
// Set the current user from the request
permissions.setUser(req.user);
// Check if the user has permission
const hasPermission = permissions.check(
resource,
operation,
req.params.id ? { id: req.params.id } : null,
req.body
);
if (hasPermission) {
next();
} else {
res.status(403).json({ error: 'Permission denied' });
}
};
}
// Usage
app.get('/api/users', checkPermission('users', 'read'), (req, res) => {
// Handle request
});
app.post('/api/users', checkPermission('users', 'create'), (req, res) => {
// Handle request
});
app.put('/api/users/:id', checkPermission('users', 'update'), (req, res) => {
// Handle request
});
app.delete('/api/users/:id', checkPermission('users', 'delete'), (req, res) => {
// Handle request
});
Permission Inheritance
You can implement permission inheritance for resources:
import { permissions } from '@repo/crud';
// Define permissions with inheritance
const resourcePermissions = {
// Base permissions for all resources
'*': {
read: (user) => !!user, // Any authenticated user can read
create: (user) => user.role === 'admin', // Only admins can create
update: (user) => user.role === 'admin', // Only admins can update
delete: (user) => user.role === 'admin' // Only admins can delete
},
// Override permissions for specific resources
posts: {
create: (user) => !!user, // Any authenticated user can create posts
update: (user, resource) => user.id === resource.userId || user.role === 'admin', // Users can update their own posts
delete: (user, resource) => user.id === resource.userId || user.role === 'admin' // Users can delete their own posts
}
};
// Create a permissions provider with inheritance
const permissionsProvider = permissions.createPermissionsProvider({
getPermissions: async (user) => {
return resourcePermissions;
},
getInheritedPermissions: (resource, operation) => {
// Check if the resource has the operation defined
if (resourcePermissions[resource] && resourcePermissions[resource][operation] !== undefined) {
return resourcePermissions[resource][operation];
}
// Fall back to the base permissions
return resourcePermissions['*'][operation];
}
});
API Reference
For a complete API reference, please refer to the TypeScript definitions in the package.
- Permissions System
- Features
- Basic Usage
- Setting Up Permissions
- Setting the Current User
- Integration with Authentication
- Permission Types
- Boolean Permissions
- Function Permissions
- Data-Dependent Permissions
- Permission Providers
- Creating a Permissions Provider
- Caching Permissions
- Role-Based Access Control (RBAC)
- Defining Role-Based Permissions
- Multiple Roles
- Attribute-Based Access Control (ABAC)
- Defining Attribute-Based Permissions
- Environment-Based Permissions
- UI Integration
- Conditional Rendering
- usePermissions Hook
- Advanced Usage
- Custom Permission Checks
- Permission Middleware
- Permission Inheritance
- API Reference