UNPKG

waigo

Version:

Node.js ES6 framework for reactive, data-driven apps and APIs (Koa, RethinkDB)

179 lines (129 loc) 3.71 kB
"use strict"; const co = require('co'); const waigo = global.waigo, _ = waigo._, logger = waigo.load('support/logger'), errors = waigo.load('support/errors'); const AclError = exports.AclError = errors.define('AclError'); class ACL { constructor (App) { this.App = App; this.logger = logger.create('ACL'); } /** * Initialise ACL */ * startup () { this.logger.info('Initialising'); yield this.reload(); // access to admin content must be protected if (!this.res.admin) { this.logger.info('Admin resources rules not found, so creating them now'); yield this.App.models.Acl.insert({ resource: 'admin', entityType: 'role', entity: 'admin' }); yield this.reload(); } // get notified of ACL updates this._changeFeedCursor = yield this.App.models.Acl.onChange(); if (this._changeFeedCursor) { this._changeFeedCursor.each(_.bind(this._onAclUpdated, this)); } } * shutdown () { if (this._changeFeedCursor) { yield this._changeFeedCursor.close(); this._changeFeedCursor = null; } } /** * Reload ACL rules from DB. */ * reload () { this.logger.debug('Reloading rules from db'); let data = yield this.App.models.Acl.getAll(); let res = this.res = {}, users = this.users = {}, roles = this.roles = {}; data.forEach(function(doc){ // resource perspective res[doc.resource] = res[doc.resource] || {}; res[doc.resource][doc.entityType] = res[doc.resource][doc.entityType] || {}; res[doc.resource][doc.entityType][doc.entity] = true; // entity perspsective let entity = ('user' === doc.entityType ? users : roles); entity[doc.entity] = entity[doc.entity] || {}; entity[doc.entity][doc.resource] = true; }); } /** * Callback for collection watcher. */ _onAclUpdated () { this.logger.info('Detected ACL rules change...reloading'); co(this.reload()) .catch((err) => { this.logger.error('Error reloading ACL', err.stack); }); } /** * Get whether given user can access given resource. * @param {String} resource Resource name. * @param {Object} user User object. * @return {Boolean} true if allowed; false otherwise. */ can (resource, user) { this.logger.debug('can', resource, user.id); // if resource name is "public" then everyone has access if ('public' === resource) { return true; } // if user is admin it's ok if (user.isOneOf('admin')) { return true; } // if no entry for resource then everyone has access if (!_.get(this.res, resource)) { return false; } // if user has access it's ok if (_.get(this.users, user.id + '.' + resource)) { return true; } // if one of user's roles has access it's ok let roles = user.roles || []; for (let role of roles) { if (_.get(this.roles, role + '.' + resource)) { return true; } } return false; } /** * Assert that given user can access given resource. * @param {String} resource Resource name. * @param {Object} user User object. * @throws AclError if access disallowed. */ assert (resource, user) { this.logger.debug('assert', resource, user.id); if (!this.can(resource, user)) { throw new AclError(`User ${user.id} does not have permission to access: ${resource}`, 403); } } } exports.ACL = ACL; /** * Initialise ACL * * @param {App} App The App instance. */ exports.init = function*(App) { var a = new ACL(App); yield a.startup(); return a; };