All files / src/server/utils sanitize.utils.js

94.2% Statements 65/69
90.19% Branches 46/51
100% Functions 8/8
94.11% Lines 64/68

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 148 149 150 151 152 1536x 6x 6x 6x   6x   44x   4x 3x   2x 2x   1x 1x                 36x 36x           1x 1x   43x     6x   43x   3x 3x   1x 1x           38x 38x         1x 1x   43x     6x 30x 30x 30x 3x   30x           1x   30x 30x   30x     6x 228x 228x 314x 314x   228x                         6x 761x 30x 30x     30x 730x       30x 228x   228x     228x 1x         227x 227x 44x     1x       226x 2x             26x   26x       6x      
const sanitize = require('sanitize-html');
const errors = require('./error.utils');
const validator = require('validator');
const xss = require('xss');
 
let parseValue = function (type, value) {
  let result;
  switch (type) {
    case 'number':
      result = validator.toFloat(value);
      break;
    case 'date':
      result = validator.stripLow(xss(sanitize(value)));
      break;
    case 'boolean':
      result = validator.toBoolean(value);
      break;
    case 'string':
    case 'reference':
    case 'uri':
    case 'token':
      // strip any html tags from the query
      // xss helps prevent html from slipping in
      // strip a certain range of unicode characters
      // replace any non word characters
      result = validator.stripLow(xss(sanitize(value)));
      break;
    case 'json_string':
      result = JSON.parse(value);
      break;
    default:
      // Pass the value through, unknown types will fail when being validated
      result = value;
      break;
  }
  return result;
};
 
let validateType = function (type, value) {
  let result;
  switch (type) {
    case 'number':
      result = typeof value === 'number' && !Number.isNaN(value);
      break;
    case 'boolean':
      result = typeof value === 'boolean';
      break;
    case 'string':
    case 'reference':
    case 'uri':
    case 'token':
    case 'date':
      result = typeof value === 'string';
      break;
    case 'json_string':
      result = typeof value === 'object';
      break;
    default:
      result = false;
      break;
  }
  return result;
};
 
let parseParams = (req) => {
  let params = {};
  let isSearch = req.url && req.url.endsWith('_search');
  if (req.query && req.method === 'GET' && Object.keys(req.query).length) {
    Object.assign(params, req.query);
  }
  if (
    req.body &&
    ['PUT', 'POST'].includes(req.method) &&
    Object.keys(req.body).length &&
    isSearch
  ) {
    Object.assign(params, req.body);
  }
  Eif (req.params && Object.keys(req.params).length) {
    Object.assign(params, req.params);
  }
  return params;
};
 
let findMatchWithName = (name = '', params = {}) => {
  let keys = Object.getOwnPropertyNames(params);
  let match = keys.find((key) => {
    let parameter = key.split(':')[0];
    return name === parameter;
  });
  return { field: match, value: params[match] };
};
 
/**
 * @function sanitizeMiddleware
 * @summary Sanitize the arguments by removing extra arguments, escaping some, and
 * throwing errors if arg should throw when an invalid one is passed. This will replace
 * req.body and/or req.params with a clean object
 * @param {Array<Object>} config - Sanitize config for how to deal with params
 * @param {string} config.name - Argument name
 * @param {string} config.type - Argument type. Acceptable types are (boolean, string, number)
 * @param {boolean} required - Should we throw if this argument is present and invalid, default is false
 */
let sanitizeMiddleware = function (config) {
  return function (req, res, next) {
    let currentArgs = parseParams(req);
    let cleanArgs = {};
 
    // filter only ones with version or no version
    let version_specific_params = config.filter((param) => {
      return !param.versions || param.versions === req.params.base_version;
    });
 
    // Check each argument in the config
    for (let i = 0; i < version_specific_params.length; i++) {
      let conf = version_specific_params[i];
 
      let { field, value } = findMatchWithName(conf.name, currentArgs);
 
      // If the argument is required but not present
      if (!value && conf.required) {
        return next(errors.invalidParameter(conf.name + ' is required', req.params.base_version));
      }
 
      // Try to cast the type to the correct type, do this first so that if something
      // returns as NaN we can bail on it
      try {
        if (value) {
          cleanArgs[field] = parseValue(conf.type, value);
        }
      } catch (err) {
        return next(errors.invalidParameter(conf.name + ' is invalid', req.params.base_version));
      }
 
      // If we have the arg and the type is wrong, throw invalid arg
      if (cleanArgs[field] !== undefined && !validateType(conf.type, cleanArgs[field])) {
        return next(
          errors.invalidParameter('Invalid parameter: ' + conf.name, req.params.base_version)
        );
      }
    }
 
    // Save the cleaned arguments on the request for later use, we must only use these later on
    req.sanitized_args = cleanArgs;
 
    next();
  };
};
 
module.exports = {
  sanitizeMiddleware,
};