arbac
v0.5.1
Published
Zero-dependency type-safe Attribute enhanced Role-Based Access Control for typescript.
Maintainers
Readme
npm install arbac
Zero-dependency type-safe Attribute enhanced Role-Based Access Control for typescript.
Features
- Simple centralized access control declaration, role lists can be supplied from any source
- Attributes can be supplied in (almost) any form and from any source, type-safe and extendable
- Synchronous and performant
- Can be used with any runtime
Example usage
import { ARBAC, ARBACError } from 'arbac';
enum BlogRole {
Administrator = 'admin',
Reader = 'reader',
Writer = 'writer',
}
enum BlogAction {
CreatePost,
ReadPost,
UpdatePost,
DeletePost,
}
type UserID = string;
const USER1: UserID = 'user1';
const USER2: UserID = 'user2';
interface BlogActionAttributes {
[BlogAction.DeletePost]: {
authorId: UserID;
actorId: UserID;
};
[BlogAction.UpdatePost]: {
authorId: UserID;
actorId: UserID;
};
}
const actorIsAuthor = (
attrs: BlogActionAttributes[BlogAction.UpdatePost | BlogAction.DeletePost],
) => attrs.authorId === attrs.actorId;
const rbac = ARBAC.fromObjects<BlogRole, BlogAction, BlogActionAttributes>(
{
[BlogRole.Reader]: {
[BlogAction.ReadPost]: true,
},
[BlogRole.Writer]: {
// special property to extend other roles, local properties override inherited ones
[ARBAC.EXTENDS_ROLE]: BlogRole.Reader,
[BlogAction.CreatePost]: true,
[BlogAction.UpdatePost]: actorIsAuthor,
[BlogAction.DeletePost]: actorIsAuthor,
},
[BlogRole.Administrator]: {
[BlogAction.ReadPost]: true,
[BlogAction.DeletePost]: true,
},
},
{
// list of actions should be passed if they have number values, can be detected otherwise
actions: [
BlogAction.CreatePost,
BlogAction.ReadPost,
BlogAction.UpdatePost,
BlogAction.DeletePost,
],
// hideAttributesInErrors: false, // attributes aren't passed to errors on asserts by default to avoid leaks
// makeError: (action, roles, attrs) => new Error('custom'), // generate custom errors on asserts
// roles: [...], // list of roles should be passed if they have number values, can be detected otherwise
},
);
expect(rbac.permits(BlogAction.ReadPost, [BlogRole.Reader])).toBe(true);
// first level arrays are flattened, roles are deduplicated, undefined values ignored
expect(
rbac.permits(BlogAction.ReadPost, [
BlogRole.Reader,
[BlogRole.Reader, undefined],
undefined,
]),
).toBe(true);
expect(rbac.actionRelevantRoles(BlogAction.DeletePost).all).toEqual([
BlogRole.Administrator,
BlogRole.Writer,
]); // finding all roles that can interact with particular action
rbac.assertPermits(BlogAction.CreatePost, [
BlogRole.Reader,
BlogRole.Writer,
BlogRole.Administrator,
]);
rbac.assertPermits(BlogAction.DeletePost, [BlogRole.Administrator], {
authorId: USER1,
actorId: USER2,
}); // types won't allow you to skip attributes here
expect(() =>
rbac.assertPermits(BlogAction.CreatePost, [BlogRole.Reader]),
).toThrow(ARBACError); // role check won't permit this
expect(() =>
rbac.assertPermits(
BlogAction.UpdatePost,
[BlogRole.Reader, BlogRole.Writer],
{
authorId: USER1,
actorId: USER2,
},
),
).toThrow(ARBACError); // attribute check won't permit thisMore examples at src/examples.test.ts
