rab-access
v0.1.0
Published
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.
Readme
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
npm install rab-accessQuick Start
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); // trueCore Concepts
Permission Schema
A permission schema defines the relationship between permissions and roles:
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
Rab.grant(AppRoles.admin)Conditional Grant
Rab.grant(AppRoles.user).ifEqual(
Rab.auth('organizationId'),
Rab.params('organizationId')
)Grant with Field Restrictions
Rab.grant(AppRoles.user)
.columns(['name', 'email'])
.ifEqual(Rab.auth('id'), Rab.params('userId'))Grant with Filters
Rab.grant(AppRoles.manager)
.filters({ active: true })
.lookupFilters({
department: {
id: Rab.auth('departmentIds')
}
})Field References
Access different parts of the request context:
// 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.statusAPI 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 checkoptions.role: string - User's roleoptions.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.
Rab.grant('user').ifEqual(
Rab.auth('organizationId'),
Rab.params('organizationId')
)ifContains(fieldOne, fieldTwo)
Adds a contains condition to the grant.
Rab.grant('manager').ifContains(
Rab.auth('departmentIds'),
Rab.params('departmentId')
)columns(columns)
Restricts access to specific fields.
Rab.grant('user').columns(['name', 'email', 'createdAt'])filters(filters)
Adds data filtering conditions.
Rab.grant('user').filters({ active: true, deleted: false })lookupFilters(filters)
Adds lookup-based filtering.
Rab.grant('manager').lookupFilters({
department: {
id: ['departments', 'managedBy']
}
})validator(method, variables)
Adds custom validation.
Rab.grant('user').validator('customValidation', {
threshold: 100,
context: 'user-action'
})extend(role, permission)
Inherits permissions from another grant.
Rab.grant('admin').extend('user', 'canReadProfile')Integration Examples
With AtomAPI Framework
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
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:
interface PermissionGrant {
isAuthorized: boolean;
columns?: string[];
filters?: Record<string, any>;
lookupFilters?: Record<string, Record<string, string[]>>;
// Additional grant metadata
}Advanced Usage
Multi-level Inheritance
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
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:
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.
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License.
