# 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') && ( )}
); } ``` ### Vue ```vue ``` ### 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 ; }; ``` ### 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 ``` ### v-cannot ```vue ``` ### v-permission ```vue ``` ### v-role ```vue ``` ## Composables ### Composables ```vue ``` ### 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 ``` ### Cannot Component ```vue ``` ### ProtectedRoute Component ```vue ``` ### 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 ``` ## 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 ``` ### 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 && ( )} {canDelete && ( )} {isAdmin && ( )}
) } ``` ### 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 (
No permission

}>
) } ``` ### Cannot Component ```tsx import { Cannot } from '@fire-shield/react' function UpgradePrompt() { return (

Upgrade to unlock premium features

) } ``` ### 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 */}
) } ``` ### 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(', ')}

) } ``` ## 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}

) } ``` ## 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 // ❌ Avoid: Less readable {canDelete && } ``` ### 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}

{canPublish && !post.published && ( )}
) } ``` ### User Management Dashboard ```tsx import { RequirePermission, Can } from '@fire-shield/react' function UserManagement() { return (

User Management

) } ``` ## 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 ( ); } ``` ```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') && ( )} {rbac.hasPermission(user, 'post:write') && ( )} {rbac.hasPermission(user, 'post: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 ``` ```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 ``` ## Components ### Conditional Rendering ```vue ``` ### Dynamic Actions ```vue ``` ### Form Protection ```vue ``` ## Layouts ### Protected Layout ```vue ``` ```vue ``` ## 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: '' }) 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
``` ### *fsHasRole ```html

Admin Panel

``` ### *fsCannotPermission ```html

Upgrade to Premium!

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

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

Admin Controls

Upgrade to Premium for more features!

`, 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 ``` ### Check Permissions ```svelte {#if $canWrite} {/if} {#if $isAdmin} Admin Panel {/if} ``` ## Reactive Stores ### can(permission) ```svelte
{#if $canWrite} {/if} {#if $canPublish} {/if} {#if $canDelete} {/if}
``` ### hasRole(role) ```svelte ``` ### authorize(permission) ```svelte {#if !$result.allowed}

Access denied: {$result.reason}

{:else} {/if} ``` ### canAll(permissions) ```svelte {#if $hasFullAccess} {/if} ``` ### canAny(permissions) ```svelte {#if $canAccessPosts} View Posts {/if} ``` ## Svelte Actions ### use:can ```svelte
``` ### use:role ```svelte

Admin Controls

Admin Controls

``` ### use:cannot ```svelte

Upgrade to Premium for more features!

``` ## Component Examples ### Navigation Menu ```svelte ``` ### Post Actions ```svelte
{#if $canEdit} {/if} {#if $canPublish} {/if} {#if $canDelete} {/if}
``` ### User Login/Logout ```svelte {#if isLoggedIn}

Logged in as: {$rbacStore.user?.id}

{:else} {/if} ``` ### Form with Conditional Fields ```svelte
{#if $canEditEmail} {/if} {#if $canEditRole} {/if}
``` ### Using Actions ```svelte

Admin Controls

This section is only visible to administrators

Upgrade to Premium for more features!

``` ## SvelteKit Integration ### Layout with RBAC ```svelte ``` ### 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
``` ## 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} {/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')} {/if} {#if rbac.hasPermission(user, 'post:delete')} {/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 (
{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 */} ; } // 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