Permissions System
Learn about the role-based access control (RBAC) and permission management system.
Overview
The permissions system implements a flexible role-based access control (RBAC) mechanism combined with subscription tier features. It handles:
- Role-based permissions
- Resource-level access control
- Subscription tier features
- Multi-tenant access management
Permission Structure
Resources
Resources represent entities that can be protected:
type Resource =
| 'post' // Blog posts
| 'comment' // Post comments
| 'like' // Post likes
| 'user' // User accounts
| 'apiKey' // API authentication keys
| 'invite' // User invitations
| 'tenant' // Multi-tenant spaces
| 'organization' // Organization settings
| 'billing' // Billing information
| 'plan' // Subscription plans
| 'subscriber' // Newsletter subscribers
| 'bookmark' // Saved posts
| 'settings' // Application settings
| '*'; // Wildcard for all resources
Actions
Actions define operations that can be performed on resources:
type Action =
| 'create' // Create new resource
| 'read' // View resource
| 'update' // Modify existing resource
| 'delete' // Remove resource
| 'publish' // Make resource public
| 'unpublish' // Make resource private
| 'like' // Like/unlike resource
| 'bookmark' // Bookmark/unbookmark resource
| 'comment' // Comment on resource
| 'activate' // Activate/deactivate resource
| 'invite' // Invite users
| '*'; // Wildcard for all actions
Roles and Permissions
The system defines several roles with different permission levels:
Owner
- Has complete access to all resources and actions
- Permission:
'*'
(full access)
Admin
[
'post:*', // Full control over posts
'comment:*', // Full control over comments
'user:*', // Full control over users
'apiKey:*', // Full control over API keys
'invite:*', // Full control over invitations
'tenant:*', // Full control over tenants
'billing:*', // Full control over billing
'subscriber:*', // Full control over subscribers
];
Collaborator
[
'post:*', // Full control over posts
'comment:*', // Full control over comments
'like:*', // Full control over likes
'bookmark:*', // Full control over bookmarks
];
User
[
'post:*', // Full control over posts
'comment:*', // Full control over comments
'like:*', // Full control over likes
'bookmark:*', // Full control over bookmarks
];
Subscription Tiers
The system includes subscription-based feature limits:
type TierFeature = `max_tenants:${number}` | 'unlimited_tenants';
const tierFeatures = {
STARTER: [
'max_tenants:1', // Limited to 1 tenant
],
PRO: [
'max_tenants:5', // Limited to 5 tenants
],
ENTERPRISE: [
'unlimited_tenants', // Unlimited tenants
],
};
Usage Examples
Server-Side Permission Checks
import { permissionService } from '@/lib/permissions';
// Check if user can create an API key
const canCreateApiKey = await permissionService.can({
user: currentUser,
resource: 'apiKey',
action: 'create',
});
// Check if user can access premium feature
const canAccessPremium = await permissionService.can({
user: currentUser,
resource: 'feature',
action: 'access',
requiredPlan: Tier.PRO,
});
// Check multiple permissions at once
const canPerformActions = await permissionService.checkMultiple([
{
user: currentUser,
resource: 'post',
action: 'create',
},
{
user: currentUser,
resource: 'comment',
action: 'delete',
},
]);
Client-Side Permission Hooks
The usePermissions
hook provides easy access to permission checks in React components:
import { usePermissions } from '@/hooks/use-permissions';
function PostActions({ post }) {
const { can } = usePermissions();
// Basic permission check
const canEdit = await can({
resource: 'post',
action: 'update'
});
if (!canEdit) return null;
return <EditButton post={post} />;
}
function PremiumFeature() {
const { can } = usePermissions();
// Check premium feature access
const canAccess = await can({
resource: 'feature',
action: 'access',
requiredPlan: Tier.PRO
});
if (!canAccess) {
return <UpgradePrompt />;
}
return <PremiumContent />;
}
With Loading States
function ProtectedButton({ onClick }) {
const { can } = usePermissions();
const [loading, setLoading] = useState(false);
const [allowed, setAllowed] = useState(false);
useEffect(() => {
const checkPermission = async () => {
setLoading(true);
const hasPermission = await can({
resource: 'feature',
action: 'access'
});
setAllowed(hasPermission);
setLoading(false);
};
checkPermission();
}, [can]);
if (loading) return <Spinner />;
if (!allowed) return null;
return <Button onClick={onClick}>Protected Action</Button>;
}
Best Practices
- Always Check Permissions: Validate permissions before performing protected actions:
// In server actions
const canPerformAction = await permissionService.can({
user,
resource,
action,
});
if (!canPerformAction) {
throw new Error('Unauthorized');
}
- Use Type-Safe Permission Checks: Leverage TypeScript for type safety:
import type { Action, Resource } from '@/config/permissions.config';
function checkPermission(resource: Resource, action: Action) {
// TypeScript ensures valid resource and action values
}
- Handle Loading States: Always account for the asynchronous nature of permission checks:
function ProtectedComponent() {
const [isAllowed, setIsAllowed] = useState<boolean | null>(null);
// ... handle loading state
}
- Group Related Checks: Use
checkMultiple
for related permissions:
const [canEditAndDelete] = await checkMultiple([
{ resource: 'post', action: 'update' },
{ resource: 'post', action: 'delete' },
]);
Remember that permission checks are asynchronous operations. Always await the results and handle loading states appropriately in your UI components.