# Fire Shield RBAC — Complete Documentation > Version 3.1.1 > Repository: https://github.com/khapu2906/fire-shield > Compact version: https://fire-shield.dev/llms-small.txt > Index: https://fire-shield.dev/llms.txt ======================================================================== # SECTION: Guide ======================================================================== ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/what-is-fire-shield ──────────────────────────────────────────────────────────────────────── # What is Fire Shield? Fire Shield is a modern, type-safe RBAC (Role-Based Access Control) library for JavaScript and TypeScript applications. ## Overview Fire Shield provides a flexible and performant way to manage permissions and roles in your applications. Whether you're building a web app, API, or full-stack application, Fire Shield has you covered with framework-specific adapters for Vue, React, Next.js, Express, and more. ## Key Features ### Type-Safe Written in TypeScript with comprehensive type definitions. Catch permission errors at compile-time, not runtime. ```typescript // Full type inference and safety const rbac = new RBAC() rbac.createRole('admin', ['posts:*']) rbac.hasPermission(user, 'posts:write') // Type-checked ``` ### Zero Dependencies The core library has zero dependencies, keeping your bundle size small. Framework adapters only depend on their respective frameworks. ### Framework-Agnostic Use Fire Shield anywhere JavaScript runs: - **Frontend**: Vue, React, Next.js, Nuxt, Angular, Svelte - **Backend**: Express, Fastify, Hono - **Runtime**: Node.js, Deno, Bun, Edge Runtime ### Blazing Fast Optimized with: - Bit-level permission checking - Efficient role hierarchy resolution - Minimal memory footprint - Tree-shakeable code ### Flexible Permission Patterns Support for wildcard permissions: ```typescript rbac.createRole('admin', ['posts:*', 'users:*']) rbac.hasPermission(admin, 'posts:write') // ✅ rbac.hasPermission(admin, 'posts:delete') // ✅ rbac.hasPermission(admin, 'users:create') // ✅ ``` ### Role Hierarchy Define role inheritance chains: ```typescript rbac.setRoleHierarchy({ admin: ['editor', 'viewer'], editor: ['viewer'] }) // Admin automatically inherits editor and viewer permissions ``` ### Audit Logging Built-in audit logging for compliance: ```typescript const rbac = new RBAC({ auditLogger: new BufferedAuditLogger(async (logs) => { await saveToDatabase(logs) }) }) ``` ## Why Fire Shield? ### Compared to Other Libraries | Feature | Fire Shield | accesscontrol | casl | casbin | |---------|------------|---------------|------|--------| | TypeScript | ✅ Full | ⚠️ Partial | ✅ Full | ⚠️ Partial | | Zero Dependencies | ✅ | ❌ | ❌ | ❌ | | Bundle Size | 🎯 3.2KB | 5.8KB | 12KB | 45KB | | Framework Adapters | ✅ 9+ | ❌ | ⚠️ Limited | ⚠️ Limited | | Role Hierarchy | ✅ | ❌ | ❌ | ✅ | | Performance | ⚡ Fastest | Fast | Medium | Slow | ### Use Cases Fire Shield is perfect for: - 🏢 **Enterprise Applications** - Multi-tenant SaaS with complex permission requirements - 📱 **Web Applications** - User dashboards with role-based features - 🔐 **APIs** - Protecting endpoints with permission-based middleware - 🎮 **Content Management** - Managing content creation, editing, and publishing workflows - 👥 **User Management** - Admin panels with granular access control ## Architecture Fire Shield is built with a modular 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 ``` Each adapter is: - **Optional** - Only install what you need - **Tree-shakeable** - Only import what you use - **Type-safe** - Full TypeScript support - **Well-tested** - 100% test coverage ## Getting Started Ready to add Fire Shield to your project? - [Installation Guide →](/guide/installation) - [Quick Start →](/guide/getting-started) - [Framework Integrations →](/frameworks/vue) ## Community & Support - 📖 [Documentation](/) - 🐛 [Issue Tracker](https://github.com/khapu2906/fire-shield/issues) - 💬 [Discussions](https://github.com/khapu2906/fire-shield/discussions) - 📦 [NPM](https://www.npmjs.com/package/@fire-shield/core) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/getting-started ──────────────────────────────────────────────────────────────────────── # Getting Started Get up and running with Fire Shield in minutes. ## Installation Install the core package: ::: code-group ```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 Set up role inheritance for cleaner permission management: ```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 Use wildcards for flexible permission patterns: ```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 Fire Shield provides adapters for popular frameworks. Choose your framework to continue: - [Vue.js](/frameworks/vue) - Directives, composables, and router guards - [React](/frameworks/react) - Hooks and components - [Next.js](/frameworks/next) - Server and client components - [Nuxt](/frameworks/nuxt) - Auto-imports and middleware - [Angular](/frameworks/angular) - Services and guards - [Svelte](/frameworks/svelte) - Stores and actions - [Express](/frameworks/express) - Middleware - [Fastify](/frameworks/fastify) - Plugins and hooks - [Hono](/frameworks/hono) - Middleware ## Next Steps - Learn about [Permissions](/guide/permissions) - Understand [Roles](/guide/roles) - Explore [Role Hierarchy](/guide/role-hierarchy) - Master [Wildcards](/guide/wildcards) - Set up [Audit Logging](/guide/audit-logging) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/installation ──────────────────────────────────────────────────────────────────────── # Installation Install Fire Shield in your project with your preferred package manager. ## Core Package All Fire Shield integrations require the core package: ::: code-group ```bash [npm] npm install @fire-shield/core ``` ```bash [yarn] yarn add @fire-shield/core ``` ```bash [pnpm] pnpm add @fire-shield/core ``` ::: ## Framework Adapters Install the adapter for your framework: ### 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 - Node.js 18.0.0 or higher - TypeScript 5.0 or higher (for TypeScript projects) ## CDN Usage For quick prototyping or demos, you can use Fire Shield via CDN: ```html ``` ::: warning CDN usage is not recommended for production applications. Use a package manager instead. ::: ## Next Steps - [Getting Started Guide](/guide/getting-started) - [Framework Integration](/frameworks/vue) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/permissions ──────────────────────────────────────────────────────────────────────── # Permissions Permissions are the foundation of Fire Shield's access control system. ## What are Permissions? A permission is a string that represents an action a user can perform on a resource. Permissions typically follow a `resource:action` pattern: ```typescript 'posts:read' // Read posts 'posts:write' // Write/create posts 'posts:delete' // Delete posts 'users:manage' // Manage users ``` ## Permission Format ### Basic Format ``` resource:action ``` Examples: - `posts:read` - `comments:write` - `settings:update` ### Nested Resources Use colons to create hierarchical permissions: ``` admin:users:read admin:users:write admin:users:delete admin:settings:write ``` ### Wildcard Permissions Use `*` to grant all actions on a resource: ```typescript 'posts:*' // All post actions 'admin:*' // All admin actions '*:read' // Read all resources '*' // All permissions (superuser) ``` [Learn more about wildcards →](/guide/wildcards) ## 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 Check if user has all required permissions: ```typescript const permissions = ['posts:read', 'posts:write', 'posts:publish'] const hasAll = permissions.every(permission => rbac.hasPermission(user, permission) ) ``` Check if user has any of the permissions: ```typescript const hasAny = permissions.some(permission => rbac.hasPermission(user, permission) ) ``` ## Permission Naming Conventions ### Recommended Patterns **CRUD Operations:** ```typescript 'posts:create' 'posts:read' 'posts:update' 'posts:delete' ``` **Domain-Specific Actions:** ```typescript 'posts:publish' 'posts:archive' 'posts:restore' 'comments:approve' 'comments:moderate' ``` **Administrative Actions:** ```typescript 'users:manage' 'roles:assign' 'settings:configure' 'system:admin' ``` ### Best Practices 1. **Be Specific** ```typescript // ✅ Good 'posts:publish' 'posts:archive' // ❌ Avoid 'posts:action1' 'posts:action2' ``` 2. **Use Consistent Naming** ```typescript // ✅ Good - consistent pattern 'posts:read' 'posts:write' 'posts:delete' // ❌ Avoid - inconsistent 'posts:read' 'posts:create' 'posts:remove' ``` 3. **Keep Hierarchy Logical** ```typescript // ✅ Good - clear hierarchy 'admin:users:read' 'admin:users:write' 'admin:settings:read' // ❌ Avoid - confusing hierarchy 'users:admin:read' 'settings:read:admin' ``` ## Permission Matching Fire Shield uses exact matching for permissions: ```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 Wildcards provide flexible 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 Permissions can be granted dynamically at runtime: ```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 For advanced use cases, you can use 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 - Learn about [Roles](/guide/roles) - Master [Wildcards](/guide/wildcards) - Explore [Role Hierarchy](/guide/role-hierarchy) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/roles ──────────────────────────────────────────────────────────────────────── # Roles Roles group permissions together and can be assigned to users. ## What are Roles? A role is a collection of permissions that can be assigned to users. Instead of granting permissions individually, you assign roles to users. ```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 Users can have 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 Create inheritance chains between roles: ```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) ``` [Learn more about Role Hierarchy →](/guide/role-hierarchy) ## 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 Roles can be assigned/removed at runtime: ```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 Check if user has a specific role: ```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 Full type safety for roles: ```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 - Learn about [Role Hierarchy](/guide/role-hierarchy) - Master [Permissions](/guide/permissions) - Explore [Wildcards](/guide/wildcards) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/role-hierarchy ──────────────────────────────────────────────────────────────────────── # Role Hierarchy Understanding and implementing role hierarchies in Fire Shield. ## What is Role Hierarchy? Role hierarchy is a level-based system where higher-level roles can perform actions on behalf of lower-level roles. This creates a natural organizational structure that mirrors real-world authority relationships. ## Key Concepts **Level-Based System:** - Each role is assigned a numeric level - Higher numbers = more privileged - Level determines if a role can "act as" another role **Use Cases:** - Organizational hierarchies (CEO > Manager > Employee) - Permission delegation (Admin can act as any lower role) - Resource access control (only higher roles can manage lower-role resources) - Approval workflows (requires someone of higher level) ## 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 Check if a role can act as another role: ```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 - Learn about [Permissions](/guide/permissions) - Explore [Roles](/guide/roles) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/wildcards ──────────────────────────────────────────────────────────────────────── # Wildcards Wildcards provide flexible and powerful permission patterns. ## What are Wildcards? Wildcards use the `*` symbol to match multiple permissions with a single pattern. Instead of granting each permission individually, you can use wildcards to grant groups of permissions. ```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 Grant all actions on a specific resource: ```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 Grant specific action across all resources: ```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 Grant all permissions (superuser): ```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 Use wildcards with hierarchical permissions: ```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 Explicit permissions take priority over wildcards: ```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 Wildcards only match the segment they're in: ```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 Mix wildcards with 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 Wildcards are highly optimized: ```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 Always test wildcard permissions thoroughly: ```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 - Learn about [Role Hierarchy](/guide/role-hierarchy) - Understand [Permissions](/guide/permissions) - Explore [API Reference](/api/core) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/deny-permissions ──────────────────────────────────────────────────────────────────────── # Deny Permissions Learn how to explicitly deny permissions to override role-based grants. ## Overview Deny Permissions is a powerful feature in Fire Shield that allows you to explicitly revoke specific permissions from users, overriding their role-based permissions. **Key Concepts:** - **Deny > Allow**: Denied permissions ALWAYS take precedence over granted permissions - **User-Specific**: Denies are applied per user, not per role - **Temporary Revocation**: Great for temporary access restrictions without changing roles - **Wildcard Support**: Can deny entire permission groups with wildcards ## Why Use Deny Permissions? ### Use Cases 1. **Temporary Restrictions** - Suspend user's write access during investigation - Revoke specific permissions temporarily without role change 2. **Fine-Grained Control** - Remove one permission from a role without creating a new role - Override role permissions for specific users 3. **Security Incidents** - Quickly revoke access when user account is compromised - Disable dangerous permissions immediately 4. **Feature Flags** - Disable beta features for specific users - Roll out features gradually 5. **Compliance** - Enforce regulatory restrictions on specific users - Implement separation of duties ## 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 You can use wildcards to deny entire groups of permissions. ### 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 Denied permissions ALWAYS take precedence, regardless of how permissions are granted. ```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 ``` **Priority Order:** 1. 🔴 **Deny** (highest priority) 2. 🟡 **Direct User Permissions** 3. 🟢 **Role Permissions** (lowest priority) ### Multiple Denies You can deny multiple permissions for the same user: ```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)` Deny a specific permission for a user. ```typescript rbac.denyPermission( userId: string, // User ID permission: string // Permission to deny (supports wildcards) ): void ``` **Example:** ```typescript rbac.denyPermission('user-123', 'post:delete'); rbac.denyPermission('admin-456', 'user:*'); ``` ### `allowPermission(userId, permission)` Remove a denied permission (restore to role default). ```typescript rbac.allowPermission( userId: string, // User ID permission: string // Permission to allow back ): void ``` **Example:** ```typescript rbac.allowPermission('user-123', 'post:delete'); ``` ### `isDenied(userId, permission)` Check if a specific permission is denied for a user. ```typescript rbac.isDenied( userId: string, // User ID permission: string // Permission to check ): boolean ``` **Returns:** `true` if permission is explicitly denied, `false` otherwise **Example:** ```typescript if (rbac.isDenied('user-123', 'post:delete')) { console.log('This permission is denied'); } ``` ### `getDeniedPermissions(userId)` Get all denied permissions for a user. ```typescript rbac.getDeniedPermissions( userId: string // User ID ): string[] ``` **Returns:** Array of denied permission strings **Example:** ```typescript const denied = rbac.getDeniedPermissions('user-123'); // ['post:delete', 'user:write'] ``` ## Best Practices ### 1. Document Denies Always document why a permission was denied: ```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 Implement automatic expiration: ```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 Inform users when permissions are denied: ```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 Provide helpers for common deny scenarios: ```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 Track all deny operations: ```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 **Deny Permission:** - ✅ Temporary and reversible - ✅ Doesn't affect role definition - ✅ User-specific - ✅ Faster to implement **Remove from Role:** - ❌ Changes role structure - ❌ Affects all users with that role - ❌ Requires role management - ✅ More permanent solution ### vs. Creating New Role **Deny Permission:** - ✅ No role proliferation - ✅ Easy to manage - ✅ Quick to apply/remove **New Role:** - ❌ Role explosion - ❌ Hard to maintain - ✅ Better for permanent changes ## Next Steps - [Wildcards](/guide/wildcards) - Pattern matching in permissions - [Audit Logging](/guide/audit-logging) - Track permission changes - [Role Hierarchy](/guide/role-hierarchy) - Organize roles effectively - [API Reference](/api/core) - Complete API documentation ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/audit-logging ──────────────────────────────────────────────────────────────────────── # Audit Logging Complete guide to implementing audit logging for security, compliance, and debugging. ## Why Audit Logging? Audit logging provides: - **Security**: Track unauthorized access attempts - **Compliance**: Meet GDPR, SOC2, HIPAA requirements - **Debugging**: Identify permission configuration issues - **Analytics**: Understand user behavior patterns - **Forensics**: Investigate security incidents ## 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 Development logger that outputs to console: ```typescript import { ConsoleAuditLogger } from '@fire-shield/core'; const logger = new ConsoleAuditLogger(); const rbac = new RBAC({ auditLogger: logger }); ``` **Output Example:** ``` [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 Production logger with batching: ```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 Log to multiple destinations: ```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 - Explore [Core API](/api/core) - Learn about [Performance](/guide/performance) - Check out [API Reference](/api/audit) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/performance ──────────────────────────────────────────────────────────────────────── # Performance Optimization Guide to optimizing Fire Shield RBAC for maximum performance. ## Performance Overview Fire Shield is designed for high performance: - **O(1)** bit-based permission checks - **O(n)** string-based permission checks - Built-in caching - Minimal memory footprint - Zero dependencies ## Bit-Based vs String-Based ### Bit-Based (Recommended) Ultra-fast permission checks using bitwise operations: ```typescript const rbac = new RBAC({ useBitSystem: true }); // Default // O(1) permission check rbac.hasPermission(user, 'post:read'); // ~0.001ms ``` **Pros:** - Extremely fast (bitwise AND operation) - Low memory usage - Built-in caching **Cons:** - Maximum 31 permissions - Requires bit assignment ### String-Based Flexible but slower permission checks: ```typescript const rbac = new RBAC({ useBitSystem: false }); // O(n) permission check rbac.hasPermission(user, 'post:read'); // ~0.1ms ``` **Pros:** - Unlimited permissions - No bit management - More flexible **Cons:** - Slower than bit-based - Higher memory usage ## 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 - Learn about [TypeScript](/guide/typescript) - Explore [Core API](/api/core) - Check out [Audit Logging](/guide/audit-logging) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/guide/typescript ──────────────────────────────────────────────────────────────────────── # TypeScript Guide Complete guide to using Fire Shield with TypeScript for maximum type safety. ## Why TypeScript? TypeScript provides: - **Type Safety**: Catch errors at compile time - **IntelliSense**: Better IDE autocomplete - **Documentation**: Self-documenting code - **Refactoring**: Safe code changes - **Maintainability**: Easier to understand and modify ## Basic Types ### RBACUser The core user type with roles and permissions: ```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 Add custom fields to the user type: ```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 Enable strict mode in `tsconfig.json`: ```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 - Explore [Core API](/api/core) - Learn about [TypeScript Types](/api/types) - Check out [Performance Guide](/guide/performance) ======================================================================== # SECTION: API Reference ======================================================================== ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/api/core ──────────────────────────────────────────────────────────────────────── # Core API Reference Complete API reference for `@fire-shield/core`. ## RBAC Class The main class for managing roles, permissions, and access control. ### Constructor ```typescript new RBAC(options?: RBACOptions) ``` #### Parameters - `options` (optional): Configuration options - `config?: RBACConfigSchema` - Config-based initialization - `preset?: PresetConfig` - Preset configuration - `useBitSystem?: boolean` - Enable bit-level permission checking (default: true) - `strictMode?: boolean` - Enable strict mode for bit permissions - `auditLogger?: AuditLogger` - Custom audit logger - `enableWildcards?: boolean` - Enable wildcard permission matching (default: true) - `enableCache?: boolean` - Enable permission caching (default: false) - `cacheOptions?: PermissionCacheOptions` - Cache configuration - `lazyRoles?: boolean` - Enable lazy role evaluation (default: false) - `optimizeMemory?: boolean` - Enable memory optimization (default: false) #### 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 Create RBAC instance from JSON configuration. ```typescript static fromJSONConfig(json: string, options?: RBACOptions): RBAC ``` **Parameters:** - `json` - JSON string containing PresetConfig - `options` - Additional RBAC options **Returns:** New RBAC instance **Example:** ```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 Validate PresetConfig structure. ```typescript static validateConfig(config: PresetConfig): void ``` **Throws:** Error if config is invalid **Example:** ```typescript try { RBAC.validateConfig(myConfig) console.log('Config is valid') } catch (error) { console.error('Invalid config:', error.message) } ``` ### Instance Methods #### createRole Create a new role with permissions. ```typescript createRole(roleName: string, permissions: string[]): void ``` **Parameters:** - `roleName` - Name of the role - `permissions` - Array of permission strings **Example:** ```typescript rbac.createRole('admin', ['posts:*', 'users:*']) rbac.createRole('editor', ['posts:read', 'posts:write']) ``` #### deleteRole Delete a role. ```typescript deleteRole(roleName: string): void ``` **Example:** ```typescript rbac.deleteRole('editor') ``` #### grant Grant additional permissions to an existing role. ```typescript grant(roleName: string, permissions: string[]): void ``` **Example:** ```typescript rbac.grant('editor', ['posts:delete']) ``` #### revoke Revoke permissions from a role. ```typescript revoke(roleName: string, permissions: string[]): void ``` **Example:** ```typescript rbac.revoke('editor', ['posts:delete']) ``` #### hasPermission Check if a user has a specific permission. ```typescript hasPermission(user: RBACUser, permission: string): boolean ``` **Parameters:** - `user` - User object with roles - `permission` - Permission string to check **Returns:** `true` if user has permission, `false` otherwise **Example:** ```typescript const user = { id: '1', roles: ['editor'] } const canWrite = rbac.hasPermission(user, 'posts:write') // true const canDelete = rbac.hasPermission(user, 'posts:delete') // false ``` #### setRoleHierarchy Define role inheritance chains. ```typescript setRoleHierarchy(hierarchy: Record): void ``` **Parameters:** - `hierarchy` - Object mapping roles to their parent roles **Example:** ```typescript rbac.setRoleHierarchy({ admin: ['editor', 'moderator'], editor: ['viewer'], moderator: ['viewer'] }) ``` #### getRolePermissions Get all permissions for a role (including inherited). ```typescript getRolePermissions(roleName: string): string[] ``` **Returns:** Array of permission strings **Example:** ```typescript const permissions = rbac.getRolePermissions('editor') // ['posts:read', 'posts:write'] ``` #### getUserPermissions Get all permissions for a user (across all roles). ```typescript getUserPermissions(user: RBACUser): string[] ``` **Returns:** Array of permission strings **Example:** ```typescript const user = { id: '1', roles: ['editor', 'moderator'] } const permissions = rbac.getUserPermissions(user) ``` #### hasAnyPermission Check if user has any of the specified permissions. ```typescript hasAnyPermission(user: RBACUser, permissions: string[]): boolean ``` **Example:** ```typescript const user = { id: '1', roles: ['editor'] } const canEdit = rbac.hasAnyPermission(user, ['posts:write', 'posts:delete']) ``` #### hasAllPermissions Check if user has all of the specified permissions. ```typescript hasAllPermissions(user: RBACUser, permissions: string[]): boolean ``` **Example:** ```typescript const user = { id: '1', roles: ['admin'] } const hasFullAccess = rbac.hasAllPermissions(user, ['posts:read', 'posts:write', 'posts:delete']) ``` ### Deny Permissions Deny permissions allow you to explicitly revoke specific permissions for individual users, even if their roles grant them. Denies always take precedence over allows. #### denyPermission Deny a specific permission for a user. ```typescript denyPermission(userId: string, permission: string): void ``` **Parameters:** - `userId` - User ID to deny permission for - `permission` - Permission to deny (supports wildcards) **Example:** ```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 Remove a denied permission for a user. ```typescript allowPermission(userId: string, permission: string): void ``` **Example:** ```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 Get all denied permissions for a user. ```typescript getDeniedPermissions(userId: string): string[] ``` **Returns:** Array of denied permission strings **Example:** ```typescript const deniedPerms = rbac.getDeniedPermissions('user-123') console.log(deniedPerms) // ['posts:delete', 'users:ban'] ``` #### clearDeniedPermissions Clear all denied permissions for a user. ```typescript clearDeniedPermissions(userId: string): void ``` **Example:** ```typescript rbac.clearDeniedPermissions('user-123') // All denies removed, user permissions back to role-based ``` ### Cache Management When `enableCache: true` is set, permission checks are cached for better performance. #### invalidateUserCache Invalidate all cached permission checks for a specific user. ```typescript invalidateUserCache(userId: string): void ``` **Example:** ```typescript // User's roles changed, clear their cache rbac.invalidateUserCache('user-123') ``` #### invalidatePermissionCache Invalidate cached checks for a specific permission across all users. ```typescript invalidatePermissionCache(permission: string): void ``` **Example:** ```typescript // Permission definition changed, clear all caches for it rbac.invalidatePermissionCache('posts:delete') ``` #### getCacheStats Get cache statistics. ```typescript getCacheStats(): CacheStats | undefined ``` **Returns:** Cache statistics or undefined if cache is disabled **Example:** ```typescript const stats = rbac.getCacheStats() console.log(stats) // { // hits: 1250, // misses: 50, // hitRate: 0.96, // size: 450, // maxSize: 10000 // } ``` ### Lazy Role Evaluation When `lazyRoles: true` is set, roles are only evaluated when first accessed, reducing initial load time and memory usage. #### getEvaluatedRoles Get list of roles that have been evaluated. ```typescript getEvaluatedRoles(): string[] ``` **Example:** ```typescript const evaluated = rbac.getEvaluatedRoles() console.log(evaluated) // ['admin', 'editor'] ``` #### getPendingRoles Get list of roles not yet evaluated. ```typescript getPendingRoles(): string[] ``` **Example:** ```typescript const pending = rbac.getPendingRoles() console.log(pending) // ['viewer', 'guest', 'moderator'] ``` #### getLazyRoleStats Get lazy role evaluation statistics. ```typescript getLazyRoleStats(): LazyRoleStats ``` **Returns:** Object with lazy role statistics **Example:** ```typescript const stats = rbac.getLazyRoleStats() console.log(stats) // { // enabled: true, // pending: 5, // evaluated: 2, // total: 7 // } ``` #### isRolePending Check if a role is pending evaluation. ```typescript isRolePending(roleName: string): boolean ``` **Example:** ```typescript if (rbac.isRolePending('viewer')) { console.log('Viewer role not yet loaded') } ``` #### evaluateAllRoles Force evaluation of all pending roles. ```typescript evaluateAllRoles(): void ``` **Example:** ```typescript // Load all roles immediately rbac.evaluateAllRoles() ``` ### Memory Optimization When `optimizeMemory: true` is set, Fire Shield uses string interning and other techniques to reduce memory usage. #### getMemoryStats Get memory optimization statistics. ```typescript getMemoryStats(): MemoryStats ``` **Returns:** Object with memory statistics **Example:** ```typescript const stats = rbac.getMemoryStats() console.log(stats) // { // enabled: true, // stringPoolSize: 150, // roleMaskCacheSize: 25, // wildcardPatternCacheSize: 10, // estimatedMemorySaved: 45000 // bytes // } ``` #### compactMemory Compact memory by cleaning up unused resources. ```typescript compactMemory(): { stringsRemoved: number; cacheEntriesRemoved: number } ``` **Returns:** Object with cleanup statistics **Example:** ```typescript const result = rbac.compactMemory() console.log(`Removed ${result.stringsRemoved} strings and ${result.cacheEntriesRemoved} cache entries`) ``` ## Types ### RBACUser User object with role assignments. ```typescript interface RBACUser { id: string roles: string[] [key: string]: any // Additional user properties } ``` **Example:** ```typescript const user: RBACUser = { id: 'user-123', roles: ['editor', 'moderator'], email: 'user@example.com', name: 'John Doe' } ``` ### RBACOptions Configuration options for RBAC instance. ```typescript interface RBACOptions { auditLogger?: AuditLogger bitPermissions?: boolean } ``` ### AuditLogger Interface for custom audit loggers. ```typescript interface AuditLogger { log(event: AuditEvent): void | Promise } ``` ### AuditEvent Audit log event structure. ```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 Built-in audit logger with buffering. ```typescript new BufferedAuditLogger( handler: (logs: AuditEvent[]) => Promise, options?: BufferedAuditLoggerOptions ) ``` #### Parameters - `handler` - Function to process buffered logs - `options` (optional): - `maxBufferSize?: number` - Max buffer size (default: 100) - `flushIntervalMs?: number` - Flush interval (default: 5000) #### 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 Manually flush buffered logs. ```typescript flush(): Promise ``` **Example:** ```typescript await auditLogger.flush() ``` ### Custom Audit Logger Implement 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 Fluent API for building RBAC configurations. ```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 Start defining a role. ```typescript role(name: string): RBACBuilder ``` #### grant Grant permissions to current role. ```typescript grant(permissions: string[]): RBACBuilder ``` #### hierarchy Set role hierarchy. ```typescript hierarchy(hierarchy: Record): RBACBuilder ``` #### build Build and return RBAC instance. ```typescript build(): RBAC ``` ## Utilities ### matchPermission Check if permission matches pattern (including wildcards). ```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 Parse permission string into parts. ```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 Base error class for RBAC errors. ```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 Fire Shield includes several performance optimizations for large-scale applications. ### Bit-Level Permissions Enable bit-level permission checking for better performance: ```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 Fire Shield includes explicit permission caching with TTL and size limits: ```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 Fire Shield supports lazy role evaluation for faster startup: ```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 Fire Shield includes memory optimization through string interning: ```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 For optimal performance in production: ```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 - Learn about [Permissions](/guide/permissions) - Understand [Deny Permissions](/guide/deny-permissions) - Explore [Framework Integrations](/frameworks/vue) - Use [CLI Tool](/frameworks/cli) for config validation - Integrate with [AI Agents via MCP](/frameworks/mcp) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/api/builder ──────────────────────────────────────────────────────────────────────── # RBAC Builder Fluent API for building RBAC configurations with a chainable interface. ## Overview RBACBuilder provides a convenient way to construct RBAC instances with a fluent, chainable API that makes configuration more readable and maintainable. ## 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() ``` Creates a new RBAC builder instance. **Example:** ```typescript const builder = new RBACBuilder(); ``` ### useBitSystem() Enable bit-based permission system (default). ```typescript useBitSystem(): RBACBuilder ``` **Returns:** The builder instance for chaining **Example:** ```typescript const rbac = new RBACBuilder() .useBitSystem() .build(); ``` ### useLegacySystem() Use string-based permission system instead of bit-based. ```typescript useLegacySystem(): RBACBuilder ``` **Returns:** The builder instance for chaining **Example:** ```typescript const rbac = new RBACBuilder() .useLegacySystem() .build(); ``` **When to use:** - Need more than 31 permissions - Permissions change frequently - Debugging/development ### withPreset(preset) Load from a preset configuration. ```typescript withPreset(preset: PresetConfig): RBACBuilder ``` **Parameters:** - `preset` - Preset configuration object **Returns:** The builder instance for chaining **Example 1: Using Built-in Preset** ```typescript import { defaultPreset } from '@fire-shield/core'; const rbac = new RBACBuilder() .withPreset(defaultPreset) .build(); ``` **Example 2: Loading from JSON Config File** ```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(); ``` **Example 3: Async File Loading** ```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(); ``` **Example Config File (rbac.config.json):** ```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 } } ``` **Benefits of Config Files:** - ✅ Separate configuration from code - ✅ Easy to version control - ✅ Share configs across team - ✅ Hot reload in development - ✅ Validate before loading ### addPermission(name, bit?, options?) Add a permission to the system. ```typescript addPermission( name: string, bit?: number, options?: { resource?: string; action?: string; description?: string; metadata?: Record; } ): RBACBuilder ``` **Parameters:** - `name` - Permission name (e.g., 'user:read') - `bit` - Optional manual bit value (must be power of 2) - `options` - Optional metadata **Returns:** The builder instance for chaining **Example:** ```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?) Add a role with permissions. ```typescript addRole( name: string, permissions: string[], options?: { level?: number; description?: string; metadata?: Record; } ): RBACBuilder ``` **Parameters:** - `name` - Role name - `permissions` - Array of permission names - `options` - Optional metadata **Returns:** The builder instance for chaining **Example:** ```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?) Enable or disable wildcard permission matching. ```typescript enableWildcards(enabled: boolean = true): RBACBuilder ``` **Parameters:** - `enabled` - Whether to enable wildcards (default: true) **Returns:** The builder instance for chaining **Example:** ```typescript const rbac = new RBACBuilder() .enableWildcards(true) .addRole('admin', ['*']) .build(); ``` ### withAuditLogger(logger) Add an audit logger to the RBAC instance. ```typescript withAuditLogger(logger: AuditLogger): RBACBuilder ``` **Parameters:** - `logger` - Audit logger instance **Returns:** The builder instance for chaining **Example:** ```typescript import { ConsoleAuditLogger } from '@fire-shield/core'; const rbac = new RBACBuilder() .withAuditLogger(new ConsoleAuditLogger()) .build(); ``` ### build() Build and return the RBAC instance. ```typescript build(): RBAC ``` **Returns:** Configured RBAC instance **Example:** ```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 All builder methods return the builder instance, allowing you to chain calls: ```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 Full TypeScript support with type inference: ```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 - Learn about [Audit Logging](/api/audit) - Explore [TypeScript Types](/api/types) - Check out [Core API](/api/core) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/api/audit ──────────────────────────────────────────────────────────────────────── # Audit Logging Comprehensive audit logging system for tracking permission checks, authorization events, and compliance. ## Overview Fire Shield provides built-in audit logging to track all permission checks, helping with: - **Security**: Track unauthorized access attempts - **Compliance**: GDPR, SOC2, HIPAA requirements - **Debugging**: Find permission logic errors - **Analytics**: Understand user behavior patterns ## Audit Loggers ### ConsoleAuditLogger Logs events to console (ideal for development). ```typescript import { RBAC, ConsoleAuditLogger } from '@fire-shield/core'; const rbac = new RBAC({ auditLogger: new ConsoleAuditLogger() }); ``` **Output:** ``` [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 Buffers events and flushes in batches (recommended for production). ```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 }); ``` **Options:** ```typescript interface BufferedAuditLoggerOptions { maxBufferSize?: number; // Default: 100 flushIntervalMs?: number; // Default: 5000 } ``` ### MultiAuditLogger Log to multiple destinations simultaneously. ```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; } ``` **Example:** ```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 Implement your own 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 Track failed authorization attempts and alert on suspicious activity: ```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 Store audit logs for compliance (GDPR, HIPAA, SOC2): ```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 Track permission usage patterns: ```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 Store logs in database with automatic 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 Always use BufferedAuditLogger in production: ```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 Make logging operations async: ```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 For high-traffic applications, sample logs: ```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 - Learn about [Core API](/api/core) - Explore [TypeScript Types](/api/types) - Check out [Performance Guide](/guide/performance) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/api/types ──────────────────────────────────────────────────────────────────────── # TypeScript Types Complete TypeScript type definitions for Fire Shield RBAC library. ## Core Types ### IRBAC **v3.0.0** - Core interface for RBAC implementations. Enables polymorphic usage of RBAC and RBACAggregator. ```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[]; } ``` **Purpose:** The `IRBAC` interface defines the contract that all RBAC implementations must follow. It enables polymorphic usage, allowing you to use `RBAC` and `RBACAggregator` interchangeably. **Implementations:** - `RBAC` - Standard RBAC implementation - `RBACAggregator` - Multi-domain RBAC aggregator **Example - Polymorphic Usage:** ```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! ``` **Example - Framework Adaptor Integration:** ```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 Represents a user in the RBAC system. ```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; } ``` **Example:** ```typescript const user: RBACUser = { id: 'user-123', roles: ['editor', 'moderator'], permissions: ['beta:feature'], permissionMask: 127 }; ``` ### AuthorizationResult Result of an authorization check with detailed information. ```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; } ``` **Example:** ```typescript const result: AuthorizationResult = rbac.authorize(user, 'admin:delete'); if (!result.allowed) { console.log(result.reason); // "User lacks permission: admin:delete" } ``` ### AuthorizationContext Context for authorization with additional metadata. ```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>; } ``` **Example:** ```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 Main configuration object for RBAC instance. ```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; } ``` **Example:** ```typescript const config: RBACConfig = { useBitSystem: true, strictMode: true, enableWildcards: true, auditLogger: new ConsoleAuditLogger() }; const rbac = new RBAC(config); ``` ### RBACConfigSchema Schema for defining permissions and roles. ```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; }; } ``` **Example:** ```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 Preset configuration with permissions and roles. ```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 Event logged during permission checks. ```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 Additional context for audit events. ```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 Interface for audit logging implementations. ```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>; } ``` **Example:** ```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 Represents a role with permissions and metadata. ```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 Type alias for permission bitmask. ```typescript type PermissionMask = number; ``` **Usage:** ```typescript const readMask: PermissionMask = 1; // 2^0 const writeMask: PermissionMask = 2; // 2^1 const combinedMask: PermissionMask = readMask | writeMask; // 3 ``` ## Permission Types ### PermissionDefinition Definition for a permission. ```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 Definition for a role. ```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 Serialized state of RBAC system. ```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[]>; } ``` **Example:** ```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 Utility functions for type checking. ### isRBACUser ```typescript function isRBACUser(obj: any): obj is RBACUser { return ( typeof obj === 'object' && typeof obj.id === 'string' && Array.isArray(obj.roles) ); } ``` **Usage:** ```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> Add metadata to any type. ```typescript type WithMetadata<T> = T & { metadata?: Record<string, any>; }; ``` **Example:** ```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' } ``` **Usage:** ```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 - Learn about [Core API](/api/core) - Explore [RBAC Builder](/api/builder) - Check out [TypeScript Guide](/guide/typescript) ======================================================================== # SECTION: Frameworks — Frontend ======================================================================== ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/vue ──────────────────────────────────────────────────────────────────────── # Vue.js Integration Fire Shield provides first-class support for Vue.js 3 with composables, directives, and router guards. ## 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 Show elements only if user has permission: ```vue ``` ### v-cannot Hide elements if user has permission (inverse of v-can): ```vue ``` ### v-permission Alias for v-can directive: ```vue ``` ### v-role Show elements only if user has specific role: ```vue ``` ## Composables ### Composables Access RBAC functionality in your components using the dedicated 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 Conditionally render content based on permissions: ```vue ``` ### Cannot Component Inverse of Can component: ```vue ``` ### ProtectedRoute Component Protect entire routes: ```vue ``` ### RequirePermission Component Require specific permission to render: ```vue ``` ## Router Guards ### Route Meta Configuration Protect routes using meta fields: ```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 For custom logic alongside RBAC guards, pass a custom `onUnauthorized` callback: ```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 User permissions update automatically when user changes: ```vue ``` ## TypeScript Support Full TypeScript support with type inference: ```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 Check out the [Vue example app](https://github.com/khapu2906/fire-shield/tree/main/examples/vue) for a complete working example. ## Next Steps - Explore [API Reference](/api/core) - Learn about [Role Hierarchy](/guide/role-hierarchy) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/react ──────────────────────────────────────────────────────────────────────── # React Integration Fire Shield provides hooks and components for React applications. ## 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 Access RBAC functionality in your components using the dedicated 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 Conditionally render content based on permissions: ```tsx import { Can } from '@fire-shield/react' function PostActions() { return (
No permission

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

Upgrade to unlock premium features

) } ``` ### RequirePermission Component Throw error or show fallback if permission is missing: ```tsx import { RequirePermission } from '@fire-shield/react' function AdminPanel() { return ( Access Denied} >

Admin Panel

{/* Admin content */}
) } ``` ### Denied Component Render if the user has the permission explicitly denied: ```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 Note: React hooks cannot be called inside route loaders. Use the built-in `ProtectedRoute` component from `@fire-shield/react` for route-level protection: ```tsx import { ProtectedRoute } from '@fire-shield/react' // Usage with react-router-dom } /> ``` ## Updating User Update user permissions dynamically by passing a new `user` prop to `RBACProvider`: ```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 Full TypeScript support with type inference: ```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 For Next.js, use the dedicated [@fire-shield/next](/frameworks/next) package which provides server-side support. ### Other SSR Frameworks Provide initial user state from server: ```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 - Explore [API Reference](/api/core) - Learn about [Permissions](/guide/permissions) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/next ──────────────────────────────────────────────────────────────────────── # Next.js Integration Fire Shield provides seamless integration with Next.js for both App Router (Next.js 13+) and Pages Router (Next.js 12). ## 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 Protect routes using Next.js middleware with the adapter's `middleware` method: ```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 Check permissions in 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 Protect API routes using the adapter's `withPermission` or `withRole` methods: ```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 }); } ); ``` You can also check permissions manually: ```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 Use the adapter's `requirePermission` or `requireRole` methods in 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 Hide/show UI elements based on permissions: ```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 The adapter provides `withPermissionPagesRouter` and `withRolePagesRouter` for Pages Router API routes: ```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: {} }; }; } ``` **Usage:** ```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?)` Creates a new Next.js adapter instance. **Options:** - `getUser?: (req) => RBACUser | Promise` - Extract user from request - `onUnauthorized?: (result, req, res?) => Response | void` - Custom unauthorized handler - `onError?: (error, req, res?) => Response | void` - Custom error handler ### Methods #### `adapter.middleware(permission)` Returns an async function `(request: NextRequest) => Promise`. Use in `middleware.ts` for route-level protection. #### `adapter.withPermission(permission, handler)` HOC for App Router route handlers. Wraps a handler with a permission check. ```typescript export const GET = rbacAdapter.withPermission('user:read', async (req) => { return NextResponse.json({ users: await getUsers() }); }); ``` #### `adapter.withRole(role, handler)` HOC for App Router route handlers. Wraps a handler with a role check. ```typescript export const GET = rbacAdapter.withRole('admin', async (req) => { return NextResponse.json({ stats: await getAdminStats() }); }); ``` #### `adapter.withPermissionPagesRouter(permission, handler)` HOC for Pages Router API routes. #### `adapter.withRolePagesRouter(role, handler)` HOC for Pages Router API routes with role check. #### `adapter.checkPermission(user, permission)` Returns `Promise`. Use in Server Components or Server Actions. #### `adapter.requirePermission(user, permission)` Throws an error if the user lacks the permission. Use in Server Actions. #### `adapter.requireRole(user, role)` Throws an error if the user lacks the role. Use in Server Actions. #### `adapter.withNotDenied(permission, handler)` HOC that blocks access if the given permission is explicitly denied for the user. #### `adapter.denyPermission(request, permission)` Explicitly deny a permission for the user found in the request. #### `adapter.allowPermission(request, permission)` Remove an explicit deny for the user found in the request. #### `adapter.isDenied(request, permission)` Returns `Promise` indicating whether the permission is explicitly denied. ### `createPermissionChecker(rbac, getUser)` Standalone helper that creates a reusable permission-checking function: ```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)` Standalone helper that creates a reusable role-checking function: ```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 Check permissions based on resource ownership: ```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 - Explore [API Reference](/api/core) - Learn about [Permissions](/guide/permissions) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/nuxt ──────────────────────────────────────────────────────────────────────── # Nuxt Integration Fire Shield provides a Nuxt module for seamless RBAC integration with composables, server utilities, and middleware. ## Installation ```bash npm install @fire-shield/nuxt@3.1.1 @fire-shield/core@3.1.1 ``` ## Setup ### 1. Add Module Add Fire Shield to your Nuxt configuration: ```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() Main composable for accessing RBAC functionality: ```vue ``` **API:** ```typescript interface UseFireShieldReturn { can: (permission: string) => boolean; hasRole: (role: string) => boolean; authorize: (permission: string) => AuthorizationResult; rbac: RBAC; setUser: (user: RBACUser | null) => void; } ``` ### useRBAC() Access RBAC instance directly: ```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'); } }); ``` **Usage:** ```vue ``` ## Components ### Conditional Rendering ```vue ``` ### Dynamic Actions ```vue ``` ### Form Protection ```vue ``` ## Layouts ### Protected Layout ```vue ``` **Usage:** ```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; }); ``` **Usage:** ```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 - Explore [API Reference](/api/core) - Learn about [Permissions](/guide/permissions) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/angular ──────────────────────────────────────────────────────────────────────── # Angular Integration Fire Shield provides Angular integration with services, directives, and route guards for comprehensive RBAC support. ## Features - Injectable RBACService with RxJS observables - Structural directives (`*fsCanPermission`, `*fsHasRole`, `*fsCannotPermission`) - Route guards for protecting routes - Reactive state management with BehaviorSubject - Full TypeScript support - Compatible with standalone components (Angular 14+) ## Installation ```bash npm install @fire-shield/angular@3.1.1 @fire-shield/core@3.1.1 ``` ## Setup ### 1. Initialize RBAC Create an initialization function: ```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 Injectable service that provides RBAC functionality. ### 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 Synchronous permission check: ```typescript const allowed = this.rbacService.can('post:write'); ``` #### can$(permission: string): Observable<boolean> Reactive permission check: ```typescript this.rbacService.can$('post:write').subscribe(allowed => { console.log('Can write:', allowed); }); ``` #### hasRole(role: string): boolean Synchronous role check: ```typescript const isAdmin = this.rbacService.hasRole('admin'); ``` #### hasRole$(role: string): Observable<boolean> Reactive role check: ```typescript this.rbacService.hasRole$('admin').subscribe(isAdmin => { console.log('Is admin:', isAdmin); }); ``` #### authorize(permission: string): AuthorizationResult Get detailed authorization result: ```typescript const result = this.rbacService.authorize('post:delete'); if (!result.allowed) { console.log('Denied:', result.reason); } ``` #### canAll(permissions: string[]): boolean Check if user has all permissions: ```typescript const hasAll = this.rbacService.canAll(['post:read', 'post:write']); ``` #### canAny(permissions: string[]): boolean Check if user has any permission: ```typescript const hasAny = this.rbacService.canAny(['post:read', 'post:write']); ``` ## Directives ### *fsCanPermission Conditionally renders content based on permission: ```html
``` ### *fsHasRole Conditionally renders content based on role: ```html

Admin Panel

``` ### *fsCannotPermission Inverse conditional rendering (show when permission is NOT present): ```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 Protect routes based on permissions: ```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 Protect routes based on roles: ```typescript export const routes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [canActivateRole], data: { role: 'admin', redirectTo: '/unauthorized' } } ]; ``` ### canActivateRBAC Combined guard for both permissions and roles: ```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 Add RBAC context to HTTP requests: ```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); }) ); } } ``` **Register interceptor:** ```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 - Explore [API Reference](/api/core) - Learn about [Permissions](/guide/permissions) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/svelte ──────────────────────────────────────────────────────────────────────── # Svelte Integration Fire Shield provides Svelte integration with reactive stores, actions, and full TypeScript support for both Svelte 4 and 5. ## Features - Reactive RBAC stores with writable and derived stores - Svelte actions (`use:can`, `use:role`, `use:cannot`) - Automatically updates when user changes - Full TypeScript support - Compatible with Svelte 4 & 5 ## 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?) Creates a reactive RBAC store. ```typescript import { RBAC } from '@fire-shield/core'; import { createRBACStore } from '@fire-shield/svelte'; const rbac = new RBAC(); const store = createRBACStore(rbac, null); ``` **Returns:** ```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) Returns a derived store that checks if user has permission: ```svelte
{#if $canWrite} {/if} {#if $canPublish} {/if} {#if $canDelete} {/if}
``` ### hasRole(role) Returns a derived store that checks if user has role: ```svelte ``` ### authorize(permission) Returns a derived store with full authorization result: ```svelte {#if !$result.allowed}

Access denied: {$result.reason}

{:else} {/if} ``` ### canAll(permissions) Check if user has all permissions: ```svelte {#if $hasFullAccess} {/if} ``` ### canAny(permissions) Check if user has any permission: ```svelte {#if $canAccessPosts} View Posts {/if} ``` ## Svelte Actions ### use:can Conditionally render element based on permission: ```svelte
``` ### use:role Conditionally render element based on role: ```svelte

Admin Controls

Admin Controls

``` ### use:cannot Inverse conditional rendering (show when permission is NOT present): ```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 - Explore [API Reference](/api/core) - Learn about [Permissions](/guide/permissions) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/sveltekit ──────────────────────────────────────────────────────────────────────── # SvelteKit Integration Fire Shield provides SvelteKit hooks and server-side middleware for full-stack RBAC authorization. ## Features - Server-side hooks for request protection - Client-side permission checks - TypeScript support with `App.Locals` augmentation - SSR-compatible - Works with all SvelteKit adapters - Seamless integration with SvelteKit's load functions ## 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) Creates a SvelteKit hook for RBAC authorization. **Parameters:** ```typescript interface RBACHandleOptions { getUser?: (event: RequestEvent) => Promise | RBACUser | undefined; onUnauthorized?: (event: RequestEvent, result: AuthorizationResult) => Response; } ``` **Example:** ```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) Server-side guard for protecting routes. **Example:** ```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) Server-side guard for role-based protection. **Example:** ```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 Augment `App.Locals` for type safety: ```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 Share RBAC data across all pages: ```typescript // src/routes/+layout.server.ts import type { LayoutServerLoad } from './$types'; export const load: LayoutServerLoad = async ({ locals }) => { return { user: locals.user, rbac: locals.rbac }; }; ``` Now all pages have access to `user` and `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 1. **Use server-side protection** - Always protect sensitive operations on the server 2. **Share RBAC in layouts** - Make user and rbac available to all pages via layout load 3. **Type-safe with App.Locals** - Augment App.Locals for TypeScript support 4. **Cache permission checks** - Enable caching for better performance 5. **Handle unauthorized gracefully** - Provide good error messages and redirects ## Advanced Example Complete authentication flow with protected routes: ```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 - [Svelte Integration](/frameworks/svelte) - Client-side Svelte features - [Core Concepts](/guide/permissions) - Understanding permissions - [API Reference](/api/core) - Complete API documentation ======================================================================== # SECTION: Frameworks — Backend ======================================================================== ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/express ──────────────────────────────────────────────────────────────────────── # Express Integration Fire Shield provides middleware for Express applications to protect routes with RBAC. ## 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 Protect routes with a permission requirement: ```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 Use the adapter instance to check a permission (picks up shared options automatically): ```typescript app.post('/posts', rbacMiddleware.permission('posts:write'), (req, res) => { res.json({ message: 'Post created' }) } ) ``` ### adapter.any / adapter.all Require any or all of a set of permissions: ```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 Protect routes with role requirements: ```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 Check permissions by resource and action: ```typescript import { requireResourceAction } from '@fire-shield/express' app.delete('/users/:id', requireResourceAction('users', 'delete', { rbac }), (req, res) => { res.json({ message: 'User deleted' }) } ) ``` ### denyPermission Middleware that explicitly denies a permission for the request user (stores the denial in RBAC): ```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 Middleware that removes an explicit denial for a permission: ```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 Middleware that blocks the request if the permission is explicitly denied for the user: ```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 By default, Fire Shield returns `403 Forbidden` when permission is denied: ```typescript // Default response for denied access { error: 'Forbidden', message: 'Insufficient permissions' } ``` ### Custom Error Handler Customize the error response: ```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 Use Express 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 Access the authorization result in route handlers via `req.authorization`: ```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 Check permissions based on resource ownership: ```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 Full TypeScript support with type definitions: ```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 - Explore [Fastify Integration](/frameworks/fastify) - Learn about [Permissions](/guide/permissions) - Check out [API Reference](/api/core) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/fastify ──────────────────────────────────────────────────────────────────────── # Fastify Integration Fire Shield provides plugins and hooks for Fastify applications to protect routes with RBAC. ## 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 Protect routes with a permission requirement: ```typescript // Single permission fastify.post('/posts', { preHandler: rbacHooks.permission('posts:write') }, async (request, reply) => { return { message: 'Post created' } }) ``` ### adapter.any / adapter.all Require any or all of a set of permissions: ```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 Use the standalone function when you need a one-off hook without a shared adapter: ```typescript import { requirePermission } from '@fire-shield/fastify' fastify.post('/posts', { preHandler: requirePermission('posts:write', { rbac }) }, async (request, reply) => { return { message: 'Post created' } }) ``` ### requireRole Protect routes with role requirements: ```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 Check permissions by resource and action: ```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 Hooks that explicitly deny or restore a permission for the request user: ```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 Hook that blocks the request if the permission is explicitly denied (even if the role grants it): ```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 Use without an adapter when you need one-off hooks: ```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 Programmatic checks inside route handlers: ```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 By default, Fire Shield throws a Fastify error with `403 Forbidden`: ```json { "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions" } ``` ### Custom Error Handler Customize the error response: ```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 Use Fastify error hooks: ```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 Access the authorization result in route handlers via `request.authorization`: ```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 Check permissions based on resource ownership: ```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 Full TypeScript support with type definitions: ```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 Document permission requirements in OpenAPI: ```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 - Explore [Express Integration](/frameworks/express) - Learn about [Permissions](/guide/permissions) - Check out [API Reference](/api/core) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/hono ──────────────────────────────────────────────────────────────────────── # Hono Integration Fire Shield provides Hono middleware for RBAC authorization, compatible with edge runtimes like Cloudflare Workers, Deno Deploy, Vercel Edge, and more. ## Features - Edge runtime compatible (Cloudflare Workers, Deno Deploy, Vercel Edge) - Lightweight and fast middleware - Support for permission and role checks - Flexible configuration options - TypeScript support ## 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?) Creates a new Hono adapter instance. **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; } ``` **Example:** ```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) Check if user has specific permission: ```typescript app.get('/admin', rbacMiddleware.permission('admin:access'), (c) => c.json({ admin: true }) ); ``` ### role(role: string) Check if user has specific role: ```typescript app.get('/admin', rbacMiddleware.role('admin'), (c) => c.json({ admin: true }) ); ``` ### resourceAction(resource: string, action: string) Check resource:action permission: ```typescript app.delete('/users/:id', rbacMiddleware.resourceAction('user', 'delete'), (c) => c.json({ deleted: true }) ); ``` ### all(...permissions: string[]) Check if user has ALL specified permissions (AND logic): ```typescript app.post('/admin/users', rbacMiddleware.all('user:create', 'user:write'), (c) => c.json({ created: true }) ); ``` ### any(...permissions: string[]) Check if user has ANY of specified permissions (OR logic): ```typescript app.get('/dashboard', rbacMiddleware.any('admin:access', 'moderator:access'), (c) => c.json({ dashboard: 'data' }) ); ``` ### middleware(customOptions: Partial<HonoRBACOptions>) Create custom middleware with route-specific options: ```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 - Explore [API Reference](/api/core) - Learn about [Permissions](/guide/permissions) - Check out [Examples](/examples/basic-usage) ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/graphql ──────────────────────────────────────────────────────────────────────── # GraphQL Integration Fire Shield provides GraphQL directives and middleware for field-level and resolver-level authorization in GraphQL servers. ## Features - Custom directives (`@hasPermission`, `@hasRole`, `@hasAnyPermission`, `@hasAllPermissions`, `@notDenied`, `@isDenied`) - Field-level authorization - Type-safe context interface - Works with Apollo Server, GraphQL Yoga, and others - TypeScript support - Schema-first approach ## 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?)` Applies all Fire Shield directives to a schema in one call. **Parameters:** ```typescript applyFireShieldDirectives(schema: GraphQLSchema, config?: { hasPermission?: { directiveName?: string }; hasRole?: { directiveName?: string }; hasAnyPermission?: { directiveName?: string }; hasAllPermissions?: { directiveName?: string }; }): GraphQLSchema ``` **Example:** ```typescript import { applyFireShieldDirectives } from '@fire-shield/graphql'; let schema = makeExecutableSchema({ typeDefs, resolvers }); schema = applyFireShieldDirectives(schema); ``` ### Individual Directive Factories You can also apply directives individually: ```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` A ready-made string containing all directive type definitions. Include it in your `typeDefs` to avoid writing them by hand: ```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 } ``` Extend it with your own context properties: ```typescript interface MyContext extends GraphQLRBACContext { db: Database; logger: Logger; } ``` ## Available Directives ### @hasPermission Requires a specific permission: ```graphql type Query { users: [User!]! @hasPermission(permission: "user:read") } ``` ### @hasRole Requires a specific role: ```graphql type Mutation { deleteUser(id: ID!): Boolean! @hasRole(role: "admin") } ``` ### @hasAnyPermission Requires at least one of the specified permissions: ```graphql type Query { posts: [Post!]! @hasAnyPermission(permissions: ["post:read", "post:write"]) } ``` ### @hasAllPermissions Requires all of the specified permissions: ```graphql type Mutation { deletePost(id: ID!): Boolean! @hasAllPermissions(permissions: ["post:delete", "post:admin"]) } ``` ### @notDenied Blocks access if the given permission is explicitly denied for the user: ```graphql type Query { sensitiveData: JSON! @notDenied(permission: "data:sensitive") } ``` ### @isDenied Only executes the field resolver if the given permission IS explicitly denied for the user (useful for returning restricted fallback data): ```graphql type Query { restrictedMessage: String @isDenied(permission: "data:sensitive") } ``` ## Context Requirements The GraphQL context must include `rbac` and optionally `user`: ```typescript const context = ({ req }) => ({ rbac: myRBACInstance, user: { id: req.user.id, roles: req.user.roles, // Optional: direct permissions permissions: req.user.permissions, }, }); ``` ## Error Handling The directives throw `GraphQLError` with specific error codes: ```typescript { extensions: { code: 'UNAUTHENTICATED' | 'FORBIDDEN' | 'RBAC_NOT_CONFIGURED', requiredPermission?: string, requiredRole?: string, requiredPermissions?: string[], } } ``` Example error handler: ```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' }, }); ``` Then use in schema: ```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 You can also check permissions directly in resolvers: ```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 For complex authorization logic: ```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 Protect specific fields within a type: ```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 Protect GraphQL subscriptions: ```graphql type Subscription { postCreated: Post! @hasPermission(permission: "post:subscribe") adminNotifications: AdminNotification! @hasRole(role: "admin") } ``` ## TypeScript Support Full type definitions included: ```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 1. **Use directives for simple checks** - Clean and declarative 2. **Use resolver logic for complex authorization** - More flexibility 3. **Combine field-level and resolver authorization** - Defense in depth 4. **Always validate on the server** - Never trust client checks 5. **Cache permission results** - Enable caching for performance 6. **Provide clear error messages** - Help clients understand why access was denied ## Next Steps - [tRPC Integration](/frameworks/trpc) - Type-safe RPC with RBAC - [Core Concepts](/guide/permissions) - Understanding permissions - [API Reference](/api/core) - Complete API documentation ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/trpc ──────────────────────────────────────────────────────────────────────── # tRPC Integration Fire Shield provides type-safe tRPC middleware for end-to-end type-safe RBAC authorization. ## Features - Type-safe permission checks - Procedure-level authorization - Context-based authorization - Input validation with authorization - Full TypeScript inference - Works with all tRPC adapters (Next.js, Express, Fastify, etc.) ## 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) tRPC middleware that checks for a specific permission. **Example:** ```typescript export const protectedQuery = publicProcedure.use(requirePermission('resource:read')); ``` ### requireRole(role) tRPC middleware that checks for a specific role. **Example:** ```typescript export const adminQuery = publicProcedure.use(requireRole('admin')); ``` ### createRBACMiddleware(options) Create custom RBAC middleware with options. **Parameters:** ```typescript interface RBACMiddlewareOptions { getUser?: (ctx: any) => RBACUser | undefined; getRBAC?: (ctx: any) => RBAC; onUnauthorized?: (result: AuthorizationResult, ctx: any) => void; } ``` **Example:** ```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 For complex authorization logic: ```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 Require 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 Custom error messages: ```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 1. **Use middleware for common checks** - DRY authorization logic 2. **Leverage TypeScript** - Get type-safe permission strings 3. **Cache permission results** - Enable caching for performance 4. **Validate inputs with Zod** - Combine auth with validation 5. **Use nested routers** - Organize by permission requirements 6. **Provide clear errors** - Help clients understand access issues ## 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 - [GraphQL Integration](/frameworks/graphql) - GraphQL with RBAC - [Core Concepts](/guide/permissions) - Understanding permissions - [API Reference](/api/core) - Complete API documentation ======================================================================== # SECTION: Frameworks — Mobile ======================================================================== ──────────────────────────────────────────────────────────────────────── ## Source: https://fire-shield.dev/frameworks/react-native ──────────────────────────────────────────────────────────────────────── # React Native Integration Fire Shield provides React Native hooks and components for mobile RBAC authorization, compatible with both Expo and bare React Native projects. ## Features - React hooks for permission checks (`useRBAC`, `usePermission`, `useRole`) - Component-based authorization (``, ``) - Async Storage integration for persistence - TypeScript support - Works with Expo and bare React Native - Zero native dependencies ## 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 */}