@inspire-platform/sails-hook-permissions
Version:
Comprehensive user permissions and entitlements system for sails.js and Waterline. Supports user authentication with passport.js, role-based permissioning, object ownership, and row-level security.
280 lines (231 loc) • 7.63 kB
JavaScript
let _ = require('lodash');
let helpers = {
/**
* Return all tracked models.
*
* @param {Object} criteria
* @param {Array} criteria.select Fields to select, defaults to: id, name, identity.
* @return {Promise}
*/
findModels: function findModels(criteria = {}) {
if (false === 'select' in criteria) {
criteria.select = ['id','name','identity'];
}
// find models using criteria
return Model.find(criteria);
},
/**
* Lookup all permissions granted to one user (user, and role relations).
*
* @param {Object} user User object
* @param {Object} options
* @param {String} options.model
* @param {String} options.action
* @param {Array} options.populate
* @returns {Promise}
*/
findUserPermissions: function findUserPermissions(user = {}, options = {}) {
// find user
let findOneUser = User.findOne(user.id);
// populate roles and permissions
findOneUser.populate('roles', {active: true});
// exec
return findOneUser.then((user) => {
// find all permissions assigned directly to user, or via role
let permCriteria = {
or: [
{
user: user.id
}
]
};
// push every role onto `or`
_.forEach(user.roles, function(role){
permCriteria.or.push({role: role.id});
});
// specific model and/or action id?
['model', 'action'].forEach((property) => {
if (
true === property in options &&
(
true === _.isFinite(options[property]) ||
(
true === _.isString(options[property]) &&
1 <= options[property].length
)
)
) {
// yep, set it
permCriteria[property] = options[property];
}
});
// specific model (object)?
if (true === _.has(options, ['model', 'id'])) {
permCriteria.model = options.model.id;
}
// init perm find promise
let findPerm = Permission.find(permCriteria);
// populate any permissions associations?
if (
true === 'populate' in options &&
true === Array.isArray(options.populate) &&
1 <= options.populate.length
) {
// yes, loop them
options.populate.forEach((assoc) => {
// is object?
if (true === _.isPlainObject(assoc)) {
// yes, has criteria?
if ('criteria' in assoc) {
// yes, use it!
findPerm.populate(assoc.model, assoc.criteria);
} else {
// no criteria, just model
findPerm.populate(assoc.model);
}
} else {
// nothing fancy
findPerm.populate(assoc);
}
});
}
// finally return promise
return findPerm.then((permissions) => {
// options for applying base permissions
let baseOpts = {
model: permCriteria.model,
action: permCriteria.action
};
// apply base permissions
helpers.applyUserBasePermissions(user, permissions, baseOpts);
// all done
return permissions;
});
})
},
/**
* Apply all configured base permissions to array of permissions.
*
* This method mutates the permissions array (append only).
*
* @param {Object} user User object
* @param {Array} permissions Array of permissions (mutated)
* @param {Object} options
* @param {String} options.model
* @param {String} options.action
*/
applyUserBasePermissions: function (user, permissions, options) {
// might need to inject some base perms
if (_.has(sails.config, 'permissions.basePermissions')) {
// copy ref to save typing ;)
let basePerms = sails.config.permissions.basePermissions;
// loop each base type grouping
['self', 'global'].forEach((baseType) => {
// have some permissions?
if (baseType in basePerms && _.isArray(basePerms[baseType])) {
// yep, let's go
let basePermsType = basePerms[baseType];
// loop all of them
basePermsType.forEach((permOfType) => {
// must be an object
if (
true === _.isPlainObject(permOfType) &&
true === 'model' in permOfType &&
true === 'action' in permOfType
) {
let modelId = sails.hooks.permissions._modelCache[permOfType.model].id;
// specific model and/or action?
if (
(
true === 'model' in options &&
options.model !== modelId
) ||
(
true === 'action' in options &&
options.action !== permOfType.action
)
) {
// skip to next permission
return;
};
// forge permission
let basePermission = {
_isBase: true,
model: modelId,
action: permOfType.action,
relation: 'user',
user: user.id,
criteria: [],
objectFilters: []
};
// handle criteria and/or object filters
['criteria', 'objectFilters'].forEach((prop) => {
if (
true === prop in permOfType &&
true === _.isArray(permOfType[prop])
) {
basePermission[prop] = permOfType[prop];
}
});
// is self type?
if ('self' === baseType) {
// yes, add object filter for user id
basePermission.objectFilters.push({
objectId: user.id
});
}
// append base permission
permissions.push(basePermission);
}
});
}
});
}
},
/**
* Lookup all features granted to one user (user, and role relations).
*
* @param {Object} user User object
* @param {Object} options
* @returns {Promise}
*/
findUserFeatures: function findUserFeatures(user = {}, options = {}) {
// find user
let findOneUser = User.findOne(user.id);
// populate user roles and features
findOneUser
.populate('roles', {active: true})
.populate('features', {active: true});
// exec
return findOneUser.then((user) => {
// user belongs to any roles?
if (user.roles.length >= 1) {
// find all features assigned to user via role.
// this requires a second role query with features populated.
let extRoleCriteria = {
or: []
};
// push every role onto `or`
_.forEach(user.roles, function (role) {
extRoleCriteria.or.push({id: role.id});
});
// init roles + features find promise
let findExtRoles = Role.find(extRoleCriteria).populate('features', {active: true});
// try to find role features
return findExtRoles.then((roles) => {
// need one flat array (including directly assigned)
let features = Array.prototype.concat(
user.features,
_.flatten(_.map(roles, (role) => role.features))
);
// unique by id
return _.uniq(features, 'id');
});
} else {
// no roles, return only directly assigned user features
return user.features;
}
});
}
};
module.exports = helpers;