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