UNPKG

rab-access

Version:

A TypeScript library for Role-Based Access Control (RBAC) that provides a flexible and powerful permission system with conditional grants, field-level access control, and validation capabilities.

384 lines (302 loc) 8.65 kB
# rab-access A TypeScript library for Role-Based Access Control (RBAC) that provides a flexible and powerful permission system with conditional grants, field-level access control, and validation capabilities. ## Features - **Schema-based Permission Definition**: Define permissions using declarative schemas - **Role-based Access Control**: Grant permissions based on user roles - **Conditional Grants**: Support for conditional permissions based on field comparisons - **Field-level Permissions**: Control access to specific fields/columns - **Permission Inheritance**: Extend permissions from other grants - **Custom Validation**: Support for custom validation functions - **Filtering**: Built-in support for lookup filters and data filtering - **Type Safety**: Full TypeScript support with type safety ## Installation ```bash npm install rab-access ``` ## Quick Start ```typescript import { Rab } from 'rab-access'; // Define your roles enum AppRoles { administrator = 'administrator', system_admin = 'system_admin', user = 'user', } // Define your permissions enum AppAccess { canUpdateShopDetails = 'canUpdateShopDetails', canChangeShopApprovalStatus = 'canChangeShopApprovalStatus', canReadAllUsers = 'canReadAllUsers', } // Create a permission schema const shopPermissions = Rab.schema({ [AppAccess.canUpdateShopDetails]: [ Rab.grant(AppRoles.administrator).ifEqual( Rab.auth('shopId'), Rab.params('shopId') ), Rab.grant(AppRoles.system_admin), ], [AppAccess.canChangeShopApprovalStatus]: [ Rab.grant(AppRoles.system_admin), ], }); // Check permissions const grant = await shopPermissions.getGrant({ permission: AppAccess.canUpdateShopDetails, role: AppRoles.administrator, request: { user: { shopId: '123' }, params: { shopId: '123' }, }, }); console.log(grant.isAuthorized); // true ``` ## Core Concepts ### Permission Schema A permission schema defines the relationship between permissions and roles: ```typescript const permissions = Rab.schema({ [AppAccess.canReadUser]: [ Rab.grant(AppRoles.user).ifEqual( Rab.auth('id'), Rab.params('userId') ), Rab.grant(AppRoles.admin), ], }); ``` ### Grant Types #### Simple Role Grant ```typescript Rab.grant(AppRoles.admin) ``` #### Conditional Grant ```typescript Rab.grant(AppRoles.user).ifEqual( Rab.auth('organizationId'), Rab.params('organizationId') ) ``` #### Grant with Field Restrictions ```typescript Rab.grant(AppRoles.user) .columns(['name', 'email']) .ifEqual(Rab.auth('id'), Rab.params('userId')) ``` #### Grant with Filters ```typescript Rab.grant(AppRoles.manager) .filters({ active: true }) .lookupFilters({ department: { id: Rab.auth('departmentIds') } }) ``` ### Field References Access different parts of the request context: ```typescript // Reference user fields Rab.auth('id') // user.id Rab.auth(['profile', 'organizationId']) // user.profile.organizationId // Reference request parameters Rab.params('shopId') // params.shopId Rab.params(['nested', 'field']) // params.nested.field // Reference query parameters Rab.query('status') // query.status ``` ## API Reference ### Rab Class #### `Rab.schema(config)` Creates a new permission schema. **Parameters:** - `config`: Record<string, RabGrant[]> - Permission configuration **Returns:** `Rab` instance #### `Rab.grant(role)` Creates a new grant for a specific role. **Parameters:** - `role`: string - The role name **Returns:** `RabGrant` instance #### `getGrant(options)` Evaluates permissions for a specific request. **Parameters:** - `options.permission`: string - Permission to check - `options.role`: string - User's role - `options.request`: object - Request context (user, params, query) - `options.validations`: object - Custom validation functions **Returns:** `Promise<PermissionGrant>` ### RabGrant Class #### `ifEqual(fieldOne, fieldTwo)` Adds an equality condition to the grant. ```typescript Rab.grant('user').ifEqual( Rab.auth('organizationId'), Rab.params('organizationId') ) ``` #### `ifContains(fieldOne, fieldTwo)` Adds a contains condition to the grant. ```typescript Rab.grant('manager').ifContains( Rab.auth('departmentIds'), Rab.params('departmentId') ) ``` #### `columns(columns)` Restricts access to specific fields. ```typescript Rab.grant('user').columns(['name', 'email', 'createdAt']) ``` #### `filters(filters)` Adds data filtering conditions. ```typescript Rab.grant('user').filters({ active: true, deleted: false }) ``` #### `lookupFilters(filters)` Adds lookup-based filtering. ```typescript Rab.grant('manager').lookupFilters({ department: { id: ['departments', 'managedBy'] } }) ``` #### `validator(method, variables)` Adds custom validation. ```typescript Rab.grant('user').validator('customValidation', { threshold: 100, context: 'user-action' }) ``` #### `extend(role, permission)` Inherits permissions from another grant. ```typescript Rab.grant('admin').extend('user', 'canReadProfile') ``` ## Integration Examples ### With AtomAPI Framework ```typescript import { Rab } from 'rab-access'; // Define permission bloc @Injectable() export class UserPermissionBloc implements PermissionAbstractBloc { getMetaData() { return { schema: Rab.schema({ [AppAccess.canUpdateUser]: [ Rab.grant(AppRoles.user).ifEqual( Rab.auth('id'), Rab.params('userId') ), Rab.grant(AppRoles.admin), ], }), validations: { customValidation: async (context) => { // Custom validation logic return context.user.verified === true; }, }, }; } } // Use in controller @Put('/users/:userId', { permission: AppAccess.canUpdateUser, bodySchema: updateUserSchema, }) export class UpdateUserController implements AtomApiPut<T> { handler: T['request'] = async (request) => { // Access the resolved permission grant const grant = request.accessGrant; // Use grant information for business logic if (grant.columns) { // Filter response based on allowed columns } return this.userService.update(request.params.userId, request.body); }; } ``` ### Custom Validation Functions ```typescript const userPermissions = Rab.schema({ [AppAccess.canDeleteUser]: [ Rab.grant(AppRoles.admin).validator('canDeleteUser', { minAccountAge: 30, }), ], }); // Validation function const validations = { canDeleteUser: async (context) => { const { user, variables } = context; const accountAge = Date.now() - user.createdAt; const minAge = variables.minAccountAge * 24 * 60 * 60 * 1000; return accountAge >= minAge; }, }; ``` ## Permission Grant Response The `getGrant` method returns a `PermissionGrant` object: ```typescript interface PermissionGrant { isAuthorized: boolean; columns?: string[]; filters?: Record<string, any>; lookupFilters?: Record<string, Record<string, string[]>>; // Additional grant metadata } ``` ## Advanced Usage ### Multi-level Inheritance ```typescript const permissions = Rab.schema({ [AppAccess.canReadBasicProfile]: [ Rab.grant(AppRoles.user).columns(['name', 'email']), ], [AppAccess.canReadFullProfile]: [ Rab.grant(AppRoles.admin) .extend(AppRoles.user, AppAccess.canReadBasicProfile) .columns(['*']), ], }); ``` ### Complex Conditional Logic ```typescript const permissions = Rab.schema({ [AppAccess.canManageProject]: [ Rab.grant(AppRoles.project_manager) .ifEqual(Rab.auth('departmentId'), Rab.params('departmentId')) .ifContains(Rab.auth('projectIds'), Rab.params('projectId')) .validator('hasActiveSubscription'), ], }); ``` ## Error Handling The library provides built-in error handling: ```typescript try { const grant = await permissions.getGrant({ permission: 'nonexistent', role: 'user', request: { user: {}, params: {} }, }); } catch (error) { console.error('Permission evaluation failed:', error.message); // Grant will return { isAuthorized: false } } ``` ## Building Run `nx build rab-access` to build the library. ## Running Tests Run `nx test rab-access` to execute the unit tests via [Jest](https://jestjs.io). ## Contributing 1. Fork the repository 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request ## License This project is licensed under the MIT License.