Simple dependency-free package for ACL. It can handle hundreds of roles easily.
Features
- dependency-free
- built-in TypeScript support
- can manipulate rules on the fly
- the generated permission token can be saved on client side (for example in JWT token) to avoid database queries to verify permission
- you can use as middleware in express/fastify/NestJS
- unit tests, 100% coverage
Install
npm install aclatraz
How it works
- Create the rules (they can be roles as well)
- Load them into Aclatraz
- Generate permission tokens based on rules
- Verify token
Documentation
Examples
TypeScript example
import { Aclatraz } from 'aclatraz';
// Create an ACL instance. Don't forget to store
// somewhere the rules (preferably in DB, or in a file)
const acl = new Aclatraz([
{
id: 1,
slug: 'READ_USER',
},
{
id: 2,
slug: 'SUPERADMIN',
name: 'Glorious superadmin rule',
},
]);
// Add new rule on the fly
acl.addRule({
id: 3,
slug: 'CREATE_USER',
name: 'Protect user creation with this rule',
});
// User with this token can access id: 1 rule
let permissionToken = acl.generateAclCode([1]);
console.log(acl.verify(permissionToken, 1)); // true
console.log(acl.verify(permissionToken, 2)); // false
console.log(acl.verify(permissionToken, 3)); // false
// Grant superadmin permission to the user
permissionToken = acl.grantPermission(permissionToken, [2, 3]);
console.log(acl.verify(permissionToken, 1)); // true
console.log(acl.verify(permissionToken, 2)); // true
console.log(acl.verify(permissionToken, 3)); // true
// Revoke the superadmin permission
permissionToken = acl.revokePermission(permissionToken, [2]);
console.log(acl.verify(permissionToken, 1)); // true
console.log(acl.verify(permissionToken, 2)); // false
console.log(acl.verify(permissionToken, 3)); // true
example)
How to use as an express middleware (here is a more detailedimport jwt from 'jsonwebtoken';
import express from 'express';
import { Aclatraz } from 'aclatraz';
const app = express();
// Create the Aclatraz instance. Store the rules in redis/database etc.
export const acl = new Aclatraz([
{
id: 1,
slug: 'ADMIN',
},
{
id: 2,
slug: 'USER',
},
{
id: 3,
slug: 'READ_OTHER_USERS',
},
{
id: 4,
slug: 'CREATE_USER',
},
]);
// Create a guard which takes the permission from JWT
function permissionGuard(...permissionList) {
return (req, res, next) => {
// Get the authorization header
const { authorization } = req.headers;
if (!authorization) {
return res.sendStatus(401);
}
// Get the token from the Bearer token
const [_, token] = authorization.split(' ');
// Decode the token
const user = jwt.decode(token, 'JWTSECRET');
// The Aclatraz permission token stored under user.permission in this example
const permission = user.permission;
// Check if the user permission matches any of the rules
let permissionGranted = false;
for (const ruleId of permissionList) {
if (acl.verify(permission, ruleId)) {
permissionGranted = true;
break;
}
}
// No match, then forbidden
if (!permissionGranted) {
return res.sendStatus(403);
}
// There is a match, we can let the user in
next();
};
}
// Actual endpoint. The second parameter is our guard as a middleware, where we can define the rules by their ID
app.get('/guardedEndpoint', permissionGuard(1, 2), (req, res) => {
res.send('Nice! You have permission to see this!');
});
app.listen(3000, () => {
console.log('app is listening...');
});
How to use as a fastify middleware
import jwt from 'jsonwebtoken';
import fastify from 'fastify';
import { Aclatraz } from 'aclatraz';
const app = fastify({
logger: true,
});
// Create the Aclatraz instance. Store the rules in redis/database etc.
export const acl = new Aclatraz([
{
id: 1,
slug: 'ADMIN',
},
{
id: 2,
slug: 'USER',
},
{
id: 3,
slug: 'READ_OTHER_USERS',
},
{
id: 4,
slug: 'CREATE_USER',
},
]);
// Create a guard which takes the permission from JWT
function permissionGuard(...permissionList) {
return (request, reply, done) => {
// Get the authorization header
const { authorization } = request.headers;
if (!authorization) {
return reply.status(401).send('Unauthorized');
}
// Get the token from the Bearer token
const [_, token] = authorization.split(' ');
// Decode the token
const user = jwt.decode(token, 'JWTSECRET');
// The Aclatraz permission token stored under user.permission in this example
const permission = user.permission;
// Check if the user permission matches any of the rules
let permissionGranted = false;
for (const ruleId of permissionList) {
if (acl.verify(permission, ruleId)) {
permissionGranted = true;
break;
}
}
// No match, then forbidden
if (!permissionGranted) {
return res.status(403).send({
status: 'error',
message: 'No permission granted for this endpoint',
});
}
// There is a match, we can let the user in
done();
};
}
// Actual endpoint. The second parameter is our guard as a middleware using onRequest hook, where we can define the rules by their ID
app.get(
'/guardedEndpoint',
{
onRequest: permissionGuard(1, 2),
},
(request, reply) => {
reply.send('Nice! You have permission to see this!');
}
);
app.listen(3000, (err) => {
if (err) {
app.log.error(err);
process.exit(1);
}
});
How to use as a NestJS guard
Soon...
Interfaces
interface AclRule {
id: number;
name?: string;
slug: string;
}
interface AclConfig {
chunkSize: number;
encoding: number;
padding: number;
paddingChar: string;
}
interface AclRuleTemplate {
[key: number]: {
slug: string;
name?: string;
};
}
Methods
constructor(aclRules?: AclRule[], options?: Partial<AclConfig>)
Create a new Aclatraz instance.
addRule(aclRule: AclRule): void;
Add a rule.
setRule(id: number, aclRule: Partial<AclRule>): void;
Update a rule by its ID.
delRule(id: number): void;
Delete a rule.
setOptions(aclConfig: Partial<AclConfig>): void;
Update the Aclatraz options.
getRules(): AclRule[];
List the rules.
verify(permission: string, ruleId: number): boolean;
Check if the given permission token has access to the rule.
generateAclCode(ruleIdList: number[]): string;
Generate the permission token from the given rules.
generateRuleTemplate(): string;
Soon...
grantPermission(currentPermission: string, ruleList: number[]): string;
Grant permission to an existing token. You can use this to create a new one, give empty string as currentPermission.
revokePermission(currentPermission: string, ruleList: number[]): string;
Revoke permission from an existing token.