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.
264 lines (257 loc) • 8.24 kB
JavaScript
'use strict';
function _extends() {
_extends = Object.assign || function assign(target) {
for(var i = 1; i < arguments.length; i++){
var source = arguments[i];
for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends.apply(this, arguments);
}
class RabGrant {
columns(columns) {
this._columns = columns;
return this;
}
lookupFilters(filters) {
this._lookupFilters = filters;
return this;
}
filters(filters) {
this._filters = filters;
return this;
}
ifEqual(fieldOne, fieldTwo) {
if (!fieldOne || !fieldTwo) {
throw new Error('Rab:ifEqual requires at least two arguments');
}
this._checks = this._checks || [];
this._checks.push({
operation: 'ifEqual',
fields: [
fieldOne,
fieldTwo
]
});
return this;
}
ifContains(fieldOne, fieldTwo) {
if (!fieldOne || !fieldTwo) {
throw new Error('Rab:ifContains requires at two arguments');
}
this._checks = this._checks || [];
this._checks.push({
operation: 'ifContains',
fields: [
fieldOne,
fieldTwo
]
});
return this;
}
validator(method) {
this._checks = this._checks || [];
this._checks.push({
operation: 'validator',
method
});
return this;
}
extend(role, permission) {
this._extends = {
permission,
role
};
return this;
}
get json() {
return {
role: this.role,
lookupFilters: this._lookupFilters,
columns: this._columns,
filters: this._filters,
check: this._checks,
extends: this._extends
};
}
get getColumns() {
return this._columns;
}
get extendsConfig() {
return this._extends;
}
constructor(role){
this.role = role;
}
}
function getValueFromPath(object, path) {
let result = object;
if (path.length == 0) return undefined;
for (const key of path){
if (result == null || !result[key]) {
return undefined;
}
result = result[key];
}
return result;
}
const resolvePath = (configObject, payload)=>{
const result = {};
if (!configObject) return result;
for(const key in configObject){
if (payload) {
const value = getValueFromPath(payload, configObject[key]);
if (value !== undefined) {
result[key] = value;
}
}
}
return result;
};
function mergeArray(firstElement, secondElement) {
const merged = [
...firstElement != null ? firstElement : [],
...secondElement != null ? secondElement : []
];
return merged.length > 0 ? merged : undefined;
}
function mergeObject(firstElement, secondElement, fallback) {
const merged = _extends({}, firstElement, secondElement);
return Object.keys(merged).length > 0 ? merged : undefined;
}
const evaluatePermission = async (options)=>{
const { request, grant, validations } = options;
const getCheckOperands = (check)=>{
return resolvePath({
fieldOne: check.fields[0],
fieldTwo: check.fields[1]
}, request);
};
if (grant == null ? void 0 : grant.check) {
for (const check of grant.check){
switch(check.operation){
case 'ifEqual':
{
const operands = getCheckOperands(check);
if (operands.fieldOne !== operands.fieldTwo) {
return {
isAuthorized: false
};
}
break;
}
case 'ifContains':
{
const operands = getCheckOperands(check);
if (Array.isArray(operands.fieldTwo) && operands.fieldTwo.includes(operands.fieldOne)) {
break;
}
return {
isAuthorized: false
};
}
case 'validator':
{
const method = check == null ? void 0 : check.method;
const funCall = method ? validations == null ? void 0 : validations[method] : undefined;
if (funCall) {
await funCall(request);
} else {
console.error('Missing Validation function function', method);
return {
isAuthorized: false
};
}
break;
}
default:
return {
isAuthorized: false
};
}
}
}
return {
isAuthorized: true,
fields: grant.columns,
filters: resolvePath(grant == null ? void 0 : grant.filters, request),
lookupFilters: (grant == null ? void 0 : grant.lookupFilters) ? Object.keys(grant.lookupFilters).reduce((acc, key)=>_extends({}, acc, {
[key]: resolvePath(grant.lookupFilters[key], request)
}), {}) : undefined
};
};
class Rab {
get permissions() {
return this.permissionMap;
}
static schema(config) {
return new Rab(config);
}
static grant(role) {
return new RabGrant(role);
}
static auth(path) {
return [
'auth',
...Array.isArray(path) ? path : [
path
]
];
}
static params(path) {
return [
'params',
...Array.isArray(path) ? path : [
path
]
];
}
static query(path) {
return [
'query',
...Array.isArray(path) ? path : [
path
]
];
}
resolvePermissions(permission, role) {
var _mergeArray;
const grants = this.permissionMap[permission] || [];
const grandInstance = grants.find((el)=>el.role == role);
if (!grandInstance) throw new Error('Rab: permission not found for role: ' + role + ', permission:' + permission);
const currentGrant = grandInstance.json;
const extendedGrand = currentGrant.extends ? this.resolvePermissions(currentGrant.extends.permission, currentGrant.extends.role) : undefined;
const columns = (_mergeArray = mergeArray(currentGrant.columns)) == null ? void 0 : _mergeArray.map((el)=>{
if (typeof el == 'string') return el;
return el.field;
});
return _extends({}, currentGrant, {
columns: mergeArray(columns, extendedGrand == null ? void 0 : extendedGrand.columns),
check: mergeArray(currentGrant.check, extendedGrand == null ? void 0 : extendedGrand.check),
filters: mergeObject(extendedGrand == null ? void 0 : extendedGrand.filters, currentGrant.filters),
lookupFilters: mergeObject(extendedGrand == null ? void 0 : extendedGrand.lookupFilters, currentGrant.lookupFilters)
});
}
async getGrant(options) {
const { permission, request, validations, role } = options;
try {
const grant = this.resolvePermissions(permission, role);
return await evaluatePermission({
grant,
validations,
request
});
} catch (e) {
console.error(e);
return {
isAuthorized: false
};
}
}
constructor(config){
this.permissionMap = {};
this.permissionMap = config;
}
}
exports.Rab = Rab;