imicros-acl
Version:
Moleculer service for access control
158 lines (137 loc) • 7.09 kB
JavaScript
/**
* @license MIT, imicros.de (c) 2019 Andreas Leinen
*/
;
const _ = require("lodash");
module.exports = ({service}) => {
/*
function getOwnerId({ctx, abort = false}) {
let ownerId = _.get(ctx.meta,"acl.ownerId",null);
if (!ownerId && abort) throw new Error("access not authorized");
return ownerId;
}
*/
function getEnvironment(ctx) {
let timestamp = Date.now();
return {
timestamp: timestamp,
date: new Date(timestamp).toLocaleDateString(),
day: new Date(timestamp).getDay(),
time: new Date(timestamp).toLocaleTimeString(),
action: ctx.action,
meta: ctx.meta
};
}
/**
* isAuthorized
*/
function isAuthorized( { ctx = null, result = {}, abort = false } = {} ) {
let acl = _.get(ctx,"meta.acl",null);
let user = _.get(ctx,"meta.user",null);
let { meta: { service = null } } = ctx;
let environment = getEnvironment(ctx);
// not authenticated
if (!user && !service) {
ctx.broker.logger.debug("not authorized access", { user, service, action: ctx.action } );
if (abort) throw new Error("access not authorized (not authenticated)");
return false;
}
// no access at all
if (!acl) {
ctx.broker.logger.debug("not authorized access", { user, service, action: ctx.action } );
if (abort) throw new Error("access not authorized");
return false;
}
// unrestricted access
if ( acl.unrestricted ) {
return true;
// restricted access - check grant
} else if (acl.restricted) {
if ( !acl.grants || !Array.isArray(acl.grants) ) {
ctx.broker.logger.debug("restricted access - but no grants set");
if (abort) throw new Error("access not authorized");
return false;
}
ctx.broker.logger.debug("restricted access - check grants", { count: acl.grants.length });
for (let i=0; i<acl.grants.length; i++ ) {
ctx.broker.logger.debug("check grant", { id: acl.grants[i].id, user: user, action: ctx.action.name, params: ctx.params, result: result, environment: environment });
// call grant function with { user, action, params, result, environment }
let check = acl.grants[i].function({user: user, action: ctx.action.name, params: ctx.params, result: result, environment: environment });
ctx.broker.logger.debug("check grant result", { result: check });
if (check.acl.result === "allow") {
ctx.broker.logger.debug("access granted",{ rule: check.acl.rule });
return true;
}
}
ctx.broker.logger.debug("restricted access - not granted");
if (abort) throw new Error("access not authorized");
return false;
}
// access token not verified - no access
if (abort) throw new Error("access not authorized");
return false;
}
/**
* Expose middleware
*/
return {
// wrap local action - call acl
localAction(next, action) {
return async function(ctx) {
// get acl
if (action.acl) {
if (ctx.meta.acl && ( !ctx.meta.acl.nodeID || ctx.meta.acl.nodeID !== ctx.broker.nodeID )) {
let token = ctx.meta.acl.accessToken;
ctx.meta.acl = null;
if (token) {
let params = { token: token }, opt = { meta: ctx.meta };
try {
ctx.broker.logger.debug("call acl service to verify token", { params: params , meta: opt.meta });
let result = await ctx.call(service+".verify",params,opt);
if (result.acl && result.acl.grants) {
ctx.broker.logger.debug("convert grant functions received", { count: result.acl.grants.length});
for (let i=0; i<result.acl.grants.length; i++) {
result.acl.grants[i].function = new Function(result.acl.grants[i].function)();
if (result.acl.grants[i].function && {}.toString.call(result.acl.grants[i].function) !== "[object Function]") {
ctx.broker.logger.error("unvalid grant function received");
throw new Error("unvalid ruleset");
}
}
}
ctx.meta.acl = result.acl;
ctx.meta.acl.accessToken = token;
ctx.meta.acl.nodeID = ctx.broker.nodeID;
ctx.broker.logger.debug("access token verified and acl data set", { acl: ctx.meta.acl });
} catch (err) {
ctx.broker.logger.debug("Failed to verify access token");
}
}
}
}
if (action.acl == "core" && (!ctx.meta.acl || ctx.meta.acl.core !== true)) {
ctx.broker.logger.debug("access to core function rejected", { acl: ctx.meta.acl, action });
throw new Error("access not authorized");
}
// short path for usage in services
ctx.meta.ownerId = _.get(ctx.meta,"acl.ownerId", null);
// acl check before call based on action and parameters or after call based on the result
ctx.broker.logger.debug("call wrapped action", { action: action.name, acl: action.acl });
if (action.acl === "before" || action.acl === "always" || action.acl === true ) {
isAuthorized( { ctx: ctx, action: action.name, params: ctx.params, abort: true } );
}
if (action.acl === "after" || action.acl === "always") {
const res = await next(ctx);
isAuthorized( { ctx: ctx, action: action.name, params: ctx.params, result: res, abort: true } );
return res;
} else {
return next(ctx);
}
};
},
// After broker started
async started(broker) {
// wait for acl service
await broker.waitForServices([service]);
}
};
};