# Fire Shield RBAC — Compact Reference
> Version 3.1.1 | Headings and code examples only.
> Full docs: https://fire-shield.dev/llms-full.txt
============================================================
## Guide
============================================================
### What is Fire Shield?
> Source: https://fire-shield.dev/guide/what-is-fire-shield
# What is Fire Shield?
## Overview
## Key Features
### Type-Safe
```typescript
// Full type inference and safety
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*'])
rbac.hasPermission(user, 'posts:write') // Type-checked
```
### Zero Dependencies
### Framework-Agnostic
### Blazing Fast
### Flexible Permission Patterns
```typescript
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.hasPermission(admin, 'posts:write') // ✅
rbac.hasPermission(admin, 'posts:delete') // ✅
rbac.hasPermission(admin, 'users:create') // ✅
```
### Role Hierarchy
```typescript
rbac.setRoleHierarchy({
admin: ['editor', 'viewer'],
editor: ['viewer']
})
// Admin automatically inherits editor and viewer permissions
```
### Audit Logging
```typescript
const rbac = new RBAC({
auditLogger: new BufferedAuditLogger(async (logs) => {
await saveToDatabase(logs)
})
})
```
## Why Fire Shield?
### Compared to Other Libraries
### Use Cases
## Architecture
```
@fire-shield/core # Core RBAC engine
├── @fire-shield/vue # Vue.js adapter
├── @fire-shield/react # React adapter
├── @fire-shield/next # Next.js adapter
└── ... # Other framework adapters
```
## Getting Started
## Community & Support
### Getting Started
> Source: https://fire-shield.dev/guide/getting-started
# Getting Started
## Installation
```bash [npm]
npm install @fire-shield/core
```
```bash [yarn]
yarn add @fire-shield/core
```
```bash [pnpm]
pnpm add @fire-shield/core
```
## Your First RBAC Setup
### 1. Create an RBAC Instance
```typescript
import { RBAC } from '@fire-shield/core'
const rbac = new RBAC()
```
### 2. Define Roles and Permissions
```typescript
// Create roles with permissions
rbac.createRole('admin', [
'posts:*', // All post operations
'users:*', // All user operations
'settings:*' // All settings operations
])
rbac.createRole('editor', [
'posts:read',
'posts:write',
'posts:publish'
])
rbac.createRole('viewer', [
'posts:read'
])
```
### 3. Check Permissions
```typescript
const user = {
id: 'user-123',
roles: ['editor']
}
// Check if user has permission
if (rbac.hasPermission(user, 'posts:write')) {
// Allow user to write posts
console.log('✅ User can write posts')
} else {
console.log('❌ Access denied')
}
// Check multiple permissions
const canEdit = rbac.hasPermission(user, 'posts:write') // ✅ true
const canDelete = rbac.hasPermission(user, 'posts:delete') // ❌ false
const canRead = rbac.hasPermission(user, 'posts:read') // ✅ true
```
## Role Hierarchy
```typescript
// Admin inherits all editor and viewer permissions
rbac.setRoleHierarchy({
admin: ['editor', 'viewer'],
editor: ['viewer']
})
// Now admins automatically have editor and viewer permissions
const admin = { id: 'admin-1', roles: ['admin'] }
rbac.hasPermission(admin, 'posts:read') // ✅ true (inherited from viewer)
rbac.hasPermission(admin, 'posts:write') // ✅ true (inherited from editor)
rbac.hasPermission(admin, 'users:delete') // ✅ true (admin's own permission)
```
## Wildcard Permissions
```typescript
rbac.createRole('admin', [
'posts:*', // All post operations
'users:*', // All user operations
'admin:*' // All admin operations
])
const admin = { id: 'admin-1', roles: ['admin'] }
// All these checks return true
rbac.hasPermission(admin, 'posts:read')
rbac.hasPermission(admin, 'posts:write')
rbac.hasPermission(admin, 'posts:delete')
rbac.hasPermission(admin, 'users:create')
rbac.hasPermission(admin, 'admin:settings:write')
```
## Framework Integration
## Next Steps
### Installation
> Source: https://fire-shield.dev/guide/installation
# Installation
## Core Package
```bash [npm]
npm install @fire-shield/core
```
```bash [yarn]
yarn add @fire-shield/core
```
```bash [pnpm]
pnpm add @fire-shield/core
```
## Framework Adapters
### Vue.js
```bash
npm install @fire-shield/vue @fire-shield/core
```
### React
```bash
npm install @fire-shield/react @fire-shield/core
```
### Next.js
```bash
npm install @fire-shield/next @fire-shield/core
```
### Nuxt
```bash
npm install @fire-shield/nuxt @fire-shield/core
```
### Angular
```bash
npm install @fire-shield/angular @fire-shield/core
```
### Svelte
```bash
npm install @fire-shield/svelte @fire-shield/core
```
### Express
```bash
npm install @fire-shield/express @fire-shield/core
```
### Fastify
```bash
npm install @fire-shield/fastify @fire-shield/core
```
### Hono
```bash
npm install @fire-shield/hono @fire-shield/core
```
## Requirements
## CDN Usage
```html
```
## Next Steps
### Permissions
> Source: https://fire-shield.dev/guide/permissions
# Permissions
## What are Permissions?
```typescript
'posts:read' // Read posts
'posts:write' // Write/create posts
'posts:delete' // Delete posts
'users:manage' // Manage users
```
## Permission Format
### Basic Format
```
resource:action
```
### Nested Resources
```
admin:users:read
admin:users:write
admin:users:delete
admin:settings:write
```
### Wildcard Permissions
```typescript
'posts:*' // All post actions
'admin:*' // All admin actions
'*:read' // Read all resources
'*' // All permissions (superuser)
```
## Checking Permissions
### Basic Check
```typescript
import { RBAC } from '@fire-shield/core'
const rbac = new RBAC()
rbac.createRole('editor', ['posts:read', 'posts:write'])
const user = { id: '1', roles: ['editor'] }
// Check single permission
if (rbac.hasPermission(user, 'posts:write')) {
console.log('User can write posts')
}
// Returns boolean
const canDelete = rbac.hasPermission(user, 'posts:delete') // false
const canRead = rbac.hasPermission(user, 'posts:read') // true
```
### Multiple Permissions
```typescript
const permissions = ['posts:read', 'posts:write', 'posts:publish']
const hasAll = permissions.every(permission =>
rbac.hasPermission(user, permission)
)
```
```typescript
const hasAny = permissions.some(permission =>
rbac.hasPermission(user, permission)
)
```
## Permission Naming Conventions
### Recommended Patterns
```typescript
'posts:create'
'posts:read'
'posts:update'
'posts:delete'
```
```typescript
'posts:publish'
'posts:archive'
'posts:restore'
'comments:approve'
'comments:moderate'
```
```typescript
'users:manage'
'roles:assign'
'settings:configure'
'system:admin'
```
### Best Practices
```typescript
// ✅ Good
'posts:publish'
'posts:archive'
// ❌ Avoid
'posts:action1'
'posts:action2'
```
```typescript
// ✅ Good - consistent pattern
'posts:read'
'posts:write'
'posts:delete'
// ❌ Avoid - inconsistent
'posts:read'
'posts:create'
'posts:remove'
```
```typescript
// ✅ Good - clear hierarchy
'admin:users:read'
'admin:users:write'
'admin:settings:read'
// ❌ Avoid - confusing hierarchy
'users:admin:read'
'settings:read:admin'
```
## Permission Matching
```typescript
rbac.createRole('editor', ['posts:write'])
const user = { id: '1', roles: ['editor'] }
// Exact match - works
rbac.hasPermission(user, 'posts:write') // ✅ true
// Different action - doesn't match
rbac.hasPermission(user, 'posts:read') // ❌ false
// Different resource - doesn't match
rbac.hasPermission(user, 'comments:write') // ❌ false
```
### Wildcard Matching
```typescript
rbac.createRole('admin', ['posts:*'])
const admin = { id: '1', roles: ['admin'] }
// All of these match
rbac.hasPermission(admin, 'posts:read') // ✅ true
rbac.hasPermission(admin, 'posts:write') // ✅ true
rbac.hasPermission(admin, 'posts:delete') // ✅ true
rbac.hasPermission(admin, 'posts:anything') // ✅ true
```
## Dynamic Permissions
```typescript
const rbac = new RBAC()
// Start with basic role
rbac.createRole('user', ['posts:read'])
// Add more permissions later
rbac.grant('user', ['comments:read', 'comments:write'])
// Remove permissions
rbac.revoke('user', ['comments:write'])
```
## Permission Objects
```typescript
interface Permission {
resource: string
action: string
conditions?: {
ownerId?: string
status?: string
// Custom conditions
}
}
```
## Common Patterns
### Content Management
```typescript
rbac.createRole('author', [
'posts:create',
'posts:read',
'posts:update:own', // Can only update own posts
])
rbac.createRole('editor', [
'posts:create',
'posts:read',
'posts:update',
'posts:publish',
])
rbac.createRole('admin', [
'posts:*',
'users:*',
'settings:*',
])
```
### Multi-tenant Applications
```typescript
rbac.createRole('tenant-admin', [
'tenant:settings:write',
'tenant:users:manage',
'tenant:billing:view',
])
rbac.createRole('tenant-member', [
'tenant:data:read',
'tenant:data:write',
])
```
### API Access
```typescript
rbac.createRole('api-consumer', [
'api:read',
'api:ratelimit:standard',
])
rbac.createRole('api-premium', [
'api:read',
'api:write',
'api:ratelimit:premium',
])
```
## Next Steps
### Roles
> Source: https://fire-shield.dev/guide/roles
# Roles
## What are Roles?
```typescript
// Without roles - tedious
user.permissions = ['posts:read', 'posts:write', 'comments:read', 'comments:write']
// With roles - clean
user.roles = ['editor']
rbac.createRole('editor', ['posts:read', 'posts:write', 'comments:read', 'comments:write'])
```
## Creating Roles
### Basic Role Creation
```typescript
import { RBAC } from '@fire-shield/core'
const rbac = new RBAC()
// Create role with permissions
rbac.createRole('viewer', [
'posts:read',
'comments:read'
])
rbac.createRole('editor', [
'posts:read',
'posts:write',
'posts:publish',
'comments:read',
'comments:write'
])
rbac.createRole('admin', [
'posts:*',
'comments:*',
'users:*',
'settings:*'
])
```
### Role with Wildcards
```typescript
// All post permissions
rbac.createRole('post-admin', ['posts:*'])
// All permissions (superuser)
rbac.createRole('superuser', ['*'])
```
## Assigning Roles to Users
```typescript
const user = {
id: 'user-123',
roles: ['editor']
}
// Check permissions
rbac.hasPermission(user, 'posts:write') // ✅ true
rbac.hasPermission(user, 'users:delete') // ❌ false
```
### Multiple Roles
```typescript
const user = {
id: 'user-123',
roles: ['editor', 'moderator']
}
// Has permissions from both roles
rbac.createRole('editor', ['posts:write'])
rbac.createRole('moderator', ['comments:moderate'])
rbac.hasPermission(user, 'posts:write') // ✅ true
rbac.hasPermission(user, 'comments:moderate') // ✅ true
```
## Modifying Roles
### Adding Permissions
```typescript
// Grant additional permissions to a role
rbac.grant('editor', ['posts:delete'])
// Now editors can delete posts
rbac.hasPermission({ roles: ['editor'] }, 'posts:delete') // ✅ true
```
### Removing Permissions
```typescript
// Revoke permissions from a role
rbac.revoke('editor', ['posts:delete'])
// Editors can no longer delete posts
rbac.hasPermission({ roles: ['editor'] }, 'posts:delete') // ❌ false
```
### Deleting Roles
```typescript
// Remove a role completely
rbac.deleteRole('editor')
```
## Role Hierarchy
```typescript
rbac.setRoleHierarchy({
admin: ['editor', 'moderator'],
editor: ['viewer'],
moderator: ['viewer']
})
// Admin inherits all permissions from editor, moderator, and viewer
const admin = { id: '1', roles: ['admin'] }
rbac.hasPermission(admin, 'posts:read') // ✅ true (from viewer)
rbac.hasPermission(admin, 'posts:write') // ✅ true (from editor)
```
## Common Role Patterns
### Content Management System
```typescript
// Viewer - can only read
rbac.createRole('viewer', [
'posts:read',
'comments:read'
])
// Author - can create and edit own content
rbac.createRole('author', [
'posts:read',
'posts:create',
'posts:update:own',
'comments:read',
'comments:create'
])
// Editor - can edit all content
rbac.createRole('editor', [
'posts:read',
'posts:create',
'posts:update',
'posts:publish',
'comments:read',
'comments:write',
'comments:moderate'
])
// Admin - full control
rbac.createRole('admin', [
'posts:*',
'comments:*',
'users:*',
'settings:*'
])
```
### SaaS Application
```typescript
// Free tier
rbac.createRole('free', [
'projects:read',
'projects:create:limited',
'api:read:ratelimited'
])
// Pro tier
rbac.createRole('pro', [
'projects:*',
'teams:read',
'api:read',
'api:write:limited'
])
// Enterprise tier
rbac.createRole('enterprise', [
'projects:*',
'teams:*',
'api:*',
'billing:manage',
'support:priority'
])
```
### Organization Structure
```typescript
// Team member
rbac.createRole('member', [
'team:read',
'projects:read',
'tasks:read',
'tasks:write:assigned'
])
// Team lead
rbac.createRole('lead', [
'team:read',
'projects:*',
'tasks:*',
'reports:read'
])
// Department manager
rbac.createRole('manager', [
'department:read',
'teams:*',
'projects:*',
'budgets:read',
'reports:*'
])
```
## Dynamic Role Assignment
```typescript
const user = {
id: 'user-123',
roles: ['viewer']
}
// Promote user
function promoteUser(user, newRole) {
if (!user.roles.includes(newRole)) {
user.roles.push(newRole)
}
}
promoteUser(user, 'editor')
// user.roles = ['viewer', 'editor']
// Demote user
function demoteUser(user, role) {
user.roles = user.roles.filter(r => r !== role)
}
demoteUser(user, 'viewer')
// user.roles = ['editor']
```
## Role Checking
```typescript
const user = { id: '1', roles: ['editor', 'moderator'] }
// Check single role
function hasRole(user, role) {
return user.roles.includes(role)
}
hasRole(user, 'editor') // ✅ true
hasRole(user, 'admin') // ❌ false
// Check any of multiple roles
function hasAnyRole(user, roles) {
return roles.some(role => user.roles.includes(role))
}
hasAnyRole(user, ['admin', 'editor']) // ✅ true
// Check all roles
function hasAllRoles(user, roles) {
return roles.every(role => user.roles.includes(role))
}
hasAllRoles(user, ['editor', 'moderator']) // ✅ true
hasAllRoles(user, ['editor', 'admin']) // ❌ false
```
## Best Practices
### 1. Use Descriptive Names
```typescript
// ✅ Good - clear purpose
rbac.createRole('content-editor', [...])
rbac.createRole('billing-admin', [...])
// ❌ Avoid - unclear
rbac.createRole('role1', [...])
rbac.createRole('special-user', [...])
```
### 2. Keep Roles Focused
```typescript
// ✅ Good - focused roles
rbac.createRole('post-editor', ['posts:*'])
rbac.createRole('user-manager', ['users:*'])
// ❌ Avoid - too broad
rbac.createRole('everything', ['*'])
```
### 3. Use Role Hierarchy
```typescript
// ✅ Good - clear hierarchy
rbac.createRole('viewer', ['read:*'])
rbac.createRole('editor', ['read:*', 'write:*'])
rbac.createRole('admin', ['*'])
rbac.setRoleHierarchy({
admin: ['editor'],
editor: ['viewer']
})
// ❌ Avoid - duplicate permissions
rbac.createRole('viewer', ['read:*'])
rbac.createRole('editor', ['read:*', 'write:*']) // Duplicates viewer permissions
```
### 4. Plan for Growth
```typescript
// ✅ Good - extensible
rbac.createRole('basic-user', ['app:read'])
rbac.createRole('power-user', ['app:read', 'app:advanced'])
rbac.createRole('admin', ['app:*'])
// Easy to add new tiers later
rbac.createRole('premium-user', ['app:read', 'app:advanced', 'app:premium'])
```
## TypeScript Support
```typescript
// Define role types
type Role = 'admin' | 'editor' | 'viewer'
interface User {
id: string
roles: Role[]
}
const user: User = {
id: '123',
roles: ['editor'] // ✅ Type-safe
// roles: ['invalid'] // ❌ Type error
}
```
## Next Steps
### Role Hierarchy
> Source: https://fire-shield.dev/guide/role-hierarchy
# Role Hierarchy
## What is Role Hierarchy?
## Key Concepts
## Setting Up Hierarchy
### Basic Setup
```typescript
import { RBAC } from '@fire-shield/core';
const rbac = new RBAC();
// Create roles
rbac.createRole('employee', ['document:read', 'document:create']);
rbac.createRole('manager', ['document:*', 'team:view']);
rbac.createRole('admin', ['*']);
// Set hierarchy levels
const hierarchy = rbac.getRoleHierarchy();
hierarchy.setRoleLevel('employee', 1);
hierarchy.setRoleLevel('manager', 5);
hierarchy.setRoleLevel('admin', 10);
```
### With RBAC Builder
```typescript
import { RBACBuilder } from '@fire-shield/core';
const rbac = new RBACBuilder()
.addRole('employee', ['document:read'], { level: 1 })
.addRole('manager', ['document:*'], { level: 5 })
.addRole('admin', ['*'], { level: 10 })
.build();
```
## Checking Hierarchy
### canActAs
```typescript
// Can admin act as manager?
rbac.canActAsRole('admin', 'manager'); // true (10 >= 5)
// Can manager act as admin?
rbac.canActAsRole('manager', 'admin'); // false (5 < 10)
// Can manager act as employee?
rbac.canActAsRole('manager', 'employee'); // true (5 >= 1)
```
### Direct Hierarchy Access
```typescript
const hierarchy = rbac.getRoleHierarchy();
// Check if role1 can act as role2
hierarchy.canActAs('admin', 'manager'); // true
// Check if role1 has higher level than role2
hierarchy.hasHigherLevel('admin', 'employee'); // true
// Get role level
const adminLevel = hierarchy.getRoleLevel('admin'); // 10
```
## Real-World Examples
### Corporate Hierarchy
```typescript
const rbac = new RBACBuilder()
// C-Level
.addRole('ceo', ['*'], {
level: 100,
description: 'Chief Executive Officer'
})
.addRole('cto', ['technology:*', 'team:*'], {
level: 90,
description: 'Chief Technology Officer'
})
// Management
.addRole('director', ['department:*', 'team:manage'], {
level: 50,
description: 'Department Director'
})
.addRole('manager', ['team:*', 'project:*'], {
level: 30,
description: 'Team Manager'
})
// Individual Contributors
.addRole('senior-engineer', ['project:*', 'code:*'], {
level: 20,
description: 'Senior Engineer'
})
.addRole('engineer', ['code:read', 'code:write'], {
level: 10,
description: 'Engineer'
})
.addRole('intern', ['code:read'], {
level: 1,
description: 'Intern'
})
.build();
// CEO can act as anyone
rbac.canActAsRole('ceo', 'intern'); // true
// Manager can act as engineers
rbac.canActAsRole('manager', 'engineer'); // true
// Engineer cannot act as manager
rbac.canActAsRole('engineer', 'manager'); // false
```
### Permission Delegation
```typescript
function canDelegatePermission(
delegator: RBACUser,
delegatee: RBACUser,
permission: string
): boolean {
// Check if delegator has the permission
if (!rbac.hasPermission(delegator, permission)) {
return false;
}
// Check if delegator can act as delegatee (higher level)
const delegatorRoles = delegator.roles;
const delegateeRoles = delegatee.roles;
for (const delegatorRole of delegatorRoles) {
for (const delegateeRole of delegateeRoles) {
if (rbac.canActAsRole(delegatorRole, delegateeRole)) {
return true;
}
}
}
return false;
}
// Usage
const admin = { id: 'admin-1', roles: ['admin'] };
const manager = { id: 'mgr-1', roles: ['manager'] };
canDelegatePermission(admin, manager, 'team:manage'); // true
```
### Resource Access Control
```typescript
interface Document {
id: string;
ownerId: string;
ownerRole: string;
content: string;
}
function canAccessDocument(
user: RBACUser,
document: Document
): boolean {
// Owner can always access
if (user.id === document.ownerId) {
return true;
}
// Check if user's role is higher than document owner's role
for (const userRole of user.roles) {
const userLevel = rbac.getRoleHierarchy().getRoleLevel(userRole);
const ownerLevel = rbac.getRoleHierarchy().getRoleLevel(document.ownerRole);
if (userLevel > ownerLevel) {
return true;
}
}
return false;
}
// Usage
const manager = { id: 'mgr-1', roles: ['manager'] };
const employeeDoc = {
id: 'doc-1',
ownerId: 'emp-1',
ownerRole: 'employee',
content: 'Employee document'
};
canAccessDocument(manager, employeeDoc); // true (manager > employee)
```
### Approval Workflows
```typescript
interface ApprovalRequest {
id: string;
requesterId: string;
requesterRole: string;
action: string;
status: 'pending' | 'approved' | 'rejected';
}
function canApproveRequest(
approver: RBACUser,
request: ApprovalRequest
): boolean {
// Must have approval permission
if (!rbac.hasPermission(approver, 'approval:grant')) {
return false;
}
// Approver must be higher level than requester
const hierarchy = rbac.getRoleHierarchy();
for (const approverRole of approver.roles) {
const approverLevel = hierarchy.getRoleLevel(approverRole);
const requesterLevel = hierarchy.getRoleLevel(request.requesterRole);
if (approverLevel > requesterLevel) {
return true;
}
}
return false;
}
// Usage
const manager = { id: 'mgr-1', roles: ['manager'] };
const employeeRequest: ApprovalRequest = {
id: 'req-1',
requesterId: 'emp-1',
requesterRole: 'employee',
action: 'vacation',
status: 'pending'
};
canApproveRequest(manager, employeeRequest); // true
```
## Advanced Patterns
### Multi-Role Users
```typescript
const user = {
id: 'user-1',
roles: ['engineer', 'team-lead'] // Multiple roles
};
// Check if user can act as any role
function canActAsAnyRole(user: RBACUser, targetRole: string): boolean {
return user.roles.some(role => rbac.canActAsRole(role, targetRole));
}
canActAsAnyRole(user, 'intern'); // true if either engineer or team-lead can
```
### Cross-Department Hierarchy
```typescript
const rbac = new RBACBuilder()
// Engineering
.addRole('engineering-director', ['engineering:*'], { level: 50 })
.addRole('engineering-manager', ['engineering:team:*'], { level: 30 })
.addRole('engineer', ['engineering:code:*'], { level: 10 })
// Sales
.addRole('sales-director', ['sales:*'], { level: 50 })
.addRole('sales-manager', ['sales:team:*'], { level: 30 })
.addRole('sales-rep', ['sales:deal:*'], { level: 10 })
// Executive (above all departments)
.addRole('ceo', ['*'], { level: 100 })
.build();
// CEO can act as any role
rbac.canActAsRole('ceo', 'engineering-director'); // true
rbac.canActAsRole('ceo', 'sales-director'); // true
// Directors cannot cross departments
rbac.canActAsRole('engineering-director', 'sales-director'); // false (same level)
```
### Dynamic Level Assignment
```typescript
function assignRoleLevel(role: string, departmentSize: number): number {
const baseLevel = {
'ceo': 100,
'director': 50,
'manager': 30,
'lead': 20,
'member': 10
};
// Adjust level based on department size
const sizeMultiplier = Math.log10(departmentSize);
return Math.floor(baseLevel[role] * (1 + sizeMultiplier * 0.1));
}
// Large department managers get slightly higher level
const largeTeamManager = assignRoleLevel('manager', 100); // ~33
const smallTeamManager = assignRoleLevel('manager', 10); // ~30
```
## Best Practices
### 1. Use Meaningful Levels
```typescript
// ✅ Good: Clear hierarchy
.addRole('ceo', ['*'], { level: 100 })
.addRole('director', ['department:*'], { level: 50 })
.addRole('manager', ['team:*'], { level: 30 })
.addRole('employee', ['task:*'], { level: 10 })
// ❌ Avoid: Random numbers
.addRole('admin', ['*'], { level: 42 })
.addRole('user', ['read'], { level: 7 })
```
### 2. Leave Room for Growth
```typescript
// ✅ Good: Spacing allows insertions
.addRole('ceo', ['*'], { level: 100 })
.addRole('vp', ['division:*'], { level: 80 })
.addRole('director', ['department:*'], { level: 60 })
.addRole('manager', ['team:*'], { level: 40 })
// Now you can add 'senior-manager' at level 50
```
### 3. Document Hierarchy
```typescript
// ✅ Good: Clear descriptions
.addRole('admin', ['*'], {
level: 100,
description: 'System administrator - can manage all resources',
metadata: {
canDelegate: true,
maxDelegationLevel: 90
}
})
```
### 4. Validate Hierarchy
```typescript
// Validate hierarchy on startup
function validateHierarchy(rbac: RBAC) {
const hierarchy = rbac.getRoleHierarchy();
const roles = hierarchy.getAllRoles();
for (const role of roles) {
const level = hierarchy.getRoleLevel(role);
console.log(`${role}: level ${level}`);
if (level === 0) {
console.warn(`⚠️ Role ${role} has no level set`);
}
}
}
```
## Common Pitfalls
### 1. Circular Dependencies
```typescript
// ❌ Avoid: Same levels can cause issues
rbac.getRoleHierarchy().setRoleLevel('admin', 10);
rbac.getRoleHierarchy().setRoleLevel('manager', 10);
// Who can act as who?
rbac.canActAsRole('admin', 'manager'); // false (10 >= 10 is false)
```
### 2. Forgetting to Set Levels
```typescript
// ❌ Avoid: Roles without levels
rbac.createRole('admin', ['*']); // No level set!
// ✅ Good: Always set levels
rbac.createRole('admin', ['*']);
rbac.getRoleHierarchy().setRoleLevel('admin', 10);
```
## Next Steps
### Wildcards
> Source: https://fire-shield.dev/guide/wildcards
# Wildcards
## What are Wildcards?
```typescript
// Without wildcards - tedious
rbac.createRole('admin', [
'posts:read',
'posts:write',
'posts:delete',
'posts:publish',
'posts:archive'
// ... many more
])
// With wildcards - clean
rbac.createRole('admin', ['posts:*'])
```
## Wildcard Patterns
### Resource Wildcard
```typescript
rbac.createRole('post-admin', ['posts:*'])
const admin = { id: '1', roles: ['post-admin'] }
// All post actions are allowed
rbac.hasPermission(admin, 'posts:read') // ✅ true
rbac.hasPermission(admin, 'posts:write') // ✅ true
rbac.hasPermission(admin, 'posts:delete') // ✅ true
rbac.hasPermission(admin, 'posts:anything') // ✅ true
```
### Action Wildcard
```typescript
rbac.createRole('reader', ['*:read'])
const reader = { id: '1', roles: ['reader'] }
// Can read all resources
rbac.hasPermission(reader, 'posts:read') // ✅ true
rbac.hasPermission(reader, 'comments:read') // ✅ true
rbac.hasPermission(reader, 'users:read') // ✅ true
// Cannot perform other actions
rbac.hasPermission(reader, 'posts:write') // ❌ false
```
### Full Wildcard
```typescript
rbac.createRole('superuser', ['*'])
const superuser = { id: '1', roles: ['superuser'] }
// Everything is allowed
rbac.hasPermission(superuser, 'posts:read') // ✅ true
rbac.hasPermission(superuser, 'posts:delete') // ✅ true
rbac.hasPermission(superuser, 'users:manage') // ✅ true
rbac.hasPermission(superuser, 'anything:goes') // ✅ true
```
### Nested Wildcards
```typescript
rbac.createRole('admin', ['admin:*'])
const admin = { id: '1', roles: ['admin'] }
// All admin actions are allowed
rbac.hasPermission(admin, 'admin:users:read') // ✅ true
rbac.hasPermission(admin, 'admin:users:write') // ✅ true
rbac.hasPermission(admin, 'admin:settings:write') // ✅ true
rbac.hasPermission(admin, 'admin:logs:view') // ✅ true
// Non-admin actions are not allowed
rbac.hasPermission(admin, 'posts:read') // ❌ false
```
## Wildcard Matching Rules
### Exact Match Priority
```typescript
rbac.createRole('editor', [
'posts:*',
'!posts:delete' // Explicitly deny delete
])
// Note: Explicit denials are handled at application level
// Fire Shield returns true for posts:delete due to posts:*
// You need to check denials separately
```
### Partial Wildcards
```typescript
rbac.createRole('role', ['admin:users:*'])
const user = { id: '1', roles: ['role'] }
// Matches within admin:users namespace
rbac.hasPermission(user, 'admin:users:read') // ✅ true
rbac.hasPermission(user, 'admin:users:write') // ✅ true
// Doesn't match other namespaces
rbac.hasPermission(user, 'admin:settings:read') // ❌ false
rbac.hasPermission(user, 'users:read') // ❌ false
```
## Common Patterns
### Admin Roles
```typescript
// Full system admin
rbac.createRole('system-admin', ['*'])
// Module-specific admins
rbac.createRole('user-admin', ['users:*'])
rbac.createRole('content-admin', ['posts:*', 'comments:*'])
rbac.createRole('billing-admin', ['billing:*', 'invoices:*'])
```
### Read-Only Access
```typescript
// Read everything
rbac.createRole('auditor', ['*:read'])
// Read specific modules
rbac.createRole('reporter', [
'analytics:*:read',
'reports:*:read',
'logs:read'
])
```
### Tiered Access
```typescript
// Basic tier - limited access
rbac.createRole('basic', [
'app:read',
'profile:write:own'
])
// Pro tier - more features
rbac.createRole('pro', [
'app:read',
'app:advanced:*',
'profile:*',
'export:*'
])
// Enterprise tier - full access
rbac.createRole('enterprise', ['*'])
```
## Combining Wildcards and Specific Permissions
```typescript
rbac.createRole('moderator', [
'posts:read',
'posts:write',
'comments:*', // All comment actions
'users:read'
])
const moderator = { id: '1', roles: ['moderator'] }
// Specific post permissions
rbac.hasPermission(moderator, 'posts:read') // ✅ true
rbac.hasPermission(moderator, 'posts:write') // ✅ true
rbac.hasPermission(moderator, 'posts:delete') // ❌ false
// All comment permissions
rbac.hasPermission(moderator, 'comments:read') // ✅ true
rbac.hasPermission(moderator, 'comments:moderate') // ✅ true
rbac.hasPermission(moderator, 'comments:delete') // ✅ true
```
## Performance Considerations
```typescript
// ✅ Efficient - single wildcard
rbac.createRole('admin', ['posts:*'])
// ✅ Also efficient - few specific permissions
rbac.createRole('editor', [
'posts:read',
'posts:write',
'posts:publish'
])
// ⚠️ Consider wildcard - many similar permissions
rbac.createRole('admin', [
'posts:read',
'posts:write',
'posts:delete',
'posts:publish',
'posts:archive',
'posts:restore',
// ... 20 more post permissions
])
// Better: rbac.createRole('admin', ['posts:*'])
```
## Best Practices
### 1. Use Wildcards for Admin Roles
```typescript
// ✅ Good - clear admin intent
rbac.createRole('admin', ['posts:*'])
// ❌ Avoid - tedious and error-prone
rbac.createRole('admin', [
'posts:read',
'posts:write',
// ... might miss some permissions
])
```
### 2. Be Specific When Needed
```typescript
// ✅ Good - specific access
rbac.createRole('editor', [
'posts:read',
'posts:write',
'posts:publish'
// Deliberately excludes posts:delete
])
// ❌ Avoid - too broad
rbac.createRole('editor', ['posts:*'])
// Includes delete, which editors shouldn't have
```
### 3. Document Wildcard Usage
```typescript
// ✅ Good - documented
rbac.createRole('content-moderator', [
'posts:*', // Full post management
'comments:*' // Full comment management
// Note: Cannot manage users
])
// ❌ Avoid - unclear scope
rbac.createRole('moderator', ['*'])
// What does this role actually do?
```
### 4. Combine with Role Hierarchy
```typescript
// ✅ Good - clear progression
rbac.createRole('viewer', ['posts:read'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
rbac.createRole('admin', ['posts:*'])
rbac.setRoleHierarchy({
admin: ['editor'],
editor: ['viewer']
})
```
## Advanced Patterns
### Namespace-Based Access
```typescript
// Organize permissions by namespace
rbac.createRole('api-full', ['api:*'])
rbac.createRole('api-read', ['api:*:read'])
const apiUser = { id: '1', roles: ['api-full'] }
rbac.hasPermission(apiUser, 'api:v1:read') // ✅ true
rbac.hasPermission(apiUser, 'api:v2:write') // ✅ true
```
### Multi-Tenant Wildcards
```typescript
// Tenant-scoped permissions
rbac.createRole('tenant-admin', [
'tenant:*', // All tenant operations
'tenant:users:*', // All tenant user operations
'tenant:billing:read' // Can only read billing
])
```
### Feature Flags
```typescript
// Feature-based permissions
rbac.createRole('beta-tester', [
'app:*',
'features:beta:*', // All beta features
'features:experimental:read' // Can view experimental features
])
```
## Testing Wildcards
```typescript
describe('Admin permissions', () => {
it('should allow all post actions', () => {
rbac.createRole('admin', ['posts:*'])
const admin = { id: '1', roles: ['admin'] }
expect(rbac.hasPermission(admin, 'posts:read')).toBe(true)
expect(rbac.hasPermission(admin, 'posts:write')).toBe(true)
expect(rbac.hasPermission(admin, 'posts:delete')).toBe(true)
})
it('should not allow non-post actions', () => {
rbac.createRole('admin', ['posts:*'])
const admin = { id: '1', roles: ['admin'] }
expect(rbac.hasPermission(admin, 'users:delete')).toBe(false)
})
})
```
## Next Steps
### Deny Permissions
> Source: https://fire-shield.dev/guide/deny-permissions
# Deny Permissions
## Overview
## Why Use Deny Permissions?
### Use Cases
## Basic Usage
### Deny a Permission
```typescript
import { RBAC } from '@fire-shield/core';
const rbac = new RBAC();
rbac.createRole('editor', ['post:read', 'post:write', 'post:delete']);
const user = { id: 'user-123', roles: ['editor'] };
// User has all editor permissions initially
rbac.hasPermission(user, 'post:delete'); // ✅ true
// Deny delete permission for this specific user
rbac.denyPermission('user-123', 'post:delete');
// Now they can't delete, even though their role allows it
rbac.hasPermission(user, 'post:delete'); // ❌ false
rbac.hasPermission(user, 'post:write'); // ✅ true (still works)
```
### Allow Permission (Remove Deny)
```typescript
// Restore the denied permission
rbac.allowPermission('user-123', 'post:delete');
// Permission restored to role default
rbac.hasPermission(user, 'post:delete'); // ✅ true
```
### Check If Permission Is Denied
```typescript
// Check if a specific permission is denied for a user
const isDenied = rbac.isDenied('user-123', 'post:delete');
if (isDenied) {
console.log('This permission is explicitly denied');
}
```
### Get All Denied Permissions
```typescript
// Get list of all denied permissions for a user
const deniedPermissions = rbac.getDeniedPermissions('user-123');
console.log('Denied permissions:', deniedPermissions);
// ['post:delete', 'user:write']
```
## Wildcard Denies
### Deny All Permissions in a Resource
```typescript
const rbac = new RBAC({ enableWildcards: true });
rbac.createRole('admin', ['user:*', 'post:*']);
const user = { id: 'admin-123', roles: ['admin'] };
// Deny all user-related permissions
rbac.denyPermission('admin-123', 'user:*');
// Admin can no longer access user operations
rbac.hasPermission(user, 'user:read'); // ❌ false
rbac.hasPermission(user, 'user:write'); // ❌ false
rbac.hasPermission(user, 'user:delete'); // ❌ false
// But can still access posts
rbac.hasPermission(user, 'post:read'); // ✅ true
rbac.hasPermission(user, 'post:write'); // ✅ true
```
### Deny Specific Action Across Resources
```typescript
// Deny all delete operations
rbac.denyPermission('user-123', '*:delete');
rbac.hasPermission(user, 'post:delete'); // ❌ false
rbac.hasPermission(user, 'user:delete'); // ❌ false
rbac.hasPermission(user, 'comment:delete'); // ❌ false
// Other actions still work
rbac.hasPermission(user, 'post:write'); // ✅ true
rbac.hasPermission(user, 'user:read'); // ✅ true
```
## Priority Rules
### Deny Always Wins
```typescript
const rbac = new RBAC();
rbac.createRole('admin', ['*']); // Admin has ALL permissions
const user = {
id: 'admin-123',
roles: ['admin'],
permissions: ['super:admin'] // Even with direct permission
};
// Deny one permission
rbac.denyPermission('admin-123', 'delete:database');
// Deny overrides role AND direct permissions
rbac.hasPermission(user, 'delete:database'); // ❌ false
rbac.hasPermission(user, 'create:user'); // ✅ true
```
### Multiple Denies
```typescript
// Deny multiple permissions
rbac.denyPermission('user-123', 'post:delete');
rbac.denyPermission('user-123', 'user:delete');
rbac.denyPermission('user-123', 'comment:delete');
// Check all denies
const denied = rbac.getDeniedPermissions('user-123');
// ['post:delete', 'user:delete', 'comment:delete']
```
## Advanced Patterns
### Temporary Suspension
```typescript
function suspendUserWrites(userId: string) {
// Deny all write operations
rbac.denyPermission(userId, '*:write');
rbac.denyPermission(userId, '*:delete');
rbac.denyPermission(userId, '*:create');
console.log(`User ${userId} suspended from write operations`);
}
function restoreUserAccess(userId: string) {
// Remove all denies
const denied = rbac.getDeniedPermissions(userId);
for (const permission of denied) {
rbac.allowPermission(userId, permission);
}
console.log(`User ${userId} access restored`);
}
// Use it
suspendUserWrites('suspicious-user-123');
// Later, after investigation
restoreUserAccess('suspicious-user-123');
```
### Feature Flags
```typescript
class FeatureFlags {
constructor(private rbac: RBAC) {}
disableFeature(userId: string, feature: string) {
this.rbac.denyPermission(userId, `feature:${feature}`);
}
enableFeature(userId: string, feature: string) {
this.rbac.allowPermission(userId, `feature:${feature}`);
}
isFeatureEnabled(user: RBACUser, feature: string): boolean {
return this.rbac.hasPermission(user, `feature:${feature}`);
}
}
// Usage
const flags = new FeatureFlags(rbac);
// Beta features are denied by default for some users
flags.disableFeature('user-123', 'ai-assistant');
flags.disableFeature('user-123', 'advanced-analytics');
// Enable for specific users
flags.enableFeature('beta-tester-456', 'ai-assistant');
```
### Compliance Enforcement
```typescript
class ComplianceManager {
private rbac: RBAC;
enforceSODPolicy(userId: string) {
// Separation of Duties: Users can't approve their own requests
if (this.userCreatesRequests(userId)) {
// Deny approval permission
this.rbac.denyPermission(userId, 'request:approve');
}
}
enforceDataResidency(userId: string, region: string) {
// GDPR: EU users can only access EU data
if (region === 'EU') {
this.rbac.denyPermission(userId, 'data:us:*');
this.rbac.denyPermission(userId, 'data:asia:*');
}
}
}
```
### Emergency Lockdown
```typescript
function emergencyLockdown(userId: string, reason: string) {
// Deny ALL permissions
rbac.denyPermission(userId, '*');
// Log the event
console.log(`EMERGENCY: User ${userId} locked down. Reason: ${reason}`);
// Send alert
sendSecurityAlert({
type: 'LOCKDOWN',
userId,
reason,
timestamp: new Date()
});
}
// Usage
emergencyLockdown('compromised-user-789', 'Suspected account breach');
```
## Framework Integration
### React
```tsx
import { useRBAC, useDenyPermission, useIsDenied } from '@fire-shield/react';
function UserManagement() {
const { can } = useRBAC();
const denyPermission = useDenyPermission();
const isDeniedDelete = useIsDenied('user:delete');
const handleSuspendUser = (userId: string) => {
// Deny critical permissions
denyPermission('user:delete');
denyPermission('user:write');
toast.success('User suspended');
};
return (
{isDeniedDelete && (
Delete permission is currently denied
)}
{can('user:suspend') && (
handleSuspendUser('user-123')}>
Suspend User
)}
);
}
```
### Vue
```vue
Delete permission is currently denied
Suspend User
```
### Express
```typescript
import { createExpressRBAC } from '@fire-shield/express';
const rbacMiddleware = createExpressRBAC(rbac, {
getUser: (req) => req.user
});
// Endpoint to deny permission
app.post('/admin/deny-permission', async (req, res) => {
const { userId, permission } = req.body;
// Verify admin
if (!rbac.hasPermission(req.user, 'admin:permissions')) {
return res.status(403).json({ error: 'Forbidden' });
}
// Deny permission
rbac.denyPermission(userId, permission);
res.json({ success: true });
});
// Check denied permissions
app.get('/users/:id/denied-permissions', async (req, res) => {
const denied = rbac.getDeniedPermissions(req.params.id);
res.json({ deniedPermissions: denied });
});
```
## API Reference
### `denyPermission(userId, permission)`
```typescript
rbac.denyPermission(
userId: string, // User ID
permission: string // Permission to deny (supports wildcards)
): void
```
```typescript
rbac.denyPermission('user-123', 'post:delete');
rbac.denyPermission('admin-456', 'user:*');
```
### `allowPermission(userId, permission)`
```typescript
rbac.allowPermission(
userId: string, // User ID
permission: string // Permission to allow back
): void
```
```typescript
rbac.allowPermission('user-123', 'post:delete');
```
### `isDenied(userId, permission)`
```typescript
rbac.isDenied(
userId: string, // User ID
permission: string // Permission to check
): boolean
```
```typescript
if (rbac.isDenied('user-123', 'post:delete')) {
console.log('This permission is denied');
}
```
### `getDeniedPermissions(userId)`
```typescript
rbac.getDeniedPermissions(
userId: string // User ID
): string[]
```
```typescript
const denied = rbac.getDeniedPermissions('user-123');
// ['post:delete', 'user:write']
```
## Best Practices
### 1. Document Denies
```typescript
interface PermissionDeny {
userId: string;
permission: string;
reason: string;
deniedAt: Date;
deniedBy: string;
}
class AuditedRBAC {
private denies: PermissionDeny[] = [];
denyWithReason(
userId: string,
permission: string,
reason: string,
deniedBy: string
) {
this.rbac.denyPermission(userId, permission);
this.denies.push({
userId,
permission,
reason,
deniedAt: new Date(),
deniedBy
});
}
getDenyReason(userId: string, permission: string): string | null {
const deny = this.denies.find(d =>
d.userId === userId && d.permission === permission
);
return deny?.reason || null;
}
}
```
### 2. Time-Limited Denies
```typescript
class TemporaryDenies {
private rbac: RBAC;
private expirations = new Map();
denyUntil(userId: string, permission: string, until: Date) {
this.rbac.denyPermission(userId, permission);
const key = `${userId}:${permission}`;
this.expirations.set(key, until);
// Schedule removal
const timeout = until.getTime() - Date.now();
setTimeout(() => {
this.rbac.allowPermission(userId, permission);
this.expirations.delete(key);
}, timeout);
}
}
// Usage
const temp = new TemporaryDenies(rbac);
// Deny for 24 hours
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
temp.denyUntil('user-123', 'post:delete', tomorrow);
```
### 3. Notify Users
```typescript
function denyWithNotification(
userId: string,
permission: string,
reason: string
) {
rbac.denyPermission(userId, permission);
// Send notification
sendEmail({
to: getUserEmail(userId),
subject: 'Permission Revoked',
body: `Your ${permission} permission has been revoked. Reason: ${reason}`
});
}
```
### 4. Bulk Operations
```typescript
function suspendAllWrites(userId: string) {
const writePermissions = ['*:write', '*:delete', '*:create', '*:update'];
for (const permission of writePermissions) {
rbac.denyPermission(userId, permission);
}
}
function removeAllDenies(userId: string) {
const denied = rbac.getDeniedPermissions(userId);
for (const permission of denied) {
rbac.allowPermission(userId, permission);
}
}
```
### 5. Integration with Audit Logs
```typescript
const rbac = new RBAC({
auditLogger: {
log: (event) => {
if (event.action === 'deny_permission') {
console.log('Permission denied:', event);
database.logSecurityEvent(event);
}
}
}
});
```
## Common Patterns
### Role Downgrade Without Role Change
```typescript
// Temporarily reduce admin to read-only without changing their role
function downgradeToReadOnly(userId: string) {
rbac.denyPermission(userId, '*:write');
rbac.denyPermission(userId, '*:delete');
rbac.denyPermission(userId, '*:create');
rbac.denyPermission(userId, '*:update');
// They keep their role but can only read
}
```
### Progressive Access
```typescript
// Start with minimal access, gradually enable features
function onboardNewUser(userId: string) {
// Deny advanced features initially
rbac.denyPermission(userId, 'feature:analytics');
rbac.denyPermission(userId, 'feature:export');
rbac.denyPermission(userId, 'feature:api');
// After 30 days, enable analytics
setTimeout(() => {
rbac.allowPermission(userId, 'feature:analytics');
}, 30 * 24 * 60 * 60 * 1000);
}
```
### Context-Based Restrictions
```typescript
// Deny permissions based on context
function enforceWorkHours(userId: string) {
const hour = new Date().getHours();
// Deny sensitive operations outside work hours (9 AM - 5 PM)
if (hour < 9 || hour >= 17) {
rbac.denyPermission(userId, 'financial:*');
rbac.denyPermission(userId, 'user:delete');
} else {
rbac.allowPermission(userId, 'financial:*');
rbac.allowPermission(userId, 'user:delete');
}
}
```
## Comparison with Other Approaches
### vs. Removing from Role
### vs. Creating New Role
## Next Steps
### Audit Logging
> Source: https://fire-shield.dev/guide/audit-logging
# Audit Logging
## Why Audit Logging?
## Quick Start
```typescript
import { RBAC, ConsoleAuditLogger } from '@fire-shield/core';
const rbac = new RBAC({
auditLogger: new ConsoleAuditLogger()
});
// All permission checks are automatically logged
rbac.hasPermission(user, 'post:delete');
// [AUDIT] ✓ ALLOWED: User user-123 - post:delete
```
## Built-in Loggers
### ConsoleAuditLogger
```typescript
import { ConsoleAuditLogger } from '@fire-shield/core';
const logger = new ConsoleAuditLogger();
const rbac = new RBAC({ auditLogger: logger });
```
```
[AUDIT 2025-01-15T10:30:00.000Z] ✓ ALLOWED: User user-123 - post:read
[AUDIT 2025-01-15T10:30:01.000Z] ✗ DENIED: User user-123 - admin:delete
Reason: User lacks permission: admin:delete
```
### BufferedAuditLogger
```typescript
import { BufferedAuditLogger } from '@fire-shield/core';
const logger = new BufferedAuditLogger(
async (events) => {
await database.auditLogs.insertMany(events);
},
{
maxBufferSize: 100, // Flush after 100 events
flushIntervalMs: 5000 // Or every 5 seconds
}
);
const rbac = new RBAC({ auditLogger: logger });
```
### MultiAuditLogger
```typescript
import { MultiAuditLogger, ConsoleAuditLogger, BufferedAuditLogger } from '@fire-shield/core';
const logger = new MultiAuditLogger([
new ConsoleAuditLogger(), // Development
new BufferedAuditLogger(async (events) => {
await database.save(events); // Production
})
]);
const rbac = new RBAC({ auditLogger: logger });
```
## Custom Loggers
### Basic Custom Logger
```typescript
import type { AuditLogger, AuditEvent } from '@fire-shield/core';
class FileAuditLogger implements AuditLogger {
async log(event: AuditEvent): Promise {
const logEntry = `${new Date(event.timestamp).toISOString()} - ${event.userId} - ${event.permission} - ${event.allowed ? 'ALLOW' : 'DENY'}\n`;
await fs.appendFile('audit.log', logEntry);
}
}
const rbac = new RBAC({ auditLogger: new FileAuditLogger() });
```
### Database Logger
```typescript
class DatabaseLogger implements AuditLogger {
async log(event: AuditEvent): Promise {
await db.auditLogs.insert({
userId: event.userId,
permission: event.permission,
allowed: event.allowed,
reason: event.reason,
timestamp: new Date(event.timestamp),
context: event.context
});
}
}
```
### Security Monitoring Logger
```typescript
class SecurityMonitorLogger implements AuditLogger {
private failedAttempts = new Map();
private readonly threshold = 5;
log(event: AuditEvent): void {
if (!event.allowed) {
const count = (this.failedAttempts.get(event.userId) || 0) + 1;
this.failedAttempts.set(event.userId, count);
if (count >= this.threshold) {
this.alertSecurityTeam({
userId: event.userId,
attempts: count,
permission: event.permission,
timestamp: event.timestamp
});
}
} else {
// Reset on successful check
this.failedAttempts.delete(event.userId);
}
}
private alertSecurityTeam(alert: any) {
console.warn('🚨 Security Alert:', alert);
// Send to monitoring service
}
}
```
## Real-World Examples
### E-Commerce Compliance
```typescript
class ComplianceLogger implements AuditLogger {
async log(event: AuditEvent): Promise {
await db.auditLogs.insert({
...event,
// Compliance metadata
retentionPeriod: 7 * 365, // 7 years
encrypted: true,
compliance: {
gdpr: true,
pciDss: event.permission.includes('payment'),
hipaa: event.permission.includes('health')
}
});
// Also send to compliance monitoring
await fetch('https://compliance.example.com/audit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event)
});
}
}
```
### Analytics Logger
```typescript
class AnalyticsLogger implements AuditLogger {
private buffer: AuditEvent[] = [];
log(event: AuditEvent): void {
this.buffer.push(event);
if (this.buffer.length >= 100) {
this.sendAnalytics();
}
}
private sendAnalytics() {
const stats = {
total: this.buffer.length,
allowed: this.buffer.filter(e => e.allowed).length,
denied: this.buffer.filter(e => !e.allowed).length,
byPermission: this.groupByPermission(),
byUser: this.groupByUser()
};
console.log('📊 Permission Analytics:', stats);
this.buffer = [];
}
private groupByPermission() {
return this.buffer.reduce((acc, event) => {
acc[event.permission] = (acc[event.permission] || 0) + 1;
return acc;
}, {} as Record);
}
private groupByUser() {
return this.buffer.reduce((acc, event) => {
acc[event.userId] = (acc[event.userId] || 0) + 1;
return acc;
}, {} as Record);
}
}
```
### Rotating Logger
```typescript
class RotatingFileLogger implements AuditLogger {
private currentDate = new Date().toISOString().split('T')[0];
private buffer: string[] = [];
async log(event: AuditEvent): Promise {
const logDate = new Date(event.timestamp).toISOString().split('T')[0];
// Rotate log file if date changed
if (logDate !== this.currentDate) {
await this.flush();
await this.rotateOldLogs();
this.currentDate = logDate;
}
const entry = JSON.stringify(event);
this.buffer.push(entry);
if (this.buffer.length >= 100) {
await this.flush();
}
}
async flush(): Promise {
if (this.buffer.length === 0) return;
const filename = `audit-${this.currentDate}.log`;
await fs.appendFile(filename, this.buffer.join('\n') + '\n');
this.buffer = [];
}
private async rotateOldLogs() {
const maxAge = 90 * 24 * 60 * 60 * 1000; // 90 days
const cutoff = Date.now() - maxAge;
const files = await fs.readdir('.');
for (const file of files) {
if (file.startsWith('audit-') && file.endsWith('.log')) {
const stats = await fs.stat(file);
if (stats.mtimeMs < cutoff) {
await fs.unlink(file);
}
}
}
}
}
```
## Querying Audit Logs
### By User
```typescript
// Find all logs for a specific user
const userLogs = await db.auditLogs.find({
userId: 'user-123'
}).sort({ timestamp: -1 }).limit(100);
// Find denied attempts for user
const deniedLogs = await db.auditLogs.find({
userId: 'user-123',
allowed: false
});
```
### By Permission
```typescript
// Find all attempts for a permission
const permissionLogs = await db.auditLogs.find({
permission: 'admin:delete'
});
// Find denied admin permission attempts
const deniedAdminLogs = await db.auditLogs.find({
permission: { $regex: '^admin:' },
allowed: false
});
```
### By Time Range
```typescript
// Last 24 hours
const recentLogs = await db.auditLogs.find({
timestamp: {
$gte: Date.now() - 24 * 60 * 60 * 1000
}
});
// Specific date range
const rangeLogs = await db.auditLogs.find({
timestamp: {
$gte: new Date('2025-01-01').getTime(),
$lte: new Date('2025-01-31').getTime()
}
});
```
### Aggregation
```typescript
// Count by permission
const permissionCounts = await db.auditLogs.aggregate([
{
$group: {
_id: '$permission',
count: { $sum: 1 },
deniedCount: {
$sum: { $cond: ['$allowed', 0, 1] }
}
}
},
{ $sort: { count: -1 } }
]);
// Failed attempts by user
const userFailures = await db.auditLogs.aggregate([
{ $match: { allowed: false } },
{
$group: {
_id: '$userId',
failures: { $sum: 1 },
permissions: { $addToSet: '$permission' }
}
},
{ $sort: { failures: -1 } }
]);
```
## Best Practices
### 1. Always Buffer in Production
```typescript
// ✅ Good: Buffered logging
const logger = new BufferedAuditLogger(
async (events) => await db.insert(events),
{ maxBufferSize: 100 }
);
// ❌ Avoid: Synchronous logging on every check
const logger = {
log: (event) => db.insert(event) // Blocks!
};
```
### 2. Include Context
```typescript
// ✅ Good: Rich context
const context = {
roles: user.roles,
ip: request.ip,
userAgent: request.headers['user-agent'],
resource: 'document',
resourceId: docId
};
rbac.authorize(user, 'document:edit', context);
// Logs will include all context
```
### 3. Rotate Old Logs
```typescript
// Schedule log rotation
setInterval(async () => {
const cutoff = Date.now() - 90 * 24 * 60 * 60 * 1000;
await db.auditLogs.deleteMany({
timestamp: { $lt: cutoff }
});
}, 24 * 60 * 60 * 1000);
```
### 4. Monitor Failed Attempts
```typescript
// Alert on suspicious activity
async function checkForSuspiciousActivity() {
const recentFailures = await db.auditLogs.countDocuments({
userId: user.id,
allowed: false,
timestamp: { $gte: Date.now() - 60 * 60 * 1000 }
});
if (recentFailures > 10) {
await alertSecurityTeam({
userId: user.id,
failures: recentFailures
});
}
}
```
### 5. Sampling for High Traffic
```typescript
class SamplingLogger implements AuditLogger {
private sampleRate = 0.1; // 10%
log(event: AuditEvent): void {
// Always log denied events
if (!event.allowed) {
this.saveLog(event);
return;
}
// Sample allowed events
if (Math.random() < this.sampleRate) {
this.saveLog(event);
}
}
private saveLog(event: AuditEvent) {
// Save to database
}
}
```
## Performance Optimization
### Async Logging
```typescript
class AsyncLogger implements AuditLogger {
async log(event: AuditEvent): Promise {
// Fire and forget
this.saveToDatabase(event).catch(err => {
console.error('Audit log error:', err);
});
}
private async saveToDatabase(event: AuditEvent) {
await db.auditLogs.insert(event);
}
}
```
### Batch Processing
```typescript
class BatchLogger implements AuditLogger {
private queue: AuditEvent[] = [];
private processing = false;
log(event: AuditEvent): void {
this.queue.push(event);
if (!this.processing && this.queue.length >= 50) {
this.processBatch();
}
}
private async processBatch() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const batch = this.queue.splice(0, 50);
try {
await db.auditLogs.insertMany(batch);
} catch (error) {
console.error('Batch insert failed:', error);
// Re-queue failed events
this.queue.unshift(...batch);
} finally {
this.processing = false;
}
}
}
```
## Compliance Requirements
### GDPR
```typescript
class GDPRCompliantLogger implements AuditLogger {
async log(event: AuditEvent): Promise {
await db.auditLogs.insert({
...event,
// GDPR metadata
dataSubject: event.userId,
processingPurpose: 'access_control',
legalBasis: 'legitimate_interest',
retentionPeriod: 90, // days
consentId: event.context?.consentId
});
}
// Implement right to erasure
async eraseUserData(userId: string) {
await db.auditLogs.updateMany(
{ userId },
{ $set: { userId: 'REDACTED', anonymized: true } }
);
}
}
```
### SOC 2
```typescript
class SOC2Logger implements AuditLogger {
async log(event: AuditEvent): Promise {
await db.auditLogs.insert({
...event,
// SOC 2 requirements
immutable: true, // Cannot be modified
signed: this.signEvent(event),
controlId: 'AC-2', // Access control
severity: event.allowed ? 'info' : 'warning'
});
}
private signEvent(event: AuditEvent): string {
// Digital signature for integrity
return crypto
.createHmac('sha256', SECRET_KEY)
.update(JSON.stringify(event))
.digest('hex');
}
}
```
## Next Steps
### Performance Optimization
> Source: https://fire-shield.dev/guide/performance
# Performance Optimization
## Performance Overview
## Bit-Based vs String-Based
### Bit-Based (Recommended)
```typescript
const rbac = new RBAC({ useBitSystem: true }); // Default
// O(1) permission check
rbac.hasPermission(user, 'post:read'); // ~0.001ms
```
### String-Based
```typescript
const rbac = new RBAC({ useBitSystem: false });
// O(n) permission check
rbac.hasPermission(user, 'post:read'); // ~0.1ms
```
## Benchmarks
### Permission Check Performance
```typescript
// Bit-based system
// 1,000,000 checks: ~100ms (0.0001ms per check)
// String-based system
// 1,000,000 checks: ~1000ms (0.001ms per check)
// 10x faster with bit-based
```
### Memory Usage
```typescript
// 10 roles, 20 permissions
// Bit-based: ~2KB
// String-based: ~5KB
// 100 roles, 200 permissions
// Bit-based: ~20KB
// String-based: ~150KB
```
## Optimization Techniques
### 1. Use Bit-Based System
```typescript
// ✅ Good: Fast bit-based checks
const rbac = new RBAC({ useBitSystem: true });
rbac.registerPermission('user:read', 1);
rbac.registerPermission('user:write', 2);
rbac.registerPermission('user:delete', 4);
// Lightning fast
rbac.hasPermission(user, 'user:read');
```
### 2. Pre-compute Permission Masks
```typescript
// ✅ Good: Pre-compute and store mask
const user = {
id: 'user-1',
roles: ['editor'],
permissionMask: rbac.createPermissionMask(['post:read', 'post:write'])
};
// Fast permission check using mask
rbac.hasPermission(user, 'post:read');
// ❌ Avoid: Computing on every request
const user = {
id: 'user-1',
roles: ['editor']
// No mask - computed every time
};
```
### 3. Cache User Permissions
```typescript
// ✅ Good: Cache permissions per session
class UserCache {
private cache = new Map();
getUser(userId: string): RBACUser | undefined {
return this.cache.get(userId);
}
setUser(userId: string, user: RBACUser) {
this.cache.set(userId, {
...user,
permissionMask: rbac.getUserPermissionMask(user)
});
}
clearUser(userId: string) {
this.cache.delete(userId);
}
}
const cache = new UserCache();
// Cache on login
cache.setUser(user.id, user);
// Fast lookups
const cachedUser = cache.getUser(user.id);
rbac.hasPermission(cachedUser, 'post:read');
```
### 4. Batch Permission Checks
```typescript
// ✅ Good: Single check for multiple permissions
const permissions = ['post:read', 'post:write', 'post:delete'];
const hasAll = rbac.hasAllPermissions(user, permissions);
// ❌ Avoid: Multiple individual checks
const canRead = rbac.hasPermission(user, 'post:read');
const canWrite = rbac.hasPermission(user, 'post:write');
const canDelete = rbac.hasPermission(user, 'post:delete');
```
### 5. Minimize Wildcard Usage
```typescript
// ✅ Good: Specific permissions for frequent checks
rbac.createRole('editor', ['post:read', 'post:write', 'post:publish']);
// ❌ Avoid: Wildcards for frequently checked permissions
rbac.createRole('editor', ['post:*']); // Slower pattern matching
```
### 6. Optimize Role Hierarchy
```typescript
// ✅ Good: Shallow hierarchy (2-3 levels)
rbac.getRoleHierarchy().setRoleLevel('admin', 10);
rbac.getRoleHierarchy().setRoleLevel('editor', 5);
rbac.getRoleHierarchy().setRoleLevel('user', 1);
// ❌ Avoid: Deep hierarchy (many levels)
// Each level adds overhead
```
## Caching Strategies
### Session-Based Caching
```typescript
class SessionCache {
private sessions = new Map;
expires: number;
}>();
set(sessionId: string, user: RBACUser, ttl: number = 3600000) {
this.sessions.set(sessionId, {
user,
permissions: new Set(rbac.getUserPermissions(user)),
expires: Date.now() + ttl
});
}
get(sessionId: string): RBACUser | null {
const session = this.sessions.get(sessionId);
if (!session) return null;
if (Date.now() > session.expires) {
this.sessions.delete(sessionId);
return null;
}
return session.user;
}
hasPermission(sessionId: string, permission: string): boolean {
const session = this.sessions.get(sessionId);
return session?.permissions.has(permission) || false;
}
}
```
### LRU Cache
```typescript
class LRUPermissionCache {
private cache: Map;
private maxSize: number;
constructor(maxSize: number = 1000) {
this.cache = new Map();
this.maxSize = maxSize;
}
check(userId: string, permission: string): boolean | undefined {
const key = `${userId}:${permission}`;
const cached = this.cache.get(key);
if (cached !== undefined) {
// Move to end (most recently used)
this.cache.delete(key);
this.cache.set(key, cached);
}
return cached;
}
set(userId: string, permission: string, allowed: boolean) {
const key = `${userId}:${permission}`;
// Remove oldest if at capacity
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, allowed);
}
}
const cache = new LRUPermissionCache(1000);
// Check cache first
const userId = 'user-1';
const permission = 'post:read';
let allowed = cache.check(userId, permission);
if (allowed === undefined) {
// Not in cache, check RBAC
allowed = rbac.hasPermission(user, permission);
cache.set(userId, permission, allowed);
}
```
## Database Optimization
### Store Permission Masks
```typescript
// Store pre-computed permission mask in database
interface UserDocument {
id: string;
email: string;
roles: string[];
permissionMask: number; // Store computed mask
updatedAt: Date;
}
// On user update, recompute mask
async function updateUserRoles(userId: string, roles: string[]) {
const mask = rbac.createPermissionMask(
roles.flatMap(role => rbac.getRolePermissions(role))
);
await db.users.updateOne(
{ id: userId },
{
$set: {
roles,
permissionMask: mask,
updatedAt: new Date()
}
}
);
}
// Fast permission checks
const user = await db.users.findOne({ id: userId });
rbac.hasPermission(user, 'post:read'); // Uses cached mask
```
### Index Audit Logs
```typescript
// Create indexes for fast queries
await db.auditLogs.createIndex({ userId: 1, timestamp: -1 });
await db.auditLogs.createIndex({ permission: 1, allowed: 1 });
await db.auditLogs.createIndex({ timestamp: 1 }, { expireAfterSeconds: 7776000 }); // 90 days
```
## Load Testing
### Basic Load Test
```typescript
import { performance } from 'perf_hooks';
function loadTest(iterations: number = 1000000) {
const rbac = new RBAC({ useBitSystem: true });
rbac.registerPermission('test:read', 1);
rbac.createRole('tester', ['test:read']);
const user = { id: 'test-1', roles: ['tester'] };
const start = performance.now();
for (let i = 0; i < iterations; i++) {
rbac.hasPermission(user, 'test:read');
}
const end = performance.now();
const duration = end - start;
console.log(`${iterations} checks in ${duration.toFixed(2)}ms`);
console.log(`Average: ${(duration / iterations).toFixed(6)}ms per check`);
}
loadTest(); // 1,000,000 checks in ~100ms
```
### Concurrent Load Test
```typescript
async function concurrentLoadTest(
users: number = 100,
checksPerUser: number = 10000
) {
const rbac = new RBAC({ useBitSystem: true });
rbac.registerPermission('test:read', 1);
rbac.createRole('tester', ['test:read']);
const start = performance.now();
const promises = Array.from({ length: users }, (_, i) => {
const user = { id: `user-${i}`, roles: ['tester'] };
return Promise.resolve().then(() => {
for (let j = 0; j < checksPerUser; j++) {
rbac.hasPermission(user, 'test:read');
}
});
});
await Promise.all(promises);
const end = performance.now();
const total = users * checksPerUser;
console.log(`${total} checks with ${users} users in ${(end - start).toFixed(2)}ms`);
}
concurrentLoadTest(); // 1,000,000 checks in ~150ms
```
## Production Recommendations
### 1. Always Use Bit-Based System
```typescript
// ✅ Production
const rbac = new RBAC({ useBitSystem: true });
```
### 2. Buffer Audit Logging
```typescript
// ✅ Production: Buffered logging
const logger = new BufferedAuditLogger(
async (events) => await db.insert(events),
{ maxBufferSize: 100, flushIntervalMs: 5000 }
);
```
### 3. Cache User Sessions
```typescript
// ✅ Production: Session cache
const cache = new SessionCache();
cache.set(sessionId, user, 3600000); // 1 hour
```
### 4. Monitor Performance
```typescript
// ✅ Production: Performance monitoring
const start = performance.now();
const result = rbac.hasPermission(user, permission);
const duration = performance.now() - start;
if (duration > 1) {
console.warn(`Slow permission check: ${duration}ms`);
}
```
### 5. Optimize Database Queries
```typescript
// ✅ Production: Indexed queries
await db.users.createIndex({ id: 1 });
await db.users.createIndex({ 'roles': 1 });
await db.auditLogs.createIndex({ userId: 1, timestamp: -1 });
```
## Common Bottlenecks
### 1. Database Round-Trips
```typescript
// ❌ Slow: Multiple queries
const user = await db.users.findOne({ id: userId });
const roles = await db.roles.find({ name: { $in: user.roles } });
// ✅ Fast: Single query with join
const user = await db.users.findOne({ id: userId })
.populate('roles');
```
### 2. Synchronous Audit Logging
```typescript
// ❌ Slow: Synchronous logging
const logger = {
log: (event) => fs.appendFileSync('audit.log', JSON.stringify(event))
};
// ✅ Fast: Async buffered logging
const logger = new BufferedAuditLogger(async (events) => {
await fs.appendFile('audit.log', events.map(e => JSON.stringify(e)).join('\n'));
});
```
### 3. Wildcard Overuse
```typescript
// ❌ Slow: Many wildcard checks
rbac.createRole('user', ['resource:*']);
rbac.hasPermission(user, 'resource:specific:action'); // Pattern matching
// ✅ Fast: Specific permissions
rbac.createRole('user', ['resource:read', 'resource:write']);
rbac.hasPermission(user, 'resource:read'); // Direct lookup
```
## Next Steps
### TypeScript Guide
> Source: https://fire-shield.dev/guide/typescript
# TypeScript Guide
## Why TypeScript?
## Basic Types
### RBACUser
```typescript
import type { RBACUser } from '@fire-shield/core';
const user: RBACUser = {
id: 'user-123',
roles: ['editor'],
permissions: ['beta:feature'], // Optional
permissionMask: 3 // Optional
};
```
### Extending RBACUser
```typescript
interface AppUser extends RBACUser {
email: string;
name: string;
department: string;
createdAt: Date;
}
const user: AppUser = {
id: 'user-123',
roles: ['editor'],
email: 'user@example.com',
name: 'John Doe',
department: 'Engineering',
createdAt: new Date()
};
// Still works with RBAC
rbac.hasPermission(user, 'post:write');
```
## Type-Safe Permissions
### Using Const Assertions
```typescript
const PERMISSIONS = {
// User permissions
USER_READ: 'user:read',
USER_WRITE: 'user:write',
USER_DELETE: 'user:delete',
// Post permissions
POST_READ: 'post:read',
POST_WRITE: 'post:write',
POST_DELETE: 'post:delete',
// Admin permissions
ADMIN_ACCESS: 'admin:access',
} as const;
// Type-safe permission type
type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS];
// Type-safe permission check
function checkPermission(user: RBACUser, permission: Permission): boolean {
return rbac.hasPermission(user, permission);
}
// ✅ Valid
checkPermission(user, PERMISSIONS.USER_READ);
// ❌ TypeScript error
checkPermission(user, 'invalid:permission');
```
### Using String Literal Types
```typescript
type Permission =
| 'user:read'
| 'user:write'
| 'user:delete'
| 'post:read'
| 'post:write'
| 'post:delete';
function hasPermission(user: RBACUser, permission: Permission): boolean {
return rbac.hasPermission(user, permission);
}
// ✅ Type-safe
hasPermission(user, 'user:read');
// ❌ TypeScript error
hasPermission(user, 'invalid:permission');
```
## Type-Safe Roles
### String Literal Types
```typescript
type Role = 'admin' | 'editor' | 'viewer';
function hasRole(user: RBACUser, role: Role): boolean {
return user.roles.includes(role);
}
// ✅ Type-safe
hasRole(user, 'admin');
// ❌ TypeScript error
hasRole(user, 'invalid-role');
```
### Role Configuration
```typescript
interface RoleConfig {
name: Role;
permissions: Permission[];
level: number;
description: string;
}
const roles: RoleConfig[] = [
{
name: 'admin',
permissions: ['user:read', 'user:write', 'post:read', 'post:write'],
level: 10,
description: 'Administrator'
},
{
name: 'editor',
permissions: ['post:read', 'post:write'],
level: 5,
description: 'Content Editor'
},
{
name: 'viewer',
permissions: ['post:read'],
level: 1,
description: 'Viewer'
}
];
```
## Generic Functions
### Type-Safe Permission Checker
```typescript
function createPermissionChecker(
rbac: RBAC,
user: RBACUser
) {
return {
can: (permission: T): boolean => {
return rbac.hasPermission(user, permission);
},
canAll: (permissions: T[]): boolean => {
return rbac.hasAllPermissions(user, permissions);
},
canAny: (permissions: T[]): boolean => {
return rbac.hasAnyPermission(user, permissions);
}
};
}
// Usage
const checker = createPermissionChecker(rbac, user);
checker.can('user:read'); // Type-safe ✅
checker.can('invalid'); // Type error ❌
```
### Type-Safe User Factory
```typescript
function createUser(
id: string,
roles: Role[],
additional?: Omit
): T {
return {
id,
roles,
...additional
} as T;
}
// Usage
interface CustomUser extends RBACUser {
email: string;
name: string;
}
const user = createUser('user-1', ['editor'], {
email: 'user@example.com',
name: 'John Doe'
});
```
## Discriminated Unions
### Authorization Results
```typescript
type AuthorizationSuccess = {
allowed: true;
user: RBACUser;
};
type AuthorizationFailure = {
allowed: false;
reason: string;
};
type AuthResult = AuthorizationSuccess | AuthorizationFailure;
function authorize(user: RBACUser, permission: Permission): AuthResult {
const result = rbac.authorize(user, permission);
if (result.allowed) {
return {
allowed: true,
user: result.user!
};
} else {
return {
allowed: false,
reason: result.reason || 'Permission denied'
};
}
}
// Type-safe usage
const result = authorize(user, 'user:read');
if (result.allowed) {
console.log(result.user); // Type: RBACUser
} else {
console.log(result.reason); // Type: string
}
```
## Type Guards
### Custom Type Guards
```typescript
function isRBACUser(value: unknown): value is RBACUser {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
typeof (value as any).id === 'string' &&
'roles' in value &&
Array.isArray((value as any).roles)
);
}
// Usage
function checkUser(maybeUser: unknown) {
if (isRBACUser(maybeUser)) {
// TypeScript knows this is an RBACUser
rbac.hasPermission(maybeUser, 'post:read');
}
}
```
### Advanced Type Guards
```typescript
function hasPermissions(
user: RBACUser,
permissions: T
): user is RBACUser & { permissions: T } {
return permissions.every(p => rbac.hasPermission(user, p));
}
// Usage
if (hasPermissions(user, ['post:read', 'post:write'] as const)) {
// TypeScript knows user has these permissions
}
```
## Utility Types
### WithMetadata
```typescript
type WithMetadata = T & {
metadata?: Record;
};
type UserWithMetadata = WithMetadata;
const user: UserWithMetadata = {
id: 'user-1',
roles: ['editor'],
metadata: {
department: 'Engineering',
hireDate: '2025-01-01'
}
};
```
### Partial Permissions
```typescript
type PartialPermissions = {
[K in Permission]?: boolean;
};
function checkPermissions(user: RBACUser): PartialPermissions {
const perms: PartialPermissions = {};
for (const perm of Object.values(PERMISSIONS)) {
perms[perm] = rbac.hasPermission(user, perm);
}
return perms;
}
// Usage
const userPermissions = checkPermissions(user);
console.log(userPermissions['user:read']); // boolean | undefined
```
## Namespaced Permissions
```typescript
namespace Permissions {
export namespace User {
export const READ = 'user:read';
export const WRITE = 'user:write';
export const DELETE = 'user:delete';
}
export namespace Post {
export const READ = 'post:read';
export const WRITE = 'post:write';
export const DELETE = 'post:delete';
}
}
// Usage
rbac.hasPermission(user, Permissions.User.READ);
rbac.hasPermission(user, Permissions.Post.WRITE);
```
## Configuration Types
### Strongly Typed Configuration
```typescript
interface RBACConfigTyped {
roles: {
name: Role;
permissions: Permission[];
level: number;
}[];
permissions: {
name: Permission;
bit: number;
}[];
}
const config: RBACConfigTyped = {
roles: [
{ name: 'admin', permissions: ['user:read', 'user:write'], level: 10 }
],
permissions: [
{ name: 'user:read', bit: 1 },
{ name: 'user:write', bit: 2 }
]
};
```
## Best Practices
### 1. Define Types Early
```typescript
// ✅ Good: Define at the start
type Permission = 'user:read' | 'user:write';
type Role = 'admin' | 'editor';
interface AppUser extends RBACUser {
email: string;
}
// Use throughout the app
```
### 2. Use Const Assertions
```typescript
// ✅ Good: Type-safe constants
const PERMISSIONS = {
USER_READ: 'user:read',
USER_WRITE: 'user:write'
} as const;
type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS];
```
### 3. Avoid Any
```typescript
// ❌ Avoid
function checkPermission(user: any, permission: any) {
return rbac.hasPermission(user, permission);
}
// ✅ Good
function checkPermission(user: RBACUser, permission: Permission) {
return rbac.hasPermission(user, permission);
}
```
### 4. Use Generics
```typescript
// ✅ Good: Generic type-safe wrapper
function withPermission(
user: T,
permission: Permission,
action: (user: T) => void
) {
if (rbac.hasPermission(user, permission)) {
action(user);
}
}
```
### 5. Strict Mode
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}
```
## Framework-Specific Types
### React
```typescript
import type { RBACUser } from '@fire-shield/react';
interface Props {
user: RBACUser;
permission: Permission;
}
const ProtectedButton: React.FC = ({ user, permission }) => {
const { can } = useRBAC();
if (!can(permission)) {
return null;
}
return Action ;
};
```
### Vue
```typescript
import type { RBACUser } from '@fire-shield/vue';
interface UserState {
user: RBACUser | null;
isAuthenticated: boolean;
}
const userState = reactive({
user: null,
isAuthenticated: false
});
```
### Express
```typescript
import type { Request, Response } from 'express';
import type { RBACUser } from '@fire-shield/core';
// Extend Express Request type
declare global {
namespace Express {
interface Request {
user?: RBACUser;
}
}
}
// Type-safe middleware
function requirePermission(permission: Permission) {
return (req: Request, res: Response, next: Function) => {
if (!req.user || !rbac.hasPermission(req.user, permission)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
```
## Testing with TypeScript
### Type-Safe Tests
```typescript
import { describe, it, expect } from 'vitest';
import type { RBACUser } from '@fire-shield/core';
describe('RBAC', () => {
const admin: RBACUser = {
id: 'admin-1',
roles: ['admin']
};
const viewer: RBACUser = {
id: 'viewer-1',
roles: ['viewer']
};
it('should allow admin to delete', () => {
expect(rbac.hasPermission(admin, 'post:delete')).toBe(true);
});
it('should deny viewer to delete', () => {
expect(rbac.hasPermission(viewer, 'post:delete')).toBe(false);
});
});
```
## Next Steps
============================================================
## API Reference
============================================================
### Core API Reference
> Source: https://fire-shield.dev/api/core
# Core API Reference
## RBAC Class
### Constructor
```typescript
new RBAC(options?: RBACOptions)
```
#### Parameters
#### Example
```typescript
import { RBAC, BufferedAuditLogger } from '@fire-shield/core'
// Basic usage
const rbac = new RBAC({
auditLogger: new BufferedAuditLogger(async (logs) => {
console.log('Audit logs:', logs)
}),
useBitSystem: true
})
// With performance features
const rbacOptimized = new RBAC({
enableCache: true,
cacheOptions: {
ttl: 60000, // 1 minute
maxSize: 10000
},
lazyRoles: true,
optimizeMemory: true
})
```
### Static Methods
#### fromJSONConfig
```typescript
static fromJSONConfig(json: string, options?: RBACOptions): RBAC
```
```typescript
const configJson = JSON.stringify({
permissions: [
{ name: 'posts:read' },
{ name: 'posts:write' }
],
roles: [
{ name: 'editor', permissions: ['posts:read', 'posts:write'] }
]
})
const rbac = RBAC.fromJSONConfig(configJson, {
enableCache: true
})
```
#### validateConfig
```typescript
static validateConfig(config: PresetConfig): void
```
```typescript
try {
RBAC.validateConfig(myConfig)
console.log('Config is valid')
} catch (error) {
console.error('Invalid config:', error.message)
}
```
### Instance Methods
#### createRole
```typescript
createRole(roleName: string, permissions: string[]): void
```
```typescript
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
```
#### deleteRole
```typescript
deleteRole(roleName: string): void
```
```typescript
rbac.deleteRole('editor')
```
#### grant
```typescript
grant(roleName: string, permissions: string[]): void
```
```typescript
rbac.grant('editor', ['posts:delete'])
```
#### revoke
```typescript
revoke(roleName: string, permissions: string[]): void
```
```typescript
rbac.revoke('editor', ['posts:delete'])
```
#### hasPermission
```typescript
hasPermission(user: RBACUser, permission: string): boolean
```
```typescript
const user = { id: '1', roles: ['editor'] }
const canWrite = rbac.hasPermission(user, 'posts:write') // true
const canDelete = rbac.hasPermission(user, 'posts:delete') // false
```
#### setRoleHierarchy
```typescript
setRoleHierarchy(hierarchy: Record): void
```
```typescript
rbac.setRoleHierarchy({
admin: ['editor', 'moderator'],
editor: ['viewer'],
moderator: ['viewer']
})
```
#### getRolePermissions
```typescript
getRolePermissions(roleName: string): string[]
```
```typescript
const permissions = rbac.getRolePermissions('editor')
// ['posts:read', 'posts:write']
```
#### getUserPermissions
```typescript
getUserPermissions(user: RBACUser): string[]
```
```typescript
const user = { id: '1', roles: ['editor', 'moderator'] }
const permissions = rbac.getUserPermissions(user)
```
#### hasAnyPermission
```typescript
hasAnyPermission(user: RBACUser, permissions: string[]): boolean
```
```typescript
const user = { id: '1', roles: ['editor'] }
const canEdit = rbac.hasAnyPermission(user, ['posts:write', 'posts:delete'])
```
#### hasAllPermissions
```typescript
hasAllPermissions(user: RBACUser, permissions: string[]): boolean
```
```typescript
const user = { id: '1', roles: ['admin'] }
const hasFullAccess = rbac.hasAllPermissions(user, ['posts:read', 'posts:write', 'posts:delete'])
```
### Deny Permissions
#### denyPermission
```typescript
denyPermission(userId: string, permission: string): void
```
```typescript
// Deny specific permission
rbac.denyPermission('user-123', 'posts:delete')
// Deny with wildcard
rbac.denyPermission('user-456', 'admin:*')
// User will now be denied even if their role grants the permission
const user = { id: 'user-123', roles: ['admin'] }
rbac.hasPermission(user, 'posts:delete') // false
```
#### allowPermission
```typescript
allowPermission(userId: string, permission: string): void
```
```typescript
// Remove the deny
rbac.allowPermission('user-123', 'posts:delete')
// User can now use the permission again
rbac.hasPermission(user, 'posts:delete') // true (if role grants it)
```
#### getDeniedPermissions
```typescript
getDeniedPermissions(userId: string): string[]
```
```typescript
const deniedPerms = rbac.getDeniedPermissions('user-123')
console.log(deniedPerms) // ['posts:delete', 'users:ban']
```
#### clearDeniedPermissions
```typescript
clearDeniedPermissions(userId: string): void
```
```typescript
rbac.clearDeniedPermissions('user-123')
// All denies removed, user permissions back to role-based
```
### Cache Management
#### invalidateUserCache
```typescript
invalidateUserCache(userId: string): void
```
```typescript
// User's roles changed, clear their cache
rbac.invalidateUserCache('user-123')
```
#### invalidatePermissionCache
```typescript
invalidatePermissionCache(permission: string): void
```
```typescript
// Permission definition changed, clear all caches for it
rbac.invalidatePermissionCache('posts:delete')
```
#### getCacheStats
```typescript
getCacheStats(): CacheStats | undefined
```
```typescript
const stats = rbac.getCacheStats()
console.log(stats)
// {
// hits: 1250,
// misses: 50,
// hitRate: 0.96,
// size: 450,
// maxSize: 10000
// }
```
### Lazy Role Evaluation
#### getEvaluatedRoles
```typescript
getEvaluatedRoles(): string[]
```
```typescript
const evaluated = rbac.getEvaluatedRoles()
console.log(evaluated) // ['admin', 'editor']
```
#### getPendingRoles
```typescript
getPendingRoles(): string[]
```
```typescript
const pending = rbac.getPendingRoles()
console.log(pending) // ['viewer', 'guest', 'moderator']
```
#### getLazyRoleStats
```typescript
getLazyRoleStats(): LazyRoleStats
```
```typescript
const stats = rbac.getLazyRoleStats()
console.log(stats)
// {
// enabled: true,
// pending: 5,
// evaluated: 2,
// total: 7
// }
```
#### isRolePending
```typescript
isRolePending(roleName: string): boolean
```
```typescript
if (rbac.isRolePending('viewer')) {
console.log('Viewer role not yet loaded')
}
```
#### evaluateAllRoles
```typescript
evaluateAllRoles(): void
```
```typescript
// Load all roles immediately
rbac.evaluateAllRoles()
```
### Memory Optimization
#### getMemoryStats
```typescript
getMemoryStats(): MemoryStats
```
```typescript
const stats = rbac.getMemoryStats()
console.log(stats)
// {
// enabled: true,
// stringPoolSize: 150,
// roleMaskCacheSize: 25,
// wildcardPatternCacheSize: 10,
// estimatedMemorySaved: 45000 // bytes
// }
```
#### compactMemory
```typescript
compactMemory(): { stringsRemoved: number; cacheEntriesRemoved: number }
```
```typescript
const result = rbac.compactMemory()
console.log(`Removed ${result.stringsRemoved} strings and ${result.cacheEntriesRemoved} cache entries`)
```
## Types
### RBACUser
```typescript
interface RBACUser {
id: string
roles: string[]
[key: string]: any // Additional user properties
}
```
```typescript
const user: RBACUser = {
id: 'user-123',
roles: ['editor', 'moderator'],
email: 'user@example.com',
name: 'John Doe'
}
```
### RBACOptions
```typescript
interface RBACOptions {
auditLogger?: AuditLogger
bitPermissions?: boolean
}
```
### AuditLogger
```typescript
interface AuditLogger {
log(event: AuditEvent): void | Promise
}
```
### AuditEvent
```typescript
interface AuditEvent {
timestamp: Date
userId: string
action: 'permission_check' | 'role_grant' | 'role_revoke'
resource: string
permission?: string
result: boolean
metadata?: Record
}
```
## Audit Logging
### BufferedAuditLogger
```typescript
new BufferedAuditLogger(
handler: (logs: AuditEvent[]) => Promise,
options?: BufferedAuditLoggerOptions
)
```
#### Parameters
#### Example
```typescript
import { BufferedAuditLogger } from '@fire-shield/core'
const auditLogger = new BufferedAuditLogger(
async (logs) => {
await saveLogsToDatabase(logs)
},
{
maxBufferSize: 50,
flushIntervalMs: 3000
}
)
const rbac = new RBAC({ auditLogger })
```
#### Methods
##### flush
```typescript
flush(): Promise
```
```typescript
await auditLogger.flush()
```
### Custom Audit Logger
```typescript
class CustomAuditLogger implements AuditLogger {
async log(event: AuditEvent) {
console.log('Audit event:', event)
// Send to logging service
await fetch('/api/audit', {
method: 'POST',
body: JSON.stringify(event)
})
}
}
const rbac = new RBAC({
auditLogger: new CustomAuditLogger()
})
```
## RBAC Builder
```typescript
import { RBACBuilder } from '@fire-shield/core'
const rbac = new RBACBuilder()
.role('admin')
.grant(['posts:*', 'users:*'])
.role('editor')
.grant(['posts:read', 'posts:write'])
.role('viewer')
.grant(['posts:read'])
.hierarchy({
admin: ['editor'],
editor: ['viewer']
})
.build()
```
### Methods
#### role
```typescript
role(name: string): RBACBuilder
```
#### grant
```typescript
grant(permissions: string[]): RBACBuilder
```
#### hierarchy
```typescript
hierarchy(hierarchy: Record): RBACBuilder
```
#### build
```typescript
build(): RBAC
```
## Utilities
### matchPermission
```typescript
import { matchPermission } from '@fire-shield/core'
matchPermission('posts:write', 'posts:*') // true
matchPermission('posts:write', 'posts:read') // false
matchPermission('admin:users:delete', 'admin:*') // true
```
### parsePermission
```typescript
import { parsePermission } from '@fire-shield/core'
const parts = parsePermission('posts:write')
// { resource: 'posts', action: 'write' }
const nested = parsePermission('admin:users:delete')
// { resource: 'admin:users', action: 'delete' }
```
## Error Handling
### RBACError
```typescript
class RBACError extends Error {
constructor(message: string, public code: string) {
super(message)
this.name = 'RBACError'
}
}
```
### Common Errors
```typescript
// Permission denied
throw new RBACError('Insufficient permissions', 'PERMISSION_DENIED')
// Role not found
throw new RBACError('Role does not exist', 'ROLE_NOT_FOUND')
// Invalid permission format
throw new RBACError('Invalid permission format', 'INVALID_PERMISSION')
```
## Performance
### Bit-Level Permissions
```typescript
const rbac = new RBAC({ useBitSystem: true }) // Default: true
// Permissions are stored as bits
// Much faster for large permission sets
rbac.createRole('admin', ['posts:*'])
const user = { id: '1', roles: ['admin'] }
rbac.hasPermission(user, 'posts:write') // Optimized bit check
```
### Permission Caching
```typescript
const rbac = new RBAC({
enableCache: true,
cacheOptions: {
ttl: 60000, // Cache for 1 minute
maxSize: 10000, // Max 10k entries
cleanupInterval: 30000 // Cleanup every 30 seconds
}
})
// First call - computes and caches
rbac.hasPermission(user, 'posts:write')
// Subsequent calls - served from cache (very fast)
rbac.hasPermission(user, 'posts:write') // < 1ms
// Monitor cache performance
const stats = rbac.getCacheStats()
console.log(`Hit rate: ${stats.hitRate * 100}%`)
```
### Lazy Role Evaluation
```typescript
const rbac = new RBAC({
lazyRoles: true,
config: largeConfig // Config with 1000+ roles
})
// Only loads roles when first accessed
const stats = rbac.getLazyRoleStats()
console.log(`Loaded: ${stats.evaluated}/${stats.total} roles`)
// Force load all roles when needed
rbac.evaluateAllRoles()
```
### Memory Optimization
```typescript
const rbac = new RBAC({
optimizeMemory: true,
config: largeConfig
})
// Monitor memory savings
const stats = rbac.getMemoryStats()
console.log(`Memory saved: ${stats.estimatedMemorySaved} bytes`)
console.log(`String pool: ${stats.stringPoolSize} unique strings`)
// Cleanup unused resources
const result = rbac.compactMemory()
console.log(`Cleaned up ${result.stringsRemoved} strings`)
```
### Performance Best Practices
```typescript
const rbac = new RBAC({
useBitSystem: true, // Fast bit-based checks
enableCache: true, // Cache permission checks
cacheOptions: {
ttl: 300000, // 5 minute cache
maxSize: 50000 // Large cache for many users
},
lazyRoles: true, // Load roles on demand
optimizeMemory: true, // Reduce memory footprint
enableWildcards: true // Support flexible permissions
})
// Clear cache when roles change
rbac.createRole('newRole', ['posts:*'])
rbac.invalidatePermissionCache('posts:write')
// Monitor performance
setInterval(() => {
const cacheStats = rbac.getCacheStats()
const memoryStats = rbac.getMemoryStats()
console.log('Cache hit rate:', cacheStats.hitRate)
console.log('Memory saved:', memoryStats.estimatedMemorySaved)
}, 60000)
```
## Next Steps
### RBAC Builder
> Source: https://fire-shield.dev/api/builder
# RBAC Builder
## Overview
## Basic Usage
```typescript
import { RBACBuilder } from '@fire-shield/core';
const rbac = new RBACBuilder()
.useBitSystem()
.addPermission('user:read', 1)
.addPermission('user:write', 2)
.addPermission('user:delete', 4)
.addRole('user', ['user:read'], { level: 1 })
.addRole('admin', ['user:read', 'user:write', 'user:delete'], { level: 10 })
.build();
```
## API Reference
### Constructor
```typescript
new RBACBuilder()
```
```typescript
const builder = new RBACBuilder();
```
### useBitSystem()
```typescript
useBitSystem(): RBACBuilder
```
```typescript
const rbac = new RBACBuilder()
.useBitSystem()
.build();
```
### useLegacySystem()
```typescript
useLegacySystem(): RBACBuilder
```
```typescript
const rbac = new RBACBuilder()
.useLegacySystem()
.build();
```
### withPreset(preset)
```typescript
withPreset(preset: PresetConfig): RBACBuilder
```
```typescript
import { defaultPreset } from '@fire-shield/core';
const rbac = new RBACBuilder()
.withPreset(defaultPreset)
.build();
```
```typescript
import fs from 'fs';
import { RBACBuilder } from '@fire-shield/core';
// Read config file
const configFile = fs.readFileSync('./rbac.config.json', 'utf-8');
const config = JSON.parse(configFile);
// Build RBAC from config
const rbac = new RBACBuilder()
.withPreset(config)
.build();
```
```typescript
import { promises as fs } from 'fs';
import { RBACBuilder } from '@fire-shield/core';
async function loadRBAC() {
const configFile = await fs.readFile('./rbac.config.json', 'utf-8');
const config = JSON.parse(configFile);
return new RBACBuilder()
.withPreset(config)
.build();
}
const rbac = await loadRBAC();
```
```json
{
"name": "my-app-rbac",
"version": "1.0.0",
"permissions": [
{
"name": "user:read",
"bit": 1,
"resource": "user",
"action": "read",
"description": "Read user data"
},
{
"name": "user:write",
"bit": 2,
"resource": "user",
"action": "write",
"description": "Create/update users"
},
{
"name": "post:read",
"bit": 4,
"resource": "post",
"action": "read"
},
{
"name": "post:write",
"bit": 8,
"resource": "post",
"action": "write"
}
],
"roles": [
{
"name": "admin",
"permissions": ["user:read", "user:write", "post:read", "post:write"],
"level": 10,
"description": "Administrator with full access"
},
{
"name": "editor",
"permissions": ["post:read", "post:write"],
"level": 5,
"description": "Content editor"
},
{
"name": "viewer",
"permissions": ["user:read", "post:read"],
"level": 1,
"description": "Read-only access"
}
],
"options": {
"autoBitAssignment": true,
"startBitValue": 1
}
}
```
### addPermission(name, bit?, options?)
```typescript
addPermission(
name: string,
bit?: number,
options?: {
resource?: string;
action?: string;
description?: string;
metadata?: Record;
}
): RBACBuilder
```
```typescript
const rbac = new RBACBuilder()
.addPermission('user:read', 1, {
resource: 'user',
action: 'read',
description: 'Read user information'
})
.addPermission('user:write', 2, {
resource: 'user',
action: 'write',
description: 'Write user information'
})
.build();
```
### addRole(name, permissions, options?)
```typescript
addRole(
name: string,
permissions: string[],
options?: {
level?: number;
description?: string;
metadata?: Record;
}
): RBACBuilder
```
```typescript
const rbac = new RBACBuilder()
.addRole('admin', ['user:*', 'post:*'], {
level: 10,
description: 'System administrator'
})
.addRole('editor', ['post:read', 'post:write'], {
level: 5,
description: 'Content editor'
})
.build();
```
### enableWildcards(enabled?)
```typescript
enableWildcards(enabled: boolean = true): RBACBuilder
```
```typescript
const rbac = new RBACBuilder()
.enableWildcards(true)
.addRole('admin', ['*'])
.build();
```
### withAuditLogger(logger)
```typescript
withAuditLogger(logger: AuditLogger): RBACBuilder
```
```typescript
import { ConsoleAuditLogger } from '@fire-shield/core';
const rbac = new RBACBuilder()
.withAuditLogger(new ConsoleAuditLogger())
.build();
```
### build()
```typescript
build(): RBAC
```
```typescript
const rbac = new RBACBuilder()
.addRole('user', ['post:read'])
.build();
```
## Complete Examples
### Blog System
```typescript
import { RBACBuilder } from '@fire-shield/core';
const rbac = new RBACBuilder()
.useBitSystem()
// Add permissions
.addPermission('post:read', 1)
.addPermission('post:write', 2)
.addPermission('post:delete', 4)
.addPermission('post:publish', 8)
.addPermission('comment:read', 16)
.addPermission('comment:write', 32)
.addPermission('comment:moderate', 64)
// Add roles
.addRole('viewer', ['post:read', 'comment:read'], {
level: 1,
description: 'Can only view content'
})
.addRole('author', ['post:read', 'post:write', 'comment:read', 'comment:write'], {
level: 5,
description: 'Can create and edit own posts'
})
.addRole('editor', ['post:*', 'comment:*'], {
level: 8,
description: 'Can manage all posts and comments'
})
.addRole('admin', ['*'], {
level: 10,
description: 'Full system access'
})
.build();
```
### E-Commerce System
```typescript
import { RBACBuilder, BufferedAuditLogger } from '@fire-shield/core';
const auditLogger = new BufferedAuditLogger(async (logs) => {
await database.auditLogs.insertMany(logs);
});
const rbac = new RBACBuilder()
.useBitSystem()
.withAuditLogger(auditLogger)
// Customer permissions
.addPermission('product:view', 1)
.addPermission('cart:manage', 2)
.addPermission('order:create', 4)
.addPermission('order:view:own', 8)
// Admin permissions
.addPermission('product:manage', 16)
.addPermission('order:view:all', 32)
.addPermission('order:update', 64)
.addPermission('inventory:manage', 128)
// Roles
.addRole('customer', ['product:view', 'cart:manage', 'order:create', 'order:view:own'], {
level: 1
})
.addRole('manager', ['product:manage', 'order:view:all', 'order:update', 'inventory:manage'], {
level: 5
})
.addRole('admin', ['*'], {
level: 10
})
.build();
```
### Multi-Tenant SaaS
```typescript
import { RBACBuilder } from '@fire-shield/core';
const rbac = new RBACBuilder()
.useBitSystem()
.enableWildcards(true)
// Workspace permissions
.addPermission('workspace:read')
.addPermission('workspace:update')
.addPermission('workspace:delete')
.addPermission('workspace:invite')
.addPermission('data:read')
.addPermission('data:write')
.addPermission('billing:view')
.addPermission('billing:update')
// Roles
.addRole('workspace-guest', ['workspace:read', 'data:read'], {
level: 1,
description: 'Read-only guest access'
})
.addRole('workspace-member', ['workspace:read', 'data:*'], {
level: 3,
description: 'Regular team member'
})
.addRole('workspace-admin', ['workspace:*', 'data:*', 'workspace:invite'], {
level: 7,
description: 'Workspace administrator'
})
.addRole('workspace-owner', ['*'], {
level: 10,
description: 'Workspace owner with full access'
})
.build();
```
## Method Chaining
```typescript
const rbac = new RBACBuilder()
.useBitSystem() // 1. Choose system
.enableWildcards(true) // 2. Enable wildcards
.withAuditLogger(logger) // 3. Add audit logging
.addPermission('user:read', 1) // 4. Add permissions
.addPermission('user:write', 2)
.addRole('user', ['user:read']) // 5. Add roles
.addRole('admin', ['*'])
.build(); // 6. Build RBAC instance
```
## TypeScript Support
```typescript
import { RBACBuilder, type RBACUser } from '@fire-shield/core';
const rbac = new RBACBuilder()
.addRole('admin', ['user:*'])
.build();
const user: RBACUser = {
id: 'user-1',
roles: ['admin']
};
rbac.hasPermission(user, 'user:read'); // Type-safe
```
## Best Practices
### 1. Group Related Configuration
```typescript
// ✅ Good: Logical grouping
const rbac = new RBACBuilder()
// System configuration
.useBitSystem()
.enableWildcards(true)
// Permissions
.addPermission('user:read', 1)
.addPermission('user:write', 2)
// Roles
.addRole('user', ['user:read'])
.addRole('admin', ['*'])
.build();
```
### 2. Use Descriptive Options
```typescript
// ✅ Good: Include descriptions
const rbac = new RBACBuilder()
.addRole('moderator', ['comment:moderate', 'post:flag'], {
level: 6,
description: 'Community moderator with content moderation powers',
metadata: {
department: 'Community',
maxActions: 100
}
})
.build();
```
### 3. Separate Configuration
```typescript
// config/rbac.ts
import { RBACBuilder } from '@fire-shield/core';
import { auditLogger } from './audit';
export function createRBAC() {
return new RBACBuilder()
.withAuditLogger(auditLogger)
.addPermission('user:read', 1)
.addPermission('user:write', 2)
.addRole('user', ['user:read'])
.addRole('admin', ['*'])
.build();
}
```
## Next Steps
### Audit Logging
> Source: https://fire-shield.dev/api/audit
# Audit Logging
## Overview
## Audit Loggers
### ConsoleAuditLogger
```typescript
import { RBAC, ConsoleAuditLogger } from '@fire-shield/core';
const rbac = new RBAC({
auditLogger: new ConsoleAuditLogger()
});
```
```
[AUDIT 2025-01-15T10:30:00.000Z] ✓ ALLOWED: User user-123 - post:read
[AUDIT 2025-01-15T10:30:01.000Z] ✗ DENIED: User user-123 - admin:delete
Reason: User lacks permission: admin:delete
```
### BufferedAuditLogger
```typescript
import { BufferedAuditLogger } from '@fire-shield/core';
const logger = new BufferedAuditLogger(
async (events) => {
// Save to database
await db.auditLogs.insertMany(events);
},
{
maxBufferSize: 100, // Flush after 100 events
flushIntervalMs: 5000 // Or every 5 seconds
}
);
const rbac = new RBAC({ auditLogger: logger });
```
```typescript
interface BufferedAuditLoggerOptions {
maxBufferSize?: number; // Default: 100
flushIntervalMs?: number; // Default: 5000
}
```
### MultiAuditLogger
```typescript
import { MultiAuditLogger, ConsoleAuditLogger, BufferedAuditLogger } from '@fire-shield/core';
const logger = new MultiAuditLogger([
new ConsoleAuditLogger(),
new BufferedAuditLogger(async (events) => {
await db.auditLogs.insertMany(events);
})
]);
const rbac = new RBAC({ auditLogger: logger });
```
## Audit Event Structure
```typescript
interface AuditEvent {
// Event type
type: 'permission_check' | 'authorization' | 'role_check';
// User information
userId: string;
// Permission being checked
permission: string;
// Result
allowed: boolean;
// Reason for denial (if denied)
reason?: string;
// Additional context
context?: {
roles?: string[];
ip?: string;
userAgent?: string;
resource?: string;
action?: string;
metadata?: Record;
};
// Timestamp (milliseconds since epoch)
timestamp: number;
}
```
```typescript
{
type: 'permission_check',
userId: 'user-123',
permission: 'post:write',
allowed: false,
reason: 'User lacks permission: post:write',
context: {
roles: ['viewer'],
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...'
},
timestamp: 1642345678901
}
```
## Custom Audit Logger
```typescript
import type { AuditLogger, AuditEvent } from '@fire-shield/core';
class DatabaseAuditLogger implements AuditLogger {
async log(event: AuditEvent): Promise {
await database.auditLogs.insert({
type: event.type,
userId: event.userId,
permission: event.permission,
allowed: event.allowed,
reason: event.reason,
context: event.context,
createdAt: new Date(event.timestamp)
});
}
}
const rbac = new RBAC({
auditLogger: new DatabaseAuditLogger()
});
```
## Real-World Examples
### Security Monitoring
```typescript
class SecurityMonitorLogger implements AuditLogger {
private failedAttempts = new Map();
log(event: AuditEvent): void {
if (!event.allowed) {
// Track failed attempts
const count = (this.failedAttempts.get(event.userId) || 0) + 1;
this.failedAttempts.set(event.userId, count);
// Alert after 5 failed attempts
if (count >= 5) {
this.alertSecurityTeam({
userId: event.userId,
failedAttempts: count,
lastPermission: event.permission,
timestamp: event.timestamp
});
}
} else {
// Reset on successful check
this.failedAttempts.delete(event.userId);
}
}
private alertSecurityTeam(alert: any) {
console.warn('⚠️ Security Alert:', alert);
// Send to monitoring service
}
}
```
### Compliance Logging
```typescript
class ComplianceLogger implements AuditLogger {
async log(event: AuditEvent): Promise {
// Store with encryption
await database.auditLogs.insert({
...event,
encrypted: true,
retentionPeriod: '7years', // Compliance requirement
compliance: {
gdpr: true,
hipaa: event.permission.includes('medical'),
soc2: true
}
});
// Also send to compliance monitoring service
await fetch('https://compliance.example.com/audit', {
method: 'POST',
body: JSON.stringify(event)
});
}
}
```
### Analytics Logger
```typescript
class AnalyticsLogger implements AuditLogger {
private analytics = new Map();
log(event: AuditEvent): void {
// Track permission usage
const key = `${event.permission}:${event.allowed ? 'allowed' : 'denied'}`;
const count = (this.analytics.get(key) || 0) + 1;
this.analytics.set(key, count);
// Send to analytics service every 100 events
if (this.getTotalCount() % 100 === 0) {
this.sendAnalytics();
}
}
private getTotalCount(): number {
return Array.from(this.analytics.values()).reduce((a, b) => a + b, 0);
}
private sendAnalytics() {
const stats = Object.fromEntries(this.analytics);
console.log('📊 Permission Analytics:', stats);
}
}
```
### Database Logger with Rotation
```typescript
class RotatingDatabaseLogger implements AuditLogger {
private buffer: AuditEvent[] = [];
private readonly maxAge = 90 * 24 * 60 * 60 * 1000; // 90 days
async log(event: AuditEvent): Promise {
this.buffer.push(event);
// Flush every 50 events
if (this.buffer.length >= 50) {
await this.flush();
}
}
async flush(): Promise {
if (this.buffer.length === 0) return;
// Insert new logs
await db.auditLogs.insertMany(this.buffer);
this.buffer = [];
// Delete old logs
const cutoff = Date.now() - this.maxAge;
await db.auditLogs.deleteMany({
timestamp: { $lt: cutoff }
});
}
}
```
## Querying Audit Logs
### Filter by User
```typescript
const userLogs = await db.auditLogs.find({
userId: 'user-123'
}).sort({ timestamp: -1 }).limit(100);
```
### Filter by Permission
```typescript
const permissionLogs = await db.auditLogs.find({
permission: 'admin:delete'
}).sort({ timestamp: -1 });
```
### Failed Attempts
```typescript
const failedAttempts = await db.auditLogs.find({
allowed: false
}).sort({ timestamp: -1 });
```
### Time Range
```typescript
const recentLogs = await db.auditLogs.find({
timestamp: {
$gte: Date.now() - 24 * 60 * 60 * 1000 // Last 24 hours
}
});
```
## Performance Considerations
### Buffering
```typescript
// ✅ Good: Buffered for performance
const logger = new BufferedAuditLogger(async (events) => {
await db.auditLogs.insertMany(events);
}, { maxBufferSize: 100 });
// ❌ Avoid: Synchronous logging on every check
const logger = {
log: (event) => db.auditLogs.insert(event) // Blocks on every check!
};
```
### Async Logging
```typescript
class AsyncLogger implements AuditLogger {
async log(event: AuditEvent): Promise {
// Don't await - fire and forget
this.saveToDatabase(event).catch(err => {
console.error('Failed to save audit log:', err);
});
}
private async saveToDatabase(event: AuditEvent) {
await db.auditLogs.insert(event);
}
}
```
### Sampling
```typescript
class SamplingLogger implements AuditLogger {
private sampleRate = 0.1; // Log 10% of events
log(event: AuditEvent): void {
// Always log denied events
if (!event.allowed) {
this.saveLog(event);
return;
}
// Sample allowed events
if (Math.random() < this.sampleRate) {
this.saveLog(event);
}
}
private saveLog(event: AuditEvent) {
// Save to database
}
}
```
## Best Practices
### 1. Use Buffered Logging in Production
```typescript
// ✅ Good: Buffered logging
const logger = new BufferedAuditLogger(
async (events) => await db.auditLogs.insertMany(events),
{ maxBufferSize: 100, flushIntervalMs: 5000 }
);
```
### 2. Include Context
```typescript
// ✅ Good: Rich context
const result = rbac.authorize(user, 'admin:delete', {
ip: request.ip,
userAgent: request.headers['user-agent'],
resource: 'user',
resourceId: userId
});
```
### 3. Rotate Old Logs
```typescript
// Scheduled job to delete old logs
setInterval(async () => {
const cutoff = Date.now() - 90 * 24 * 60 * 60 * 1000; // 90 days
await db.auditLogs.deleteMany({ timestamp: { $lt: cutoff } });
}, 24 * 60 * 60 * 1000); // Run daily
```
### 4. Monitor Failed Attempts
```typescript
// Alert on repeated failures
const failures = await db.auditLogs.countDocuments({
userId: user.id,
allowed: false,
timestamp: { $gte: Date.now() - 60 * 60 * 1000 } // Last hour
});
if (failures > 10) {
await alertSecurityTeam(user.id);
}
```
## Next Steps
### TypeScript Types
> Source: https://fire-shield.dev/api/types
# TypeScript Types
## Core Types
### IRBAC
```typescript
interface IRBAC {
/**
* Register a new permission
*/
registerPermission(permissionName: string): void;
/**
* Create a new role with permissions
*/
createRole(roleName: string, permissions: string[]): void;
/**
* Check if user has a specific permission
*/
hasPermission(user: RBACUser, permission: string): boolean;
/**
* Check if user has all specified permissions
*/
hasAllPermissions(user: RBACUser, permissions: string[]): boolean;
/**
* Check if user has any of the specified permissions
*/
hasAnyPermission(user: RBACUser, permissions: string[]): boolean;
/**
* Authorize user with detailed result
*/
authorize(user: RBACUser, permission: string): AuthorizationResult;
/**
* Grant permission to role
*/
grantPermission(roleName: string, permission: string): void;
/**
* Revoke permission from role
*/
revokePermission(roleName: string, permission: string): void;
/**
* Get all registered permissions
*/
getPermissions(): string[];
/**
* Get all registered roles
*/
getRoles(): string[];
/**
* Get all permissions for a user
*/
getUserPermissions(user: RBACUser): string[];
/**
* Deny permission for user
*/
denyPermission(userId: string, permission: string): void;
/**
* Remove denied permission for user
*/
allowPermission(userId: string, permission: string): void;
/**
* Get denied permissions for user
*/
getDeniedPermissions(userId: string): string[];
}
```
```typescript
import { RBAC, RBACAggregator, type IRBAC } from '@fire-shield/core';
function setupPermissions(instance: IRBAC) {
instance.registerPermission('posts:read');
instance.registerPermission('posts:write');
instance.createRole('editor', ['posts:read', 'posts:write']);
}
// Works with RBAC
const rbac = new RBAC();
setupPermissions(rbac);
// Also works with RBACAggregator
const aggregator = new RBACAggregator();
aggregator.addInstance('default', new RBAC());
setupPermissions(aggregator); // ✅ Polymorphic!
```
```typescript
import { type IRBAC } from '@fire-shield/core';
import { RBACProvider } from '@fire-shield/react';
// Accept any IRBAC implementation
function createRBACAdapter(rbacInstance: IRBAC) {
return (
);
}
// Can use RBAC or RBACAggregator
const rbac = new RBAC();
const aggregator = new RBACAggregator();
createRBACAdapter(rbac); // ✅ Works
createRBACAdapter(aggregator); // ✅ Also works
```
### RBACUser
```typescript
interface RBACUser {
/**
* Unique identifier for the user
*/
id: string;
/**
* Array of role names assigned to the user
*/
roles: string[];
/**
* Optional direct permissions (additive to role permissions)
*/
permissions?: string[];
/**
* Optional permission bitmask (for bit-based system)
*/
permissionMask?: number;
}
```
```typescript
const user: RBACUser = {
id: 'user-123',
roles: ['editor', 'moderator'],
permissions: ['beta:feature'],
permissionMask: 127
};
```
### AuthorizationResult
```typescript
interface AuthorizationResult {
/**
* Whether the permission is allowed
*/
allowed: boolean;
/**
* Reason for denial (only present when allowed = false)
*/
reason?: string;
/**
* The user object that was checked
*/
user?: RBACUser;
}
```
```typescript
const result: AuthorizationResult = rbac.authorize(user, 'admin:delete');
if (!result.allowed) {
console.log(result.reason); // "User lacks permission: admin:delete"
}
```
### AuthorizationContext
```typescript
interface AuthorizationContext {
/**
* User to check authorization for
*/
user: RBACUser;
/**
* Resource being accessed
*/
resource: string;
/**
* Action being performed
*/
action: string;
/**
* Optional additional context
*/
metadata?: Record<string, any>;
}
```
```typescript
const context: AuthorizationContext = {
user: currentUser,
resource: 'document',
action: 'edit',
metadata: {
documentId: 'doc-123',
ownerId: 'user-456'
}
};
const result = rbac.authorizeWithContext(context);
```
## Configuration Types
### RBACConfig
```typescript
interface RBACConfig {
/**
* Use bit-based permission system
* @default true
*/
useBitSystem?: boolean;
/**
* Enable strict mode (throws errors on invalid operations)
* @default false
*/
strictMode?: boolean;
/**
* Enable wildcard permission matching
* @default true
*/
enableWildcards?: boolean;
/**
* Preset configuration to load
*/
preset?: PresetConfig;
/**
* Configuration schema
*/
config?: RBACConfigSchema;
/**
* Optional audit logger
*/
auditLogger?: AuditLogger;
}
```
```typescript
const config: RBACConfig = {
useBitSystem: true,
strictMode: true,
enableWildcards: true,
auditLogger: new ConsoleAuditLogger()
};
const rbac = new RBAC(config);
```
### RBACConfigSchema
```typescript
interface RBACConfigSchema {
/**
* Permission definitions
*/
permissions: Array<{
name: string;
bit?: number;
resource?: string;
action?: string;
description?: string;
metadata?: Record<string, any>;
}>;
/**
* Role definitions
*/
roles: Array<{
name: string;
permissions: string[];
level?: number;
description?: string;
metadata?: Record<string, any>;
}>;
/**
* Optional configuration options
*/
options?: {
autoBitAssignment?: boolean;
validatePermissions?: boolean;
};
}
```
```typescript
const schema: RBACConfigSchema = {
permissions: [
{ name: 'user:read', bit: 1, resource: 'user', action: 'read' },
{ name: 'user:write', bit: 2, resource: 'user', action: 'write' }
],
roles: [
{
name: 'admin',
permissions: ['user:read', 'user:write'],
level: 10,
description: 'Administrator role'
}
]
};
```
### PresetConfig
```typescript
interface PresetConfig {
/**
* Preset name
*/
name: string;
/**
* Preset description
*/
description?: string;
/**
* Permission definitions
*/
permissions: Array<{
name: string;
bit?: number;
resource?: string;
action?: string;
}>;
/**
* Role definitions
*/
roles: Array<{
name: string;
permissions: string[];
level?: number;
}>;
/**
* Configuration options
*/
options?: {
autoBitAssignment?: boolean;
};
}
```
## Audit Types
### AuditEvent
```typescript
interface AuditEvent {
/**
* Type of event
*/
type: 'permission_check' | 'authorization' | 'role_check';
/**
* User ID that triggered the event
*/
userId: string;
/**
* Permission being checked
*/
permission: string;
/**
* Whether permission was allowed
*/
allowed: boolean;
/**
* Reason for denial (if denied)
*/
reason?: string;
/**
* Additional context
*/
context?: AuditEventContext;
/**
* Timestamp (milliseconds since epoch)
*/
timestamp: number;
}
```
### AuditEventContext
```typescript
interface AuditEventContext {
/**
* User roles at time of check
*/
roles?: string[];
/**
* IP address of request
*/
ip?: string;
/**
* User agent string
*/
userAgent?: string;
/**
* Resource being accessed
*/
resource?: string;
/**
* Action being performed
*/
action?: string;
/**
* Additional metadata
*/
metadata?: Record<string, any>;
}
```
### AuditLogger
```typescript
interface AuditLogger {
/**
* Log an audit event
* Can be sync or async
*/
log(event: AuditEvent): void | Promise<void>;
/**
* Optional flush method for buffered loggers
*/
flush?(): void | Promise<void>;
}
```
```typescript
class CustomAuditLogger implements AuditLogger {
log(event: AuditEvent): void {
console.log(`[AUDIT] ${event.userId} - ${event.permission}: ${event.allowed}`);
}
flush(): void {
// Flush buffered events
}
}
```
## Role Types
### UserRole
```typescript
interface UserRole {
/**
* Role name
*/
name: string;
/**
* Permission names assigned to this role
*/
permissions: string[];
/**
* Permission bitmask (bit-based system only)
*/
permissionMask?: number;
/**
* Optional role description
*/
description?: string;
/**
* Optional metadata
*/
metadata?: Record<string, any>;
}
```
### PermissionMask
```typescript
type PermissionMask = number;
```
```typescript
const readMask: PermissionMask = 1; // 2^0
const writeMask: PermissionMask = 2; // 2^1
const combinedMask: PermissionMask = readMask | writeMask; // 3
```
## Permission Types
### PermissionDefinition
```typescript
interface PermissionDefinition {
/**
* Permission name
*/
name: string;
/**
* Optional manual bit value (must be power of 2)
*/
bit?: number;
/**
* Optional resource name
*/
resource?: string;
/**
* Optional action name
*/
action?: string;
/**
* Optional description
*/
description?: string;
/**
* Optional metadata
*/
metadata?: Record<string, any>;
}
```
### RoleDefinition
```typescript
interface RoleDefinition {
/**
* Role name
*/
name: string;
/**
* Permission names
*/
permissions: string[];
/**
* Optional hierarchy level
*/
level?: number;
/**
* Optional description
*/
description?: string;
/**
* Optional metadata
*/
metadata?: Record<string, any>;
}
```
## State Types
### RBACSystemState
```typescript
interface RBACSystemState {
/**
* Bit-based system enabled
*/
useBitSystem: boolean;
/**
* Wildcards enabled
*/
enableWildcards: boolean;
/**
* All registered permissions
*/
permissions: Array<{
name: string;
bit?: number;
}>;
/**
* All registered roles
*/
roles: Array<{
name: string;
permissions: string[];
permissionMask?: number;
}>;
/**
* Role hierarchy levels
*/
hierarchy: Record<string, number>;
/**
* Denied permissions by user ID
*/
denyList: Record<string, string[]>;
}
```
```typescript
// Serialize
const state: RBACSystemState = rbac.serialize();
// Save to storage
localStorage.setItem('rbac-state', JSON.stringify(state));
// Restore
const savedState = JSON.parse(localStorage.getItem('rbac-state')!);
rbac.deserialize(savedState);
```
## Type Guards
### isRBACUser
```typescript
function isRBACUser(obj: any): obj is RBACUser {
return (
typeof obj === 'object' &&
typeof obj.id === 'string' &&
Array.isArray(obj.roles)
);
}
```
```typescript
if (isRBACUser(maybeUser)) {
// TypeScript knows this is an RBACUser
rbac.hasPermission(maybeUser, 'post:read');
}
```
### isAuditEvent
```typescript
function isAuditEvent(obj: any): obj is AuditEvent {
return (
typeof obj === 'object' &&
typeof obj.type === 'string' &&
typeof obj.userId === 'string' &&
typeof obj.permission === 'string' &&
typeof obj.allowed === 'boolean' &&
typeof obj.timestamp === 'number'
);
}
```
## Generic Types
### WithMetadata<T>
```typescript
type WithMetadata<T> = T & {
metadata?: Record<string, any>;
};
```
```typescript
type UserWithMetadata = WithMetadata<RBACUser>;
const user: UserWithMetadata = {
id: 'user-1',
roles: ['admin'],
metadata: {
department: 'Engineering',
hireDate: '2025-01-01'
}
};
```
## Const Enums
### PermissionCheckType
```typescript
const enum PermissionCheckType {
PERMISSION_CHECK = 'permission_check',
AUTHORIZATION = 'authorization',
ROLE_CHECK = 'role_check'
}
```
```typescript
const event: AuditEvent = {
type: PermissionCheckType.PERMISSION_CHECK,
userId: 'user-1',
permission: 'post:read',
allowed: true,
timestamp: Date.now()
};
```
## Import Paths
```typescript
// Core types
import type {
RBACUser,
AuthorizationResult,
AuthorizationContext
} from '@fire-shield/core';
// Configuration types
import type {
RBACConfig,
RBACConfigSchema,
PresetConfig
} from '@fire-shield/core';
// Audit types
import type {
AuditEvent,
AuditEventContext,
AuditLogger
} from '@fire-shield/core';
// Role types
import type {
UserRole,
PermissionMask,
PermissionDefinition,
RoleDefinition
} from '@fire-shield/core';
// State types
import type {
RBACSystemState
} from '@fire-shield/core';
```
## Type Utilities
### Extending Types
```typescript
// Extend RBACUser with custom fields
interface AppUser extends RBACUser {
email: string;
name: string;
department: string;
}
const user: AppUser = {
id: 'user-1',
roles: ['editor'],
email: 'user@example.com',
name: 'John Doe',
department: 'Engineering'
};
```
### Type-Safe Permissions
```typescript
// Define allowed permissions as const
const PERMISSIONS = {
USER_READ: 'user:read',
USER_WRITE: 'user:write',
POST_READ: 'post:read',
POST_WRITE: 'post:write',
} as const;
type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS];
// Type-safe permission checks
function checkPermission(user: RBACUser, permission: Permission): boolean {
return rbac.hasPermission(user, permission);
}
// ✅ Valid
checkPermission(user, PERMISSIONS.USER_READ);
// ❌ Type error
checkPermission(user, 'invalid:permission');
```
### Type-Safe Roles
```typescript
// Define allowed roles
type Role = 'admin' | 'editor' | 'viewer';
function hasRole(user: RBACUser, role: Role): boolean {
return user.roles.includes(role);
}
// ✅ Valid
hasRole(user, 'admin');
// ❌ Type error
hasRole(user, 'invalid-role');
```
## Best Practices
### 1. Always Use TypeScript
```typescript
// ✅ Good: Full type safety
import type { RBACUser } from '@fire-shield/core';
const user: RBACUser = {
id: 'user-1',
roles: ['editor']
};
```
### 2. Define Custom Types
```typescript
// ✅ Good: Define app-specific types
interface AppUser extends RBACUser {
email: string;
name: string;
}
```
### 3. Use Type Guards
```typescript
// ✅ Good: Validate at runtime
if (isRBACUser(userData)) {
rbac.hasPermission(userData, 'post:read');
}
```
### 4. Const Assertions
```typescript
// ✅ Good: Type-safe constants
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = typeof ROLES[number];
```
## Next Steps
============================================================
## Frameworks — Frontend
============================================================
### Vue.js Integration
> Source: https://fire-shield.dev/frameworks/vue
# Vue.js Integration
## Installation
```bash
npm install @fire-shield/vue@3.1.1 @fire-shield/core@3.1.1
```
## Setup
### Basic Setup
```typescript
// main.ts
import { createApp } from 'vue'
import { createVueRouterRBAC } from '@fire-shield/vue'
import { RBAC } from '@fire-shield/core'
import router from './router'
import App from './App.vue'
// Initialize RBAC
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
rbac.createRole('viewer', ['posts:read'])
// Create user ref
const currentUser = ref({ id: '1', roles: ['editor'] })
// Create Vue RBAC plugin
const { install: installRBAC } = createVueRouterRBAC(router, {
rbac,
getUser: () => currentUser.value,
onUnauthorized: (to) => {
router.push('/unauthorized')
}
})
// Create and setup app
const app = createApp(App)
app.use(router)
app.use(installRBAC)
app.mount('#app')
```
## Directives
### v-can
```vue
Create Post
```
### v-cannot
```vue
Upgrade to access premium features
Upgrade Now
```
### v-permission
```vue
Delete Post
```
### v-role
```vue
Admin Panel
Editor Tools
```
## Composables
### Composables
```vue
Current user: {{ user?.id }}
Create Post
Admin Panel
```
### API
```typescript
// Returns a ComputedRef - reactive permission check
useCan(permission: string): ComputedRef
// Returns a ComputedRef - reactive role check
useRole(role: string): ComputedRef
// Returns Ref - current user
useUser(): Ref
// Returns the RBAC instance directly
useRBAC(): RBAC
// Returns ComputedRef
useAuthorize(permission: string): ComputedRef
// Returns ComputedRef - checks all permissions
useAllPermissions(...permissions: string[]): ComputedRef
// Returns ComputedRef - checks any permission
useAnyPermission(...permissions: string[]): ComputedRef
// Returns a function to explicitly deny a permission for the current user
useDenyPermission(): (permission: string) => void
// Returns a function to remove a previously denied permission
useAllowPermission(): (permission: string) => void
// Returns ComputedRef - denied permissions for the current user
useDeniedPermissions(): ComputedRef
// Returns ComputedRef - whether a specific permission is explicitly denied
useIsDenied(permission: string): ComputedRef
```
## Components
### Can Component
```vue
Create Post
You don't have permission to delete posts
Delete Post
```
### Cannot Component
```vue
Upgrade to unlock premium features
Upgrade Now
```
### ProtectedRoute Component
```vue
Access Denied
```
### RequirePermission Component
```vue
```
## Router Guards
### Route Meta Configuration
```typescript
// router/index.ts
import { createRouter } from 'vue-router'
const router = createRouter({
routes: [
{
path: '/posts',
component: PostsPage,
meta: { permission: 'posts:read' }
},
{
path: '/admin',
component: AdminPage,
meta: { role: 'admin' }
},
{
path: '/settings',
component: SettingsPage,
meta: { permission: 'settings:write' }
}
]
})
```
### Enable Global Guards
```typescript
const { install } = createVueRouterRBAC(router, {
rbac,
getUser: () => currentUser.value,
enableGuards: true, // Enable automatic route guards
onUnauthorized: (to, from) => {
// Redirect to login or show error
return '/unauthorized'
}
})
```
### Manual Guard Usage
```typescript
const { install } = createVueRouterRBAC(router, {
rbac,
getUser: () => currentUser.value,
enableGuards: true,
onUnauthorized: (to, result) => {
if (to.path === '/special') {
// Special handling for specific routes
router.push('/special-unauthorized')
return
}
router.push('/unauthorized')
}
})
```
## Reactive Permission Updates
```vue
Current user: {{ user?.id }}
Create Post
```
## TypeScript Support
```typescript
import { useCan, useRole } from '@fire-shield/vue'
import type { RBACUser } from '@fire-shield/core'
// Type-safe user
const user: RBACUser = {
id: 'user-123',
roles: ['editor']
}
// Type-safe composables
const canWrite = useCan('posts:write') // ComputedRef
const isEditor = useRole('editor') // ComputedRef
canWrite.value // true/false
isEditor.value // true/false
```
## Best Practices
### 1. Use Directives for Simple Cases
```vue
Delete
Delete
```
### 2. Use Composables for Logic
```vue
```
### 3. Use Components for Complex Conditions
```vue
```
### 4. Protect Routes at Router Level
```typescript
// ✅ Good: Centralized route protection
{
path: '/admin',
meta: { permission: 'admin:access' },
component: AdminPage
}
// ❌ Avoid: Component-level protection for routes
```
## Examples
## Next Steps
### React Integration
> Source: https://fire-shield.dev/frameworks/react
# React Integration
## Installation
```bash
npm install @fire-shield/react@3.1.1 @fire-shield/core@3.1.1
```
## Setup
### Basic Setup
```typescript
// App.tsx
import { RBACProvider } from '@fire-shield/react'
import { RBAC } from '@fire-shield/core'
import { useState } from 'react'
// Initialize RBAC
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
rbac.createRole('viewer', ['posts:read'])
function App() {
const [user, setUser] = useState({
id: '1',
roles: ['editor']
})
return (
)
}
```
## Hooks
### Hooks
```tsx
import { useRBAC, usePermission, useRole, useUser } from '@fire-shield/react'
function PostEditor() {
const rbac = useRBAC() // Returns RBAC instance directly
const user = useUser() // Returns RBACUser | null
const canWrite = usePermission('posts:write')
const canDelete = usePermission('posts:delete')
const isAdmin = useRole('admin')
const handleDelete = () => {
if (!canDelete) {
alert('No permission to delete')
return
}
deletePost()
}
return (
Post Editor
Current user: {user?.id}
{canWrite && (
Create Post
)}
{canDelete && (
Delete Post
)}
{isAdmin && (
Admin Panel
)}
)
}
```
### Hook API
```typescript
// Returns RBAC instance from context
useRBAC(): RBAC
// Check if current user has permission - returns boolean
usePermission(permission: string): boolean
// Check if current user has role - returns boolean
useRole(role: string): boolean
// Get current user from context
useUser(): RBACUser | null
// Get full authorization result
useAuthorize(permission: string): AuthorizationResult
// Check if user has ALL specified permissions
useAllPermissions(...permissions: string[]): boolean
// Check if user has ANY of the specified permissions
useAnyPermission(...permissions: string[]): boolean
// Returns a function to explicitly deny a permission for the current user
useDenyPermission(): (permission: string) => void
// Returns a function to remove a previously denied permission
useAllowPermission(): (permission: string) => void
// Returns array of explicitly denied permissions for the current user
useDeniedPermissions(): string[]
// Check if a specific permission is explicitly denied for the current user
useIsDenied(permission: string): boolean
```
## Components
### Can Component
```tsx
import { Can } from '@fire-shield/react'
function PostActions() {
return (
Create Post
No permission}>
Delete Post
)
}
```
### Cannot Component
```tsx
import { Cannot } from '@fire-shield/react'
function UpgradePrompt() {
return (
Upgrade to unlock premium features
Upgrade Now
)
}
```
### RequirePermission Component
```tsx
import { RequirePermission } from '@fire-shield/react'
function AdminPanel() {
return (
Access Denied}
>
Admin Panel
{/* Admin content */}
)
}
```
### Denied Component
```tsx
import { Denied, NotDenied } from '@fire-shield/react'
function PostActions() {
return (
{/* Shows children only when permission is explicitly denied */}
Your delete access has been revoked.
{/* Shows children when permission is NOT explicitly denied */}
Delete Post
)
}
```
### Deny / Allow Permissions at Runtime
```tsx
import { useDenyPermission, useAllowPermission, useDeniedPermissions } from '@fire-shield/react'
function AdminControls() {
const denyPermission = useDenyPermission()
const allowPermission = useAllowPermission()
const denied = useDeniedPermissions()
return (
Denied: {denied.join(', ')}
denyPermission('posts:delete')}>Revoke Delete
allowPermission('posts:delete')}>Restore Delete
)
}
```
## React Router Integration
### Protected Routes
```tsx
import { Navigate } from 'react-router-dom'
import { usePermission } from '@fire-shield/react'
function ProtectedRoute({ permission, children }) {
const hasPermission = usePermission(permission)
if (!hasPermission) {
return
}
return children
}
// Usage in router
}
/>
```
### Route Guards
```tsx
import { ProtectedRoute } from '@fire-shield/react'
// Usage with react-router-dom
}
/>
```
## Updating User
```tsx
import { useState } from 'react'
import { RBACProvider } from '@fire-shield/react'
import { useUser } from '@fire-shield/react'
// In a parent component, manage user state and pass it to RBACProvider
function App() {
const [user, setUser] = useState({ id: '1', roles: ['editor'] })
const switchToAdmin = () => {
setUser({ id: 'admin-1', roles: ['admin'] })
}
const switchToViewer = () => {
setUser({ id: 'viewer-1', roles: ['viewer'] })
}
return (
Current user: {user?.id}
Switch to Admin
Switch to Viewer
)
}
```
## TypeScript Support
```typescript
import { usePermission, useRole, useUser } from '@fire-shield/react'
import type { RBACUser } from '@fire-shield/core'
// Type-safe user
const user: RBACUser = {
id: 'user-123',
roles: ['editor']
}
// Type-safe hooks
const canWrite = usePermission('posts:write') // boolean
const isEditor = useRole('editor') // boolean
```
## Best Practices
### 1. Use Components for JSX
```tsx
// ✅ Good: Declarative and clean
Delete
// ❌ Avoid: Less readable
{canDelete && Delete }
```
### 2. Use Hooks for Logic
```tsx
// ✅ Good: Clear permission check
import { usePermission } from '@fire-shield/react'
const canDelete = usePermission('posts:delete')
const handleDelete = () => {
if (!canDelete) {
showError('No permission')
return
}
deletePost()
}
```
### 3. Memoize Permission Checks
```tsx
import { usePermission } from '@fire-shield/react'
function ExpensiveComponent() {
const canWrite = usePermission('posts:write')
const canDelete = usePermission('posts:delete')
const canPublish = usePermission('posts:publish')
return (
// Use permission values
)
}
```
### 4. Handle Loading States
```tsx
import { useUser, usePermission } from '@fire-shield/react'
function UserProfile() {
const user = useUser()
const canEdit = usePermission('profile:edit')
if (!user) {
return Loading...
}
return (
{user.id}
{canEdit && }
)
}
```
## Server-Side Rendering (SSR)
### Next.js Integration
### Other SSR Frameworks
```tsx
// server.tsx
const initialUser = await getUserFromSession(req)
const html = renderToString(
)
```
## Testing
### Testing Components with RBAC
```tsx
import { render, screen } from '@testing-library/react'
import { RBACProvider } from '@fire-shield/react'
import { RBAC } from '@fire-shield/core'
function renderWithRBAC(component, user) {
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*'])
rbac.createRole('viewer', ['posts:read'])
return render(
{component}
)
}
test('shows delete button for admin', () => {
renderWithRBAC(
,
{ id: '1', roles: ['admin'] }
)
expect(screen.getByText('Delete Post')).toBeInTheDocument()
})
test('hides delete button for viewer', () => {
renderWithRBAC(
,
{ id: '2', roles: ['viewer'] }
)
expect(screen.queryByText('Delete Post')).not.toBeInTheDocument()
})
```
## Examples
### Blog Post Management
```tsx
import { Can, usePermission } from '@fire-shield/react'
function PostCard({ post }) {
const canPublish = usePermission('posts:publish')
return (
{post.title}
{post.excerpt}
Read More
Edit
Delete
{canPublish && !post.published && (
Publish
)}
)
}
```
### User Management Dashboard
```tsx
import { RequirePermission, Can } from '@fire-shield/react'
function UserManagement() {
return (
User Management
Add New User
Export Users
)
}
```
## Next Steps
### Next.js Integration
> Source: https://fire-shield.dev/frameworks/next
# Next.js Integration
## Installation
```bash
npm install @fire-shield/next@3.1.1 @fire-shield/core@3.1.1
```
## Setup
### Initialize RBAC and Adapter
```typescript
// lib/rbac.ts
import { RBAC } from '@fire-shield/core';
import { NextRBACAdapter } from '@fire-shield/next';
import { getUser } from './auth';
export const rbac = new RBAC();
// Define roles
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
// Create the adapter
export const rbacAdapter = new NextRBACAdapter(rbac, {
getUser: (req) => getUser(req),
});
```
## App Router (Next.js 13+)
### Middleware Protection
```typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { rbacAdapter } from './lib/rbac';
const adminMiddleware = rbacAdapter.middleware('admin:access');
export async function middleware(request: NextRequest) {
// Protect admin routes
if (request.nextUrl.pathname.startsWith('/admin')) {
const response = await adminMiddleware(request);
if (response) return response; // returns 403 if unauthorized
}
return NextResponse.next();
}
export const config = {
matcher: ['/admin/:path*', '/api/admin/:path*'],
};
```
### Route-Based Protection
```typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { rbac } from './lib/rbac';
const protectedRoutes = {
'/admin': 'admin:access',
'/posts/create': 'post:write',
'/users': 'user:read',
};
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
const user = getUserFromRequest(request);
for (const [route, permission] of Object.entries(protectedRoutes)) {
if (pathname.startsWith(route)) {
if (!rbac.hasPermission(user, permission)) {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
}
}
return NextResponse.next();
}
```
### Server Components
```typescript
// app/admin/page.tsx
import { rbac } from '@/lib/rbac';
import { getUser } from '@/lib/auth';
import { redirect } from 'next/navigation';
export default async function AdminPage() {
const user = await getUser();
if (!rbac.hasPermission(user, 'admin:access')) {
redirect('/unauthorized');
}
return (
Admin Dashboard
{/* Admin content */}
);
}
```
### API Routes
```typescript
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rbacAdapter } from '@/lib/rbac';
import { getUsers, createUser } from '@/lib/users';
export const GET = rbacAdapter.withPermission(
'user:read',
async (request: NextRequest) => {
const users = await getUsers();
return NextResponse.json({ users });
}
);
export const POST = rbacAdapter.withPermission(
'user:create',
async (request: NextRequest) => {
const body = await request.json();
const newUser = await createUser(body);
return NextResponse.json({ user: newUser });
}
);
```
```typescript
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rbac } from '@/lib/rbac';
import { getUser } from '@/lib/auth';
export async function GET(request: NextRequest) {
const user = await getUser(request);
// Check permission with detailed result
const result = rbac.authorize(user, 'user:read');
if (!result.allowed) {
return NextResponse.json(
{ error: 'Forbidden', reason: result.reason },
{ status: 403 }
);
}
const users = await getUsers();
return NextResponse.json({ users });
}
```
### API Routes with Role Check
```typescript
// app/api/admin/stats/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rbacAdapter } from '@/lib/rbac';
export const GET = rbacAdapter.withRole(
'admin',
async (request: NextRequest) => {
const stats = await getAdminStats();
return NextResponse.json({ stats });
}
);
```
### Server Actions
```typescript
// app/actions/users.ts
'use server';
import { rbacAdapter } from '@/lib/rbac';
import { getUser } from '@/lib/auth';
import { revalidatePath } from 'next/cache';
export async function deleteUser(userId: string) {
const user = await getUser();
// Throws if permission is missing
await rbacAdapter.requirePermission(user, 'user:delete');
await db.users.delete(userId);
revalidatePath('/admin/users');
return { success: true };
}
export async function updateUserRole(userId: string, newRole: string) {
const user = await getUser();
await rbacAdapter.requirePermission(user, 'user:update');
await db.users.update(userId, { role: newRole });
revalidatePath('/admin/users');
return { success: true };
}
```
### Client Component Protection
```typescript
// components/AdminButton.tsx
'use client';
import { useUser } from '@/hooks/useUser';
import { rbac } from '@/lib/rbac';
export function AdminButton() {
const user = useUser();
if (!rbac.hasPermission(user, 'admin:access')) {
return null;
}
return (
Admin Panel
);
}
```
```typescript
// components/PostActions.tsx
'use client';
import { useUser } from '@/hooks/useUser';
import { rbac } from '@/lib/rbac';
export function PostActions({ postId }: { postId: string }) {
const user = useUser();
return (
{rbac.hasPermission(user, 'post:read') && (
viewPost(postId)}>View
)}
{rbac.hasPermission(user, 'post:write') && (
editPost(postId)}>Edit
)}
{rbac.hasPermission(user, 'post:delete') && (
deletePost(postId)}>Delete
)}
);
}
```
## Pages Router (Next.js 12)
### API Routes
```typescript
// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { rbac } from '@/lib/rbac';
import { getUser } from '@/lib/auth';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const user = await getUser(req);
if (!rbac.hasPermission(user, 'user:read')) {
return res.status(403).json({ error: 'Forbidden' });
}
const users = await getUsers();
res.json({ users });
}
```
### Pages Router with Adapter HOC
```typescript
// pages/api/admin/users.ts
import { rbacAdapter } from '@/lib/rbac';
export default rbacAdapter.withPermissionPagesRouter(
'user:read',
async (req, res) => {
const users = await getUsers();
res.json({ users });
}
);
```
```typescript
// pages/api/admin/stats.ts
import { rbacAdapter } from '@/lib/rbac';
export default rbacAdapter.withRolePagesRouter(
'admin',
async (req, res) => {
const stats = await getAdminStats();
res.json({ stats });
}
);
```
### HOC for Page Protection
```typescript
// lib/withPermission.ts
import { GetServerSideProps, GetServerSidePropsContext } from 'next';
import { rbac } from './rbac';
import { getUser } from './auth';
export function withPermission(
permission: string,
getServerSidePropsFunc?: GetServerSideProps
): GetServerSideProps {
return async (context: GetServerSidePropsContext) => {
const user = await getUser(context);
if (!rbac.hasPermission(user, permission)) {
return {
redirect: {
destination: '/unauthorized',
permanent: false,
},
};
}
if (getServerSidePropsFunc) {
return getServerSidePropsFunc(context);
}
return { props: {} };
};
}
```
```typescript
// pages/admin/users.tsx
import { withPermission } from '@/lib/withPermission';
export const getServerSideProps = withPermission('user:read', async () => {
const users = await getUsers();
return { props: { users } };
});
export default function UsersPage({ users }) {
return (
Users
{/* Render users */}
);
}
```
## API Reference
### `new NextRBACAdapter(rbac, options?)`
### Methods
#### `adapter.middleware(permission)`
#### `adapter.withPermission(permission, handler)`
```typescript
export const GET = rbacAdapter.withPermission('user:read', async (req) => {
return NextResponse.json({ users: await getUsers() });
});
```
#### `adapter.withRole(role, handler)`
```typescript
export const GET = rbacAdapter.withRole('admin', async (req) => {
return NextResponse.json({ stats: await getAdminStats() });
});
```
#### `adapter.withPermissionPagesRouter(permission, handler)`
#### `adapter.withRolePagesRouter(role, handler)`
#### `adapter.checkPermission(user, permission)`
#### `adapter.requirePermission(user, permission)`
#### `adapter.requireRole(user, role)`
#### `adapter.withNotDenied(permission, handler)`
#### `adapter.denyPermission(request, permission)`
#### `adapter.allowPermission(request, permission)`
#### `adapter.isDenied(request, permission)`
### `createPermissionChecker(rbac, getUser)`
```typescript
import { createPermissionChecker } from '@fire-shield/next';
const checkPermission = createPermissionChecker(rbac, getUser);
// In a route or component:
const allowed = await checkPermission(request, 'post:write');
```
### `createRoleChecker(getUser)`
```typescript
import { createRoleChecker } from '@fire-shield/next';
const checkRole = createRoleChecker(getUser);
const isAdmin = await checkRole(request, 'admin');
```
## Authentication Integration
### With NextAuth.js
```typescript
// lib/auth.ts
import { getServerSession } from 'next-auth/next';
import { authOptions } from './authOptions';
import type { RBACUser } from '@fire-shield/core';
export async function getUser(): Promise {
const session = await getServerSession(authOptions);
if (!session?.user) {
return null;
}
return {
id: session.user.id,
roles: session.user.roles || [],
};
}
```
```typescript
// middleware.ts
import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { rbac } from './lib/rbac';
export async function middleware(request: NextRequest) {
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
});
const user = token
? { id: token.sub as string, roles: token.roles as string[] }
: null;
if (!user || !rbac.hasPermission(user, 'admin:access')) {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: '/admin/:path*',
};
```
## Audit Logging
### Database Audit Logger
```typescript
// lib/auditLogger.ts
import { BufferedAuditLogger } from '@fire-shield/core';
import { db } from '@/lib/database';
export const auditLogger = new BufferedAuditLogger(
async (events) => {
await db.auditLogs.insertMany(
events.map((event) => ({
type: event.type,
userId: event.userId,
permission: event.permission,
allowed: event.allowed,
reason: event.reason,
context: event.context,
createdAt: new Date(event.timestamp),
}))
);
},
{
maxBufferSize: 100,
flushIntervalMs: 5000,
}
);
```
```typescript
// lib/rbac.ts
import { RBAC } from '@fire-shield/core';
import { auditLogger } from './auditLogger';
export const rbac = new RBAC({ auditLogger });
```
### API Route for Audit Logs
```typescript
// app/api/audit-logs/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rbacAdapter } from '@/lib/rbac';
import { db } from '@/lib/database';
export const GET = rbacAdapter.withPermission(
'audit:read',
async (request: NextRequest) => {
const logs = await db.auditLogs.find().sort({ createdAt: -1 }).limit(100);
return NextResponse.json({ logs });
}
);
```
## Dynamic Permissions
```typescript
// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rbac } from '@/lib/rbac';
import { getUser } from '@/lib/auth';
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const user = await getUser(request);
const post = await getPost(params.id);
// Check if user owns the post
const isOwner = post.authorId === user.id;
// Check permission with ownership
const canDelete =
rbac.hasPermission(user, 'post:delete:any') ||
(isOwner && rbac.hasPermission(user, 'post:delete:own'));
if (!canDelete) {
return NextResponse.json(
{ error: 'You do not have permission to delete this post' },
{ status: 403 }
);
}
await deletePost(params.id);
return NextResponse.json({ success: true });
}
```
## Best Practices
### 1. Centralize Permission Checks
```typescript
// lib/permissions.ts
import { rbac } from './rbac';
import type { RBACUser } from '@fire-shield/core';
export const permissions = {
canManageUsers: (user: RBACUser) => rbac.hasPermission(user, 'user:manage'),
canEditPost: (user: RBACUser, post: Post) =>
rbac.hasPermission(user, 'post:edit:any') ||
(post.authorId === user.id && rbac.hasPermission(user, 'post:edit:own')),
canDeletePost: (user: RBACUser, post: Post) =>
rbac.hasPermission(user, 'post:delete:any') ||
(post.authorId === user.id && rbac.hasPermission(user, 'post:delete:own')),
};
```
### 2. Use TypeScript for Type Safety
```typescript
// types/permissions.ts
export type Permission =
| 'user:read'
| 'user:write'
| 'user:delete'
| 'post:read'
| 'post:write'
| 'post:delete'
| 'admin:access';
export type Role = 'admin' | 'editor' | 'viewer';
```
### 3. Create Reusable Middleware
```typescript
// lib/middleware/requirePermission.ts
import { NextRequest, NextResponse } from 'next/server';
import { rbac } from '../rbac';
import { getUser } from '../auth';
export function requirePermission(permission: string) {
return async (request: NextRequest) => {
const user = await getUser(request);
if (!rbac.hasPermission(user, permission)) {
return NextResponse.json(
{ error: 'Forbidden', required: permission },
{ status: 403 }
);
}
return NextResponse.next();
};
}
```
## TypeScript Support
```typescript
import type { RBACUser } from '@fire-shield/core';
interface User extends RBACUser {
email: string;
name: string;
}
const user: User = {
id: 'user-123',
roles: ['editor'],
email: 'user@example.com',
name: 'John Doe',
};
rbac.hasPermission(user, 'post:write');
```
## Next Steps
### Nuxt Integration
> Source: https://fire-shield.dev/frameworks/nuxt
# Nuxt Integration
## Installation
```bash
npm install @fire-shield/nuxt@3.1.1 @fire-shield/core@3.1.1
```
## Setup
### 1. Add Module
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@fire-shield/nuxt'],
fireShield: {
// Define roles
roles: {
admin: ['user:*', 'post:*'],
editor: ['post:read', 'post:write'],
viewer: ['post:read']
},
// Enable features
auditLogging: true,
enableWildcards: true
}
});
```
### 2. Initialize User State
```typescript
// plugins/auth.ts
export default defineNuxtPlugin(async (nuxtApp) => {
const user = await $fetch('/api/auth/me');
if (user) {
const { setUser } = useFireShield();
setUser(user);
}
});
```
## Composables
### useFireShield()
```vue
Create Post
Admin Panel
```
```typescript
interface UseFireShieldReturn {
can: (permission: string) => boolean;
hasRole: (role: string) => boolean;
authorize: (permission: string) => AuthorizationResult;
rbac: RBAC;
setUser: (user: RBACUser | null) => void;
}
```
### useRBAC()
```typescript
const rbac = useRBAC();
// Create dynamic roles
rbac.createRole('moderator', ['comment:moderate', 'post:flag']);
// Check permissions
rbac.hasPermission(user, 'comment:moderate');
// Deny permissions
rbac.denyPermission(user.id, 'system:delete');
```
## Server Routes
### Basic Protection
```typescript
// server/api/users.get.ts
import { defineEventHandler } from 'h3';
import { useRBAC } from '#fire-shield';
export default defineEventHandler(async (event) => {
const rbac = useRBAC();
const user = event.context.user;
if (!rbac.hasPermission(user, 'user:read')) {
throw createError({
statusCode: 403,
message: 'Forbidden'
});
}
const users = await getUsers();
return { users };
});
```
### Using defineRBACEventHandler
```typescript
// server/api/admin/users.get.ts
export default defineRBACEventHandler('user:read', async (event) => {
const users = await getUsers();
return { users };
});
```
### Using defineRBACRoleHandler
```typescript
// server/api/admin/stats.get.ts
export default defineRBACRoleHandler('admin', async (event) => {
const stats = await getAdminStats();
return { stats };
});
```
### Dynamic Permission Checks
```typescript
// server/api/posts/[id].delete.ts
import { defineEventHandler, createError } from 'h3';
import { useRBAC } from '#fire-shield';
export default defineEventHandler(async (event) => {
const rbac = useRBAC();
const user = event.context.user;
const postId = event.context.params.id;
const post = await getPost(postId);
// Check ownership
const isOwner = post.authorId === user.id;
const canDelete =
rbac.hasPermission(user, 'post:delete:any') ||
(isOwner && rbac.hasPermission(user, 'post:delete:own'));
if (!canDelete) {
throw createError({
statusCode: 403,
message: 'You do not have permission to delete this post'
});
}
await deletePost(postId);
return { success: true };
});
```
## Middleware
### Route Protection
```typescript
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { can } = useFireShield();
// Protect admin routes
if (to.path.startsWith('/admin')) {
if (!can('admin:access')) {
return navigateTo('/unauthorized');
}
}
// Protect specific routes
const protectedRoutes = {
'/posts/create': 'post:write',
'/users/manage': 'user:manage'
};
const permission = protectedRoutes[to.path];
if (permission && !can(permission)) {
return navigateTo('/unauthorized');
}
});
```
### Role-Based Middleware
```typescript
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { hasRole } = useFireShield();
if (!hasRole('admin')) {
return navigateTo('/unauthorized');
}
});
```
```vue
Admin Panel
```
## Components
### Conditional Rendering
```vue
```
### Dynamic Actions
```vue
{{ action.label }}
```
### Form Protection
```vue
```
## Layouts
### Protected Layout
```vue
Access Denied
You do not have permission to access the admin panel
```
```vue
Admin Dashboard
```
## Server Middleware
### Authentication Middleware
```typescript
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
const token = getCookie(event, 'auth-token');
if (token) {
const user = await validateToken(token);
event.context.user = user;
}
});
```
### RBAC Middleware
```typescript
// server/middleware/rbac.ts
export default defineEventHandler(async (event) => {
const rbac = useRBAC();
const user = event.context.user;
// Add RBAC helper to context
event.context.can = (permission: string) =>
user ? rbac.hasPermission(user, permission) : false;
event.context.hasRole = (role: string) =>
user ? user.roles.includes(role) : false;
});
```
```typescript
// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
if (!event.context.can('post:read')) {
throw createError({
statusCode: 403,
message: 'Forbidden'
});
}
const posts = await getPosts();
return { posts };
});
```
## Audit Logging
### Enable in Configuration
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
fireShield: {
auditLogging: true
}
});
```
### Custom Audit Logger
```typescript
// server/plugins/audit.ts
import { defineNitroPlugin } from '#imports';
import { BufferedAuditLogger } from '@fire-shield/core';
export default defineNitroPlugin((nitroApp) => {
const logger = new BufferedAuditLogger(
async (events) => {
await $fetch('/api/audit-logs', {
method: 'POST',
body: { events }
});
},
{ maxBufferSize: 50, flushIntervalMs: 3000 }
);
const rbac = useRBAC();
rbac.setAuditLogger(logger);
});
```
### Audit Log API
```typescript
// server/api/audit-logs.post.ts
export default defineRBACEventHandler('audit:write', async (event) => {
const { events } = await readBody(event);
await db.auditLogs.insertMany(events);
return { success: true };
});
```
## Authentication Integration
### With Nuxt Auth
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@sidebase/nuxt-auth', '@fire-shield/nuxt'],
auth: {
// Auth configuration
}
});
```
```typescript
// middleware/auth.global.ts
import { useAuth } from '#auth';
export default defineNuxtRouteMiddleware(async (to, from) => {
const { status, data } = useAuth();
if (status.value === 'authenticated' && data.value) {
const { setUser } = useFireShield();
setUser({
id: data.value.user.id,
roles: data.value.user.roles
});
}
});
```
## Multi-Tenant Support
```typescript
// server/api/tenant/[tenantId]/data.get.ts
export default defineEventHandler(async (event) => {
const rbac = useRBAC();
const user = event.context.user;
const tenantId = event.context.params.tenantId;
// Check tenant-specific permission
const permission = `tenant:${tenantId}:read`;
if (!rbac.hasPermission(user, permission)) {
throw createError({
statusCode: 403,
message: 'You do not have access to this tenant'
});
}
const data = await getTenantData(tenantId);
return { data };
});
```
## Best Practices
### 1. Centralize Permission Checks
```typescript
// composables/usePermissions.ts
export const usePermissions = () => {
const { can, hasRole } = useFireShield();
return {
canManageUsers: () => can('user:manage'),
canEditPost: (post: Post, user: User) =>
can('post:edit:any') ||
(post.authorId === user.id && can('post:edit:own')),
canDeletePost: (post: Post, user: User) =>
can('post:delete:any') ||
(post.authorId === user.id && can('post:delete:own')),
isAdmin: () => hasRole('admin'),
};
};
```
### 2. Use Module Configuration
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
fireShield: {
roles: {
admin: ['*'],
editor: ['post:*', 'comment:moderate'],
user: ['post:read', 'comment:create']
},
auditLogging: true,
enableWildcards: true
}
});
```
### 3. Error Handling
```typescript
// server/api/protected.get.ts
export default defineEventHandler(async (event) => {
try {
const rbac = useRBAC();
const user = event.context.user;
const result = rbac.authorize(user, 'protected:access');
if (!result.allowed) {
throw createError({
statusCode: 403,
message: result.reason || 'Forbidden'
});
}
return { data: 'protected data' };
} catch (error) {
console.error('RBAC error:', error);
throw error;
}
});
```
## TypeScript Support
```typescript
import type { RBACUser } from '@fire-shield/nuxt';
interface User extends RBACUser {
email: string;
name: string;
}
const user: User = {
id: 'user-123',
roles: ['editor'],
email: 'user@example.com',
name: 'John Doe',
};
const { setUser } = useFireShield();
setUser(user);
```
## Next Steps
### Angular Integration
> Source: https://fire-shield.dev/frameworks/angular
# Angular Integration
## Features
## Installation
```bash
npm install @fire-shield/angular@3.1.1 @fire-shield/core@3.1.1
```
## Setup
### 1. Initialize RBAC
```typescript
// app.config.ts (Angular 17+)
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { RBAC } from '@fire-shield/core';
import { RBACService } from '@fire-shield/angular';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
RBACService
]
};
```
```typescript
// main.ts
import { APP_INITIALIZER } from '@angular/core';
import { RBACService } from '@fire-shield/angular';
import { RBAC } from '@fire-shield/core';
export function initializeRBAC(rbacService: RBACService) {
return () => {
const rbac = new RBAC();
// Define roles
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
// Set hierarchy
rbac.getRoleHierarchy().setRoleLevel('admin', 10);
rbac.getRoleHierarchy().setRoleLevel('editor', 5);
rbac.getRoleHierarchy().setRoleLevel('viewer', 1);
rbacService.initialize(rbac, null);
};
}
// In providers array:
{
provide: APP_INITIALIZER,
useFactory: initializeRBAC,
deps: [RBACService],
multi: true
}
```
### 2. Update User on Login
```typescript
import { Component, inject } from '@angular/core';
import { RBACService } from '@fire-shield/angular';
@Component({
selector: 'app-login',
template: 'Login '
})
export class LoginComponent {
private rbacService = inject(RBACService);
login() {
const user = { id: 'user-1', roles: ['editor'] };
this.rbacService.setUser(user);
}
}
```
## RBACService
### Methods
#### initialize(rbac: RBAC, initialUser?: RBACUser | null)
```typescript
constructor(private rbacService: RBACService) {
const rbac = new RBAC();
this.rbacService.initialize(rbac, null);
}
```
#### setUser(user: RBACUser | null)
```typescript
this.rbacService.setUser({ id: 'user-1', roles: ['editor'] });
```
#### getUser(): RBACUser | null
```typescript
const user = this.rbacService.getUser();
```
#### can(permission: string): boolean
```typescript
const allowed = this.rbacService.can('post:write');
```
#### can$(permission: string): Observable<boolean>
```typescript
this.rbacService.can$('post:write').subscribe(allowed => {
console.log('Can write:', allowed);
});
```
#### hasRole(role: string): boolean
```typescript
const isAdmin = this.rbacService.hasRole('admin');
```
#### hasRole$(role: string): Observable<boolean>
```typescript
this.rbacService.hasRole$('admin').subscribe(isAdmin => {
console.log('Is admin:', isAdmin);
});
```
#### authorize(permission: string): AuthorizationResult
```typescript
const result = this.rbacService.authorize('post:delete');
if (!result.allowed) {
console.log('Denied:', result.reason);
}
```
#### canAll(permissions: string[]): boolean
```typescript
const hasAll = this.rbacService.canAll(['post:read', 'post:write']);
```
#### canAny(permissions: string[]): boolean
```typescript
const hasAny = this.rbacService.canAny(['post:read', 'post:write']);
```
## Directives
### *fsCanPermission
```html
Create Post
```
### *fsHasRole
```html
Admin Panel
```
### *fsCannotPermission
```html
```
## Component Examples
### Basic Component with Directives
```typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
RBACService,
CanPermissionDirective,
HasRoleDirective,
CannotPermissionDirective
} from '@fire-shield/angular';
@Component({
selector: 'app-posts',
standalone: true,
imports: [
CommonModule,
CanPermissionDirective,
HasRoleDirective,
CannotPermissionDirective
],
template: `
Posts
Create Post
Create Post
Admin Options
Upgrade to access premium features
`
})
export class PostsComponent {
canWrite$ = this.rbacService.can$('post:write');
isAdmin$ = this.rbacService.hasRole$('admin');
constructor(private rbacService: RBACService) {}
}
```
### Complex Dashboard Component
```typescript
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import {
RBACService,
CanPermissionDirective,
HasRoleDirective
} from '@fire-shield/angular';
import { Observable } from 'rxjs';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [
CommonModule,
RouterModule,
CanPermissionDirective,
HasRoleDirective
],
template: `
Dashboard
Home
Posts
Users
Admin
Create Post
Admin Controls
Open Admin
Upgrade to Premium for more features!
Upgrade Now
`,
styles: [`
.upgrade-banner {
padding: 1rem;
background: #f0f0f0;
border-radius: 8px;
}
`]
})
export class DashboardComponent implements OnInit {
canManagePosts$!: Observable;
canManageUsers$!: Observable;
isAdmin$!: Observable;
constructor(private rbacService: RBACService) {}
ngOnInit() {
this.canManagePosts$ = this.rbacService.can$('post:write');
this.canManageUsers$ = this.rbacService.can$('user:write');
this.isAdmin$ = this.rbacService.hasRole$('admin');
}
createPost() {
console.log('Creating post...');
}
openAdminPanel() {
console.log('Opening admin panel...');
}
upgradeToPremium() {
console.log('Upgrading to premium...');
}
}
```
### Form with Conditional Fields
```typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import {
RBACService,
CanPermissionDirective,
HasRoleDirective
} from '@fire-shield/angular';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [
CommonModule,
FormsModule,
CanPermissionDirective,
HasRoleDirective
],
template: `
`
})
export class UserFormComponent {
user = { name: '', email: '', role: 'user' };
constructor(private rbacService: RBACService) {}
onSubmit() {
console.log('Submitting user:', this.user);
}
}
```
## Route Guards
### canActivatePermission
```typescript
import { Routes } from '@angular/router';
import { canActivatePermission } from '@fire-shield/angular';
export const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivatePermission],
data: { permission: 'admin:access', redirectTo: '/unauthorized' }
}
];
```
### canActivateRole
```typescript
export const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivateRole],
data: { role: 'admin', redirectTo: '/unauthorized' }
}
];
```
### canActivateRBAC
```typescript
export const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivateRBAC],
data: {
permission: 'admin:access',
role: 'admin',
redirectTo: '/unauthorized'
}
}
];
```
### Multiple Permissions/Roles
```typescript
export const routes: Routes = [
{
path: 'posts',
component: PostsComponent,
canActivate: [canActivatePermission],
data: {
permissions: ['post:read', 'post:write'],
requireAll: true, // Must have all permissions
redirectTo: '/unauthorized'
}
},
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [canActivatePermission],
data: {
permissions: ['admin:view', 'moderator:view'],
requireAll: false, // Must have at least one permission
redirectTo: '/unauthorized'
}
}
];
```
### Complete Route Configuration
```typescript
import { Routes } from '@angular/router';
import {
canActivatePermission,
canActivateRole,
canActivateRBAC
} from '@fire-shield/angular';
export const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'posts',
component: PostsComponent,
canActivate: [canActivatePermission],
data: { permission: 'post:read', redirectTo: '/unauthorized' }
},
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivateRole],
data: { role: 'admin', redirectTo: '/unauthorized' }
},
{
path: 'editor',
component: EditorComponent,
canActivate: [canActivateRBAC],
data: {
permissions: ['post:read', 'post:write'],
role: 'editor',
requireAll: true,
redirectTo: '/unauthorized'
}
},
{
path: 'unauthorized',
component: UnauthorizedComponent
}
];
```
## Services
### Permission Service
```typescript
// services/permissions.service.ts
import { Injectable, inject } from '@angular/core';
import { RBACService } from '@fire-shield/angular';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class PermissionsService {
private rbacService = inject(RBACService);
canManageUsers(): boolean {
return this.rbacService.can('user:manage');
}
canManageUsers$(): Observable {
return this.rbacService.can$('user:manage');
}
canEditPost(post: Post): boolean {
const user = this.rbacService.getUser();
if (!user) return false;
return (
this.rbacService.can('post:edit:any') ||
(post.authorId === user.id && this.rbacService.can('post:edit:own'))
);
}
canDeletePost(post: Post): boolean {
const user = this.rbacService.getUser();
if (!user) return false;
return (
this.rbacService.can('post:delete:any') ||
(post.authorId === user.id && this.rbacService.can('post:delete:own'))
);
}
}
```
## HTTP Interceptor
```typescript
// interceptors/rbac.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { RBACService } from '@fire-shield/angular';
import { Router } from '@angular/router';
@Injectable()
export class RBACInterceptor implements HttpInterceptor {
constructor(
private rbacService: RBACService,
private router: Router
) {}
intercept(
request: HttpRequest,
next: HttpHandler
): Observable> {
const user = this.rbacService.getUser();
// Add user roles to header
if (user) {
request = request.clone({
setHeaders: {
'X-User-Roles': user.roles.join(',')
}
});
}
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 403) {
// Handle forbidden error
this.router.navigate(['/unauthorized']);
}
return throwError(() => error);
})
);
}
}
```
```typescript
// app.config.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { RBACInterceptor } from './interceptors/rbac.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: RBACInterceptor,
multi: true
}
]
};
```
## TypeScript Support
```typescript
import type { RBACUser } from '@fire-shield/angular';
interface User extends RBACUser {
email: string;
name: string;
}
const user: User = {
id: 'user-1',
roles: ['editor'],
email: 'user@example.com',
name: 'John Doe'
};
this.rbacService.setUser(user);
```
## Best Practices
### 1. Use Observables for Reactive UI
```typescript
export class MyComponent {
canEdit$ = this.rbacService.can$('post:edit');
isAdmin$ = this.rbacService.hasRole$('admin');
constructor(private rbacService: RBACService) {}
}
```
### 2. Centralize Permission Logic
```typescript
@Injectable({ providedIn: 'root' })
export class PermissionsService {
constructor(private rbacService: RBACService) {}
canAccessAdminPanel() {
return this.rbacService.hasRole('admin');
}
}
```
### 3. Use Route Guards
```typescript
const routes: Routes = [
{
path: 'admin',
canActivate: [canActivateRole],
data: { role: 'admin' }
}
];
```
## Next Steps
### Svelte Integration
> Source: https://fire-shield.dev/frameworks/svelte
# Svelte Integration
## Features
## Installation
```bash
npm install @fire-shield/svelte@3.1.1 @fire-shield/core@3.1.1
```
## Setup
### Create RBAC Store
```typescript
// stores/rbac.ts
import { RBAC } from '@fire-shield/core';
import { createRBACStore } from '@fire-shield/svelte';
const rbac = new RBAC();
// Define roles
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
// Set hierarchy
rbac.getRoleHierarchy().setRoleLevel('admin', 10);
rbac.getRoleHierarchy().setRoleLevel('editor', 5);
rbac.getRoleHierarchy().setRoleLevel('viewer', 1);
export const rbacStore = createRBACStore(rbac, null);
```
## Store API
### createRBACStore(rbac, initialUser?)
```typescript
import { RBAC } from '@fire-shield/core';
import { createRBACStore } from '@fire-shield/svelte';
const rbac = new RBAC();
const store = createRBACStore(rbac, null);
```
```typescript
{
rbac: RBAC;
user: Writable;
can: (permission: string) => Readable;
hasRole: (role: string) => Readable;
authorize: (permission: string) => Readable;
canAll: (permissions: string[]) => Readable;
canAny: (permissions: string[]) => Readable;
}
```
## Basic Usage
### Update User
```svelte
Login
Logout
```
### Check Permissions
```svelte
{#if $canWrite}
Create Post
{/if}
{#if $isAdmin}
Admin Panel
{/if}
```
## Reactive Stores
### can(permission)
```svelte
{#if $canWrite}
Edit
{/if}
{#if $canPublish}
Publish
{/if}
{#if $canDelete}
Delete
{/if}
```
### hasRole(role)
```svelte
Home
{#if $isEditor}
Posts
{/if}
{#if $isAdmin}
Admin
{/if}
```
### authorize(permission)
```svelte
{#if !$result.allowed}
Access denied: {$result.reason}
{:else}
Delete Post
{/if}
```
### canAll(permissions)
```svelte
{#if $hasFullAccess}
Full Access Mode
{/if}
```
### canAny(permissions)
```svelte
{#if $canAccessPosts}
View Posts
{/if}
```
## Svelte Actions
### use:can
```svelte
Create Post
Create Post
```
### use:role
```svelte
```
### use:cannot
```svelte
Upgrade to Premium for more features!
Upgrade Now
```
## Component Examples
### Navigation Menu
```svelte
Home
{#if $canManagePosts}
Posts
{/if}
{#if $canManageUsers}
Users
{/if}
{#if $isAdmin}
Admin
{/if}
```
### Post Actions
```svelte
{#if $canEdit}
Edit
{/if}
{#if $canPublish}
Publish
{/if}
{#if $canDelete}
Delete
{/if}
```
### User Login/Logout
```svelte
{#if isLoggedIn}
Logged in as: {$rbacStore.user?.id}
Logout
{:else}
Login as Admin
Login as Editor
Login as Viewer
{/if}
```
### Form with Conditional Fields
```svelte
```
### Using Actions
```svelte
Create Post
Admin Controls
This section is only visible to administrators
Upgrade to Premium for more features!
Upgrade Now
```
## SvelteKit Integration
### Layout with RBAC
```svelte
Home
{#if $isAdmin}
Admin
{/if}
```
### Protected Route
```svelte
{#if $isAdmin}
Admin Dashboard
{/if}
```
### Server Load Function
```typescript
// routes/admin/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals }) => {
const user = locals.user;
if (!user || !user.roles.includes('admin')) {
throw redirect(303, '/unauthorized');
}
return {
user
};
};
```
## TypeScript Support
```typescript
import type { RBACUser } from '@fire-shield/svelte';
interface User extends RBACUser {
email: string;
name: string;
}
const user: User = {
id: 'user-1',
roles: ['editor'],
email: 'user@example.com',
name: 'John Doe',
};
rbacStore.user.set(user);
```
## Best Practices
### 1. Use Derived Stores
```typescript
// Create derived stores for commonly used checks
const canEdit = rbacStore.can('post:edit');
const canDelete = rbacStore.can('post:delete');
const isAdmin = rbacStore.hasRole('admin');
```
### 2. Centralize Permission Logic
```typescript
// lib/permissions.ts
import { rbacStore } from './stores/rbac';
import { derived } from 'svelte/store';
export const permissions = {
canManageUsers: derived(rbacStore.user, ($user) =>
rbacStore.rbac.hasPermission($user, 'user:manage')
),
canEditPost: (post: Post) =>
derived(rbacStore.user, ($user) => {
if (!$user) return false;
return (
rbacStore.rbac.hasPermission($user, 'post:edit:any') ||
(post.authorId === $user.id &&
rbacStore.rbac.hasPermission($user, 'post:edit:own'))
);
}),
};
```
### 3. Use Actions for Declarative UI
```svelte
Create Post
```
## Next Steps
### SvelteKit Integration
> Source: https://fire-shield.dev/frameworks/sveltekit
# SvelteKit Integration
## Features
## Installation
```bash
npm install @fire-shield/sveltekit@3.1.1 @fire-shield/core@3.1.1
```
## Quick Start
### 1. Initialize RBAC
```typescript
// src/lib/rbac.ts
import { RBAC } from '@fire-shield/core';
export const rbac = new RBAC();
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
```
### 2. Setup Hooks
```typescript
// src/hooks.server.ts
import { createRBACHandle } from '@fire-shield/sveltekit';
import { rbac } from '$lib/rbac';
export const handle = createRBACHandle(rbac, {
getUser: async (event) => {
// Get user from session/cookie
const session = await event.locals.getSession();
return session?.user;
}
});
```
### 3. Use in Pages
```svelte
{#if canManageUsers}
Create User
{/if}
```
## API
### createRBACHandle(rbac, options)
```typescript
interface RBACHandleOptions {
getUser?: (event: RequestEvent) => Promise | RBACUser | undefined;
onUnauthorized?: (event: RequestEvent, result: AuthorizationResult) => Response;
}
```
```typescript
// src/hooks.server.ts
import { createRBACHandle } from '@fire-shield/sveltekit';
import { redirect } from '@sveltejs/kit';
export const handle = createRBACHandle(rbac, {
getUser: async (event) => {
const sessionId = event.cookies.get('session');
if (!sessionId) return undefined;
return await getUserFromSession(sessionId);
},
onUnauthorized: (event, result) => {
throw redirect(303, '/login');
}
});
```
### requirePermission(permission)
```typescript
// src/routes/admin/+page.server.ts
import { requirePermission } from '@fire-shield/sveltekit';
export const load = requirePermission('admin:access', async (event) => {
// This only runs if user has 'admin:access' permission
const users = await getUsers();
return { users };
});
```
### requireRole(role)
```typescript
// src/routes/admin/+page.server.ts
import { requireRole } from '@fire-shield/sveltekit';
export const load = requireRole('admin', async (event) => {
// This only runs if user has 'admin' role
const settings = await getAdminSettings();
return { settings };
});
```
## Server-Side Protection
### Protecting Load Functions
```typescript
// src/routes/posts/+page.server.ts
import { requirePermission } from '@fire-shield/sveltekit';
export const load = requirePermission('post:read', async ({ locals }) => {
const posts = await db.post.findMany();
return { posts };
});
```
### Protecting Actions
```typescript
// src/routes/posts/+page.server.ts
import { requirePermission } from '@fire-shield/sveltekit';
import { fail } from '@sveltejs/kit';
export const actions = {
create: requirePermission('post:write', async ({ request, locals }) => {
const data = await request.formData();
const title = data.get('title');
await db.post.create({
data: { title, authorId: locals.user.id }
});
return { success: true };
}),
delete: requirePermission('post:delete', async ({ request }) => {
const data = await request.formData();
const postId = data.get('postId');
await db.post.delete({ where: { id: postId } });
return { success: true };
})
};
```
### Custom Authorization Logic
```typescript
// src/routes/posts/[id]/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, locals }) => {
const post = await db.post.findUnique({ where: { id: params.id } });
// Custom authorization: Check if user owns the post or is admin
const canEdit =
locals.rbac.hasPermission(locals.user, 'post:edit') &&
(post.authorId === locals.user.id || locals.rbac.hasRole(locals.user, 'admin'));
return {
post,
canEdit
};
};
```
## Client-Side Usage
### In Svelte Components
```svelte
{#if rbac.hasPermission(user, 'post:write')}
Create Post
{/if}
{#if rbac.hasPermission(user, 'post:delete')}
Delete All Posts
{/if}
```
### Reactive Permission Checks
```svelte
{#if canWrite}
{/if}
{#if isAdmin}
{/if}
```
## TypeScript Setup
```typescript
// src/app.d.ts
import type { RBAC, RBACUser } from '@fire-shield/core';
declare global {
namespace App {
interface Locals {
user?: RBACUser;
rbac: RBAC;
}
}
}
export {};
```
## Layout Load Functions
```typescript
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
return {
user: locals.user,
rbac: locals.rbac
};
};
```
```svelte
```
## Form Actions with Authorization
```svelte
{#if data.rbac.hasPermission(data.user, 'post:write')}
{:else}
You don't have permission to create posts.
{/if}
```
```typescript
// src/routes/posts/create/+page.server.ts
import { requirePermission } from '@fire-shield/sveltekit';
import { redirect } from '@sveltejs/kit';
export const actions = {
default: requirePermission('post:write', async ({ request, locals }) => {
const data = await request.formData();
await db.post.create({
data: {
title: data.get('title'),
content: data.get('content'),
authorId: locals.user.id
}
});
throw redirect(303, '/posts');
})
};
```
## API Routes Protection
```typescript
// src/routes/api/users/+server.ts
import { json } from '@sveltejs/kit';
import { requirePermission } from '@fire-shield/sveltekit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = requirePermission('user:read', async () => {
const users = await db.user.findMany();
return json(users);
});
export const POST: RequestHandler = requirePermission('user:write', async ({ request }) => {
const data = await request.json();
const user = await db.user.create({ data });
return json(user, { status: 201 });
});
export const DELETE: RequestHandler = requirePermission('user:delete', async ({ request }) => {
const { id } = await request.json();
await db.user.delete({ where: { id } });
return json({ success: true });
});
```
## Error Handling
```typescript
// src/hooks.server.ts
import { createRBACHandle } from '@fire-shield/sveltekit';
import { error } from '@sveltejs/kit';
export const handle = createRBACHandle(rbac, {
getUser: async (event) => {
return await getUserFromSession(event);
},
onUnauthorized: (event, result) => {
throw error(403, {
message: 'Access Denied',
reason: result.reason
});
}
});
```
## Best Practices
## Advanced Example
```typescript
// src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import { createRBACHandle } from '@fire-shield/sveltekit';
import { redirect } from '@sveltejs/kit';
const authHandle = async ({ event, resolve }) => {
const sessionId = event.cookies.get('session');
if (sessionId) {
const user = await getUserFromSession(sessionId);
event.locals.user = user;
}
return resolve(event);
};
const rbacHandle = createRBACHandle(rbac, {
getUser: (event) => event.locals.user,
onUnauthorized: () => {
throw redirect(303, '/login');
}
});
export const handle = sequence(authHandle, rbacHandle);
```
## Next Steps
============================================================
## Frameworks — Backend
============================================================
### Express Integration
> Source: https://fire-shield.dev/frameworks/express
# Express Integration
## Installation
```bash
npm install @fire-shield/express@3.1.1 @fire-shield/core@3.1.1 express
```
## Setup
### Basic Setup
```typescript
import express from 'express'
import { RBAC } from '@fire-shield/core'
import { ExpressRBACAdapter } from '@fire-shield/express'
const app = express()
const rbac = new RBAC()
// Define roles
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
rbac.createRole('viewer', ['posts:read'])
// Create RBAC adapter
const rbacMiddleware = new ExpressRBACAdapter(rbac, {
getUser: (req) => req.user // Get user from request
})
```
## Middleware
### requirePermission
```typescript
import { requirePermission } from '@fire-shield/express'
// Require single permission
app.post('/posts',
requirePermission('posts:write', { rbac }),
(req, res) => {
res.json({ message: 'Post created' })
}
)
app.delete('/posts/:id',
requirePermission('posts:delete', { rbac }),
(req, res) => {
res.json({ message: 'Post deleted' })
}
)
```
### adapter.permission
```typescript
app.post('/posts',
rbacMiddleware.permission('posts:write'),
(req, res) => {
res.json({ message: 'Post created' })
}
)
```
### adapter.any / adapter.all
```typescript
// Require at least one permission
app.get('/admin',
rbacMiddleware.any('admin:access', 'moderator:access'),
(req, res) => {
res.json({ message: 'Admin panel' })
}
)
// Require all permissions
app.delete('/posts/:id',
rbacMiddleware.all('posts:delete', 'posts:own'),
(req, res) => {
res.json({ message: 'Post deleted' })
}
)
```
### requireRole
```typescript
import { requireRole } from '@fire-shield/express'
// Require single role
app.get('/admin/users',
requireRole('admin', { rbac }),
(req, res) => {
res.json({ users: [] })
}
)
```
### adapter.role
```typescript
app.get('/admin/users',
rbacMiddleware.role('admin'),
(req, res) => {
res.json({ users: [] })
}
)
```
### requireResourceAction
```typescript
import { requireResourceAction } from '@fire-shield/express'
app.delete('/users/:id',
requireResourceAction('users', 'delete', { rbac }),
(req, res) => {
res.json({ message: 'User deleted' })
}
)
```
### denyPermission
```typescript
import { denyPermission } from '@fire-shield/express'
// Deny a permission for the user making this request
app.post('/restrict',
denyPermission('posts:delete', { rbac }),
(req, res) => {
res.json({ message: 'posts:delete has been denied for your account' })
}
)
```
### allowPermission
```typescript
import { allowPermission } from '@fire-shield/express'
// Remove a previously denied permission
app.post('/restore',
allowPermission('posts:delete', { rbac }),
(req, res) => {
res.json({ message: 'posts:delete has been restored' })
}
)
```
### requireNotDenied
```typescript
import { requireNotDenied } from '@fire-shield/express'
// Block if permission is in the deny list (even if role grants it)
app.delete('/posts/:id',
requireNotDenied('posts:delete', { rbac }),
rbacMiddleware.permission('posts:delete'),
(req, res) => {
res.json({ message: 'Post deleted' })
}
)
```
## Custom Error Handling
### Default Behavior
```typescript
// Default response for denied access
{
error: 'Forbidden',
message: 'Insufficient permissions'
}
```
### Custom Error Handler
```typescript
const rbacMiddleware = new ExpressRBACAdapter(rbac, {
getUser: (req) => req.user,
onUnauthorized: (result, req, res, next) => {
res.status(403).json({
error: 'Access Denied',
message: result.reason,
requiredPermission: result.reason
})
}
})
```
### Error Middleware
```typescript
app.use((err, req, res, next) => {
if (err.name === 'RBACError') {
return res.status(403).json({
error: 'RBAC Error',
message: err.message,
permission: err.permission
})
}
next(err)
})
```
## Authentication Integration
### With Passport.js
```typescript
import passport from 'passport'
app.use(passport.initialize())
app.post('/posts',
passport.authenticate('jwt', { session: false }),
rbacMiddleware.permission('posts:write'),
(req, res) => {
// req.user is set by passport
res.json({ message: 'Post created' })
}
)
```
### With Express Session
```typescript
import session from 'express-session'
app.use(session({
secret: 'your-secret',
resave: false,
saveUninitialized: true
}))
// Attach user to request from session
app.use((req, res, next) => {
if (req.session.userId) {
req.user = getUserById(req.session.userId)
}
next()
})
// Use RBAC middleware
app.post('/posts',
rbacMiddleware.permission('posts:write'),
(req, res) => {
res.json({ message: 'Post created' })
}
)
```
### With JWT
```typescript
import jwt from 'jsonwebtoken'
// JWT middleware
app.use((req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (token) {
try {
req.user = jwt.verify(token, 'secret')
} catch (err) {
// Invalid token
}
}
next()
})
// RBAC middleware
app.delete('/posts/:id',
rbacMiddleware.permission('posts:delete'),
(req, res) => {
res.json({ message: 'Post deleted' })
}
)
```
## Programmatic Permission Checks
```typescript
app.get('/posts/:id', (req, res) => {
const post = getPost(req.params.id)
// req.authorization is set by the RBAC middleware after a successful check
const authorized = req.authorization?.allowed
res.json(post)
})
```
## Dynamic Permissions
```typescript
import { requirePermission } from '@fire-shield/express'
app.delete('/posts/:id',
requirePermission('posts:delete', { rbac }),
async (req, res) => {
const post = await getPost(req.params.id)
// Check if user owns the post
const isOwner = post.authorId === req.user.id
if (!isOwner) {
return res.status(403).json({ error: 'Forbidden' })
}
await deletePost(req.params.id)
res.json({ message: 'Post deleted' })
}
)
```
## Route Organization
### Grouped Routes
```typescript
const adminRouter = express.Router()
// All admin routes require admin role
adminRouter.use(rbacMiddleware.role('admin'))
adminRouter.get('/users', (req, res) => {
res.json({ users: [] })
})
adminRouter.post('/users', (req, res) => {
res.json({ message: 'User created' })
})
app.use('/admin', adminRouter)
```
### Nested Permissions
```typescript
const postsRouter = express.Router()
// All routes require read permission
postsRouter.use(rbacMiddleware.permission('posts:read'))
postsRouter.get('/', (req, res) => {
res.json({ posts: [] })
})
// Additional permission for write
postsRouter.post('/',
rbacMiddleware.permission('posts:write'),
(req, res) => {
res.json({ message: 'Post created' })
}
)
// Additional permission for delete
postsRouter.delete('/:id',
rbacMiddleware.permission('posts:delete'),
(req, res) => {
res.json({ message: 'Post deleted' })
}
)
app.use('/posts', postsRouter)
```
## TypeScript Support
```typescript
import { Request, Response, NextFunction } from 'express'
import { RBACUser, AuthorizationResult } from '@fire-shield/core'
// Express Request is already extended by @fire-shield/express:
// req.user?: RBACUser
// req.authorization?: AuthorizationResult
// Type-safe route handler
app.get('/posts', (req: Request, res: Response) => {
if (req.authorization?.allowed) {
// Access granted
}
})
```
## Best Practices
### 1. Protect All Sensitive Routes
```typescript
// ✅ Good: Protected routes
app.post('/posts',
rbacMiddleware.permission('posts:write'),
createPost
)
app.delete('/posts/:id',
rbacMiddleware.permission('posts:delete'),
deletePost
)
// ❌ Avoid: Unprotected sensitive routes
app.delete('/posts/:id', deletePost)
```
### 2. Use Router-Level Middleware
```typescript
// ✅ Good: Protect entire router
const adminRouter = express.Router()
adminRouter.use(rbacMiddleware.role('admin'))
adminRouter.get('/users', getUsers)
adminRouter.post('/users', createUser)
// ❌ Avoid: Repeat on every route
app.get('/admin/users',
rbacMiddleware.role('admin'),
getUsers
)
app.post('/admin/users',
rbacMiddleware.role('admin'),
createUser
)
```
### 3. Handle Errors Gracefully
```typescript
// ✅ Good: Custom error handling
const rbacMiddleware = new ExpressRBACAdapter(rbac, {
getUser: (req) => req.user,
onUnauthorized: (result, req, res, next) => {
console.warn(`Access denied to ${req.path}`, {
user: req.user?.id,
reason: result.reason
})
res.status(403).json({
error: 'Forbidden',
message: 'Insufficient permissions'
})
}
})
```
### 4. Document Permission Requirements
```typescript
/**
* Create a new post
* @route POST /posts
* @permission posts:write
* @returns {Post} Created post
*/
app.post('/posts',
rbacMiddleware.permission('posts:write'),
createPost
)
```
## Complete Example
```typescript
import express from 'express'
import { RBAC } from '@fire-shield/core'
import { ExpressRBACAdapter } from '@fire-shield/express'
const app = express()
const rbac = new RBAC()
// Setup roles
rbac.createRole('admin', ['*'])
rbac.createRole('editor', ['posts:*', 'comments:*'])
rbac.createRole('viewer', ['posts:read', 'comments:read'])
// RBAC adapter
const rbacMiddleware = new ExpressRBACAdapter(rbac, {
getUser: (req) => req.user,
onUnauthorized: (result, req, res, next) => {
res.status(403).json({
error: 'Forbidden',
message: result.reason || 'Insufficient permissions'
})
}
})
app.use(express.json())
// Public routes
app.get('/health', (req, res) => {
res.json({ status: 'ok' })
})
// Protected routes
app.get('/posts',
rbacMiddleware.permission('posts:read'),
(req, res) => {
res.json({ posts: [] })
}
)
app.post('/posts',
rbacMiddleware.permission('posts:write'),
(req, res) => {
res.json({ message: 'Post created' })
}
)
app.delete('/posts/:id',
rbacMiddleware.permission('posts:delete'),
(req, res) => {
res.json({ message: 'Post deleted' })
}
)
// Admin routes
const adminRouter = express.Router()
adminRouter.use(rbacMiddleware.role('admin'))
adminRouter.get('/users', (req, res) => {
res.json({ users: [] })
})
adminRouter.post('/users', (req, res) => {
res.json({ message: 'User created' })
})
app.use('/admin', adminRouter)
app.listen(3000, () => {
console.log('Server running on http://localhost:3000')
})
```
## Next Steps
### Fastify Integration
> Source: https://fire-shield.dev/frameworks/fastify
# Fastify Integration
## Installation
```bash
npm install @fire-shield/fastify@3.1.1 @fire-shield/core@3.1.1 fastify
```
## Setup
### Basic Setup
```typescript
import Fastify from 'fastify'
import { RBAC } from '@fire-shield/core'
import { FastifyRBACAdapter, fastifyRBACPlugin } from '@fire-shield/fastify'
const fastify = Fastify()
const rbac = new RBAC()
// Define roles
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
rbac.createRole('viewer', ['posts:read'])
// Create RBAC adapter
const rbacHooks = new FastifyRBACAdapter(rbac, {
getUser: (request) => request.user
})
// Optionally register as a Fastify plugin (decorates fastify.rbac and fastify.authorize)
fastify.register(fastifyRBACPlugin(rbac))
```
## Route Protection
### adapter.permission
```typescript
// Single permission
fastify.post('/posts', {
preHandler: rbacHooks.permission('posts:write')
}, async (request, reply) => {
return { message: 'Post created' }
})
```
### adapter.any / adapter.all
```typescript
// Require at least one permission
fastify.get('/admin', {
preHandler: rbacHooks.any('admin:access', 'moderator:access')
}, async (request, reply) => {
return { message: 'Admin panel' }
})
// Require all permissions
fastify.delete('/posts/:id', {
preHandler: rbacHooks.all('posts:delete', 'posts:own')
}, async (request, reply) => {
return { message: 'Post deleted' }
})
```
### requirePermission
```typescript
import { requirePermission } from '@fire-shield/fastify'
fastify.post('/posts', {
preHandler: requirePermission('posts:write', { rbac })
}, async (request, reply) => {
return { message: 'Post created' }
})
```
### requireRole
```typescript
import { requireRole } from '@fire-shield/fastify'
// Single role
fastify.get('/admin/users', {
preHandler: requireRole('admin', { rbac })
}, async (request, reply) => {
return { users: [] }
})
```
### adapter.role
```typescript
fastify.get('/admin/users', {
preHandler: rbacHooks.role('admin')
}, async (request, reply) => {
return { users: [] }
})
```
### requireResourceAction
```typescript
import { requireResourceAction } from '@fire-shield/fastify'
fastify.delete('/users/:id', {
preHandler: requireResourceAction('users', 'delete', { rbac })
}, async (request, reply) => {
return { message: 'User deleted' }
})
```
### adapter.denyPermission / adapter.allowPermission
```typescript
// Deny a permission for the user making this request
fastify.post('/restrict', {
preHandler: rbacHooks.denyPermission('posts:delete')
}, async (request, reply) => {
return { message: 'posts:delete has been denied' }
})
// Remove a previously denied permission
fastify.post('/restore', {
preHandler: rbacHooks.allowPermission('posts:delete')
}, async (request, reply) => {
return { message: 'posts:delete has been restored' }
})
```
### adapter.notDenied
```typescript
fastify.delete('/posts/:id', {
preHandler: [
rbacHooks.notDenied('posts:delete'),
rbacHooks.permission('posts:delete')
]
}, async (request, reply) => {
return { message: 'Post deleted' }
})
```
### Standalone denyPermission / allowPermission / requireNotDenied
```typescript
import { denyPermission, allowPermission, requireNotDenied } from '@fire-shield/fastify'
fastify.post('/restrict', {
preHandler: denyPermission('posts:delete', { rbac })
}, async (request, reply) => {
return { message: 'Permission denied' }
})
```
### adapter.getDeniedPermissions / adapter.isDenied
```typescript
fastify.get('/my-permissions', async (request, reply) => {
const denied = rbacHooks.getDeniedPermissions(request)
const cannotDelete = rbacHooks.isDenied(request, 'posts:delete')
return { denied, cannotDelete }
})
```
## Custom Error Handling
### Default Behavior
```json
{
"statusCode": 403,
"error": "Forbidden",
"message": "Insufficient permissions"
}
```
### Custom Error Handler
```typescript
const rbacHooks = new FastifyRBACAdapter(rbac, {
getUser: (request) => request.user,
onUnauthorized: (result, request, reply) => {
reply.code(403).send({
error: 'Access Denied',
message: result.reason,
requiredPermission: result.reason
})
}
})
```
### Error Hook
```typescript
fastify.setErrorHandler((error, request, reply) => {
if (error.statusCode === 403) {
request.log.warn('RBAC access denied', {
user: request.user?.id,
path: request.url
})
}
reply.send(error)
})
```
## Authentication Integration
### With JWT
```typescript
import fastifyJwt from '@fastify/jwt'
// Register JWT plugin
fastify.register(fastifyJwt, {
secret: 'your-secret-key'
})
// Authentication decorator
fastify.decorate('authenticate', async (request, reply) => {
try {
await request.jwtVerify()
} catch (err) {
reply.send(err)
}
})
// Use authentication and RBAC together
fastify.post('/posts', {
preHandler: [
fastify.authenticate,
rbacHooks.permission('posts:write')
]
}, async (request, reply) => {
return { message: 'Post created' }
})
```
### With Session
```typescript
import fastifySession from '@fastify/session'
import fastifyCookie from '@fastify/cookie'
fastify.register(fastifyCookie)
fastify.register(fastifySession, {
secret: 'a-secret-with-minimum-length-of-32-characters',
cookie: { secure: false }
})
// Attach user from session
fastify.addHook('preHandler', async (request, reply) => {
if (request.session.userId) {
request.user = await getUserById(request.session.userId)
}
})
// Protected route
fastify.delete('/posts/:id', {
preHandler: rbacHooks.permission('posts:delete')
}, async (request, reply) => {
return { message: 'Post deleted' }
})
```
## Programmatic Permission Checks
```typescript
fastify.get('/posts/:id', async (request, reply) => {
const post = await getPost(request.params.id)
// request.authorization is set by the RBAC hook after a successful check
const authorized = (request as any).authorization?.allowed
return post
})
```
## Route Organization
### Grouped Routes
```typescript
// Admin routes plugin
async function adminRoutes(fastify, options) {
// All admin routes require admin role
fastify.addHook('preHandler', rbacHooks.role('admin'))
fastify.get('/users', async (request, reply) => {
return { users: [] }
})
fastify.post('/users', async (request, reply) => {
return { message: 'User created' }
})
}
fastify.register(adminRoutes, { prefix: '/admin' })
```
### Nested Permissions
```typescript
// Posts routes plugin
async function postsRoutes(fastify, options) {
// All routes require read permission
fastify.addHook('preHandler', rbacHooks.permission('posts:read'))
fastify.get('/', async (request, reply) => {
return { posts: [] }
})
// Additional permission for write
fastify.post('/', {
preHandler: rbacHooks.permission('posts:write')
}, async (request, reply) => {
return { message: 'Post created' }
})
// Additional permission for delete
fastify.delete('/:id', {
preHandler: rbacHooks.permission('posts:delete')
}, async (request, reply) => {
return { message: 'Post deleted' }
})
}
fastify.register(postsRoutes, { prefix: '/posts' })
```
## Dynamic Permissions
```typescript
fastify.delete('/posts/:id',
{ preHandler: rbacHooks.permission('posts:delete') },
async (request, reply) => {
const post = await getPost(request.params.id)
// Check if user owns the post
const isOwner = post.authorId === request.user.id
if (!isOwner) {
return reply.code(403).send({ error: 'Forbidden' })
}
await deletePost(request.params.id)
return { message: 'Post deleted' }
}
)
```
## TypeScript Support
```typescript
import { FastifyRequest, FastifyReply } from 'fastify'
import { RBACUser } from '@fire-shield/core'
// Fastify Request is already extended by @fire-shield/fastify:
// request.user?: RBACUser
// Type-safe route handler
fastify.get<{
Params: { id: string }
}>('/posts/:id', async (request, reply) => {
return { id: request.params.id }
})
```
## Best Practices
### 1. Use Hooks for Route Groups
```typescript
// ✅ Good: Apply to all routes in plugin
async function adminRoutes(fastify) {
fastify.addHook('preHandler', rbacHooks.role('admin'))
fastify.get('/users', getUsers)
fastify.post('/users', createUser)
fastify.delete('/users/:id', deleteUser)
}
// ❌ Avoid: Repeat on every route
fastify.get('/admin/users', {
preHandler: rbacHooks.role('admin')
}, getUsers)
fastify.post('/admin/users', {
preHandler: rbacHooks.role('admin')
}, createUser)
```
### 2. Chain Handlers
```typescript
// ✅ Good: Multiple handlers in array
fastify.post('/posts', {
preHandler: [
fastify.authenticate,
rbacHooks.permission('posts:write'),
validatePostData
]
}, createPost)
// ✅ Also good: Single combined handler
fastify.post('/posts', {
preHandler: rbacHooks.permission('posts:write')
}, createPost)
```
### 3. Handle Errors Properly
```typescript
// ✅ Good: Custom error handling
const rbacHooks = new FastifyRBACAdapter(rbac, {
getUser: (request) => request.user,
onUnauthorized: (result, request, reply) => {
request.log.warn({
path: request.url,
user: request.user?.id,
reason: result.reason
}, 'RBAC access denied')
reply.code(403).send({
error: 'Forbidden',
message: 'Insufficient permissions'
})
}
})
```
### 4. Document Routes
```typescript
fastify.post('/posts', {
schema: {
description: 'Create a new post',
tags: ['posts'],
summary: 'Create post',
security: [{ bearerAuth: [] }]
},
preHandler: rbacHooks.permission('posts:write')
}, async (request, reply) => {
return { message: 'Post created' }
})
```
## OpenAPI/Swagger Integration
```typescript
import fastifySwagger from '@fastify/swagger'
import fastifySwaggerUI from '@fastify/swagger-ui'
fastify.register(fastifySwagger, {
openapi: {
info: {
title: 'API Documentation',
version: '1.0.0'
},
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
}
})
fastify.register(fastifySwaggerUI, {
routePrefix: '/documentation'
})
// Document permission requirements
fastify.post('/posts', {
schema: {
description: 'Create a new post (requires posts:write permission)',
tags: ['posts'],
security: [{ bearerAuth: [] }],
response: {
200: {
type: 'object',
properties: {
message: { type: 'string' }
}
},
403: {
type: 'object',
properties: {
error: { type: 'string' }
}
}
}
},
preHandler: rbacHooks.permission('posts:write')
}, async (request, reply) => {
return { message: 'Post created' }
})
```
## Complete Example
```typescript
import Fastify from 'fastify'
import { RBAC } from '@fire-shield/core'
import { FastifyRBACAdapter, fastifyRBACPlugin } from '@fire-shield/fastify'
import fastifyJwt from '@fastify/jwt'
const fastify = Fastify({
logger: true
})
const rbac = new RBAC()
// Setup roles
rbac.createRole('admin', ['*'])
rbac.createRole('editor', ['posts:*', 'comments:*'])
rbac.createRole('viewer', ['posts:read', 'comments:read'])
// JWT plugin
fastify.register(fastifyJwt, {
secret: 'your-secret-key'
})
// RBAC adapter
const rbacHooks = new FastifyRBACAdapter(rbac, {
getUser: (request) => request.user,
onUnauthorized: (result, request, reply) => {
reply.code(403).send({
error: 'Forbidden',
message: result.reason || 'Insufficient permissions'
})
}
})
// Register RBAC plugin (optional, adds fastify.rbac and fastify.authorize decorators)
fastify.register(fastifyRBACPlugin(rbac))
// Authentication decorator
fastify.decorate('authenticate', async (request, reply) => {
try {
await request.jwtVerify()
} catch (err) {
reply.send(err)
}
})
// Public route
fastify.get('/health', async () => {
return { status: 'ok' }
})
// Protected routes
fastify.get('/posts', {
preHandler: [
fastify.authenticate,
rbacHooks.permission('posts:read')
]
}, async () => {
return { posts: [] }
})
fastify.post('/posts', {
preHandler: [
fastify.authenticate,
rbacHooks.permission('posts:write')
]
}, async () => {
return { message: 'Post created' }
})
fastify.delete('/posts/:id', {
preHandler: [
fastify.authenticate,
rbacHooks.permission('posts:delete')
]
}, async () => {
return { message: 'Post deleted' }
})
// Admin routes
async function adminRoutes(fastify) {
fastify.addHook('preHandler', rbacHooks.role('admin'))
fastify.get('/users', async () => {
return { users: [] }
})
fastify.post('/users', async () => {
return { message: 'User created' }
})
}
fastify.register(adminRoutes, { prefix: '/admin' })
fastify.listen({ port: 3000 }, (err) => {
if (err) throw err
console.log('Server running on http://localhost:3000')
})
```
## Next Steps
### Hono Integration
> Source: https://fire-shield.dev/frameworks/hono
# Hono Integration
## Features
## Installation
```bash
npm install @fire-shield/hono@3.1.1 @fire-shield/core@3.1.1 hono
```
## Quick Start
```typescript
import { Hono } from 'hono';
import { RBAC } from '@fire-shield/core';
import { HonoRBACAdapter } from '@fire-shield/hono';
const app = new Hono();
const rbac = new RBAC();
// Setup roles
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
// Create adapter
const rbacMiddleware = new HonoRBACAdapter(rbac);
// Add user to context
app.use('*', async (c, next) => {
c.set('user', { id: 'user-1', roles: ['editor'] });
await next();
});
// Protect routes
app.get('/admin/users',
rbacMiddleware.permission('user:read'),
(c) => {
return c.json({ users: [] });
}
);
export default app;
```
## API
### new HonoRBACAdapter(rbac, options?)
```typescript
interface HonoRBACOptions {
getUser?: (c: Context) => RBACUser;
getPermission?: (c: Context) => string;
getResource?: (c: Context) => string;
getAction?: (c: Context) => string;
onUnauthorized?: (result: AuthorizationResult, c: Context) => Response;
onError?: (error: Error, c: Context) => Response;
}
```
```typescript
const rbacMiddleware = new HonoRBACAdapter(rbac, {
getUser: (c) => c.get('user'),
onUnauthorized: (result, c) => {
return c.json({
error: 'Access Denied',
reason: result.reason
}, 403);
}
});
```
## Middleware Methods
### permission(permission: string)
```typescript
app.get('/admin',
rbacMiddleware.permission('admin:access'),
(c) => c.json({ admin: true })
);
```
### role(role: string)
```typescript
app.get('/admin',
rbacMiddleware.role('admin'),
(c) => c.json({ admin: true })
);
```
### resourceAction(resource: string, action: string)
```typescript
app.delete('/users/:id',
rbacMiddleware.resourceAction('user', 'delete'),
(c) => c.json({ deleted: true })
);
```
### all(...permissions: string[])
```typescript
app.post('/admin/users',
rbacMiddleware.all('user:create', 'user:write'),
(c) => c.json({ created: true })
);
```
### any(...permissions: string[])
```typescript
app.get('/dashboard',
rbacMiddleware.any('admin:access', 'moderator:access'),
(c) => c.json({ dashboard: 'data' })
);
```
### middleware(customOptions: Partial<HonoRBACOptions>)
```typescript
app.get('/api/v1/*',
rbacMiddleware.middleware({
getPermission: (c) => c.req.header('x-required-permission'),
onUnauthorized: (result, c) => c.json({ error: 'Custom error' }, 403)
}),
handler
);
```
## Edge Runtime Examples
### Cloudflare Workers
```typescript
import { Hono } from 'hono';
import { RBAC } from '@fire-shield/core';
import { HonoRBACAdapter } from '@fire-shield/hono';
const app = new Hono();
const rbac = new RBAC();
const rbacMiddleware = new HonoRBACAdapter(rbac);
rbac.createRole('admin', ['*']);
// Extract user from Cloudflare request
app.use('*', async (c, next) => {
const apiKey = c.req.header('x-api-key');
const user = await getUserFromApiKey(apiKey);
c.set('user', user);
await next();
});
// Protected routes
app.get('/admin/*',
rbacMiddleware.role('admin'),
(c) => c.json({ admin: true })
);
export default app;
```
### Deno Deploy
```typescript
import { Hono } from 'hono';
import { RBAC } from '@fire-shield/core';
import { HonoRBACAdapter } from '@fire-shield/hono';
const app = new Hono();
const rbac = new RBAC();
const rbacMiddleware = new HonoRBACAdapter(rbac);
rbac.createRole('admin', ['*']);
app.use('*', async (c, next) => {
const token = c.req.header('authorization')?.replace('Bearer ', '');
const user = await verifyToken(token);
c.set('user', user);
await next();
});
app.get('/admin',
rbacMiddleware.role('admin'),
(c) => c.json({ data: 'admin data' })
);
Deno.serve(app.fetch);
```
### Vercel Edge Functions
```typescript
import { Hono } from 'hono';
import { RBAC } from '@fire-shield/core';
import { HonoRBACAdapter } from '@fire-shield/hono';
const app = new Hono();
const rbac = new RBAC();
const rbacMiddleware = new HonoRBACAdapter(rbac);
rbac.createRole('admin', ['*']);
app.get('/admin',
rbacMiddleware.role('admin'),
(c) => c.json({ admin: true })
);
export default app;
export const config = {
runtime: 'edge',
};
```
## Custom Configuration
### Custom User Extraction
```typescript
const rbacMiddleware = new HonoRBACAdapter(rbac, {
getUser: (c) => {
// Try multiple sources
return c.get('session')?.user || c.get('user') || null;
}
});
```
### Custom Unauthorized Handler
```typescript
const rbacMiddleware = new HonoRBACAdapter(rbac, {
onUnauthorized: (result, c) => {
return c.json({
error: 'Access Denied',
required: result.reason,
userId: result.user?.id,
timestamp: Date.now()
}, 403);
}
});
```
### Custom Error Handler
```typescript
const rbacMiddleware = new HonoRBACAdapter(rbac, {
onError: (error, c) => {
console.error('RBAC Error:', error);
return c.json({
error: 'Internal Server Error',
message: error.message
}, 500);
}
});
```
## Advanced Usage
### Multi-Tenant with Wildcards
```typescript
rbac.createRole('tenant-owner', ['tenant:*']);
app.use('/tenant/:tenantId/*', async (c, next) => {
const tenantId = c.req.param('tenantId');
const user = c.get('user');
// Check tenant-specific permission
if (!rbac.hasPermission(user, `tenant:${tenantId}:access`)) {
return c.json({ error: 'Forbidden' }, 403);
}
await next();
});
// Protected tenant routes
app.get('/tenant/:tenantId/data',
rbacMiddleware.permission('tenant:data:read'),
(c) => {
const tenantId = c.req.param('tenantId');
return c.json({ tenantId, data: [] });
}
);
```
### Multiple Permissions (AND/OR)
```typescript
// User must have BOTH permissions (AND)
app.post('/admin/critical',
rbacMiddleware.all('admin:access', 'critical:write'),
(c) => c.json({ success: true })
);
// User must have AT LEAST ONE permission (OR)
app.get('/dashboard',
rbacMiddleware.any('admin:view', 'moderator:view', 'analyst:view'),
(c) => c.json({ dashboard: 'data' })
);
```
### Dynamic Permission Checking
```typescript
const rbacMiddleware = new HonoRBACAdapter(rbac, {
getPermission: (c) => {
// Extract permission from route metadata
const route = c.req.path;
const method = c.req.method.toLowerCase();
if (route.startsWith('/api/')) {
return `api:${method}`;
}
return undefined;
}
});
// Will check for 'api:post' permission
app.post('/api/data',
rbacMiddleware.middleware({}),
(c) => c.json({ created: true })
);
```
### Resource Ownership
```typescript
app.delete('/posts/:id', async (c) => {
const user = c.get('user');
const postId = c.req.param('id');
const post = await getPost(postId);
// Check ownership
const isOwner = post.authorId === user.id;
const canDelete =
rbac.hasPermission(user, 'post:delete:any') ||
(isOwner && rbac.hasPermission(user, 'post:delete:own'));
if (!canDelete) {
return c.json({ error: 'Forbidden' }, 403);
}
await deletePost(postId);
return c.json({ success: true });
});
```
## Grouping Routes
### Protected Route Group
```typescript
const adminRoutes = new Hono();
// All admin routes require admin role
adminRoutes.use('*', rbacMiddleware.role('admin'));
adminRoutes.get('/users', (c) => c.json({ users: [] }));
adminRoutes.post('/users', (c) => c.json({ created: true }));
adminRoutes.delete('/users/:id', (c) => c.json({ deleted: true }));
// Mount admin routes
app.route('/admin', adminRoutes);
```
### Nested Permissions
```typescript
const apiRoutes = new Hono();
// All API routes require base permission
apiRoutes.use('*', rbacMiddleware.permission('api:access'));
// Additional permissions for specific routes
apiRoutes.get('/data',
rbacMiddleware.permission('api:read'),
(c) => c.json({ data: [] })
);
apiRoutes.post('/data',
rbacMiddleware.permission('api:write'),
(c) => c.json({ created: true })
);
app.route('/api', apiRoutes);
```
## Audit Logging
```typescript
import { BufferedAuditLogger } from '@fire-shield/core';
const auditLogger = new BufferedAuditLogger(
async (events) => {
// Save to KV storage (Cloudflare Workers)
await KV.put('audit-logs', JSON.stringify(events));
// Or send to external service
await fetch('https://api.example.com/audit', {
method: 'POST',
body: JSON.stringify(events)
});
},
{ maxBufferSize: 50, flushIntervalMs: 3000 }
);
const rbac = new RBAC({ auditLogger });
```
## Authentication Integration
### With JWT
```typescript
import { verify } from '@tsndr/cloudflare-worker-jwt';
app.use('*', async (c, next) => {
const token = c.req.header('authorization')?.replace('Bearer ', '');
if (token) {
try {
const payload = await verify(token, 'secret');
c.set('user', {
id: payload.sub,
roles: payload.roles
});
} catch (error) {
return c.json({ error: 'Invalid token' }, 401);
}
}
await next();
});
```
### With API Key
```typescript
app.use('*', async (c, next) => {
const apiKey = c.req.header('x-api-key');
if (apiKey) {
const user = await validateApiKey(apiKey);
c.set('user', user);
}
await next();
});
```
## Error Handling
```typescript
app.onError((err, c) => {
console.error('Error:', err);
if (err.message.includes('Forbidden')) {
return c.json({
error: 'Forbidden',
message: 'You do not have permission to access this resource'
}, 403);
}
return c.json({
error: 'Internal Server Error',
message: err.message
}, 500);
});
```
## TypeScript Support
```typescript
import type { Context } from 'hono';
import type { RBACUser } from '@fire-shield/core';
interface CustomContext extends Context {
Variables: {
user: RBACUser;
};
}
app.get('/admin', (c: CustomContext) => {
const user = c.get('user');
return c.json({ user });
});
```
## Best Practices
### 1. Centralize RBAC Setup
```typescript
// lib/rbac.ts
import { RBAC } from '@fire-shield/core';
import { HonoRBACAdapter } from '@fire-shield/hono';
export const rbac = new RBAC();
export const rbacMiddleware = new HonoRBACAdapter(rbac);
// Setup roles
rbac.createRole('admin', ['*']);
rbac.createRole('editor', ['post:*']);
```
### 2. Use Route Groups
```typescript
// Good: Group protected routes
const adminRoutes = new Hono();
adminRoutes.use('*', rbacMiddleware.role('admin'));
// Avoid: Repeat middleware on every route
app.get('/admin/users', rbacMiddleware.role('admin'), handler);
app.post('/admin/users', rbacMiddleware.role('admin'), handler);
```
### 3. Handle Errors Gracefully
```typescript
app.onError((err, c) => {
console.error('RBAC Error:', err);
return c.json({ error: 'Forbidden' }, 403);
});
```
### 4. Use TypeScript
```typescript
interface User extends RBACUser {
email: string;
name: string;
}
app.use('*', async (c, next) => {
const user: User = await getUser(c);
c.set('user', user);
await next();
});
```
## Next Steps
### GraphQL Integration
> Source: https://fire-shield.dev/frameworks/graphql
# GraphQL Integration
## Features
## Installation
```bash
npm install @fire-shield/graphql@3.1.1 @fire-shield/core@3.1.1 graphql
```
## Quick Start
### 1. Setup RBAC
```typescript
import { RBAC } from '@fire-shield/core';
const rbac = new RBAC();
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
```
### 2. Add Directives to Schema
```graphql
directive @hasPermission(permission: String!) on FIELD_DEFINITION
directive @hasRole(role: String!) on FIELD_DEFINITION
directive @hasAnyPermission(permissions: [String!]!) on FIELD_DEFINITION
directive @hasAllPermissions(permissions: [String!]!) on FIELD_DEFINITION
directive @notDenied(permission: String!) on FIELD_DEFINITION
directive @isDenied(permission: String!) on FIELD_DEFINITION
type Query {
posts: [Post!]! @hasPermission(permission: "post:read")
users: [User!]! @hasPermission(permission: "user:read")
adminStats: AdminStats! @hasRole(role: "admin")
}
type Mutation {
createPost(input: CreatePostInput!): Post! @hasPermission(permission: "post:write")
deletePost(id: ID!): Boolean! @hasPermission(permission: "post:delete")
updateUser(id: ID!, input: UpdateUserInput!): User! @hasPermission(permission: "user:write")
}
type Post {
id: ID!
title: String!
content: String!
# Only editors and admins can see draft posts
draft: Boolean! @hasPermission(permission: "post:edit")
}
```
### 3. Create GraphQL Server
```typescript
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { makeExecutableSchema } from '@graphql-tools/schema';
import {
applyFireShieldDirectives,
fireShieldDirectiveTypeDefs,
} from '@fire-shield/graphql';
let schema = makeExecutableSchema({ typeDefs, resolvers });
schema = applyFireShieldDirectives(schema);
const server = new ApolloServer({ schema });
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const user = await getUserFromRequest(req);
return { rbac, user };
},
});
```
## API
### `applyFireShieldDirectives(schema, config?)`
```typescript
applyFireShieldDirectives(schema: GraphQLSchema, config?: {
hasPermission?: { directiveName?: string };
hasRole?: { directiveName?: string };
hasAnyPermission?: { directiveName?: string };
hasAllPermissions?: { directiveName?: string };
}): GraphQLSchema
```
```typescript
import { applyFireShieldDirectives } from '@fire-shield/graphql';
let schema = makeExecutableSchema({ typeDefs, resolvers });
schema = applyFireShieldDirectives(schema);
```
### Individual Directive Factories
```typescript
import {
createHasPermissionDirective,
createHasRoleDirective,
createHasAnyPermissionDirective,
createHasAllPermissionsDirective,
createNotDeniedDirective,
createIsDeniedDirective,
} from '@fire-shield/graphql';
let schema = makeExecutableSchema({ typeDefs, resolvers });
schema = createHasPermissionDirective()(schema);
schema = createHasRoleDirective()(schema);
schema = createHasAnyPermissionDirective()(schema);
schema = createHasAllPermissionsDirective()(schema);
schema = createNotDeniedDirective()(schema);
schema = createIsDeniedDirective()(schema);
```
### `fireShieldDirectiveTypeDefs`
```typescript
import { fireShieldDirectiveTypeDefs } from '@fire-shield/graphql';
const typeDefs = `
${fireShieldDirectiveTypeDefs}
type Query {
users: [User!]! @hasPermission(permission: "user:read")
}
`;
```
### `GraphQLRBACContext` Interface
```typescript
import type { GraphQLRBACContext } from '@fire-shield/graphql';
interface GraphQLRBACContext {
rbac: RBAC; // Fire Shield RBAC instance
user?: RBACUser; // Current user with roles
}
```
```typescript
interface MyContext extends GraphQLRBACContext {
db: Database;
logger: Logger;
}
```
## Available Directives
### @hasPermission
```graphql
type Query {
users: [User!]! @hasPermission(permission: "user:read")
}
```
### @hasRole
```graphql
type Mutation {
deleteUser(id: ID!): Boolean! @hasRole(role: "admin")
}
```
### @hasAnyPermission
```graphql
type Query {
posts: [Post!]! @hasAnyPermission(permissions: ["post:read", "post:write"])
}
```
### @hasAllPermissions
```graphql
type Mutation {
deletePost(id: ID!): Boolean!
@hasAllPermissions(permissions: ["post:delete", "post:admin"])
}
```
### @notDenied
```graphql
type Query {
sensitiveData: JSON! @notDenied(permission: "data:sensitive")
}
```
### @isDenied
```graphql
type Query {
restrictedMessage: String @isDenied(permission: "data:sensitive")
}
```
## Context Requirements
```typescript
const context = ({ req }) => ({
rbac: myRBACInstance,
user: {
id: req.user.id,
roles: req.user.roles,
// Optional: direct permissions
permissions: req.user.permissions,
},
});
```
## Error Handling
```typescript
{
extensions: {
code: 'UNAUTHENTICATED' | 'FORBIDDEN' | 'RBAC_NOT_CONFIGURED',
requiredPermission?: string,
requiredRole?: string,
requiredPermissions?: string[],
}
}
```
```typescript
const server = new ApolloServer({
schema,
formatError: (error) => {
if (error.extensions?.code === 'FORBIDDEN') {
return {
message: 'You do not have permission to access this resource',
extensions: error.extensions,
};
}
return error;
},
});
```
## Advanced Usage
### Custom Directive Names
```typescript
const schema = applyFireShieldDirectives(baseSchema, {
hasPermission: { directiveName: 'requiresPermission' },
hasRole: { directiveName: 'requiresRole' },
});
```
```graphql
directive @requiresPermission(permission: String!) on FIELD_DEFINITION
directive @requiresRole(role: String!) on FIELD_DEFINITION
type Query {
users: [User!]! @requiresPermission(permission: "user:read")
}
```
### Programmatic Permission Checks
```typescript
const resolvers = {
Query: {
users: (_, __, context: GraphQLRBACContext) => {
// Manual check if needed
if (!context.rbac.hasPermission(context.user, 'user:read')) {
throw new GraphQLError('Permission denied');
}
return fetchUsers();
},
},
};
```
### Dynamic Authorization in Resolvers
```typescript
const resolvers = {
Mutation: {
updatePost: async (parent, { id, input }, context: GraphQLRBACContext) => {
const post = await db.post.findUnique({ where: { id } });
// Users can only edit their own posts unless they're admin
const isOwner = context.user?.id === post.authorId;
const isAdmin = context.user?.roles.includes('admin');
if (!isOwner && !isAdmin) {
throw new GraphQLError('Access Denied', {
extensions: { code: 'FORBIDDEN' },
});
}
return await db.post.update({ where: { id }, data: input });
},
},
};
```
## Usage with Apollo Server
```typescript
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { makeExecutableSchema } from '@graphql-tools/schema';
import {
applyFireShieldDirectives,
fireShieldDirectiveTypeDefs,
} from '@fire-shield/graphql';
const typeDefs = `
${fireShieldDirectiveTypeDefs}
type Query {
posts: [Post!]! @hasPermission(permission: "post:read")
post(id: ID!): Post
}
type Mutation {
createPost(input: CreatePostInput!): Post! @hasPermission(permission: "post:write")
deletePost(id: ID!): Boolean! @hasPermission(permission: "post:delete")
}
`;
let schema = makeExecutableSchema({ typeDefs, resolvers });
schema = applyFireShieldDirectives(schema);
const server = new ApolloServer({ schema });
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const user = await getUserFromToken(req.headers.authorization);
return { rbac, user };
},
});
console.log(`Server ready at ${url}`);
```
## Usage with GraphQL Yoga
```typescript
import { createYoga } from 'graphql-yoga';
import { createServer } from 'node:http';
import { makeExecutableSchema } from '@graphql-tools/schema';
import {
applyFireShieldDirectives,
fireShieldDirectiveTypeDefs,
} from '@fire-shield/graphql';
const typeDefs = `
${fireShieldDirectiveTypeDefs}
type Query {
posts: [Post!]! @hasPermission(permission: "post:read")
}
`;
let schema = makeExecutableSchema({ typeDefs, resolvers });
schema = applyFireShieldDirectives(schema);
const yoga = createYoga({
schema,
context: async ({ request }) => {
const token = request.headers.get('authorization');
const user = await getUserFromToken(token);
return { rbac, user };
},
});
const server = createServer(yoga);
server.listen(4000, () => {
console.log('Server is running on http://localhost:4000/graphql');
});
```
## Field-Level Authorization
```graphql
type User {
id: ID!
username: String!
email: String!
# Only visible to admins
internalNotes: String @hasRole(role: "admin")
# Only visible if user has permission
privateData: JSON @hasPermission(permission: "user:private")
# Only visible with explicit permission
apiKeys: [APIKey!]! @hasPermission(permission: "user:api-keys")
}
```
## Subscription Authorization
```graphql
type Subscription {
postCreated: Post! @hasPermission(permission: "post:subscribe")
adminNotifications: AdminNotification! @hasRole(role: "admin")
}
```
## TypeScript Support
```typescript
import type { GraphQLRBACContext } from '@fire-shield/graphql';
interface MyContext extends GraphQLRBACContext {
db: Database;
logger: Logger;
}
const resolvers = {
Query: {
users: (_, __, context: MyContext) => {
context.rbac.hasPermission(context.user, 'user:read');
context.logger.info('Fetching users');
return context.db.users.findMany();
},
},
};
```
## Best Practices
## Next Steps
### tRPC Integration
> Source: https://fire-shield.dev/frameworks/trpc
# tRPC Integration
## Features
## Installation
```bash
npm install @fire-shield/trpc@3.1.1 @fire-shield/core@3.1.1 @trpc/server
```
## Quick Start
### 1. Setup RBAC
```typescript
// src/server/rbac.ts
import { RBAC } from '@fire-shield/core';
export const rbac = new RBAC();
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
```
### 2. Create RBAC Context
```typescript
// src/server/context.ts
import { inferAsyncReturnType } from '@trpc/server';
import { CreateNextContextOptions } from '@trpc/server/adapters/next';
import { rbac } from './rbac';
export async function createContext({ req, res }: CreateNextContextOptions) {
const user = await getUserFromRequest(req);
return {
req,
res,
rbac,
user
};
}
export type Context = inferAsyncReturnType;
```
### 3. Create Protected Procedures
```typescript
// src/server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { requirePermission, requireRole } from '@fire-shield/trpc';
import type { Context } from './context';
const t = initTRPC.context().create();
export const router = t.router;
export const publicProcedure = t.procedure;
// Protected procedures
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({ ctx: { ...ctx, user: ctx.user } });
});
// Permission-based procedure
export const permissionProcedure = (permission: string) =>
protectedProcedure.use(requirePermission(permission));
// Role-based procedure
export const roleProcedure = (role: string) =>
protectedProcedure.use(requireRole(role));
```
### 4. Use in Routers
```typescript
// src/server/routers/posts.ts
import { z } from 'zod';
import { router, publicProcedure, permissionProcedure } from '../trpc';
export const postsRouter = router({
// Public: Anyone can list posts
list: publicProcedure.query(async () => {
return await db.post.findMany();
}),
// Protected: Requires 'post:read' permission
getById: permissionProcedure('post:read')
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return await db.post.findUnique({ where: { id: input.id } });
}),
// Protected: Requires 'post:write' permission
create: permissionProcedure('post:write')
.input(
z.object({
title: z.string(),
content: z.string()
})
)
.mutation(async ({ input, ctx }) => {
return await db.post.create({
data: {
...input,
authorId: ctx.user.id
}
});
}),
// Protected: Requires 'post:delete' permission
delete: permissionProcedure('post:delete')
.input(z.object({ id: z.string() }))
.mutation(async ({ input }) => {
await db.post.delete({ where: { id: input.id } });
return { success: true };
})
});
```
## API
### requirePermission(permission)
```typescript
export const protectedQuery = publicProcedure.use(requirePermission('resource:read'));
```
### requireRole(role)
```typescript
export const adminQuery = publicProcedure.use(requireRole('admin'));
```
### createRBACMiddleware(options)
```typescript
interface RBACMiddlewareOptions {
getUser?: (ctx: any) => RBACUser | undefined;
getRBAC?: (ctx: any) => RBAC;
onUnauthorized?: (result: AuthorizationResult, ctx: any) => void;
}
```
```typescript
import { createRBACMiddleware } from '@fire-shield/trpc';
const customAuth = createRBACMiddleware({
getUser: (ctx) => ctx.session?.user,
getRBAC: (ctx) => ctx.rbac,
onUnauthorized: (result, ctx) => {
throw new TRPCError({
code: 'FORBIDDEN',
message: result.reason
});
}
});
export const customProcedure = publicProcedure.use(customAuth('permission:check'));
```
## Usage with Next.js
```typescript
// pages/api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '@/server/routers/_app';
import { createContext } from '@/server/context';
export default createNextApiHandler({
router: appRouter,
createContext
});
```
```typescript
// src/server/routers/_app.ts
import { router } from '../trpc';
import { postsRouter } from './posts';
import { usersRouter } from './users';
export const appRouter = router({
posts: postsRouter,
users: usersRouter
});
export type AppRouter = typeof appRouter;
```
## Client Usage
```typescript
// src/utils/trpc.ts
import { createTRPCNext } from '@trpc/next';
import type { AppRouter } from '@/server/routers/_app';
export const trpc = createTRPCNext({
config() {
return {
url: '/api/trpc'
};
}
});
```
```tsx
// pages/posts.tsx
import { trpc } from '@/utils/trpc';
export default function PostsPage() {
// Type-safe queries with automatic auth
const { data: posts } = trpc.posts.list.useQuery();
const createPost = trpc.posts.create.useMutation();
return (
createPost.mutate({
title: 'New Post',
content: 'Content here'
})
}
>
Create Post
{posts?.map((post) => (
{post.title}
))}
);
}
```
## Dynamic Authorization
```typescript
export const postsRouter = router({
update: protectedProcedure
.input(
z.object({
id: z.string(),
title: z.string().optional(),
content: z.string().optional()
})
)
.mutation(async ({ input, ctx }) => {
const post = await db.post.findUnique({ where: { id: input.id } });
// Check if user owns the post or is admin
const canEdit =
post.authorId === ctx.user.id ||
ctx.rbac.hasRole(ctx.user, 'admin') ||
ctx.rbac.hasPermission(ctx.user, 'post:edit-any');
if (!canEdit) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'You can only edit your own posts'
});
}
return await db.post.update({
where: { id: input.id },
data: input
});
})
});
```
## Role-Based Procedures
```typescript
// Admin-only procedures
export const adminRouter = router({
stats: roleProcedure('admin').query(async () => {
return await getAdminStatistics();
}),
deleteUser: roleProcedure('admin')
.input(z.object({ userId: z.string() }))
.mutation(async ({ input }) => {
await db.user.delete({ where: { id: input.userId } });
return { success: true };
})
});
```
## Multiple Permissions
```typescript
import { TRPCError } from '@trpc/server';
export const multiPermissionProcedure = protectedProcedure.use(async ({ ctx, next }) => {
const requiredPermissions = ['post:write', 'post:publish'];
const hasAll = ctx.rbac.hasAllPermissions(ctx.user, requiredPermissions);
if (!hasAll) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Missing required permissions'
});
}
return next({ ctx });
});
```
## Batch Operations with Authorization
```typescript
export const postsRouter = router({
deleteMany: permissionProcedure('post:delete')
.input(z.object({ ids: z.array(z.string()) }))
.mutation(async ({ input }) => {
await db.post.deleteMany({
where: { id: { in: input.ids } }
});
return { deleted: input.ids.length };
})
});
```
## Subscription Authorization
```typescript
export const postsRouter = router({
onPostCreated: permissionProcedure('post:subscribe')
.subscription(() => {
return observable((emit) => {
const onPost = (post: Post) => emit.next(post);
eventEmitter.on('post:created', onPost);
return () => {
eventEmitter.off('post:created', onPost);
};
});
})
});
```
## Error Handling
```typescript
import { TRPCError } from '@trpc/server';
export const permissionProcedureWithError = (permission: string) =>
protectedProcedure.use(async ({ ctx, next }) => {
if (!ctx.rbac.hasPermission(ctx.user, permission)) {
throw new TRPCError({
code: 'FORBIDDEN',
message: `Missing required permission: ${permission}`,
cause: {
permission,
user: ctx.user.id,
roles: ctx.user.roles
}
});
}
return next({ ctx });
});
```
## Nested Routers with Authorization
```typescript
// Admin router - all procedures require admin role
const adminProcedure = roleProcedure('admin');
export const adminRouter = router({
users: router({
list: adminProcedure.query(() => db.user.findMany()),
create: adminProcedure
.input(z.object({ email: z.string().email() }))
.mutation(({ input }) => db.user.create({ data: input })),
delete: adminProcedure
.input(z.object({ id: z.string() }))
.mutation(({ input }) => db.user.delete({ where: { id: input.id } }))
}),
settings: router({
get: adminProcedure.query(() => getSettings()),
update: adminProcedure
.input(z.record(z.any()))
.mutation(({ input }) => updateSettings(input))
})
});
```
## Best Practices
## Complete Example
```typescript
// src/server/rbac.ts
import { RBAC } from '@fire-shield/core';
export const rbac = new RBAC({
enableCache: true,
cacheTTL: 60000
});
rbac.createRole('admin', ['*']);
rbac.createRole('editor', ['post:*', 'category:read']);
rbac.createRole('user', ['post:read', 'post:write-own']);
// src/server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { requirePermission, requireRole } from '@fire-shield/trpc';
import type { Context } from './context';
const t = initTRPC.context().create();
export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({ ctx: { ...ctx, user: ctx.user } });
});
export const permissionProcedure = (permission: string) =>
protectedProcedure.use(requirePermission(permission));
export const roleProcedure = (role: string) =>
protectedProcedure.use(requireRole(role));
// src/server/routers/posts.ts
import { z } from 'zod';
import { router, publicProcedure, permissionProcedure } from '../trpc';
export const postsRouter = router({
list: publicProcedure.query(async () => {
return await db.post.findMany({ where: { published: true } });
}),
getById: permissionProcedure('post:read')
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return await db.post.findUnique({ where: { id: input.id } });
}),
create: permissionProcedure('post:write')
.input(
z.object({
title: z.string().min(1).max(100),
content: z.string().min(1)
})
)
.mutation(async ({ input, ctx }) => {
return await db.post.create({
data: { ...input, authorId: ctx.user.id }
});
}),
delete: permissionProcedure('post:delete')
.input(z.object({ id: z.string() }))
.mutation(async ({ input }) => {
await db.post.delete({ where: { id: input.id } });
return { success: true };
})
});
// src/server/routers/_app.ts
import { router } from '../trpc';
import { postsRouter } from './posts';
export const appRouter = router({
posts: postsRouter
});
export type AppRouter = typeof appRouter;
```
## Next Steps
============================================================
## Frameworks — Mobile
============================================================
### React Native Integration
> Source: https://fire-shield.dev/frameworks/react-native
# React Native Integration
## Features
## Installation
```bash
npm install @fire-shield/react-native@3.1.1 @fire-shield/core@3.1.1 react react-native
```
## Quick Start
```tsx
import { RBACProvider, useRBAC, Can } from '@fire-shield/react-native';
import { RBAC } from '@fire-shield/core';
// Create RBAC instance
const rbac = new RBAC();
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
// Wrap your app with RBACProvider
export default function App() {
const [user, setUser] = useState({ id: '1', roles: ['editor'] });
return (
);
}
// Use hooks in components
function PostEditor() {
const { can, hasRole } = useRBAC();
return (
{/* Component-based check */}
{/* Hook-based check */}
{can('post:delete') && (
)}
{/* Role check */}
{hasRole('admin') && (
)}
);
}
```
## API
### RBACProvider
```typescript
interface RBACProviderProps {
rbac: RBAC;
user?: RBACUser;
children: React.ReactNode;
}
```
```tsx
```
### useRBAC()
```typescript
{
rbac: RBAC;
user?: RBACUser;
setUser: (user: RBACUser | undefined) => void;
can: (permission: string) => boolean;
cannot: (permission: string) => boolean;
hasRole: (role: string) => boolean;
hasAnyRole: (roles: string[]) => boolean;
hasAllRoles: (roles: string[]) => boolean;
}
```
```tsx
function MyComponent() {
const { can, hasRole, user, setUser } = useRBAC();
const handleLogin = (userData) => {
setUser({ id: userData.id, roles: userData.roles });
};
return (
{can('post:write') && }
{hasRole('admin') && }
);
}
```
### usePermission(permission)
```tsx
function DeleteButton({ postId }) {
const canDelete = usePermission('post:delete');
if (!canDelete) return null;
return deletePost(postId)} />;
}
```
### useRole(role)
```tsx
function AdminBadge() {
const isAdmin = useRole('admin');
if (!isAdmin) return null;
return Admin ;
}
```
## Components
### \
```typescript
interface CanProps {
permission: string;
children: React.ReactNode;
fallback?: React.ReactNode;
}
```
```tsx
No access}>
```
### \
```typescript
interface CannotProps {
permission: string;
children: React.ReactNode;
}
```
```tsx
You cannot delete posts
```
## Persistence with AsyncStorage
```tsx
import { useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useRBAC } from '@fire-shield/react-native';
function App() {
const { user, setUser } = useRBAC();
// Load user from storage on mount
useEffect(() => {
AsyncStorage.getItem('user').then((data) => {
if (data) {
setUser(JSON.parse(data));
}
});
}, []);
// Save user to storage when it changes
useEffect(() => {
if (user) {
AsyncStorage.setItem('user', JSON.stringify(user));
} else {
AsyncStorage.removeItem('user');
}
}, [user]);
return ;
}
```
## Platform-Specific Features
### iOS & Android
### Web (React Native Web)
## Best Practices
## Examples
### Protected Screen Navigation
```tsx
import { useRBAC } from '@fire-shield/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
function Navigation() {
const { hasRole } = useRBAC();
return (
{hasRole('admin') && (
)}
);
}
```
### Conditional UI Elements
```tsx
function PostCard({ post }) {
const { can } = useRBAC();
return (
{post.title}
{post.content}
{can('post:write') && (
editPost(post.id)} />
)}
{can('post:delete') && (
deletePost(post.id)} />
)}
);
}
```
### Login/Logout
```tsx
function AuthButtons() {
const { user, setUser } = useRBAC();
const handleLogin = async () => {
const userData = await loginAPI();
setUser({
id: userData.id,
roles: userData.roles,
permissions: userData.permissions
});
};
const handleLogout = () => {
setUser(undefined);
};
return (
{user ? (
) : (
)}
);
}
```
## TypeScript
```tsx
import type { RBACUser } from '@fire-shield/core';
interface AppUser extends RBACUser {
name: string;
email: string;
}
function UserProfile() {
const { user } = useRBAC();
return (
{user?.name}
{user?.email}
);
}
```
## Next Steps
### Expo Integration
> Source: https://fire-shield.dev/frameworks/expo
# Expo Integration
## Features
## Installation
```bash
npx expo install @fire-shield/expo@3.1.1 @fire-shield/core@3.1.1
```
## Quick Start
```tsx
import { RBACProvider, useRBAC, Can } from '@fire-shield/expo';
import { RBAC } from '@fire-shield/core';
// Create RBAC instance
const rbac = new RBAC();
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
export default function App() {
const [user, setUser] = useState({ id: '1', roles: ['editor'] });
return (
);
}
```
## Expo-Specific Features
### 1. Persistent User Storage with AsyncStorage
```tsx
import { usePersistedUser } from '@fire-shield/expo';
function App() {
const [user, setUser] = usePersistedUser('@fire-shield:user');
return (
);
}
```
### 2. Secure Token Storage with SecureStore
```tsx
import { useSecureRBACToken } from '@fire-shield/expo';
function AuthScreen() {
const [token, setToken] = useSecureRBACToken('@fire-shield:token');
const handleLogin = async (credentials) => {
const response = await loginAPI(credentials);
// Store token securely in device keychain/keystore
await setToken(response.token);
// Set user in RBAC
setUser(response.user);
};
return ;
}
```
### 3. Development Mode Debugging
```tsx
import { useRBACDebug } from '@fire-shield/expo';
function DebugPanel() {
useRBACDebug(__DEV__); // Only enable in development
const { user } = useRBAC();
return (
User: {user?.id}
Roles: {user?.roles.join(', ')}
);
}
```
```
[Fire Shield] Permission Check:
user: user-123
roles: ['editor']
permission: post:write
result: true
```
## API
### usePersistedUser(storageKey?)
```tsx
function App() {
const [user, setUser] = usePersistedUser();
return (
);
}
```
### useSecureRBACToken(tokenKey?)
```tsx
function useAuth() {
const [token, setToken] = useSecureRBACToken();
const { setUser } = useRBAC();
const login = async (credentials) => {
const { token, user } = await loginAPI(credentials);
await setToken(token);
setUser(user);
};
const logout = async () => {
await setToken(null); // Clear token
setUser(undefined);
};
return { login, logout };
}
```
### useRBACDebug(enabled?)
```tsx
function App() {
useRBACDebug(__DEV__);
return ;
}
```
## Complete Authentication Example
```tsx
import { useState, useEffect } from 'react';
import { View, Button, Text } from 'react-native';
import { RBACProvider, useRBAC, usePersistedUser, useSecureRBACToken } from '@fire-shield/expo';
import { RBAC } from '@fire-shield/core';
const rbac = new RBAC();
rbac.createRole('admin', ['*']);
rbac.createRole('user', ['post:read', 'post:write']);
export default function App() {
const [user, setUser] = usePersistedUser();
return (
);
}
function AuthScreen() {
const { user, setUser } = useRBAC();
const [token, setToken] = useSecureRBACToken();
const handleLogin = async () => {
// Call your API
const response = await fetch('https://api.example.com/login', {
method: 'POST',
body: JSON.stringify({ username: 'admin', password: 'password' })
});
const data = await response.json();
// Store token securely
await setToken(data.token);
// Set user (automatically persisted)
setUser({
id: data.user.id,
roles: data.user.roles
});
};
const handleLogout = async () => {
await setToken(null);
setUser(undefined);
};
if (!user) {
return (
);
}
return (
Welcome, {user.id}!
Roles: {user.roles.join(', ')}
);
}
```
## EAS Build Configuration
```json
// eas.json
{
"build": {
"production": {
"env": {
"RBAC_CACHE_ENABLED": "true"
}
},
"development": {
"developmentClient": true,
"env": {
"RBAC_DEBUG": "true"
}
}
}
}
```
## Expo Go Compatibility
## Best Practices
## Performance Tips
```tsx
import { useMemo } from 'react';
function PostList() {
const { user, rbac } = useRBAC();
const canCreatePost = useMemo(
() => rbac.hasPermission(user, 'post:write'),
[user, rbac]
);
return (
{canCreatePost && }
{/* ... */}
);
}
```
## TypeScript
```tsx
import type { RBACUser } from '@fire-shield/core';
interface AppUser extends RBACUser {
email: string;
name: string;
}
const [user, setUser] = usePersistedUser();
```
## Troubleshooting
### SecureStore not available
```
Error: SecureStore is not available on this platform
```
### AsyncStorage quota exceeded
```
Error: AsyncStorage quota exceeded
```
## Next Steps
============================================================
## Tools
============================================================
### CLI Tool
> Source: https://fire-shield.dev/frameworks/cli
# CLI Tool
## Features
## Installation
### Global Installation (Recommended)
```bash
npm install -g @fire-shield/cli@3.1.1
```
```bash
fire-shield --version # 3.1.1
fire-shield info # Show CLI information
```
### Local Installation
```bash
npm install --save-dev @fire-shield/cli@3.1.1
```
```bash
npx fire-shield validate config.json
```
## Commands
### `validate` - Validate Configuration
```bash
fire-shield validate [options]
```
```bash
# Basic validation
fire-shield validate rbac-config.json
# Strict mode (stricter validation rules)
fire-shield validate rbac-config.json --strict
# Verbose output (show config details)
fire-shield validate rbac-config.json --verbose
# Both strict and verbose
fire-shield validate rbac-config.json -s -v
```
```
🔍 Validating RBAC configuration...
✓ Configuration is valid
Validated in 15ms
```
```
🔍 Validating RBAC configuration...
File: /path/to/rbac-config.json
Strict mode: disabled
✓ Configuration is valid
Configuration details:
• Name: my-app-rbac
• Version: 1.0.0
• Permissions: 12
• Roles: 4
Permissions:
• user:read [bit: 1]
• user:write [bit: 2]
• user:delete [bit: 4]
• post:read [bit: 8]
...
Roles:
• admin [level: 10]
Permissions: user:*, post:*
• editor [level: 5]
Permissions: post:read, post:write
...
Validated in 18ms
```
```
🔍 Validating RBAC configuration...
✖ Validation failed
Duplicate permission name: 'user:read' found multiple times
Validated in 12ms
```
### `check` - Check User Permission
```bash
fire-shield check --user --roles --permission [options]
```
```bash
# Check if editor can write posts
fire-shield check config.json -u user123 -r editor -p post:write
# Check with multiple roles
fire-shield check config.json -u admin1 -r admin moderator -p user:delete
# Verbose output
fire-shield check config.json -u user1 -r editor -p post:write --verbose
# Check wildcard permission
fire-shield check config.json -u admin -r admin -p post:delete -v
```
```
🔍 Checking permission...
✓ User has permission "post:write"
```
```
🔍 Checking permission...
Config file: /path/to/config.json
User: user123
Roles: editor
Permission: post:write
✓ User has permission "post:write"
User: user123
Roles: editor
Permission: post:write
Result: ALLOWED
Granted by: editor
```
```
🔍 Checking permission...
✖ User does NOT have permission "user:delete"
```
```
🔍 Checking permission...
Config file: /path/to/config.json
User: user123
Roles: editor
Permission: user:delete
✖ User does NOT have permission "user:delete"
User: user123
Roles: editor
Permission: user:delete
Result: DENIED
```
### `info` - Show CLI Information
```bash
fire-shield info
```
```
🔥 Fire Shield RBAC CLI
Version: 3.1.1
Author: khapu2906
License: DIB
Repository: https://github.com/kentphung92/fire-shield
Available commands:
validate Validate RBAC config file
check Check user permissions
init Initialize new config (coming soon)
info Show CLI information
```
### `init` - Initialize Configuration (Coming Soon)
```bash
fire-shield init [options]
```
## Configuration File Format
```json
{
"name": "my-app-rbac",
"version": "1.0.0",
"permissions": [
{ "name": "user:read", "bit": 1 },
{ "name": "user:write", "bit": 2 },
{ "name": "user:delete", "bit": 4 },
{ "name": "post:read", "bit": 8 },
{ "name": "post:write", "bit": 16 },
{ "name": "post:delete", "bit": 32 }
],
"roles": [
{
"name": "admin",
"permissions": ["user:*", "post:*"],
"level": 10
},
{
"name": "editor",
"permissions": ["post:read", "post:write"],
"level": 5
},
{
"name": "viewer",
"permissions": ["post:read"],
"level": 1
}
]
}
```
## CI/CD Integration
### GitHub Actions
```yaml
name: Validate RBAC Config
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Fire Shield CLI
run: npm install -g @fire-shield/cli
- name: Validate RBAC configuration
run: fire-shield validate config/rbac.json --strict
- name: Test admin permissions
run: fire-shield check config/rbac.json -u admin -r admin -p user:delete
```
### GitLab CI
```yaml
validate-rbac:
image: node:18
stage: test
script:
- npm install -g @fire-shield/cli
- fire-shield validate config/rbac.json --strict
- fire-shield check config/rbac.json -u admin -r admin -p user:delete
```
### Pre-commit Hook
```bash
#!/bin/bash
# .git/hooks/pre-commit
echo "Validating RBAC configuration..."
npx @fire-shield/cli validate config/rbac.json --strict
if [ $? -ne 0 ]; then
echo "❌ RBAC validation failed. Commit aborted."
exit 1
fi
echo "✅ RBAC validation passed"
```
### npm Scripts
```json
{
"scripts": {
"rbac:validate": "fire-shield validate config/rbac.json",
"rbac:validate:strict": "fire-shield validate config/rbac.json --strict",
"rbac:check:admin": "fire-shield check config/rbac.json -u admin -r admin -p user:delete",
"pretest": "npm run rbac:validate"
}
}
```
## Use Cases
### 1. Validate Before Deployment
```bash
# In deployment script
fire-shield validate config/production-rbac.json --strict
if [ $? -eq 0 ]; then
echo "✅ RBAC config valid, proceeding with deployment"
# Deploy application
else
echo "❌ RBAC config invalid, deployment aborted"
exit 1
fi
```
### 2. Test Permission Logic
```bash
# Test if new role has correct permissions
fire-shield check config.json -u test-user -r new-role -p feature:access -v
# Test admin permissions
fire-shield check config.json -u admin1 -r admin -p user:delete -v
fire-shield check config.json -u admin1 -r admin -p post:delete -v
```
### 3. Debug Permission Issues
```bash
# Check why user can't access feature
fire-shield check config.json -u user123 -r editor viewer -p feature:write --verbose
# Validate config to find issues
fire-shield validate config.json --verbose
```
### 4. Automated Testing
```bash
#!/bin/bash
# test-permissions.sh
echo "Testing permission scenarios..."
# Test 1: Admin should have all permissions
fire-shield check config.json -u admin -r admin -p user:delete
fire-shield check config.json -u admin -r admin -p post:delete
# Test 2: Editor should only write posts
fire-shield check config.json -u editor -r editor -p post:write
! fire-shield check config.json -u editor -r editor -p user:delete
# Test 3: Viewer should only read
fire-shield check config.json -u viewer -r viewer -p post:read
! fire-shield check config.json -u viewer -r viewer -p post:write
echo "✅ All tests passed"
```
## Common Errors
### File Not Found
```bash
fire-shield validate non-existent.json
```
```
✖ File not found: /path/to/non-existent.json
```
### Invalid JSON
```bash
fire-shield validate invalid.json
```
```
✖ Invalid JSON
Unexpected token } in JSON at position 123
```
### Duplicate Permissions
```json
{
"permissions": [
{ "name": "user:read", "bit": 1 },
{ "name": "user:read", "bit": 2 } // Duplicate!
]
}
```
```
✖ Validation failed
Duplicate permission name: 'user:read'
```
### Undefined Permission in Role
```json
{
"permissions": [
{ "name": "user:read", "bit": 1 }
],
"roles": [
{
"name": "admin",
"permissions": ["user:write"] // Not defined!
}
]
}
```
```
✖ Validation failed
Role 'admin' references undefined permission: 'user:write'
```
### Missing Required Options
```bash
fire-shield check config.json -u user1 -p post:write
# Missing --roles option
```
```
✖ At least one role is required (--roles ...)
```
## Tips and Best Practices
### 1. Use Strict Mode in Production
```bash
# Development
fire-shield validate config.json
# Production
fire-shield validate config.json --strict
```
### 2. Version Your Config Files
```json
{
"name": "my-app-rbac",
"version": "2.1.0", // Track changes
"permissions": [...]
}
```
### 3. Use Verbose Mode for Debugging
```bash
fire-shield validate config.json --verbose
fire-shield check config.json -u user1 -r editor -p post:write --verbose
```
### 4. Wildcard Permissions are Validated
```json
{
"roles": [
{
"name": "admin",
"permissions": ["user:*"] // ✅ Valid
}
]
}
```
### 5. Test in CI/CD
```yaml
- run: fire-shield validate config/rbac.json --strict
```
### 6. Document Permission Checks
```bash
# Document expected results in test scripts
echo "Admin should delete users"
fire-shield check config.json -u admin -r admin -p user:delete
echo "Editor should NOT delete users"
! fire-shield check config.json -u editor -r editor -p user:delete
```
## Programmatic Usage
```typescript
import { validateCommand, checkCommand } from '@fire-shield/cli';
// Validate config
try {
await validateCommand('config.json', {
strict: true,
verbose: true
});
console.log('Config valid!');
} catch (error) {
console.error('Config invalid:', error);
}
// Check permission
try {
await checkCommand('config.json', {
user: 'user123',
roles: ['editor'],
permission: 'post:write',
verbose: false
});
console.log('Permission granted');
} catch (error) {
console.error('Permission denied');
}
```
## Troubleshooting
### Command Not Found
```bash
fire-shield: command not found
```
### Permission Denied
```bash
EACCES: permission denied
```
```bash
sudo npm install -g @fire-shield/cli
# or
npm install --save-dev @fire-shield/cli
```
### Version Mismatch
```bash
npm list @fire-shield/cli
npm list @fire-shield/core
```
```bash
npm install -g @fire-shield/cli@latest
npm install @fire-shield/core@latest
```
## Next Steps
## Support
### MCP (Model Context Protocol) Integration
> Source: https://fire-shield.dev/frameworks/mcp
# MCP (Model Context Protocol) Integration
## Features
## What is MCP?
## Installation
```bash
npm install @fire-shield/mcp@3.1.1 @fire-shield/core@3.1.1 @modelcontextprotocol/sdk
```
## Quick Start
### 1. Create MCP Server
```typescript
import { RBAC } from '@fire-shield/core';
import { createMCPServer } from '@fire-shield/mcp';
// Initialize RBAC
const rbac = new RBAC();
rbac.createRole('admin', ['user:*', 'post:*']);
rbac.createRole('editor', ['post:read', 'post:write']);
rbac.createRole('viewer', ['post:read']);
// Create MCP server
const mcpServer = await createMCPServer({
rbac,
serverName: 'fire-shield-rbac',
serverVersion: '3.1.1',
debug: true
});
// Server is now running and listening for MCP requests
```
### 2. Configure AI Client (Claude Desktop)
```json
{
"mcpServers": {
"fire-shield": {
"command": "node",
"args": ["/path/to/your/mcp-server.js"]
}
}
}
```
### 3. Use in AI Conversations
## Available MCP Tools
### 1. `check_permission` - Check User Permission
```typescript
{
userId: string; // User ID
roles: string[]; // User roles
permission: string; // Permission to check
}
```
```json
{
"userId": "user123",
"roles": ["editor"],
"permission": "post:write"
}
```
```json
{
"hasPermission": true,
"allowed": true,
"reason": "Permission granted",
"userId": "user123",
"roles": ["editor"],
"permission": "post:write"
}
```
### 2. `check_role` - Check User Role
```typescript
{
userId: string; // User ID
roles: string[]; // User roles
role: string; // Role to check
}
```
```json
{
"userId": "user123",
"roles": ["editor", "viewer"],
"role": "editor"
}
```
```json
{
"hasRole": true,
"userId": "user123",
"roles": ["editor", "viewer"],
"role": "editor"
}
```
### 3. `list_permissions` - List User Permissions
```typescript
{
userId: string; // User ID
roles: string[]; // User roles
}
```
```json
{
"userId": "user123",
"roles": ["editor"]
}
```
```json
{
"userId": "user123",
"roles": ["editor"],
"permissions": ["post:read", "post:write"]
}
```
### 4. `deny_permission` - Deny Permission
```typescript
{
userId: string; // User ID
permission: string; // Permission to deny
}
```
```json
{
"userId": "user123",
"permission": "post:delete"
}
```
```json
{
"success": true,
"message": "Permission 'post:delete' denied for user user123",
"userId": "user123",
"permission": "post:delete"
}
```
### 5. `allow_permission` - Allow Permission
```typescript
{
userId: string; // User ID
permission: string; // Permission to allow
}
```
```json
{
"userId": "user123",
"permission": "post:delete"
}
```
```json
{
"success": true,
"message": "Permission 'post:delete' allowed for user user123",
"userId": "user123",
"permission": "post:delete"
}
```
### 6. `get_denied_permissions` - Get Denied Permissions
```typescript
{
userId: string; // User ID
}
```
```json
{
"userId": "user123"
}
```
```json
{
"userId": "user123",
"deniedPermissions": ["post:delete", "user:write"]
}
```
### 7. `list_roles` - List All Roles
```typescript
{} // No parameters required
```
```json
{
"roles": ["admin", "editor", "viewer"]
}
```
### 8. `get_role_permissions` - Get Role Permissions
```typescript
{
role: string; // Role name
}
```
```json
{
"role": "editor"
}
```
```json
{
"role": "editor",
"permissions": ["post:read", "post:write"]
}
```
## API Reference
### FireShieldMCPServer
```typescript
class FireShieldMCPServer {
constructor(options: FireShieldMCPOptions);
async start(): Promise;
getServer(): Server;
}
```
### FireShieldMCPOptions
```typescript
interface FireShieldMCPOptions {
rbac: RBAC; // RBAC instance
serverName?: string; // Server name (default: 'fire-shield-rbac')
serverVersion?: string; // Server version (default: '3.1.1')
debug?: boolean; // Enable debug logging (default: false)
}
```
### createMCPServer()
```typescript
async function createMCPServer(
options: FireShieldMCPOptions
): Promise
```
### startStandalone()
```typescript
async function startStandalone(config: PresetConfig): Promise
```
## Usage Examples
### Standalone Server
```typescript
// mcp-server.ts
import { RBAC } from '@fire-shield/core';
import { createMCPServer } from '@fire-shield/mcp';
async function main() {
// Initialize RBAC with your configuration
const rbac = new RBAC();
// Define roles
rbac.createRole('admin', ['*']);
rbac.createRole('editor', ['post:read', 'post:write', 'post:edit']);
rbac.createRole('viewer', ['post:read']);
// Create and start MCP server
const server = await createMCPServer({
rbac,
serverName: 'my-app-rbac',
serverVersion: '1.0.0',
debug: process.env.NODE_ENV === 'development'
});
console.error('MCP server started successfully');
}
main().catch(console.error);
```
```bash
# Build
npx tsc mcp-server.ts
# Run
node mcp-server.js
```
### Load from Config File
```typescript
// mcp-server-config.ts
import { RBAC } from '@fire-shield/core';
import { createMCPServer } from '@fire-shield/mcp';
import { readFileSync } from 'fs';
async function main() {
// Load RBAC configuration from file
const config = JSON.parse(readFileSync('./rbac-config.json', 'utf-8'));
const rbac = new RBAC({ preset: config });
// Create MCP server
await createMCPServer({
rbac,
serverName: config.name || 'fire-shield-rbac',
serverVersion: config.version || '1.0.0'
});
}
main().catch(console.error);
```
```json
{
"name": "my-app-rbac",
"version": "1.0.0",
"permissions": [
{ "name": "user:read", "bit": 1 },
{ "name": "user:write", "bit": 2 },
{ "name": "post:read", "bit": 4 },
{ "name": "post:write", "bit": 8 }
],
"roles": [
{ "name": "admin", "permissions": ["user:*", "post:*"] },
{ "name": "editor", "permissions": ["post:read", "post:write"] },
{ "name": "viewer", "permissions": ["post:read"] }
]
}
```
### Integration with Express API
```typescript
import express from 'express';
import { RBAC } from '@fire-shield/core';
import { createMCPServer } from '@fire-shield/mcp';
// Shared RBAC instance
const rbac = new RBAC();
rbac.createRole('admin', ['*']);
rbac.createRole('editor', ['post:*']);
// Start Express API
const app = express();
app.use(express.json());
app.post('/api/check-permission', (req, res) => {
const { user, permission } = req.body;
const hasPermission = rbac.hasPermission(user, permission);
res.json({ hasPermission });
});
app.listen(3000, () => {
console.log('API running on port 3000');
});
// Start MCP server (shares the same RBAC instance)
createMCPServer({
rbac,
debug: true
}).then(() => {
console.error('MCP server started');
});
```
## Claude Desktop Configuration
### Basic Configuration
```json
{
"mcpServers": {
"fire-shield": {
"command": "node",
"args": ["/absolute/path/to/mcp-server.js"]
}
}
}
```
### With Environment Variables
```json
{
"mcpServers": {
"fire-shield": {
"command": "node",
"args": ["/path/to/mcp-server.js"],
"env": {
"NODE_ENV": "production",
"RBAC_CONFIG": "/path/to/rbac-config.json"
}
}
}
}
```
### Using npx
```json
{
"mcpServers": {
"fire-shield": {
"command": "npx",
"args": [
"-y",
"tsx",
"/path/to/mcp-server.ts"
]
}
}
}
```
## AI Conversation Examples
### Example 1: Check Permission
```json
{
"userId": "john",
"roles": ["editor"],
"permission": "post:write"
}
```
### Example 2: List Permissions
```json
{
"role": "editor"
}
```
### Example 3: Deny Permission
```json
{
"userId": "alice",
"permission": "post:delete"
}
```
### Example 4: Audit Permissions
```json
{
"userId": "bob",
"roles": ["editor", "moderator"]
}
```
## Integration with Continue
### Configuration
```json
{
"experimental": {
"modelContextProtocolServers": [
{
"transport": {
"type": "stdio",
"command": "node",
"args": ["/path/to/mcp-server.js"]
}
}
]
}
}
```
### Usage
## Debugging
### Enable Debug Mode
```typescript
const server = await createMCPServer({
rbac,
debug: true // Enables debug logging to stderr
});
```
### View MCP Messages
### Test Tools Manually
```bash
npm install -g @modelcontextprotocol/inspector
# Inspect your server
mcp-inspector node mcp-server.js
```
## Best Practices
### 1. Separate MCP Server Process
```typescript
// Good: Dedicated MCP server
// mcp-server.ts
createMCPServer({ rbac });
// app.ts
// Your main application
```
### 2. Share Configuration
```typescript
// config/rbac.ts
export const rbacConfig = {
permissions: [...],
roles: [...]
};
// app.ts
const rbac = new RBAC({ preset: rbacConfig });
// mcp-server.ts
const rbac = new RBAC({ preset: rbacConfig });
```
### 3. Validate User Input
```typescript
const allowedUsers = ['user1', 'user2', 'user3'];
if (!allowedUsers.includes(userId)) {
throw new Error('Invalid user ID');
}
```
### 4. Audit MCP Operations
```typescript
const rbac = new RBAC({
auditLogger: new BufferedAuditLogger(async (logs) => {
console.log('MCP Audit:', logs);
await database.saveLogs(logs);
})
});
```
### 5. Secure Your MCP Server
## Limitations
## Troubleshooting
### MCP Server Not Starting
```bash
npm install @modelcontextprotocol/sdk
```
### Tools Not Appearing in Claude
### Permission Check Returning Wrong Results
## Performance
## Next Steps
## Resources
============================================================
## Examples
============================================================
### Basic Usage Examples
> Source: https://fire-shield.dev/examples/basic-usage
# Basic Usage Examples
## Simple Blog Application
```typescript
import { RBAC } from '@fire-shield/core'
const rbac = new RBAC()
// Define roles
rbac.createRole('admin', [
'posts:*',
'users:*',
'comments:*',
'settings:*'
])
rbac.createRole('author', [
'posts:create',
'posts:read',
'posts:update:own',
'posts:delete:own',
'comments:read'
])
rbac.createRole('viewer', [
'posts:read',
'comments:read'
])
// Set hierarchy
rbac.setRoleHierarchy({
admin: ['author', 'viewer'],
author: ['viewer']
})
// Check permissions
const admin = { id: '1', roles: ['admin'] }
const author = { id: '2', roles: ['author'] }
const viewer = { id: '3', roles: ['viewer'] }
// Admin can do everything
console.log(rbac.hasPermission(admin, 'posts:delete')) // true
console.log(rbac.hasPermission(admin, 'users:manage')) // true
// Author can create and edit
console.log(rbac.hasPermission(author, 'posts:create')) // true
console.log(rbac.hasPermission(author, 'posts:delete')) // false
// Viewer can only read
console.log(rbac.hasPermission(viewer, 'posts:read')) // true
console.log(rbac.hasPermission(viewer, 'posts:create')) // false
```
## E-Commerce Application
```typescript
const rbac = new RBAC()
// Customer role
rbac.createRole('customer', [
'products:read',
'cart:*',
'orders:create',
'orders:read:own',
'profile:update:own'
])
// Store manager
rbac.createRole('manager', [
'products:*',
'orders:read',
'orders:update',
'inventory:manage',
'reports:read'
])
// Admin
rbac.createRole('admin', [
'*' // All permissions
])
// Set hierarchy
rbac.setRoleHierarchy({
admin: ['manager'],
manager: ['customer']
})
// Usage
const customer = { id: 'cust-1', roles: ['customer'] }
const manager = { id: 'mgr-1', roles: ['manager'] }
// Customer can manage cart
console.log(rbac.hasPermission(customer, 'cart:add')) // true
console.log(rbac.hasPermission(customer, 'cart:checkout')) // true
// Manager can manage products
console.log(rbac.hasPermission(manager, 'products:create')) // true
console.log(rbac.hasPermission(manager, 'inventory:manage')) // true
// Manager inherits customer permissions
console.log(rbac.hasPermission(manager, 'cart:add')) // true
```
## Multi-Tenant SaaS
```typescript
const rbac = new RBAC()
// Workspace owner
rbac.createRole('workspace-owner', [
'workspace:*',
'members:*',
'billing:*',
'data:*'
])
// Workspace admin
rbac.createRole('workspace-admin', [
'workspace:read',
'workspace:update',
'members:invite',
'members:remove',
'data:*'
])
// Workspace member
rbac.createRole('workspace-member', [
'workspace:read',
'data:read',
'data:create',
'data:update:own'
])
// Guest
rbac.createRole('workspace-guest', [
'workspace:read',
'data:read'
])
rbac.setRoleHierarchy({
'workspace-owner': ['workspace-admin'],
'workspace-admin': ['workspace-member'],
'workspace-member': ['workspace-guest']
})
// Check tenant-specific permissions
const owner = { id: '1', roles: ['workspace-owner'] }
const member = { id: '2', roles: ['workspace-member'] }
console.log(rbac.hasPermission(owner, 'billing:update')) // true
console.log(rbac.hasPermission(member, 'billing:update')) // false
console.log(rbac.hasPermission(member, 'data:create')) // true
```
## Content Management System
```typescript
const rbac = new RBAC()
// Super admin
rbac.createRole('super-admin', ['*'])
// Content admin
rbac.createRole('content-admin', [
'content:*',
'media:*',
'categories:*'
])
// Editor
rbac.createRole('editor', [
'content:read',
'content:create',
'content:update',
'content:publish',
'media:read',
'media:upload'
])
// Contributor
rbac.createRole('contributor', [
'content:read',
'content:create',
'content:update:own',
'media:read'
])
// Reviewer
rbac.createRole('reviewer', [
'content:read',
'content:review',
'content:comment'
])
rbac.setRoleHierarchy({
'super-admin': ['content-admin'],
'content-admin': ['editor'],
'editor': ['contributor']
})
const editor = { id: '1', roles: ['editor'] }
const contributor = { id: '2', roles: ['contributor'] }
// Editor can publish
console.log(rbac.hasPermission(editor, 'content:publish')) // true
// Contributor cannot publish
console.log(rbac.hasPermission(contributor, 'content:publish')) // false
// Both can create
console.log(rbac.hasPermission(editor, 'content:create')) // true
console.log(rbac.hasPermission(contributor, 'content:create')) // true
```
## API Gateway
```typescript
const rbac = new RBAC()
// Free tier
rbac.createRole('api-free', [
'api:v1:read',
'api:ratelimit:basic' // 100 req/hour
])
// Pro tier
rbac.createRole('api-pro', [
'api:v1:*',
'api:v2:read',
'api:ratelimit:pro', // 1000 req/hour
'api:analytics:basic'
])
// Enterprise tier
rbac.createRole('api-enterprise', [
'api:*',
'api:ratelimit:unlimited',
'api:analytics:*',
'api:priority:support'
])
rbac.setRoleHierarchy({
'api-enterprise': ['api-pro'],
'api-pro': ['api-free']
})
const freeUser = { id: '1', roles: ['api-free'] }
const proUser = { id: '2', roles: ['api-pro'] }
const enterpriseUser = { id: '3', roles: ['api-enterprise'] }
// Check API access
console.log(rbac.hasPermission(freeUser, 'api:v1:read')) // true
console.log(rbac.hasPermission(freeUser, 'api:v2:read')) // false
console.log(rbac.hasPermission(proUser, 'api:v1:write')) // true
console.log(rbac.hasPermission(proUser, 'api:v2:read')) // true
console.log(rbac.hasPermission(enterpriseUser, 'api:v2:write')) // true
```
## Resource Ownership
```typescript
const rbac = new RBAC()
rbac.createRole('user', [
'posts:read',
'posts:create',
'posts:update:own',
'posts:delete:own',
'comments:create',
'comments:update:own'
])
rbac.createRole('moderator', [
'posts:read',
'posts:update',
'comments:*'
])
// Check ownership in application logic
function canEditPost(user, post) {
// User owns the post
if (post.authorId === user.id) {
return rbac.hasPermission(user, 'posts:update:own')
}
// User is moderator
return rbac.hasPermission(user, 'posts:update')
}
// Example usage
const user = { id: 'user-1', roles: ['user'] }
const moderator = { id: 'mod-1', roles: ['moderator'] }
const myPost = { id: 'post-1', authorId: 'user-1' }
const otherPost = { id: 'post-2', authorId: 'user-2' }
console.log(canEditPost(user, myPost)) // true (owns post)
console.log(canEditPost(user, otherPost)) // false (doesn't own)
console.log(canEditPost(moderator, otherPost)) // true (moderator)
```
## Dynamic Role Assignment
```typescript
const rbac = new RBAC()
rbac.createRole('free', ['app:basic'])
rbac.createRole('pro', ['app:basic', 'app:pro'])
rbac.createRole('enterprise', ['app:*'])
// Assign role based on subscription
function getUserWithRoles(user, subscription) {
const roles = ['free'] // Default role
if (subscription.plan === 'pro') {
roles.push('pro')
} else if (subscription.plan === 'enterprise') {
roles.push('enterprise')
}
// Add time-based roles
if (subscription.trial && subscription.trialEndsAt > new Date()) {
roles.push('trial')
}
return {
...user,
roles
}
}
// Usage
const subscription = { plan: 'pro', trial: false }
const userWithRoles = getUserWithRoles(
{ id: '1', email: 'user@example.com' },
subscription
)
console.log(rbac.hasPermission(userWithRoles, 'app:pro')) // true
```
## Audit Logging
```typescript
import { RBAC, BufferedAuditLogger } from '@fire-shield/core'
const auditLogger = new BufferedAuditLogger(
async (logs) => {
// Save to database
await db.auditLogs.insertMany(logs)
// Send to logging service
await analyticsService.track('audit_logs', {
count: logs.length,
events: logs
})
},
{
maxBufferSize: 50,
flushIntervalMs: 3000
}
)
const rbac = new RBAC({ auditLogger })
// All permission checks are automatically logged
rbac.hasPermission(user, 'posts:delete')
// Logs: {
// timestamp: Date,
// userId: 'user-1',
// action: 'permission_check',
// permission: 'posts:delete',
// result: true/false
// }
// Manually flush logs
await auditLogger.flush()
```
## Testing RBAC
```typescript
import { describe, it, expect } from 'vitest'
import { RBAC } from '@fire-shield/core'
describe('RBAC Configuration', () => {
const rbac = new RBAC()
beforeEach(() => {
rbac.createRole('admin', ['*'])
rbac.createRole('editor', ['posts:*'])
rbac.createRole('viewer', ['posts:read'])
rbac.setRoleHierarchy({
admin: ['editor'],
editor: ['viewer']
})
})
it('admin should have all permissions', () => {
const admin = { id: '1', roles: ['admin'] }
expect(rbac.hasPermission(admin, 'posts:write')).toBe(true)
expect(rbac.hasPermission(admin, 'users:delete')).toBe(true)
expect(rbac.hasPermission(admin, 'anything')).toBe(true)
})
it('editor should inherit viewer permissions', () => {
const editor = { id: '2', roles: ['editor'] }
expect(rbac.hasPermission(editor, 'posts:read')).toBe(true)
expect(rbac.hasPermission(editor, 'posts:write')).toBe(true)
})
it('viewer should only read', () => {
const viewer = { id: '3', roles: ['viewer'] }
expect(rbac.hasPermission(viewer, 'posts:read')).toBe(true)
expect(rbac.hasPermission(viewer, 'posts:write')).toBe(false)
})
})
```
## Next Steps
### Role Hierarchy Examples
> Source: https://fire-shield.dev/examples/role-hierarchy
# Role Hierarchy Examples
## Corporate Hierarchy
```typescript
import { RBAC, RBACBuilder } from '@fire-shield/core';
// Build corporate hierarchy
const rbac = new RBACBuilder()
// Executive Level
.addRole('ceo', ['*'], {
level: 100,
description: 'Chief Executive Officer - Full access'
})
.addRole('cto', ['technology:*', 'team:*'], {
level: 90,
description: 'Chief Technology Officer'
})
.addRole('cfo', ['finance:*', 'team:*'], {
level: 90,
description: 'Chief Financial Officer'
})
// Management Level
.addRole('director', ['department:*', 'team:manage'], {
level: 50,
description: 'Department Director'
})
.addRole('manager', ['team:*', 'project:*'], {
level: 30,
description: 'Team Manager'
})
.addRole('team-lead', ['project:*', 'code:*'], {
level: 20,
description: 'Technical Team Lead'
})
// Individual Contributors
.addRole('senior-engineer', ['code:*', 'review:*'], {
level: 15,
description: 'Senior Engineer'
})
.addRole('engineer', ['code:read', 'code:write'], {
level: 10,
description: 'Engineer'
})
.addRole('junior-engineer', ['code:read'], {
level: 5,
description: 'Junior Engineer'
})
.addRole('intern', ['code:read'], {
level: 1,
description: 'Intern - Read-only access'
})
.build();
// Usage examples
const ceo = { id: 'ceo-1', roles: ['ceo'] };
const manager = { id: 'mgr-1', roles: ['manager'] };
const engineer = { id: 'eng-1', roles: ['engineer'] };
const intern = { id: 'int-1', roles: ['intern'] };
// CEO can act as anyone
console.log(rbac.canActAsRole('ceo', 'intern')); // true
console.log(rbac.canActAsRole('ceo', 'manager')); // true
// Manager can act as engineers
console.log(rbac.canActAsRole('manager', 'engineer')); // true
console.log(rbac.canActAsRole('manager', 'intern')); // true
// Engineer cannot act as manager
console.log(rbac.canActAsRole('engineer', 'manager')); // false
// Permission checks
console.log(rbac.hasPermission(ceo, 'code:delete')); // true (has *)
console.log(rbac.hasPermission(manager, 'team:manage')); // true
console.log(rbac.hasPermission(intern, 'code:write')); // false
```
## Permission Delegation System
```typescript
interface DelegationRequest {
delegatorId: string;
delegateeId: string;
permission: string;
duration?: number; // milliseconds
}
class PermissionDelegationSystem {
constructor(private rbac: RBAC) {}
delegate(request: DelegationRequest): boolean {
const delegator = this.getUser(request.delegatorId);
const delegatee = this.getUser(request.delegateeId);
// Check if delegator has the permission
if (!this.rbac.hasPermission(delegator, request.permission)) {
console.log('Delegator does not have this permission');
return false;
}
// Check hierarchy - delegator must be higher level
const canDelegate = this.canDelegateToUser(delegator, delegatee);
if (!canDelegate) {
console.log('Delegator cannot delegate to this user (insufficient level)');
return false;
}
// Grant temporary permission
this.grantTemporaryPermission(
delegatee,
request.permission,
request.duration
);
return true;
}
private canDelegateToUser(delegator: RBACUser, delegatee: RBACUser): boolean {
const hierarchy = this.rbac.getRoleHierarchy();
for (const delegatorRole of delegator.roles) {
for (const delegateeRole of delegatee.roles) {
const delegatorLevel = hierarchy.getRoleLevel(delegatorRole);
const delegateeLevel = hierarchy.getRoleLevel(delegateeRole);
if (delegatorLevel > delegateeLevel) {
return true;
}
}
}
return false;
}
private grantTemporaryPermission(
user: RBACUser,
permission: string,
duration?: number
): void {
// Add permission to user
if (!user.permissions) {
user.permissions = [];
}
user.permissions.push(permission);
// Auto-revoke after duration
if (duration) {
setTimeout(() => {
this.revokePermission(user, permission);
}, duration);
}
}
private revokePermission(user: RBACUser, permission: string): void {
if (user.permissions) {
user.permissions = user.permissions.filter(p => p !== permission);
}
}
private getUser(userId: string): RBACUser {
// Fetch from database
return { id: userId, roles: [] };
}
}
// Usage
const delegationSystem = new PermissionDelegationSystem(rbac);
const admin = { id: 'admin-1', roles: ['admin'] };
const manager = { id: 'mgr-1', roles: ['manager'] };
// Admin delegates permission to manager for 1 hour
delegationSystem.delegate({
delegatorId: 'admin-1',
delegateeId: 'mgr-1',
permission: 'users:delete',
duration: 3600000 // 1 hour
});
```
## Approval Workflow System
```typescript
interface ApprovalRequest {
id: string;
requesterId: string;
requesterRole: string;
action: string;
resource: string;
status: 'pending' | 'approved' | 'rejected';
approvers: Array<{
userId: string;
level: number;
status: 'pending' | 'approved' | 'rejected';
timestamp?: number;
}>;
}
class ApprovalWorkflow {
constructor(private rbac: RBAC) {}
createRequest(
requesterId: string,
requesterRole: string,
action: string,
resource: string
): ApprovalRequest {
const hierarchy = this.rbac.getRoleHierarchy();
const requesterLevel = hierarchy.getRoleLevel(requesterRole);
// Determine required approval levels
const approvalLevels = this.getRequiredApprovalLevels(action);
// Find approvers at each level
const approvers = approvalLevels
.filter(level => level > requesterLevel)
.map(level => ({
userId: this.findUserAtLevel(level),
level,
status: 'pending' as const
}));
return {
id: `req-${Date.now()}`,
requesterId,
requesterRole,
action,
resource,
status: 'pending',
approvers
};
}
approve(request: ApprovalRequest, approverId: string): boolean {
const approver = this.getUser(approverId);
const hierarchy = this.rbac.getRoleHierarchy();
// Check if approver has sufficient level
const approverMaxLevel = Math.max(
...approver.roles.map(r => hierarchy.getRoleLevel(r))
);
// Update approvers
for (const a of request.approvers) {
if (a.level <= approverMaxLevel && a.status === 'pending') {
a.status = 'approved';
a.timestamp = Date.now();
}
}
// Check if all approvals received
const allApproved = request.approvers.every(a => a.status === 'approved');
if (allApproved) {
request.status = 'approved';
this.executeAction(request);
}
return allApproved;
}
private getRequiredApprovalLevels(action: string): number[] {
// Define approval requirements
const requirements: Record = {
'budget:approve:small': [30], // Manager
'budget:approve:medium': [30, 50], // Manager + Director
'budget:approve:large': [30, 50, 90], // Manager + Director + Executive
'user:delete': [50], // Director
'system:shutdown': [90, 100], // Executive + CEO
};
return requirements[action] || [30];
}
private findUserAtLevel(level: number): string {
// Find user with this role level from database
return `user-level-${level}`;
}
private executeAction(request: ApprovalRequest): void {
console.log(`Executing approved action: ${request.action}`);
}
private getUser(userId: string): RBACUser {
return { id: userId, roles: [] };
}
}
// Usage
const workflow = new ApprovalWorkflow(rbac);
// Engineer requests budget approval
const request = workflow.createRequest(
'eng-1',
'engineer',
'budget:approve:large',
'project-x'
);
console.log('Request created:', request);
// {
// id: 'req-123',
// approvers: [
// { userId: 'user-level-30', level: 30, status: 'pending' },
// { userId: 'user-level-50', level: 50, status: 'pending' },
// { userId: 'user-level-90', level: 90, status: 'pending' }
// ]
// }
// Manager approves (level 30)
workflow.approve(request, 'mgr-1');
// Director approves (level 50)
workflow.approve(request, 'dir-1');
// CEO approves (level 100) - approves both level 90 and 100
const approved = workflow.approve(request, 'ceo-1');
console.log('Fully approved:', approved); // true
```
## Document Access Control
```typescript
interface Document {
id: string;
title: string;
ownerId: string;
ownerRole: string;
classification: 'public' | 'internal' | 'confidential' | 'secret';
content: string;
}
class DocumentAccessControl {
constructor(private rbac: RBAC) {}
canAccessDocument(user: RBACUser, document: Document): boolean {
// Owner can always access
if (user.id === document.ownerId) {
return true;
}
// Check permission based on classification
const classificationPermission = `document:read:${document.classification}`;
if (this.rbac.hasPermission(user, classificationPermission)) {
return true;
}
// Check hierarchy - higher roles can access lower role documents
const hierarchy = this.rbac.getRoleHierarchy();
const ownerLevel = hierarchy.getRoleLevel(document.ownerRole);
for (const userRole of user.roles) {
const userLevel = hierarchy.getRoleLevel(userRole);
if (userLevel > ownerLevel) {
return true;
}
}
return false;
}
canEditDocument(user: RBACUser, document: Document): boolean {
// Owner can edit
if (user.id === document.ownerId) {
return this.rbac.hasPermission(user, 'document:edit:own');
}
// Higher hierarchy can edit
const hierarchy = this.rbac.getRoleHierarchy();
const ownerLevel = hierarchy.getRoleLevel(document.ownerRole);
for (const userRole of user.roles) {
const userLevel = hierarchy.getRoleLevel(userRole);
if (userLevel > ownerLevel) {
return this.rbac.hasPermission(user, 'document:edit:any');
}
}
return false;
}
canDeleteDocument(user: RBACUser, document: Document): boolean {
// Must have delete permission
if (!this.rbac.hasPermission(user, 'document:delete')) {
return false;
}
// Must be significantly higher in hierarchy (2+ levels)
const hierarchy = this.rbac.getRoleHierarchy();
const ownerLevel = hierarchy.getRoleLevel(document.ownerRole);
for (const userRole of user.roles) {
const userLevel = hierarchy.getRoleLevel(userRole);
if (userLevel >= ownerLevel + 20) { // Require 20+ level difference
return true;
}
}
return false;
}
}
// Setup permissions
rbac.createRole('intern', ['document:read:public', 'document:edit:own']);
rbac.createRole('employee', ['document:read:internal', 'document:edit:own']);
rbac.createRole('manager', ['document:read:confidential', 'document:edit:any', 'document:delete']);
rbac.createRole('director', ['document:read:secret', 'document:edit:any', 'document:delete']);
const hierarchy = rbac.getRoleHierarchy();
hierarchy.setRoleLevel('intern', 1);
hierarchy.setRoleLevel('employee', 10);
hierarchy.setRoleLevel('manager', 30);
hierarchy.setRoleLevel('director', 50);
// Usage
const docControl = new DocumentAccessControl(rbac);
const employeeDoc: Document = {
id: 'doc-1',
title: 'Project Plan',
ownerId: 'emp-1',
ownerRole: 'employee',
classification: 'internal',
content: '...'
};
const intern = { id: 'int-1', roles: ['intern'] };
const employee = { id: 'emp-2', roles: ['employee'] };
const manager = { id: 'mgr-1', roles: ['manager'] };
console.log(docControl.canAccessDocument(intern, employeeDoc)); // false (no permission)
console.log(docControl.canAccessDocument(employee, employeeDoc)); // true (same level + permission)
console.log(docControl.canAccessDocument(manager, employeeDoc)); // true (higher level)
console.log(docControl.canEditDocument(employee, employeeDoc)); // false (not owner)
console.log(docControl.canEditDocument(manager, employeeDoc)); // true (higher level + edit:any)
console.log(docControl.canDeleteDocument(manager, employeeDoc)); // false (not 20+ levels higher)
console.log(docControl.canDeleteDocument(manager, internDoc)); // true (29 levels higher)
```
## Next Steps
### Wildcard Permissions Examples
> Source: https://fire-shield.dev/examples/wildcards
# Wildcard Permissions Examples
## Resource-Based Wildcards
```typescript
import { RBAC, RBACBuilder } from '@fire-shield/core';
const rbac = new RBACBuilder()
.useBitSystem()
.enableWildcards()
// User management permissions
.addPermission('user:read')
.addPermission('user:create')
.addPermission('user:update')
.addPermission('user:delete')
// Post management permissions
.addPermission('post:read')
.addPermission('post:create')
.addPermission('post:update')
.addPermission('post:delete')
// Comment management permissions
.addPermission('comment:read')
.addPermission('comment:create')
.addPermission('comment:update')
.addPermission('comment:delete')
// Roles with wildcards
.addRole('admin', ['*']) // Everything
.addRole('moderator', ['post:*', 'comment:*', 'user:read']) // All post/comment actions
.addRole('editor', ['post:*']) // All post actions
.addRole('author', ['post:read', 'post:create', 'post:update']) // Limited post actions
.addRole('viewer', ['post:read', 'comment:read']) // Read-only
.build();
// Usage
const admin = { id: '1', roles: ['admin'] };
const moderator = { id: '2', roles: ['moderator'] };
const editor = { id: '3', roles: ['editor'] };
const author = { id: '4', roles: ['author'] };
const viewer = { id: '5', roles: ['viewer'] };
// Admin has everything
console.log(rbac.hasPermission(admin, 'user:delete')); // true
console.log(rbac.hasPermission(admin, 'system:reboot')); // true (wildcard matches all)
// Moderator can manage posts and comments
console.log(rbac.hasPermission(moderator, 'post:delete')); // true
console.log(rbac.hasPermission(moderator, 'comment:delete')); // true
console.log(rbac.hasPermission(moderator, 'user:delete')); // false
// Editor can only manage posts
console.log(rbac.hasPermission(editor, 'post:delete')); // true
console.log(rbac.hasPermission(editor, 'comment:delete')); // false
// Author has limited post permissions
console.log(rbac.hasPermission(author, 'post:create')); // true
console.log(rbac.hasPermission(author, 'post:delete')); // false
// Viewer can only read
console.log(rbac.hasPermission(viewer, 'post:read')); // true
console.log(rbac.hasPermission(viewer, 'post:write')); // false
```
## Multi-Level Wildcards
```typescript
const rbac = new RBACBuilder()
.enableWildcards()
// System administration
.addPermission('system:server:start')
.addPermission('system:server:stop')
.addPermission('system:server:restart')
.addPermission('system:database:backup')
.addPermission('system:database:restore')
.addPermission('system:logs:read')
.addPermission('system:logs:delete')
// Application management
.addPermission('app:users:create')
.addPermission('app:users:delete')
.addPermission('app:settings:read')
.addPermission('app:settings:write')
// Roles with multi-level wildcards
.addRole('sysadmin', ['system:*']) // All system operations
.addRole('dba', ['system:database:*']) // Only database operations
.addRole('devops', ['system:server:*', 'system:logs:read']) // Server ops + log reading
.addRole('app-admin', ['app:*']) // All app operations
.addRole('user-manager', ['app:users:*']) // Only user management
.build();
// Usage
const sysadmin = { id: '1', roles: ['sysadmin'] };
const dba = { id: '2', roles: ['dba'] };
const devops = { id: '3', roles: ['devops'] };
console.log(rbac.hasPermission(sysadmin, 'system:server:start')); // true
console.log(rbac.hasPermission(sysadmin, 'system:database:backup')); // true
console.log(rbac.hasPermission(dba, 'system:database:backup')); // true
console.log(rbac.hasPermission(dba, 'system:server:start')); // false
console.log(rbac.hasPermission(devops, 'system:server:restart')); // true
console.log(rbac.hasPermission(devops, 'system:database:backup')); // false
```
## Multi-Tenant Wildcards
```typescript
interface Tenant {
id: string;
name: string;
}
interface TenantUser extends RBACUser {
tenantId: string;
}
class MultiTenantRBAC {
private rbac: RBAC;
constructor() {
this.rbac = new RBACBuilder()
.enableWildcards()
// Per-tenant permissions
.addPermission('tenant:*:user:read')
.addPermission('tenant:*:user:write')
.addPermission('tenant:*:data:read')
.addPermission('tenant:*:data:write')
.addPermission('tenant:*:settings:read')
.addPermission('tenant:*:settings:write')
// Platform-wide permissions
.addPermission('platform:tenant:create')
.addPermission('platform:tenant:delete')
.addPermission('platform:analytics:read')
// Roles
.addRole('platform-admin', ['platform:*', 'tenant:*:*:*']) // Full access
.addRole('tenant-admin', []) // Permissions added dynamically per tenant
.addRole('tenant-user', []) // Permissions added dynamically per tenant
.build();
}
// Grant tenant-specific permissions
grantTenantAccess(user: TenantUser, role: string): void {
const tenantId = user.tenantId;
if (role === 'tenant-admin') {
// Admin can do everything in their tenant
if (!user.permissions) user.permissions = [];
user.permissions.push(`tenant:${tenantId}:*:*`);
} else if (role === 'tenant-user') {
// Regular user has limited access
if (!user.permissions) user.permissions = [];
user.permissions.push(`tenant:${tenantId}:data:read`);
user.permissions.push(`tenant:${tenantId}:user:read`);
}
}
canAccessTenantResource(
user: TenantUser,
tenantId: string,
resource: string,
action: string
): boolean {
const permission = `tenant:${tenantId}:${resource}:${action}`;
return this.rbac.hasPermission(user, permission);
}
}
// Usage
const multiTenant = new MultiTenantRBAC();
// Platform admin
const platformAdmin: TenantUser = {
id: 'admin-1',
roles: ['platform-admin'],
tenantId: 'tenant-1'
};
// Tenant admins
const tenant1Admin: TenantUser = {
id: 'user-1',
roles: ['tenant-admin'],
tenantId: 'tenant-1',
permissions: []
};
const tenant2Admin: TenantUser = {
id: 'user-2',
roles: ['tenant-admin'],
tenantId: 'tenant-2',
permissions: []
};
// Grant tenant-specific access
multiTenant.grantTenantAccess(tenant1Admin, 'tenant-admin');
multiTenant.grantTenantAccess(tenant2Admin, 'tenant-admin');
// Platform admin can access any tenant
console.log(multiTenant.canAccessTenantResource(
platformAdmin, 'tenant-1', 'data', 'write'
)); // true
console.log(multiTenant.canAccessTenantResource(
platformAdmin, 'tenant-2', 'data', 'write'
)); // true
// Tenant admin can only access their own tenant
console.log(multiTenant.canAccessTenantResource(
tenant1Admin, 'tenant-1', 'data', 'write'
)); // true
console.log(multiTenant.canAccessTenantResource(
tenant1Admin, 'tenant-2', 'data', 'write'
)); // false
```
## Dynamic Wildcard Permissions
```typescript
class DynamicPermissionBuilder {
private rbac: RBAC;
constructor() {
this.rbac = new RBACBuilder()
.enableWildcards()
.build();
}
// Generate permissions for a new resource type
registerResource(resourceType: string, actions: string[]): void {
for (const action of actions) {
const permission = `${resourceType}:${action}`;
this.rbac.addPermission(permission);
}
}
// Create role with wildcard for resource
createResourceRole(roleName: string, resourceType: string): void {
this.rbac.createRole(roleName, [`${resourceType}:*`]);
}
// Create role with specific actions across resources
createActionRole(roleName: string, action: string, resources: string[]): void {
const permissions = resources.map(r => `${r}:${action}`);
this.rbac.createRole(roleName, permissions);
}
// Create custom role with mixed wildcards
createCustomRole(
roleName: string,
wildcardPatterns: string[],
specificPermissions: string[]
): void {
this.rbac.createRole(roleName, [...wildcardPatterns, ...specificPermissions]);
}
}
// Usage
const builder = new DynamicPermissionBuilder();
// Register different resource types
builder.registerResource('article', ['read', 'create', 'update', 'delete', 'publish']);
builder.registerResource('video', ['read', 'upload', 'edit', 'delete', 'monetize']);
builder.registerResource('podcast', ['read', 'upload', 'edit', 'delete']);
// Create resource-specific roles
builder.createResourceRole('article-manager', 'article'); // Can do anything with articles
builder.createResourceRole('video-manager', 'video'); // Can do anything with videos
// Create action-specific roles
builder.createActionRole('content-reader', 'read', ['article', 'video', 'podcast']);
builder.createActionRole('content-creator', 'create', ['article', 'video', 'podcast']);
// Create custom mixed role
builder.createCustomRole(
'publisher',
['article:*', 'video:*'], // Full access to articles and videos
['podcast:read', 'podcast:upload'] // Limited podcast access
);
// Test permissions
const articleManager = { id: '1', roles: ['article-manager'] };
const contentReader = { id: '2', roles: ['content-reader'] };
const publisher = { id: '3', roles: ['publisher'] };
console.log(builder.rbac.hasPermission(articleManager, 'article:publish')); // true
console.log(builder.rbac.hasPermission(articleManager, 'video:upload')); // false
console.log(builder.rbac.hasPermission(contentReader, 'article:read')); // true
console.log(builder.rbac.hasPermission(contentReader, 'article:delete')); // false
console.log(builder.rbac.hasPermission(publisher, 'article:publish')); // true
console.log(builder.rbac.hasPermission(publisher, 'video:monetize')); // true
console.log(builder.rbac.hasPermission(publisher, 'podcast:delete')); // false
```
## API Endpoint Protection
```typescript
import express from 'express';
import { createExpressRBAC } from '@fire-shield/express';
const rbac = new RBACBuilder()
.enableWildcards()
// API endpoint permissions
.addPermission('api:v1:users:get')
.addPermission('api:v1:users:post')
.addPermission('api:v1:users:put')
.addPermission('api:v1:users:delete')
.addPermission('api:v1:posts:get')
.addPermission('api:v1:posts:post')
.addPermission('api:v1:posts:put')
.addPermission('api:v1:posts:delete')
// Roles
.addRole('api-admin', ['api:*']) // All API access
.addRole('api-user', ['api:v1:*:get']) // Read-only all endpoints
.addRole('user-manager', ['api:v1:users:*']) // Full user management
.addRole('content-editor', ['api:v1:posts:*']) // Full post management
.build();
const app = express();
const rbacMiddleware = createExpressRBAC(rbac, {
getUser: (req) => req.user
});
// Protect endpoints with wildcards
app.get('/api/v1/users',
rbacMiddleware.requirePermission('api:v1:users:get'),
(req, res) => res.json({ users: [] })
);
app.post('/api/v1/users',
rbacMiddleware.requirePermission('api:v1:users:post'),
(req, res) => res.json({ created: true })
);
app.delete('/api/v1/users/:id',
rbacMiddleware.requirePermission('api:v1:users:delete'),
(req, res) => res.json({ deleted: true })
);
// Dynamic permission checking
app.use('/api/v1/:resource/:id?', (req, res, next) => {
const { resource } = req.params;
const method = req.method.toLowerCase();
const permission = `api:v1:${resource}:${method}`;
if (!rbac.hasPermission(req.user, permission)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
});
```
## Feature Flag System
```typescript
class FeatureFlagSystem {
private rbac: RBAC;
constructor() {
this.rbac = new RBACBuilder()
.enableWildcards()
// Feature permissions
.addPermission('feature:beta:*')
.addPermission('feature:premium:*')
.addPermission('feature:experimental:*')
// Specific features
.addPermission('feature:beta:new-editor')
.addPermission('feature:beta:advanced-search')
.addPermission('feature:premium:analytics')
.addPermission('feature:premium:api-access')
.addPermission('feature:experimental:ai-assistant')
// Roles
.addRole('beta-tester', ['feature:beta:*'])
.addRole('premium-user', ['feature:premium:*'])
.addRole('internal-user', ['feature:*']) // All features
.build();
}
hasFeature(user: RBACUser, featureName: string): boolean {
return this.rbac.hasPermission(user, `feature:${featureName}`);
}
enableFeatureForUser(user: RBACUser, featureName: string): void {
if (!user.permissions) user.permissions = [];
user.permissions.push(`feature:${featureName}`);
}
getAvailableFeatures(user: RBACUser): string[] {
const allFeatures = [
'beta:new-editor',
'beta:advanced-search',
'premium:analytics',
'premium:api-access',
'experimental:ai-assistant'
];
return allFeatures.filter(feature => this.hasFeature(user, feature));
}
}
// Usage
const featureFlags = new FeatureFlagSystem();
const betaUser = { id: '1', roles: ['beta-tester'] };
const premiumUser = { id: '2', roles: ['premium-user'] };
const internalUser = { id: '3', roles: ['internal-user'] };
console.log(featureFlags.hasFeature(betaUser, 'beta:new-editor')); // true
console.log(featureFlags.hasFeature(betaUser, 'premium:analytics')); // false
console.log(featureFlags.hasFeature(premiumUser, 'premium:analytics')); // true
console.log(featureFlags.hasFeature(premiumUser, 'beta:new-editor')); // false
console.log(featureFlags.hasFeature(internalUser, 'experimental:ai-assistant')); // true
console.log(featureFlags.getAvailableFeatures(betaUser));
// ['beta:new-editor', 'beta:advanced-search']
```
## Next Steps
### Audit Logging Examples
> Source: https://fire-shield.dev/examples/audit-logging
# Audit Logging Examples
## Basic Audit Logging
```typescript
import { RBAC, ConsoleAuditLogger } from '@fire-shield/core';
// Create RBAC with console logger
const rbac = new RBAC({
auditLogger: new ConsoleAuditLogger()
});
rbac.createRole('admin', ['users:*']);
rbac.createRole('viewer', ['users:read']);
const admin = { id: 'admin-1', roles: ['admin'] };
const viewer = { id: 'viewer-1', roles: ['viewer'] };
// All permission checks are logged
rbac.hasPermission(admin, 'users:delete');
// Console output:
// [AUDIT] permission_check | user: admin-1 | permission: users:delete | allowed: true
rbac.hasPermission(viewer, 'users:delete');
// Console output:
// [AUDIT] permission_check | user: viewer-1 | permission: users:delete | allowed: false | reason: User lacks permission: users:delete
```
## Database Audit Logging
```typescript
import { RBAC, BufferedAuditLogger, AuditEvent } from '@fire-shield/core';
import { db } from './database'; // Your database client
// Create buffered logger that writes to database
const auditLogger = new BufferedAuditLogger(
async (events: AuditEvent[]) => {
// Batch insert audit events
await db.auditLogs.insertMany(
events.map(event => ({
type: event.type,
userId: event.userId,
permission: event.permission,
allowed: event.allowed,
reason: event.reason,
context: event.context,
timestamp: new Date(event.timestamp),
createdAt: new Date()
}))
);
},
{
maxBufferSize: 100, // Flush when buffer reaches 100 events
flushIntervalMs: 5000 // Or every 5 seconds
}
);
const rbac = new RBAC({ auditLogger });
// Events are buffered and batch-written to database
rbac.hasPermission(user, 'post:delete');
rbac.hasPermission(user, 'user:create');
// ... more checks
// Manual flush if needed
await auditLogger.flush();
```
## Security Monitoring System
```typescript
import { AuditLogger, AuditEvent } from '@fire-shield/core';
class SecurityMonitorLogger implements AuditLogger {
private failedAttempts = new Map<string, number>();
private suspiciousActivity = new Map<string, AuditEvent[]>();
log(event: AuditEvent): void {
// Track failed authorization attempts
if (!event.allowed) {
this.trackFailedAttempt(event);
}
// Detect suspicious patterns
this.detectSuspiciousActivity(event);
// Log high-privilege access
if (this.isHighPrivilegeAccess(event)) {
this.alertSecurityTeam(event);
}
}
private trackFailedAttempt(event: AuditEvent): void {
const userId = event.userId;
const count = (this.failedAttempts.get(userId) || 0) + 1;
this.failedAttempts.set(userId, count);
// Alert after 5 failed attempts
if (count >= 5) {
console.error(`🚨 SECURITY ALERT: User ${userId} has ${count} failed authorization attempts`);
this.lockUser(userId);
}
// Reset counter after 1 hour
setTimeout(() => {
this.failedAttempts.set(userId, 0);
}, 3600000);
}
private detectSuspiciousActivity(event: AuditEvent): void {
const userId = event.userId;
const recentEvents = this.suspiciousActivity.get(userId) || [];
recentEvents.push(event);
// Keep only last 10 minutes of activity
const tenMinutesAgo = Date.now() - 600000;
const filtered = recentEvents.filter(e => e.timestamp > tenMinutesAgo);
this.suspiciousActivity.set(userId, filtered);
// Alert on unusual patterns
if (this.hasUnusualPattern(filtered)) {
console.warn(`⚠️ Suspicious activity detected for user ${userId}`);
this.notifySecurityTeam(userId, filtered);
}
}
private hasUnusualPattern(events: AuditEvent[]): boolean {
// More than 50 requests in 10 minutes
if (events.length > 50) return true;
// Multiple failed high-privilege attempts
const failedHighPriv = events.filter(
e => !e.allowed && this.isHighPrivilegeAccess(e)
);
if (failedHighPriv.length > 3) return true;
// Accessing many different resources rapidly
const uniquePermissions = new Set(events.map(e => e.permission));
if (uniquePermissions.size > 20) return true;
return false;
}
private isHighPrivilegeAccess(event: AuditEvent): boolean {
const highPrivPermissions = [
'admin:*',
'system:*',
'user:delete',
'database:*'
];
return highPrivPermissions.some(pattern =>
event.permission.includes(pattern.replace('*', ''))
);
}
private alertSecurityTeam(event: AuditEvent): void {
console.log(`🔐 High-privilege access: ${event.userId} → ${event.permission}`);
// Send to monitoring service (Datadog, New Relic, etc.)
}
private lockUser(userId: string): void {
console.log(`🔒 Locking user ${userId} due to failed attempts`);
// Implement user locking logic
}
private notifySecurityTeam(userId: string, events: AuditEvent[]): void {
console.log(`📧 Notifying security team about user ${userId}`);
// Send email/Slack notification
}
}
// Usage
const rbac = new RBAC({
auditLogger: new SecurityMonitorLogger()
});
```
## Compliance Audit System
```typescript
import { AuditLogger, AuditEvent } from '@fire-shield/core';
interface ComplianceAuditLog extends AuditEvent {
// Additional compliance fields
ipAddress?: string;
userAgent?: string;
sessionId?: string;
dataClassification?: 'public' | 'internal' | 'confidential' | 'restricted';
retentionDays?: number;
}
class ComplianceAuditLogger implements AuditLogger {
private retentionPolicies = new Map<string, number>([
['public', 30],
['internal', 90],
['confidential', 365],
['restricted', 2555] // 7 years
]);
async log(event: AuditEvent): Promise<void> {
const complianceLog: ComplianceAuditLog = {
...event,
ipAddress: event.context?.ip,
userAgent: event.context?.userAgent,
sessionId: event.context?.sessionId,
dataClassification: this.classifyData(event.permission),
retentionDays: this.getRetentionPeriod(event.permission)
};
// Write to compliance database
await this.writeToComplianceLog(complianceLog);
// Check if requires immediate reporting
if (this.requiresImmediateReporting(complianceLog)) {
await this.reportToComplianceOfficer(complianceLog);
}
}
private classifyData(permission: string): string {
if (permission.includes('pii') || permission.includes('sensitive')) {
return 'restricted';
}
if (permission.includes('financial') || permission.includes('health')) {
return 'confidential';
}
if (permission.includes('internal')) {
return 'internal';
}
return 'public';
}
private getRetentionPeriod(permission: string): number {
const classification = this.classifyData(permission);
return this.retentionPolicies.get(classification) || 30;
}
private requiresImmediateReporting(log: ComplianceAuditLog): boolean {
// Report access to restricted data
if (log.dataClassification === 'restricted') {
return true;
}
// Report failed high-privilege attempts
if (!log.allowed && log.permission.includes('admin')) {
return true;
}
return false;
}
private async writeToComplianceLog(log: ComplianceAuditLog): Promise<void> {
// Write to secure, immutable storage
await db.complianceLogs.insert({
...log,
hash: this.calculateHash(log), // Tamper detection
encrypted: this.encrypt(log), // Encryption for sensitive data
expiresAt: new Date(Date.now() + log.retentionDays! * 86400000)
});
}
private async reportToComplianceOfficer(log: ComplianceAuditLog): Promise<void> {
// Send notification
await emailService.send({
to: 'compliance@company.com',
subject: `Compliance Alert: ${log.dataClassification} data access`,
body: `
User: ${log.userId}
Permission: ${log.permission}
Allowed: ${log.allowed}
IP: ${log.ipAddress}
Time: ${new Date(log.timestamp).toISOString()}
`
});
}
private calculateHash(log: ComplianceAuditLog): string {
// Calculate hash for tamper detection
return crypto
.createHash('sha256')
.update(JSON.stringify(log))
.digest('hex');
}
private encrypt(log: ComplianceAuditLog): string {
// Encrypt sensitive fields
return encryptionService.encrypt(JSON.stringify(log));
}
}
// Usage
const rbac = new RBAC({
auditLogger: new ComplianceAuditLogger()
});
```
## Multi-Channel Audit Logging
```typescript
import { MultiAuditLogger, ConsoleAuditLogger, BufferedAuditLogger } from '@fire-shield/core';
// Log to console
const consoleLogger = new ConsoleAuditLogger();
// Log to database
const dbLogger = new BufferedAuditLogger(
async (events) => await db.auditLogs.insertMany(events),
{ maxBufferSize: 50, flushIntervalMs: 3000 }
);
// Log to external monitoring service
const monitoringLogger = new BufferedAuditLogger(
async (events) => {
await fetch('https://monitoring.example.com/audit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(events)
});
},
{ maxBufferSize: 100, flushIntervalMs: 10000 }
);
// Log to Slack for critical events
class SlackAuditLogger implements AuditLogger {
async log(event: AuditEvent): Promise<void> {
// Only log critical events to Slack
if (this.isCritical(event)) {
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `🔐 Critical Access: ${event.userId} → ${event.permission}`,
attachments: [{
color: event.allowed ? 'good' : 'danger',
fields: [
{ title: 'User', value: event.userId, short: true },
{ title: 'Permission', value: event.permission, short: true },
{ title: 'Allowed', value: event.allowed.toString(), short: true },
{ title: 'Reason', value: event.reason || 'N/A', short: true }
]
}]
})
});
}
}
private isCritical(event: AuditEvent): boolean {
return event.permission.includes('admin') ||
event.permission.includes('delete') ||
event.permission.includes('system');
}
}
// Combine all loggers
const multiLogger = new MultiAuditLogger([
consoleLogger,
dbLogger,
monitoringLogger,
new SlackAuditLogger()
]);
const rbac = new RBAC({ auditLogger: multiLogger });
```
## Query and Analysis
```typescript
class AuditLogAnalyzer {
async getUserActivity(userId: string, days: number = 7): Promise<AuditEvent[]> {
const since = Date.now() - (days * 86400000);
return await db.auditLogs.find({
userId,
timestamp: { $gte: since }
}).toArray();
}
async getFailedAttempts(hours: number = 24): Promise<AuditEvent[]> {
const since = Date.now() - (hours * 3600000);
return await db.auditLogs.find({
allowed: false,
timestamp: { $gte: since }
}).toArray();
}
async getPermissionUsage(permission: string): Promise<{
total: number;
allowed: number;
denied: number;
uniqueUsers: number;
}> {
const logs = await db.auditLogs.find({ permission }).toArray();
return {
total: logs.length,
allowed: logs.filter(l => l.allowed).length,
denied: logs.filter(l => !l.allowed).length,
uniqueUsers: new Set(logs.map(l => l.userId)).size
};
}
async getSecurityReport(days: number = 30): Promise<{
totalChecks: number;
failedChecks: number;
suspiciousUsers: string[];
topPermissions: Array<{ permission: string; count: number }>;
}> {
const since = Date.now() - (days * 86400000);
const logs = await db.auditLogs.find({
timestamp: { $gte: since }
}).toArray();
// Find users with high failure rate
const userFailures = new Map<string, number>();
logs.forEach(log => {
if (!log.allowed) {
userFailures.set(log.userId, (userFailures.get(log.userId) || 0) + 1);
}
});
const suspiciousUsers = Array.from(userFailures.entries())
.filter(([_, count]) => count > 10)
.map(([userId]) => userId);
// Count permission usage
const permissionCounts = new Map<string, number>();
logs.forEach(log => {
permissionCounts.set(
log.permission,
(permissionCounts.get(log.permission) || 0) + 1
);
});
const topPermissions = Array.from(permissionCounts.entries())
.map(([permission, count]) => ({ permission, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 10);
return {
totalChecks: logs.length,
failedChecks: logs.filter(l => !l.allowed).length,
suspiciousUsers,
topPermissions
};
}
async exportAuditLogs(
startDate: Date,
endDate: Date,
format: 'json' | 'csv'
): Promise<string> {
const logs = await db.auditLogs.find({
timestamp: {
$gte: startDate.getTime(),
$lte: endDate.getTime()
}
}).toArray();
if (format === 'csv') {
return this.convertToCSV(logs);
}
return JSON.stringify(logs, null, 2);
}
private convertToCSV(logs: AuditEvent[]): string {
const headers = ['Timestamp', 'Type', 'User ID', 'Permission', 'Allowed', 'Reason'];
const rows = logs.map(log => [
new Date(log.timestamp).toISOString(),
log.type,
log.userId,
log.permission,
log.allowed.toString(),
log.reason || ''
]);
return [headers, ...rows].map(row => row.join(',')).join('\n');
}
}
// Usage
const analyzer = new AuditLogAnalyzer();
// Get user activity
const userActivity = await analyzer.getUserActivity('user-123', 7);
console.log(`User had ${userActivity.length} permission checks in last 7 days`);
// Get security report
const report = await analyzer.getSecurityReport(30);
console.log('Security Report:', report);
// Export logs for compliance
const csvData = await analyzer.exportAuditLogs(
new Date('2025-01-01'),
new Date('2025-01-31'),
'csv'
);
```
## Real-time Audit Dashboard
```typescript
import { EventEmitter } from 'events';
class RealtimeAuditLogger extends EventEmitter implements AuditLogger {
log(event: AuditEvent): void {
// Emit event for real-time processing
this.emit('audit', event);
// Also persist to database
db.auditLogs.insert(event);
}
}
// Setup real-time monitoring
const realtimeLogger = new RealtimeAuditLogger();
// Listen for audit events
realtimeLogger.on('audit', (event: AuditEvent) => {
// Send to WebSocket clients
wsServer.broadcast({
type: 'audit_event',
data: event
});
// Update real-time metrics
metrics.increment('permission_checks');
if (!event.allowed) {
metrics.increment('permission_denials');
}
});
const rbac = new RBAC({ auditLogger: realtimeLogger });
```
## Next Steps
### Multi-Tenancy Examples
> Source: https://fire-shield.dev/examples/multi-tenancy
# Multi-Tenancy Examples
## Basic Multi-Tenant Setup
```typescript
import { RBAC, RBACBuilder } from '@fire-shield/core';
interface TenantUser extends RBACUser {
tenantId: string;
}
class MultiTenantRBAC {
private rbac: RBAC;
constructor() {
this.rbac = new RBACBuilder()
.enableWildcards()
// Platform-level permissions
.addPermission('platform:tenant:create')
.addPermission('platform:tenant:delete')
.addPermission('platform:tenant:list')
.addPermission('platform:analytics:view')
// Tenant-level permissions (use with tenant ID)
.addPermission('tenant:user:create')
.addPermission('tenant:user:delete')
.addPermission('tenant:data:read')
.addPermission('tenant:data:write')
.addPermission('tenant:settings:manage')
// Platform roles
.addRole('platform-admin', ['platform:*'])
// Tenant roles (permissions granted dynamically)
.addRole('tenant-owner', [])
.addRole('tenant-admin', [])
.addRole('tenant-member', [])
.build();
}
// Check if user can access tenant
canAccessTenant(user: TenantUser, tenantId: string): boolean {
// Platform admins can access any tenant
if (user.roles.includes('platform-admin')) {
return true;
}
// User must belong to the tenant
return user.tenantId === tenantId;
}
// Check tenant-scoped permission
hasPermissionInTenant(
user: TenantUser,
tenantId: string,
permission: string
): boolean {
// Check tenant access first
if (!this.canAccessTenant(user, tenantId)) {
return false;
}
// For platform admins, grant all tenant permissions
if (user.roles.includes('platform-admin')) {
return true;
}
// Check user's permissions
return this.rbac.hasPermission(user, permission);
}
// Grant tenant-specific role permissions
grantTenantRole(user: TenantUser, role: 'owner' | 'admin' | 'member'): void {
if (!user.permissions) user.permissions = [];
if (role === 'owner') {
user.permissions.push('tenant:*');
} else if (role === 'admin') {
user.permissions.push('tenant:user:*');
user.permissions.push('tenant:data:*');
user.permissions.push('tenant:settings:*');
} else if (role === 'member') {
user.permissions.push('tenant:data:read');
user.permissions.push('tenant:data:write');
}
}
}
// Usage
const multiTenant = new MultiTenantRBAC();
// Platform admin
const platformAdmin: TenantUser = {
id: 'admin-1',
roles: ['platform-admin'],
tenantId: 'platform'
};
// Tenant users
const tenantOwner: TenantUser = {
id: 'owner-1',
roles: ['tenant-owner'],
tenantId: 'tenant-abc',
permissions: []
};
const tenantMember: TenantUser = {
id: 'member-1',
roles: ['tenant-member'],
tenantId: 'tenant-abc',
permissions: []
};
// Grant permissions based on roles
multiTenant.grantTenantRole(tenantOwner, 'owner');
multiTenant.grantTenantRole(tenantMember, 'member');
// Platform admin can access any tenant
console.log(multiTenant.canAccessTenant(platformAdmin, 'tenant-abc')); // true
console.log(multiTenant.canAccessTenant(platformAdmin, 'tenant-xyz')); // true
// Tenant users can only access their tenant
console.log(multiTenant.canAccessTenant(tenantOwner, 'tenant-abc')); // true
console.log(multiTenant.canAccessTenant(tenantOwner, 'tenant-xyz')); // false
// Permission checks
console.log(multiTenant.hasPermissionInTenant(
tenantOwner, 'tenant-abc', 'tenant:user:delete'
)); // true
console.log(multiTenant.hasPermissionInTenant(
tenantMember, 'tenant-abc', 'tenant:user:delete'
)); // false
```
## Hierarchical Multi-Tenancy
```typescript
interface Tenant {
id: string;
name: string;
parentId?: string;
level: number; // 0 = root, 1 = organization, 2 = department, etc.
}
class HierarchicalMultiTenancy {
private rbac: RBAC;
private tenants = new Map<string, Tenant>();
constructor() {
this.rbac = new RBACBuilder()
.enableWildcards()
.build();
}
// Register tenant
registerTenant(tenant: Tenant): void {
this.tenants.set(tenant.id, tenant);
}
// Get all ancestor tenants
getAncestorTenants(tenantId: string): Tenant[] {
const ancestors: Tenant[] = [];
let current = this.tenants.get(tenantId);
while (current?.parentId) {
current = this.tenants.get(current.parentId);
if (current) ancestors.push(current);
}
return ancestors;
}
// Get all descendant tenants
getDescendantTenants(tenantId: string): Tenant[] {
const descendants: Tenant[] = [];
for (const tenant of this.tenants.values()) {
if (this.isDescendant(tenant.id, tenantId)) {
descendants.push(tenant);
}
}
return descendants;
}
private isDescendant(tenantId: string, ancestorId: string): boolean {
const ancestors = this.getAncestorTenants(tenantId);
return ancestors.some(t => t.id === ancestorId);
}
// Check if user can access tenant (including parent access)
canAccessTenant(user: TenantUser, targetTenantId: string): boolean {
// User's own tenant
if (user.tenantId === targetTenantId) {
return true;
}
// Check if user's tenant is an ancestor (parent access)
const isAncestor = this.isDescendant(targetTenantId, user.tenantId);
if (isAncestor && this.rbac.hasPermission(user, 'tenant:inherit:access')) {
return true;
}
return false;
}
// Get all accessible tenants for user
getAccessibleTenants(user: TenantUser): Tenant[] {
const accessible: Tenant[] = [];
for (const tenant of this.tenants.values()) {
if (this.canAccessTenant(user, tenant.id)) {
accessible.push(tenant);
}
}
return accessible;
}
}
// Usage
const hierarchy = new HierarchicalMultiTenancy();
// Register tenant hierarchy
// Root: ACME Corp
// - Sales Dept
// - North Region
// - South Region
// - Engineering Dept
hierarchy.registerTenant({
id: 'acme',
name: 'ACME Corp',
level: 0
});
hierarchy.registerTenant({
id: 'sales',
name: 'Sales Department',
parentId: 'acme',
level: 1
});
hierarchy.registerTenant({
id: 'north-sales',
name: 'North Region Sales',
parentId: 'sales',
level: 2
});
hierarchy.registerTenant({
id: 'engineering',
name: 'Engineering Department',
parentId: 'acme',
level: 1
});
// User at Sales level with inherit permission
const salesManager: TenantUser = {
id: 'mgr-1',
roles: ['tenant-admin'],
tenantId: 'sales',
permissions: ['tenant:inherit:access']
};
// Can access own tenant
console.log(hierarchy.canAccessTenant(salesManager, 'sales')); // true
// Can access child tenant (North Region)
console.log(hierarchy.canAccessTenant(salesManager, 'north-sales')); // true
// Cannot access sibling tenant
console.log(hierarchy.canAccessTenant(salesManager, 'engineering')); // false
// Get all accessible tenants
const accessible = hierarchy.getAccessibleTenants(salesManager);
console.log(accessible.map(t => t.name));
// ['Sales Department', 'North Region Sales']
```
## Resource Isolation
```typescript
interface TenantResource {
id: string;
tenantId: string;
type: string;
data: any;
}
class TenantResourceManager {
private rbac: RBAC;
private resources = new Map<string, TenantResource>();
constructor(rbac: RBAC) {
this.rbac = rbac;
}
// Create resource (must belong to user's tenant)
createResource(
user: TenantUser,
type: string,
data: any
): TenantResource | null {
if (!this.rbac.hasPermission(user, `${type}:create`)) {
return null;
}
const resource: TenantResource = {
id: `${type}-${Date.now()}`,
tenantId: user.tenantId,
type,
data
};
this.resources.set(resource.id, resource);
return resource;
}
// Read resource (with tenant check)
readResource(
user: TenantUser,
resourceId: string
): TenantResource | null {
const resource = this.resources.get(resourceId);
if (!resource) {
return null;
}
// Verify tenant isolation
if (resource.tenantId !== user.tenantId) {
console.error('Tenant isolation violation attempted');
return null;
}
if (!this.rbac.hasPermission(user, `${resource.type}:read`)) {
return null;
}
return resource;
}
// Update resource
updateResource(
user: TenantUser,
resourceId: string,
data: any
): boolean {
const resource = this.resources.get(resourceId);
if (!resource || resource.tenantId !== user.tenantId) {
return false;
}
if (!this.rbac.hasPermission(user, `${resource.type}:update`)) {
return false;
}
resource.data = data;
return true;
}
// List resources (only from user's tenant)
listResources(user: TenantUser, type?: string): TenantResource[] {
const userResources: TenantResource[] = [];
for (const resource of this.resources.values()) {
// Tenant isolation
if (resource.tenantId !== user.tenantId) {
continue;
}
// Type filter
if (type && resource.type !== type) {
continue;
}
// Permission check
if (this.rbac.hasPermission(user, `${resource.type}:read`)) {
userResources.push(resource);
}
}
return userResources;
}
// Delete resource
deleteResource(user: TenantUser, resourceId: string): boolean {
const resource = this.resources.get(resourceId);
if (!resource || resource.tenantId !== user.tenantId) {
return false;
}
if (!this.rbac.hasPermission(user, `${resource.type}:delete`)) {
return false;
}
this.resources.delete(resourceId);
return true;
}
}
// Usage
const rbac = new RBAC();
rbac.createRole('member', ['document:read', 'document:create', 'document:update']);
rbac.createRole('admin', ['document:*']);
const resourceManager = new TenantResourceManager(rbac);
const tenant1User: TenantUser = {
id: 'user-1',
roles: ['member'],
tenantId: 'tenant-1'
};
const tenant2User: TenantUser = {
id: 'user-2',
roles: ['member'],
tenantId: 'tenant-2'
};
// Create resources
const doc1 = resourceManager.createResource(tenant1User, 'document', {
title: 'Tenant 1 Document'
});
const doc2 = resourceManager.createResource(tenant2User, 'document', {
title: 'Tenant 2 Document'
});
// Tenant isolation enforced
console.log(resourceManager.readResource(tenant1User, doc1!.id)); // ✅ Success
console.log(resourceManager.readResource(tenant1User, doc2!.id)); // ❌ null (different tenant)
// List only shows user's tenant resources
console.log(resourceManager.listResources(tenant1User).length); // 1
console.log(resourceManager.listResources(tenant2User).length); // 1
```
## Cross-Tenant Data Sharing
```typescript
interface SharedResource {
resourceId: string;
ownerTenantId: string;
sharedWith: Array<{
tenantId: string;
permissions: string[];
expiresAt?: number;
}>;
}
class CrossTenantSharing {
private rbac: RBAC;
private sharedResources = new Map<string, SharedResource>();
constructor(rbac: RBAC) {
this.rbac = rbac;
}
// Share resource with another tenant
shareResource(
owner: TenantUser,
resourceId: string,
targetTenantId: string,
permissions: string[],
expiresInMs?: number
): boolean {
// Owner must have sharing permission
if (!this.rbac.hasPermission(owner, 'resource:share')) {
return false;
}
const shared: SharedResource = {
resourceId,
ownerTenantId: owner.tenantId,
sharedWith: [{
tenantId: targetTenantId,
permissions,
expiresAt: expiresInMs ? Date.now() + expiresInMs : undefined
}]
};
this.sharedResources.set(resourceId, shared);
return true;
}
// Check if user can access shared resource
canAccessSharedResource(
user: TenantUser,
resourceId: string,
permission: string
): boolean {
const shared = this.sharedResources.get(resourceId);
if (!shared) {
return false;
}
// Owner always has access
if (user.tenantId === shared.ownerTenantId) {
return this.rbac.hasPermission(user, permission);
}
// Check if resource is shared with user's tenant
const share = shared.sharedWith.find(s => s.tenantId === user.tenantId);
if (!share) {
return false;
}
// Check expiration
if (share.expiresAt && Date.now() > share.expiresAt) {
return false;
}
// Check if permission is granted
return share.permissions.includes(permission) ||
share.permissions.includes('*');
}
// Revoke sharing
revokeSharing(
owner: TenantUser,
resourceId: string,
targetTenantId: string
): boolean {
const shared = this.sharedResources.get(resourceId);
if (!shared || shared.ownerTenantId !== owner.tenantId) {
return false;
}
shared.sharedWith = shared.sharedWith.filter(
s => s.tenantId !== targetTenantId
);
return true;
}
}
// Usage
const sharing = new CrossTenantSharing(rbac);
const tenant1Owner: TenantUser = {
id: 'owner-1',
roles: ['tenant-owner'],
tenantId: 'tenant-1',
permissions: ['resource:share']
};
const tenant2User: TenantUser = {
id: 'user-2',
roles: ['tenant-member'],
tenantId: 'tenant-2'
};
// Share resource with another tenant for 1 hour
sharing.shareResource(
tenant1Owner,
'doc-123',
'tenant-2',
['read', 'comment'],
3600000 // 1 hour
);
// Tenant 2 user can now access the shared resource
console.log(sharing.canAccessSharedResource(
tenant2User,
'doc-123',
'read'
)); // true
console.log(sharing.canAccessSharedResource(
tenant2User,
'doc-123',
'delete'
)); // false (not granted)
// Owner can revoke sharing
sharing.revokeSharing(tenant1Owner, 'doc-123', 'tenant-2');
console.log(sharing.canAccessSharedResource(
tenant2User,
'doc-123',
'read'
)); // false (revoked)
```
## Tenant-Specific Feature Flags
```typescript
interface TenantFeatures {
tenantId: string;
plan: 'free' | 'pro' | 'enterprise';
enabledFeatures: string[];
limits: {
maxUsers: number;
maxStorage: number;
maxApiCalls: number;
};
}
class TenantFeatureManager {
private tenantFeatures = new Map<string, TenantFeatures>();
// Feature matrix by plan
private planFeatures = {
free: ['basic:*', 'export:csv'],
pro: ['basic:*', 'advanced:*', 'export:*', 'api:basic'],
enterprise: ['*'] // All features
};
private planLimits = {
free: { maxUsers: 5, maxStorage: 1024, maxApiCalls: 100 },
pro: { maxUsers: 50, maxStorage: 10240, maxApiCalls: 10000 },
enterprise: { maxUsers: -1, maxStorage: -1, maxApiCalls: -1 } // Unlimited
};
registerTenant(tenantId: string, plan: 'free' | 'pro' | 'enterprise'): void {
this.tenantFeatures.set(tenantId, {
tenantId,
plan,
enabledFeatures: this.planFeatures[plan],
limits: this.planLimits[plan]
});
}
hasFeature(tenantId: string, feature: string): boolean {
const tenant = this.tenantFeatures.get(tenantId);
if (!tenant) {
return false;
}
return tenant.enabledFeatures.some(f =>
f === feature || (f.endsWith('*') && feature.startsWith(f.slice(0, -1)))
);
}
checkLimit(tenantId: string, limitType: keyof TenantFeatures['limits'], current: number): boolean {
const tenant = this.tenantFeatures.get(tenantId);
if (!tenant) {
return false;
}
const limit = tenant.limits[limitType];
// -1 means unlimited
if (limit === -1) {
return true;
}
return current < limit;
}
upgradeTenant(tenantId: string, newPlan: 'free' | 'pro' | 'enterprise'): void {
const tenant = this.tenantFeatures.get(tenantId);
if (tenant) {
tenant.plan = newPlan;
tenant.enabledFeatures = this.planFeatures[newPlan];
tenant.limits = this.planLimits[newPlan];
}
}
}
// Usage
const featureManager = new TenantFeatureManager();
featureManager.registerTenant('tenant-free', 'free');
featureManager.registerTenant('tenant-pro', 'pro');
featureManager.registerTenant('tenant-ent', 'enterprise');
// Feature checks
console.log(featureManager.hasFeature('tenant-free', 'basic:reports')); // true
console.log(featureManager.hasFeature('tenant-free', 'advanced:analytics')); // false
console.log(featureManager.hasFeature('tenant-pro', 'advanced:analytics')); // true
console.log(featureManager.hasFeature('tenant-pro', 'api:basic')); // true
console.log(featureManager.hasFeature('tenant-ent', 'any:feature')); // true (has *)
// Limit checks
console.log(featureManager.checkLimit('tenant-free', 'maxUsers', 3)); // true (3 < 5)
console.log(featureManager.checkLimit('tenant-free', 'maxUsers', 10)); // false (10 >= 5)
console.log(featureManager.checkLimit('tenant-ent', 'maxUsers', 1000000)); // true (unlimited)
```
## Next Steps
### Best Practices
> Source: https://fire-shield.dev/examples/best-practices
# Best Practices
## Permission Design
### Use Consistent Naming Conventions
```typescript
// ✅ Good: Clear, consistent pattern
const permissions = [
'user:read',
'user:create',
'user:update',
'user:delete',
'post:read',
'post:create',
'post:update',
'post:delete'
];
// ❌ Bad: Inconsistent naming
const permissions = [
'readUser',
'user-create',
'update_user',
'DeleteUser'
];
```
### Follow Resource:Action Pattern
```typescript
// ✅ Good: Resource:Action format
.addPermission('document:read')
.addPermission('document:write')
.addPermission('invoice:approve')
.addPermission('report:generate')
// ❌ Bad: Unclear structure
.addPermission('read')
.addPermission('write_document')
.addPermission('approveInvoice')
```
### Use Hierarchical Permissions
```typescript
// ✅ Good: Hierarchical structure
const rbac = new RBACBuilder()
.addPermission('api:users:read')
.addPermission('api:users:write')
.addPermission('api:posts:read')
.addPermission('api:posts:write')
.addPermission('api:admin:users:delete')
.build();
// Allows wildcard patterns
// 'api:*' → All API access
// 'api:users:*' → All user operations
// 'api:*:read' → All read operations
```
### Design for Least Privilege
```typescript
// ✅ Good: Specific permissions
.addRole('viewer', ['posts:read', 'comments:read'])
.addRole('editor', ['posts:read', 'posts:write', 'comments:read'])
.addRole('admin', ['posts:*', 'comments:*', 'users:read'])
// ❌ Bad: Overly broad permissions
.addRole('viewer', ['*']) // Too much access for a viewer
.addRole('editor', ['posts:*', 'users:*', 'system:*']) // Unnecessary permissions
```
## Role Design
### Keep Roles Simple and Focused
```typescript
// ✅ Good: Clear, single-purpose roles
.addRole('content-viewer', ['posts:read', 'comments:read'])
.addRole('content-editor', ['posts:read', 'posts:write'])
.addRole('content-publisher', ['posts:read', 'posts:write', 'posts:publish'])
.addRole('user-manager', ['users:read', 'users:create', 'users:update'])
// ❌ Bad: Kitchen sink roles
.addRole('super-user', [
'posts:*', 'comments:*', 'users:*', 'settings:*',
'billing:*', 'analytics:*', 'reports:*'
]) // Too many responsibilities
```
### Use Role Composition
```typescript
// ✅ Good: Compose multiple roles
const user = {
id: 'user-1',
roles: ['content-editor', 'comment-moderator']
};
// User gets combined permissions from both roles
// ❌ Bad: Creating duplicate mega-roles
.addRole('editor-and-moderator', [
'posts:read', 'posts:write',
'comments:read', 'comments:delete', 'comments:moderate'
]) // Harder to maintain
```
### Leverage Role Hierarchy
```typescript
// ✅ Good: Use hierarchy levels
const rbac = new RBACBuilder()
.addRole('intern', ['docs:read'], { level: 1 })
.addRole('junior', ['docs:read', 'docs:write'], { level: 5 })
.addRole('senior', ['docs:*', 'review:*'], { level: 10 })
.addRole('lead', ['docs:*', 'review:*', 'approve:*'], { level: 15 })
.addRole('manager', ['*'], { level: 20 })
.build();
// Allows automatic inheritance and delegation checks
console.log(rbac.canActAsRole('manager', 'senior')); // true
```
## Performance Optimization
### Use Bit-Based System for High Performance
```typescript
// ✅ Good: Bit-based for production
const rbac = new RBACBuilder()
.useBitSystem() // O(1) permission checks
.addPermission('user:read', 1)
.addPermission('user:write', 2)
.addPermission('post:read', 4)
.addPermission('post:write', 8)
.build();
// Ultra-fast checks using bitwise operations
rbac.hasPermission(user, 'user:read'); // ~0.0001ms
// ❌ Avoid: String-based in high-traffic apps
const rbac = new RBAC(); // 10x slower for permission checks
```
### Cache User Permissions
```typescript
// ✅ Good: Cache computed permissions
class PermissionCache {
private cache = new Map<string, Set<string>>();
private ttl = 300000; // 5 minutes
getUserPermissions(rbac: RBAC, user: RBACUser): Set<string> {
const cacheKey = `${user.id}:${user.roles.join(',')}`;
const cached = this.cache.get(cacheKey);
if (cached) {
return cached;
}
// Compute all permissions for user
const permissions = new Set<string>();
for (const role of user.roles) {
const rolePerms = rbac.getRole(role)?.permissions || [];
rolePerms.forEach(p => permissions.add(p));
}
// Cache with TTL
this.cache.set(cacheKey, permissions);
setTimeout(() => this.cache.delete(cacheKey), this.ttl);
return permissions;
}
}
const cache = new PermissionCache();
const userPerms = cache.getUserPermissions(rbac, user);
```
### Minimize Permission Checks
```typescript
// ✅ Good: Check once, reuse result
function processUserActions(user: RBACUser, actions: Action[]) {
const canWrite = rbac.hasPermission(user, 'post:write');
const canDelete = rbac.hasPermission(user, 'post:delete');
return actions.map(action => {
if (action.type === 'write' && !canWrite) return null;
if (action.type === 'delete' && !canDelete) return null;
return performAction(action);
});
}
// ❌ Bad: Repeated checks
function processUserActions(user: RBACUser, actions: Action[]) {
return actions.map(action => {
// Checks permission on every iteration
if (action.type === 'write' && !rbac.hasPermission(user, 'post:write')) {
return null;
}
// ... more repeated checks
});
}
```
## Security Best Practices
### Always Validate on Server Side
```typescript
// ✅ Good: Server-side validation
// Frontend (React)
function DeleteButton({ post }) {
const { can } = useRBAC();
// UI check for better UX
if (!can('post:delete')) {
return null;
}
return Delete ;
}
// Backend (Express)
app.delete('/posts/:id',
rbacMiddleware.requirePermission('post:delete'), // ✅ Server check
async (req, res) => {
await deletePost(req.params.id);
res.json({ success: true });
}
);
// ❌ Bad: Only client-side check
// Backend without validation
app.delete('/posts/:id', async (req, res) => {
// No permission check - SECURITY VULNERABILITY!
await deletePost(req.params.id);
res.json({ success: true });
});
```
### Deny by Default
```typescript
// ✅ Good: Explicit allow, implicit deny
function canPerformAction(user: RBACUser, action: string): boolean {
// Returns false if permission not found
return rbac.hasPermission(user, action);
}
// ❌ Bad: Allow by default
function canPerformAction(user: RBACUser, action: string): boolean {
try {
return rbac.hasPermission(user, action);
} catch (error) {
return true; // DANGEROUS! Allows on error
}
}
```
### Validate User Input
```typescript
// ✅ Good: Validate and sanitize
function createRole(name: string, permissions: string[]) {
// Validate role name
if (!/^[a-z0-9-]+$/.test(name)) {
throw new Error('Invalid role name');
}
// Validate permissions exist
const validPermissions = permissions.filter(p =>
rbac.hasPermission({ id: 'system', roles: [] }, p)
);
return rbac.createRole(name, validPermissions);
}
// ❌ Bad: No validation
function createRole(name: string, permissions: string[]) {
return rbac.createRole(name, permissions); // Could inject malicious data
}
```
### Use Audit Logging
```typescript
// ✅ Good: Comprehensive audit logging
const rbac = new RBAC({
auditLogger: new BufferedAuditLogger(
async (events) => {
await db.auditLogs.insertMany(events);
// Alert on security events
const criticalEvents = events.filter(e =>
!e.allowed && e.permission.includes('admin')
);
if (criticalEvents.length > 0) {
await alertSecurityTeam(criticalEvents);
}
},
{ maxBufferSize: 100, flushIntervalMs: 5000 }
)
});
// ❌ Bad: No audit trail
const rbac = new RBAC(); // No visibility into access patterns
```
## Code Organization
### Centralize RBAC Configuration
```typescript
// ✅ Good: Single source of truth
// lib/rbac.ts
import { RBACBuilder } from '@fire-shield/core';
export const rbac = new RBACBuilder()
.useBitSystem()
.enableWildcards()
// Define all permissions
.addPermission('user:read')
.addPermission('user:write')
.addPermission('post:read')
.addPermission('post:write')
// Define all roles
.addRole('admin', ['*'])
.addRole('editor', ['post:*'])
.addRole('viewer', ['post:read'])
.build();
// Import and use everywhere
import { rbac } from '@/lib/rbac';
// ❌ Bad: Scattered configuration
// Different files creating roles differently
// file1.ts
rbac.createRole('admin', ['*']);
// file2.ts
rbac.createRole('admin', ['users:*', 'posts:*']); // Inconsistent!
```
### Use TypeScript for Type Safety
```typescript
// ✅ Good: Type-safe permissions
const PERMISSIONS = {
USER_READ: 'user:read',
USER_WRITE: 'user:write',
POST_READ: 'post:read',
POST_WRITE: 'post:write'
} as const;
type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS];
function checkPermission(user: RBACUser, permission: Permission): boolean {
return rbac.hasPermission(user, permission);
}
// ✅ Type-safe usage
checkPermission(user, PERMISSIONS.USER_READ);
// ❌ Compiler error
checkPermission(user, 'invalid:permission');
// ❌ Bad: String literals everywhere
function checkPermission(user: RBACUser, permission: string): boolean {
return rbac.hasPermission(user, permission); // No type safety
}
checkPermission(user, 'usr:raed'); // Typo not caught
```
### Document Permissions
```typescript
// ✅ Good: Well-documented permissions
const rbac = new RBACBuilder()
/**
* User Management Permissions
* Used by: Admin panel, User settings
*/
.addPermission('user:read', 1, {
description: 'View user profiles and list users',
resource: 'user',
action: 'read'
})
.addPermission('user:write', 2, {
description: 'Create and update user accounts',
resource: 'user',
action: 'write'
})
.addPermission('user:delete', 4, {
description: 'Delete user accounts (irreversible)',
resource: 'user',
action: 'delete'
})
/**
* Content Management Permissions
* Used by: CMS, Blog platform
*/
.addPermission('post:publish', 8, {
description: 'Publish posts to public site',
resource: 'post',
action: 'publish'
})
.build();
// Generate documentation
function generatePermissionDocs(rbac: RBAC) {
const permissions = rbac.getAllPermissions();
console.log('# Permission Documentation\n');
for (const perm of permissions) {
console.log(`## ${perm.name}`);
console.log(`- **Description**: ${perm.description}`);
console.log(`- **Resource**: ${perm.resource}`);
console.log(`- **Action**: ${perm.action}\n`);
}
}
```
## Testing
### Test Permission Checks
```typescript
// ✅ Good: Comprehensive permission tests
import { describe, it, expect } from 'vitest';
import { rbac } from '@/lib/rbac';
describe('RBAC Permissions', () => {
it('admin should have all permissions', () => {
const admin = { id: '1', roles: ['admin'] };
expect(rbac.hasPermission(admin, 'user:read')).toBe(true);
expect(rbac.hasPermission(admin, 'user:write')).toBe(true);
expect(rbac.hasPermission(admin, 'user:delete')).toBe(true);
});
it('viewer should only have read permissions', () => {
const viewer = { id: '2', roles: ['viewer'] };
expect(rbac.hasPermission(viewer, 'post:read')).toBe(true);
expect(rbac.hasPermission(viewer, 'post:write')).toBe(false);
expect(rbac.hasPermission(viewer, 'post:delete')).toBe(false);
});
it('should deny access when no roles assigned', () => {
const noRole = { id: '3', roles: [] };
expect(rbac.hasPermission(noRole, 'post:read')).toBe(false);
});
it('should handle wildcard permissions', () => {
const editor = { id: '4', roles: ['editor'] };
expect(rbac.hasPermission(editor, 'post:read')).toBe(true);
expect(rbac.hasPermission(editor, 'post:write')).toBe(true);
expect(rbac.hasPermission(editor, 'post:publish')).toBe(true);
});
});
```
### Test Role Hierarchy
```typescript
describe('Role Hierarchy', () => {
it('manager can act as engineer', () => {
expect(rbac.canActAsRole('manager', 'engineer')).toBe(true);
});
it('engineer cannot act as manager', () => {
expect(rbac.canActAsRole('engineer', 'manager')).toBe(false);
});
it('roles at same level cannot delegate', () => {
expect(rbac.canActAsRole('engineer', 'designer')).toBe(false);
});
});
```
### Integration Tests
```typescript
describe('API Permission Integration', () => {
it('should protect admin endpoints', async () => {
const viewer = { id: '1', roles: ['viewer'] };
const response = await request(app)
.delete('/api/users/123')
.set('Authorization', createToken(viewer));
expect(response.status).toBe(403);
expect(response.body.error).toBe('Forbidden');
});
it('should allow admin access', async () => {
const admin = { id: '2', roles: ['admin'] };
const response = await request(app)
.delete('/api/users/123')
.set('Authorization', createToken(admin));
expect(response.status).toBe(200);
});
});
```
## Error Handling
### Handle Missing Permissions Gracefully
```typescript
// ✅ Good: User-friendly error messages
function performAction(user: RBACUser, action: string) {
const result = rbac.authorize(user, action);
if (!result.allowed) {
throw new PermissionError(
`Access denied: ${result.reason}`,
{
userId: user.id,
requiredPermission: action,
userRoles: user.roles
}
);
}
// Perform action
}
class PermissionError extends Error {
constructor(message: string, public details: any) {
super(message);
this.name = 'PermissionError';
}
}
// ❌ Bad: Generic errors
function performAction(user: RBACUser, action: string) {
if (!rbac.hasPermission(user, action)) {
throw new Error('Forbidden'); // Not helpful
}
}
```
### Log Security Events
```typescript
// ✅ Good: Detailed security logging
function requirePermission(permission: string) {
return (req, res, next) => {
const result = rbac.authorize(req.user, permission);
if (!result.allowed) {
logger.warn('Permission denied', {
userId: req.user.id,
permission,
reason: result.reason,
ip: req.ip,
userAgent: req.headers['user-agent'],
path: req.path
});
return res.status(403).json({
error: 'Forbidden',
message: 'You do not have permission to perform this action'
});
}
next();
};
}
```
## Deployment
### Environment-Specific Configuration
```typescript
// ✅ Good: Environment-aware setup
const rbac = new RBACBuilder()
.useBitSystem()
.enableWildcards()
// Production: Strict mode with audit logging
.configure({
strictMode: process.env.NODE_ENV === 'production',
auditLogger: process.env.NODE_ENV === 'production'
? new DatabaseAuditLogger()
: new ConsoleAuditLogger()
})
.build();
// Development: Additional debug permissions
if (process.env.NODE_ENV === 'development') {
rbac.createRole('developer', ['*']);
}
```
### Graceful Degradation
```typescript
// ✅ Good: Fallback when RBAC unavailable
function canPerform(user: RBACUser, permission: string): boolean {
try {
return rbac.hasPermission(user, permission);
} catch (error) {
logger.error('RBAC check failed', error);
// Fail secure: deny by default
return false;
}
}
```
## Migration and Updates
### Version Your Permission Schema
```typescript
// ✅ Good: Versioned schema
interface PermissionSchemaV1 {
version: 1;
permissions: string[];
roles: Record<string, string[]>;
}
interface PermissionSchemaV2 {
version: 2;
permissions: Array<{
name: string;
bit: number;
description: string;
}>;
roles: Array<{
name: string;
permissions: string[];
level: number;
}>;
}
function migrateSchema(old: PermissionSchemaV1): PermissionSchemaV2 {
// Migration logic
return {
version: 2,
permissions: old.permissions.map((name, i) => ({
name,
bit: Math.pow(2, i),
description: ''
})),
roles: Object.entries(old.roles).map(([name, permissions], i) => ({
name,
permissions,
level: i * 10
}))
};
}
```
## Next Steps