All files restrict-to-roles.js

91.3% Statements 42/46
90.48% Branches 38/42
100% Functions 3/3
91.11% Lines 41/45
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 1021x 1x 1x   1x             16x 16x 2x     14x 14x 1x       13x 1x     12x     1x     11x   11x 11x 11x 11x   11x 1x         10x 1x       9x 2x         9x       9x 4x       4x 1x           3x   3x 3x   3x       3x     3x       3x 2x     1x       5x 1x        
import errors from 'feathers-errors';
import isPlainObject from 'lodash.isplainobject';
import get from 'lodash.get';
 
const defaults = {
  fieldName: 'roles',
  idField: '_id',
  ownerField: 'userId',
  owner: false
};
 
export default function (options = {}) {
  if (!options.roles || !options.roles.length) {
    throw new Error(`You need to provide an array of 'roles' to check against.`);
  }
 
  return function (hook) {
    if (hook.type !== 'before') {
      throw new Error(`The 'restrictToRoles' hook should only be used as a 'before' hook.`);
    }
 
    // If it was an internal call then skip this hook
    if (!hook.params.provider) {
      return hook;
    }
 
    if (!hook.params.user) {
      // TODO (EK): Add a debugger call to remind the dev to check their hook chain
      // as they probably don't have the right hooks in the right order.
      throw new errors.NotAuthenticated();
    }
 
    options = Object.assign({}, defaults, hook.app.get('authentication'), options);
 
    let authorized = false;
    let roles = get(hook.params.user, options.fieldName);
    const id = get(hook.params.user, options.idField);
    const error = new errors.Forbidden('You do not have valid permissions to access this.');
 
    if (id === undefined) {
      throw new Error(`'${options.idField} is missing from current user.'`);
    }
 
    // If the user doesn't even have a `fieldName` field and we're not checking
    // to see if they own the requested resource return Forbidden error
    if (!options.owner && roles === undefined) {
      throw error;
    }
 
    // If the roles is not an array, normalize it
    if (!Array.isArray(roles)) {
      roles = [roles];
    }
 
    // Iterate through all the roles the user may have and check
    // to see if any one of them is in the list of permitted roles.
    authorized = roles.some(role => options.roles.indexOf(role) !== -1);
 
    // If we should allow users that own the resource and they don't already have
    // the permitted roles check to see if they are the owner of the requested resource
    if (options.owner && !authorized) {
      Iif (hook.id === null) {
        throw new errors.BadRequest('Can not verify roles when changing many resources.');
      }
 
      if (!hook.id) {
        throw new errors.MethodNotAllowed(`The 'restrictToRoles' hook should only be used on the 'get', 'update', 'patch' and 'remove' service methods if you are using the 'owner' field.`);
      }
 
      // look up the document and throw a Forbidden error if the user is not an owner
      // Set provider as undefined so we avoid an infinite loop if this hook is
      // set on the resource we are requesting.
      const params = Object.assign({}, hook.params, { provider: undefined });
 
      return hook.service.get(hook.id, params).then(data => {
        Iif (data.toJSON) {
          data = data.toJSON();
        } else Iif (data.toObject) {
          data = data.toObject();
        }
 
        let field = get(data, options.ownerField);
 
        // Handle nested Sequelize or Mongoose models
        Iif (isPlainObject(field)) {
          field = get(field, options.idField);
        }
 
        if (field === undefined || field.toString() !== id.toString()) {
          throw new errors.Forbidden('You do not have the permissions to access this.');
        }
 
        return hook;
      });
    }
 
    if (!authorized) {
      throw error;
    }
  };
}