@devtigers/simple-authz
v0.1.0
Published
A clean, dependency-free authorization library for TypeScript/JavaScript applications
Maintainers
Readme
Simple Authorization Library
A clean, dependency-free authorization library that makes it easy to check if users can access resources based on their roles and permissions.
Why This Library?
- Zero Dependencies: No external dependencies like CASL
- Simple API: Easy to understand and use
- Flexible: Supports simple checks AND complex logical operators (AND, OR, NOT)
- Extensible: Built-in providers for memory, file, and database sources
- TypeScript: Full TypeScript support
- Modern: Promise-based async API with sync fallback
Quick Start
import { authorize, MemoryPermissionProvider } from '@devtigers/casl-authz';
// 1. Set up your role-permission mapping
const provider = new MemoryPermissionProvider({
'admin': ['read:all', 'write:all', 'delete:all'],
'editor': ['read:posts', 'write:posts', 'edit:posts'],
'viewer': ['read:posts', 'read:comments']
});
// 2. Define your user
const user = {
id: 'user1',
roles: ['editor']
};
// 3. Check permissions
const result = await authorize(user, {
permission: 'write:posts'
}, provider);
if (result.allowed) {
console.log('Access granted!');
} else {
console.log('Access denied:', result.reason);
}API Reference
Core Functions
authorize(user, request, provider)
The main authorization function.
const result = await authorize(user, permissionRequest, provider);Parameters:
user: User object withid,roles, and optionalattributesrequest: Permission request object (see below)provider: Permission provider that can resolve permissions for roles
Returns: EvaluationResult with allowed, reason, and other details
authorizeSync(userPermissions, request)
Synchronous version when you already have the permissions loaded.
const permissions = await getUserPermissions(user, provider);
const result = authorizeSync(permissions, request);Permission Request Types
Simple Permission Check
{
permission: 'write:posts'
}ALL OF (AND Logic)
{
allOf: ['read:posts', 'write:posts'] // Must have ALL permissions
}ANY OF (OR Logic)
{
anyOf: ['write:posts', 'edit:posts'] // Must have AT LEAST ONE permission
}NONE OF (NOT Logic)
{
noneOf: ['delete:all', 'admin:panel'] // Must have NONE of these permissions
}Complex Combinations
You can combine these with resource context:
{
allOf: ['write:posts', 'publish:posts'],
noneOf: ['posts:locked'],
resource: {
type: 'post',
attributes: { id: '123', authorId: 'user1' }
}
}Permission Providers
Memory Permission Provider
Great for testing or simple applications:
const provider = new MemoryPermissionProvider({
'admin': ['read:all', 'write:all', 'delete:all'],
'editor': ['read:all', 'write:posts', 'edit:posts']
});
// Dynamic updates
provider.addPermission('editor', 'publish:posts');
provider.removePermission('editor', 'delete:all');File Permission Provider
Load permissions from JSON files:
// permissions.json
{
"admin": ["read:all", "write:all", "delete:all"],
"editor": ["read:all", "write:posts", "edit:posts"],
"viewer": ["read:posts", "read:comments"]
}import { FilePermissionProvider } from '@devtigers/casl-authz';
const provider = new FilePermissionProvider('./permissions.json');Database Permission Provider
Implement for your database:
class MyDatabaseProvider extends DatabasePermissionProvider {
async getPermissionsForRole(role: string): Promise<string[]> {
// Your database query here
const result = await db.query(
'SELECT permission FROM role_permissions WHERE role = ?',
[role]
);
return result.map(row => row.permission);
}
}Real-World Examples
Express.js Middleware
import { authorize } from '@devtigers/casl-authz';
function requireAuth(permissions: PermissionRequest) {
return async (req: any, res: any, next: any) => {
const user = req.user; // Set by your auth middleware
const provider = req.app.locals.permissionProvider;
const result = await authorize(user, permissions, provider);
if (result.allowed) {
next();
} else {
res.status(403).json({
error: 'Forbidden',
reason: result.reason
});
}
};
}
// Usage
app.get('/admin/users', requireAuth({ permission: 'manage:users' }), adminHandler);
app.post('/posts', requireAuth({
allOf: ['write:posts', 'publish:posts'],
noneOf: ['posts:locked']
}), createPostHandler);React Hook
import { useState, useEffect } from 'react';
import { authorize } from '@devtigers/casl-authz';
function useCan(user, permissionRequest, provider) {
const [can, setCan] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
authorize(user, permissionRequest, provider)
.then(result => {
setCan(result.allowed);
setLoading(false);
});
}, [user, permissionRequest, provider]);
return { can, loading };
}
// Usage
function EditButton({ user, provider }) {
const { can, loading } = useCan(user, { permission: 'write:posts' }, provider);
if (loading) return <button disabled>Loading...</button>;
if (!can) return null;
return <button>Edit Post</button>;
}Complex Business Logic
// User can edit post if they're an author OR editor, but not if post is locked
const canEditPost = await authorize(user, {
anyOf: ['edit:own:posts', 'edit:all:posts'],
noneOf: ['posts:locked'],
resource: {
type: 'post',
attributes: { id: postId, authorId: user.id }
}
});
// Admin can manage everything except when system is in maintenance mode
const canManageSystem = await authorize(adminUser, {
allOf: ['manage:system'],
noneOf: ['system:maintenance'],
context: { maintenanceMode: false }
});Performance Considerations
- Cache Frequently Used Permissions: Use the sync version with cached permissions for high-performance scenarios
- Lazy Loading: Load permissions only when needed
- Batch Requests: Use
getPermissionsForRoles()instead of multiplegetPermissionsForRole()calls - Database Indexing: Ensure proper indexing on role_permission tables
License
MIT License - feel free to use in commercial or open-source projects.
