express-dynacl
Advanced tools
Comparing version 1.0.0 to 2.0.0
169
index.js
@@ -0,106 +1,127 @@ | ||
var dynacl = (function(){ | ||
// initiate variable to store options | ||
var options = {}; | ||
// initiate variable to store options | ||
var options = {}; | ||
// default options to be overriden by dynacl.config | ||
const defaultOptions = { | ||
roles: {}, | ||
// function to evaluate single permission | ||
function evalPermission(permission,req){ | ||
userRoles: req => req.user ? req.user.roles : [], | ||
// if permission is a function, then evaluate its return value | ||
if(typeof permission == 'function') return (permission(req) === true); | ||
defaultRole: "guest", | ||
// if permission is boolean true, then evaluate the value | ||
else if (permission === true) return true; | ||
logString: (action,permission,role,req) => "DynACL " + (permission ? "OK" : "XX") + " ( action: " + action + (role ? ", role: " + role : "") + " )", | ||
logConsole: false, | ||
// if permission unspecified or misspecified, return false | ||
else return false; | ||
} | ||
authorized: (req,res,next) => next(), | ||
unauthorized: (req,res,next) => res.sendStatus(401) | ||
} | ||
// function to get user roles and evaluate permissions | ||
async function checkCan(action,req,params){ | ||
// function to get user roles and evaluate permissions | ||
function evalACL(resource,operation,req){ | ||
// get user roles | ||
var userRoles = options.userRoles(req) | ||
// get simple reference to acl roles | ||
var aclRoles = options.roles || {}; | ||
// clear the user roles array | ||
var currentRoles = []; | ||
// add default role | ||
if(options.defaultRole) userRoles.push(options.defaultRole); | ||
// throw an error on invalid user roles | ||
var rolesProperty = options.rolesProperty || "roles"; | ||
var userRoles = req.user && req.user[rolesProperty] ? req.user[rolesProperty] : []; | ||
// add default roles | ||
if(options.defaultRoles) userRoles = userRoles.concat(options.defaultRoles); | ||
// add default user roles | ||
if(options.userRoles) userRoles = userRoles.concat(options.userRoles); | ||
// default is no permission | ||
var permission = false; | ||
// go through all roles and check if some has permission | ||
var roleName; | ||
while(!!(roleName = userRoles.shift())){ | ||
// if strict roles property is set to true, then nonexistent roles will throw error | ||
if(options.strictRoles){ | ||
if(userRoles.some(role => !aclRoles[role])) throw new Error("Invalid role: " + role); | ||
} | ||
userRoles.push("dsaads"); | ||
// wait for the result | ||
let result = await checkRoleCan(roleName,action,req,params); | ||
// if permitted, save and stop going through the roles | ||
if(result === true) { | ||
permission = true; | ||
break; | ||
} | ||
// set user roles | ||
userRoles | ||
.filter(role => aclRoles[role]) // filter out invalid roles | ||
.forEach(role => currentRoles.push(aclRoles[role])); // assign roles to currentRoles array | ||
} | ||
// go through all roles and check if some has permission, otherwise return false | ||
return currentRoles.some(role => { | ||
// log permission check | ||
if(options.logConsole){ | ||
let logString = options.logString(action,permission,roleName,req); | ||
if(permission) console.log(logString); | ||
else console.error(logString); | ||
} | ||
// in case we have set permission for resource and action | ||
if(role[resource] && role[resource][operation]) return evalPermission(role[resource][operation],req); | ||
// return the permission | ||
return permission; | ||
} | ||
// in case we have set permission for resource and default action | ||
else if(role[resource] && role[resource]["*"]) return evalPermission(role[resource]["*"],req); | ||
async function checkRoleCan(roleName,action,req,params){ | ||
// in case we have set default permission | ||
else if(role["*"]) return evalPermission(role["*"],req); | ||
// get the role details | ||
let role = options.roles[roleName]; | ||
// if role does not exists user can't | ||
if(!role) return false; | ||
// in case we have admin role, we dont have to check anything | ||
if(role.admin) return true; | ||
// if nothing is set, user does not have permission | ||
else return false; | ||
}); | ||
} | ||
// in case we have set permission for resource and action | ||
if(role.can[action]){ | ||
let permission = role.can[action]; | ||
// if permission is a function, then evaluate its return value, otherwise evaluate the permission, | ||
// both Promise and static value will be resolved | ||
let result = await Promise.resolve(typeof permission === 'function' ? permission(req,params) : permission); | ||
if(result) return true; | ||
} | ||
// in case the role inherits, we check the parent role | ||
if(role.inherits){ | ||
// middleware factory | ||
function dynacl(resource,operation){ | ||
let result = await checkRoleCan(options.roles[role.inherits],action,req,params); | ||
// return middleware function for ExpressJS | ||
return function(req,res,next){ | ||
if(result) return true; | ||
} | ||
// evaluate permission | ||
var result = evalACL(resource,operation,req); | ||
return false; | ||
} | ||
// log access | ||
var logString = "ACL " + (result ? "OK" : "XX") + ": " + resource + "/" + operation + (req.user ? " (user: " + req.user._id + "; roles: " + (req.user.roles ? req.user.roles.join(",") : "") + ")" : " (guest)"); | ||
if(options.logConsole) console.log(logString); | ||
//TODO: logFile | ||
// if permission granted, send execution to the next middleware/route | ||
if(result) next(); | ||
// middleware factory | ||
function dynacl(action){ | ||
// if permission not granted, then end request with 401 Unauthorized | ||
else res.status(401).send("Unauthorized (" + (req.user ? "logged, no authorization" : "not logged") + ")"); | ||
// return middleware function for ExpressJS | ||
return async function(req,res,next){ | ||
} | ||
} | ||
// evaluate permission | ||
var result = await checkCan(action,req,{}); | ||
// function to configure DynACL | ||
dynacl.config = function(setOptions){ | ||
// assign configurations | ||
options = setOptions; | ||
} | ||
// if permission granted, send execution to the next middleware/route | ||
if(result) options.authorized(req,res,next); | ||
// just check the permission without using as middleware | ||
dynacl.check = evalACL; | ||
// if permission not granted, then end request with 401 Unauthorized | ||
else options.unauthorized(req,res,next) | ||
} | ||
} | ||
// function to configure DynACL | ||
dynacl.config = function(userOptions){ | ||
// assign configurations | ||
options = Object.assign({},defaultOptions,userOptions); | ||
} | ||
// just check the permission without using as middleware | ||
dynacl.can = (action,req,params) => checkCan(action,req,params || {}); | ||
return dynacl; | ||
})(); | ||
module.exports = dynacl; |
{ | ||
"name": "express-dynacl", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "Express dynamic access control list, that allows to grant access to queries based on request details", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -10,56 +10,41 @@ # express-dynacl | ||
var guest = { | ||
"nonalcoholic": { | ||
// you can use a boolean as in standard ACL roles, default is false | ||
"watch": true, | ||
"drink": true | ||
}, | ||
"alcoholic": { | ||
"watch": true | ||
} | ||
}; | ||
var options = { | ||
var guestWithId = { | ||
"alcoholic": { | ||
// or you can use a function of request | ||
"drink": function(req){ | ||
return isEligibleToDrink(req) // check if over 18/21/... | ||
roles: { | ||
"guest": { | ||
can: { | ||
"posts:list": true, | ||
"posts:edit": false | ||
} | ||
}, | ||
"user": { | ||
can: { | ||
"posts:create": true, | ||
"posts:edit": (req,params) => { | ||
return Post.find({_id:params.post.id}).then(post => post.owner === req.user.id); | ||
} | ||
}, | ||
inherits: "guest" | ||
}, | ||
"moderator":{ | ||
can: { | ||
"posts:edit": true | ||
}, | ||
inherits: "user" | ||
}, | ||
"admin: { | ||
admin: true | ||
} | ||
} | ||
}; | ||
var barowner = { | ||
"*": true // give admin role | ||
}; | ||
module.exports = { | ||
"guest": guest, | ||
"guestWithId": guestWithId, | ||
"barowner": barowner | ||
} | ||
``` | ||
Import and configure the middleware: | ||
```js | ||
var acl = require("express-dynacl"); | ||
// configure DynACL | ||
var aclOptions = { | ||
// load roles (default is no roles) | ||
roles: { | ||
"guest": require("./roles").guest, | ||
"guestWithId": require(".roles").guestWithId, | ||
"barowner": require(".roles").barowner | ||
}, | ||
}, | ||
userRoles: req => req.user ? req.user.roles : [], // get user roles | ||
// set some of the roles as default - each request will expect that user has these roles (default is none) | ||
defaultRoles: ["guest"], | ||
defaultRole: "guest", | ||
// enable logging to console (default is false) | ||
logConsole: true, | ||
logString: (role,action,result,req) => "DynACL " + (result ? "OK" : "XX") + " ( action: " + action + (result ? ", role: " + role : "") + " )", // log output string | ||
logConsole: true, // enable logging to console (default is false) | ||
// set the req.user property where roles are stored (default is req.user.roles) | ||
rolesProperty: "roles" | ||
authorized: (req,res,next) => next(), // middleware to use when authorized (default is send to next middleware) | ||
unauthorized: (req,res,next) => res.sendStatus(401) // middleware to use when unauthorized (default is to respond with 401 | ||
} | ||
@@ -66,0 +51,0 @@ |
6421
84
100