@bdcode/acl
v0.0.0-development
Published
A TypeScript-first authorization library with RBAC-first design, multi-tenant support, and optional ABAC policies
Maintainers
Readme
@bdcode/acl
A TypeScript-first authorization library with RBAC-first design, multi-tenant support, and optional ABAC policies. Built for modern applications with performance and developer experience in mind.
Features
✨ RBAC-First Authorization
- Role-Based Access Control with inheritance support
- Wildcard permissions (
inventory:*,*:read,*:*) - Direct user permissions for exceptions and overrides
- Hierarchical roles with extends capability
🏗️ Multi-Tenant Ready
- Single-tenant mode: Perfect for traditional applications
- Multi-tenant mode: Ideal for SaaS applications with tenant isolation
- Automatic tenant handling: Uses "default" tenant when none specified
- Easy migration path from single-tenant to multi-tenant
🚀 Frontend + Backend
- Server-side enforcement with database adapters
- Client-side enforcement for UI permissions (Svelte, React, Vue)
- Framework agnostic (Hono.js, Express, etc.)
- Caching support for high-performance applications
⚡ High Performance
- Adapter pattern for flexible data sources
- Cache adapter support (Redis, in-memory)
- Efficient role expansion with inheritance resolution
- Minimized database queries through intelligent caching
🔒 Type Safe & Developer Friendly
- Full TypeScript support with strict types
- Permission format validation (
resource:action) - Comprehensive error handling with meaningful messages
- Detailed enforcement results with reasoning
Installation
npm install @bdcode/acl
# or
pnpm add @bdcode/acl
# or
yarn add @bdcode/aclQuick Start
1. Basic Setup
import { ACL } from "@bdcode/acl";
// Create your data adapter (implement ACLAdapter interface)
const adapter = new MyDatabaseAdapter();
// Initialize ACL
const acl = new ACL({
adapter,
debug: true, // Enable debug logging
wildcardSupport: true, // Enable wildcard permissions
});2. Check Permissions
// Simple permission check
const allowed = await acl.can("user123", "posts", "read");
// With tenant (multi-tenant applications)
const allowed = await acl.can("user123", "posts", "read", "tenant-abc");
// Detailed enforcement result
const result = await acl.check({
userId: "user123",
resource: "posts",
action: "delete",
tenantId: "tenant-abc", // optional
});
console.log(result.allowed); // true/false
console.log(result.reason); // "RBAC: Allowed by permissions: posts:delete"
console.log(result.matchedRoles); // ["Editor", "Manager"]
console.log(result.matchedPermissions); // ["posts:delete"]3. Frontend Client Enforcer
import { ACLClient } from "@bdcode/acl";
// Get ACL data from your API endpoint
const aclData = await fetch("/api/user/acl").then((r) => r.json());
// Create client enforcer (synchronous checks!)
const enforcer = new ACLClient(aclData);
// Check permissions in your UI
if (enforcer.can("posts", "delete")) {
// Show delete button
}
if (enforcer.hasRole("Manager")) {
// Show management features
}
// Check roles and permissions
console.log(enforcer.getRoles()); // ["Editor", "Manager"]
console.log(enforcer.getPermissions()); // ["posts:read", "posts:write", ...]Core Concepts
Roles and Inheritance
// Roles can inherit from other roles
const viewerRole = {
id: "viewer",
name: "Viewer",
permissions: [
{ resource: "posts", action: "read", effect: "allow" },
{ resource: "posts", action: "list", effect: "allow" },
],
};
const editorRole = {
id: "editor",
name: "Editor",
extendsRoleId: "viewer", // Inherits all viewer permissions
permissions: [
{ resource: "posts", action: "create", effect: "allow" },
{ resource: "posts", action: "update", effect: "allow" },
],
};
const adminRole = {
id: "admin",
name: "Administrator",
extendsRoleId: "editor", // Inherits all editor + viewer permissions
permissions: [
{ resource: "posts", action: "delete", effect: "allow" },
{ resource: "users", action: "*", effect: "allow" }, // Wildcard
],
};Wildcard Permissions
// Wildcard examples
"posts:*"; // All actions on posts
"*:read"; // Read action on all resources
"*:*"; // All actions on all resources (super admin)
// The library automatically matches:
acl.can("user", "posts", "delete"); // matches "posts:*"
acl.can("user", "comments", "read"); // matches "*:read"Multi-Tenant Support
// Single-tenant usage (automatic)
const canRead = await acl.can("user123", "posts", "read");
// Multi-tenant usage (explicit)
const canRead = await acl.can("user123", "posts", "read", "company-a");
// The library handles tenant isolation automaticallyData Adapter Interface
Implement the ACLAdapter interface to connect to your data source:
import { ACLAdapter } from "@bdcode/acl";
class DrizzleACLAdapter implements ACLAdapter {
async getUser(userId: string, tenantId?: string) {
// Return user with roles
return {
id: userId,
tenantId,
roles: ["editor", "viewer"],
};
}
async getUserRoles(userId: string, tenantId?: string) {
// Return expanded roles with permissions
return roles;
}
async getRole(roleId: string, tenantId?: string) {
// Return role with permissions
return role;
}
async getRoleHierarchy(roleId: string, tenantId?: string) {
// Return role inheritance chain
return hierarchy;
}
async getUserDirectPermissions(userId: string, tenantId?: string) {
// Return direct user permissions (overrides)
return permissions;
}
async getTenant(tenantId: string) {
// Optional: Return tenant information
return tenant;
}
// ... other required methods
}Cache Adapter (Optional)
Add caching for better performance:
import { CacheAdapter } from "@bdcode/acl";
class RedisACLCache implements CacheAdapter {
async get<T>(key: string): Promise<T | null> {
const value = await redis.get(key);
return value ? JSON.parse(value) : null;
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
const serialized = JSON.stringify(value);
if (ttl) {
await redis.setex(key, ttl, serialized);
} else {
await redis.set(key, serialized);
}
}
async del(key: string): Promise<void> {
await redis.del(key);
}
}
// Use with ACL
const acl = new ACL({
adapter: new DrizzleACLAdapter(),
cache: new RedisACLCache(),
cacheTTL: 300, // 5 minutes
});Configuration Options
interface ACLConfig {
adapter: ACLAdapter; // Required: Your data adapter
cache?: CacheAdapter; // Optional: Caching layer
cacheTTL?: number; // Cache TTL in seconds (default: 300)
enablePolicies?: boolean; // Enable ABAC policies (future)
wildcardSupport?: boolean; // Enable wildcard permissions (default: true)
debug?: boolean; // Enable debug logging (default: false)
defaultTenantId?: string; // Default tenant ID (default: "default")
}Framework Integration Examples
Hono.js Middleware
import { authorize } from "./middleware/acl";
app.use("/posts/*", authMiddleware); // Ensure user is authenticated
app.get("/posts", authorize("posts", "list"), async (c) => {
// User is authorized to list posts
return c.json({ posts: [] });
});Express.js Middleware
import { ACL } from "@bdcode/acl";
const acl = new ACL({ adapter: new MyAdapter() });
const authorize = (resource: string, action: string) => {
return async (req, res, next) => {
const allowed = await acl.can(
req.user.id,
resource,
action,
req.user.tenantId,
);
if (!allowed) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
};
app.get("/posts", authorize("posts", "list"), (req, res) => {
res.json({ posts: [] });
});React Hook Example
import { useACL } from "./hooks/useACL";
function PostList() {
const { can, hasRole } = useACL();
return (
<div>
{can("posts", "create") && (
<button>Create Post</button>
)}
{hasRole("Manager") && (
<AdminPanel />
)}
</div>
);
}Sveltekit (as client app)
// src/routes/+page.svelte
<script lang="ts">
import { onMount } from "svelte";
import { ACLClient } from "@bdcode/acl";
let enforcer: ACLClient;
onMount(async () => {
const aclData = await fetch("/api/user/acl").then((r) => r.json());
enforcer = new ACLClient(aclData);
});
</script>
{#if enforcer?.can("posts", "create")}
<button>Create Post</button>
{/if}
{#if enforcer?.hasRole("Manager")}
<AdminPanel />
{/if}
API Reference
ACL Class
can(userId, resource, action, tenantId?)- Quick permission checkcheck(context)- Detailed enforcement with reasoninggetUserACLData(userId, tenantId?)- Get user data for client enforcerclearUserCache(userId, tenantId?)- Clear cached user data
ACLClient Class
can(resource, action)- Check permission (synchronous)cannot(resource, action)- Inverse permission checkhasRole(role)- Check if user has rolegetRoles()- Get all user rolesgetPermissions()- Get all user permissionsgetTenantId()- Get current tenant ID
Error Handling
The library provides specific error types for different scenarios:
import {
ACLError,
UserNotFoundError,
TenantNotFoundError,
UnauthorizedError,
RoleNotFoundError,
} from "@bdcode/acl";
try {
await acl.check({ userId: "user123", resource: "posts", action: "delete" });
} catch (error) {
if (error instanceof UserNotFoundError) {
console.error("User not found:", error.message);
} else if (error instanceof UnauthorizedError) {
console.error("Access denied:", error.message);
}
}Performance Tips
- Use caching: Implement a cache adapter for frequently accessed permissions
- Preload user data: Call
getUserACLData()once and useACLClientfor multiple checks - Optimize queries: Implement efficient database queries in your adapter
- Batch operations: Group permission checks when possible
Debugging
Enable debug mode to see detailed logging:
const acl = new ACL({
adapter: myAdapter,
debug: true, // Enable debug logging
});
// Output example:
// [ACL] Enforcement result for user123:posts:delete = true
// [ACL] Cache hit for acl:user123:default:posts:delete
// [ACL] Matched roles: ["Editor", "Manager"]Migration Guide
From v0.x to v1.x
The library is currently in development. Breaking changes will be documented here as we approach v1.0.
TypeScript Support
This library is written in TypeScript and provides full type safety:
import type {
ACLConfig,
EnforcementContext,
EnforcementResult,
Permission,
Role,
User,
} from "@bdcode/acl";
// All types are fully typed and provide excellent IntelliSenseContributing
This library is part of a larger authorization framework. See the main repository for contribution guidelines.
License
MIT © CodeContinent
@bdcode/acl - Authorization that scales with your application 🚀
