All files has-role-or-restrict.js

75% Statements 54/72
71.88% Branches 46/64
62.5% Functions 5/8
74.65% Lines 53/71
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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 1481x 1x 1x 1x   1x             18x 18x 2x     16x 16x 1x     15x         15x 1x     14x       14x     14x   14x 11x 11x 11x         14x   14x 4x       4x 4x       4x 4x             10x 10x 10x 10x   10x 1x         9x 1x       8x 2x         8x       8x 4x 1x       3x     3x   3x 3x   3x       3x     3x       3x 2x     3x         4x                                      
import errors from 'feathers-errors';
import isPlainObject from 'lodash.isplainobject';
import get from 'lodash.get';
import set from 'lodash.set';
 
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 'hasRoleOrRestrict' hook should only be used as a 'before' hook.`);
    }
 
    Iif (hook.method !== 'find' && hook.method !== 'get') {
      throw new Error(`'hasRoleOrRestrict' should only be used in a find or get hook.`);
    }
 
    // If it was an internal call then skip this hook
    if (!hook.params.provider) {
      return hook;
    }
 
    Iif (hook.result) {
      return hook;
    }
 
    options = Object.assign({}, defaults, hook.app.get('authentication'), options);
 
    // If we don't have a user we have to always use find instead of get because we must not return id queries that are unrestricted and we don't want the developer to have to add after hooks.
    let query = Object.assign({}, hook.params.query, options.restrict);
 
    if (hook.id !== null && hook.id !== undefined) {
      const id = {};
      set(id, options.idField, hook.id);
      query = Object.assign(query, id);
    }
 
    // 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 });
 
    if (!hook.params.user) {
      Iif (hook.result) {
        return hook;
      }
 
      return hook.service.find({ query }, params).then(results => {
        Iif (hook.method === 'get' && Array.isArray(results) && results.length === 1) {
          hook.result = results[0];
          return hook;
        } else {
          hook.result = results;
          return hook;
        }
      }).catch(() => {
        throw new errors.NotFound(`No record found`);
      });
    }
 
    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) {
      if (!hook.id) {
        throw new errors.MethodNotAllowed(`The 'hasRoleOrRestrict' 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
      return new Promise((resolve, reject) => {
        // 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 });
 
        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 = field[options.idField];
          }
 
          if (field === undefined || field.toString() !== id.toString()) {
            reject(new errors.Forbidden('You do not have the permissions to access this.'));
          }
 
          resolve(hook);
        }).catch(reject);
      });
    }
 
    Iif (!authorized) {
      if (hook.result) {
        return hook;
      }
 
      return hook.service.find({ query }, params).then(results => {
        if (hook.method === 'get' && Array.isArray(results) && results.length === 1) {
          hook.result = results[0];
          return hook;
        } else {
          hook.result = results;
          return hook;
        }
      }).catch(() => {
        throw new errors.NotFound(`No record found`);
      });
    }
  };
}