npm package: @bdcode/acl
Description: A TypeScript-first authorization library with RBAC-first design, multi-tenant support, and optional ABAC policies
Author: CodeContinent
Latest version: 0.0.0-development

# @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.

[![npm version](https://badge.fury.io/js/%40bdcode%2Facl.svg)](https://badge.fury.io/js/%40bdcode%2Facl)
[![Release](https://github.com/codecontinent/acl/actions/workflows/release.yml/badge.svg?event=release)](https://github.com/codecontinent/acl/actions/workflows/release.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-100%25-blue)](https://www.typescriptlang.org/)

## 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

```bash
npm install @bdcode/acl
# or
pnpm add @bdcode/acl
# or
yarn add @bdcode/acl
```

## Quick Start

### 1. Basic Setup

```typescript
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

```typescript
// 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

```typescript
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

```typescript
// 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

```typescript
// 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

```typescript
// 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 automatically
```

## Data Adapter Interface

Implement the `ACLAdapter` interface to connect to your data source:

```typescript
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:

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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)

```svelte
// 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 check
- `check(context)` - Detailed enforcement with reasoning
- `getUserACLData(userId, tenantId?)` - Get user data for client enforcer
- `clearUserCache(userId, tenantId?)` - Clear cached user data

### ACLClient Class

- `can(resource, action)` - Check permission (synchronous)
- `cannot(resource, action)` - Inverse permission check
- `hasRole(role)` - Check if user has role
- `getRoles()` - Get all user roles
- `getPermissions()` - Get all user permissions
- `getTenantId()` - Get current tenant ID

## Error Handling

The library provides specific error types for different scenarios:

```typescript
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

1. **Use caching**: Implement a cache adapter for frequently accessed permissions
2. **Preload user data**: Call `getUserACLData()` once and use `ACLClient` for multiple checks
3. **Optimize queries**: Implement efficient database queries in your adapter
4. **Batch operations**: Group permission checks when possible

## Debugging

Enable debug mode to see detailed logging:

```typescript
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:

```typescript
import type {
  ACLConfig,
  EnforcementContext,
  EnforcementResult,
  Permission,
  Role,
  User,
} from "@bdcode/acl";

// All types are fully typed and provide excellent IntelliSense
```

## Contributing

This library is part of a larger authorization framework. See the [main repository](https://github.com/codecontinent/acl) for contribution guidelines.

## License

MIT © [CodeContinent](https://github.com/codecontinent)

---

**@bdcode/acl** - Authorization that scales with your application 🚀