express_validator.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. var validator = require('validator');
  2. var _ = require('lodash');
  3. var utils = require('./utils');
  4. const { checkSchema, validationResult } = require('../check');
  5. const chainCreator = require('./chain-creator');
  6. const getCustomMethods = require('./custom-methods');
  7. const contextRunner = require('./legacy-runner');
  8. // validators and sanitizers not prefixed with is/to
  9. var additionalSanitizers = ['trim', 'ltrim', 'rtrim', 'escape', 'unescape', 'stripLow', 'whitelist', 'blacklist', 'normalizeEmail'];
  10. var allLocations = ['params', 'query', 'body', 'headers', 'cookies'];
  11. /**
  12. * Adds validation methods to request object via express middleware
  13. *
  14. * @method expressValidator
  15. * @param {object} options
  16. * @return {function} middleware
  17. */
  18. var expressValidator = function(options) {
  19. options = options || {};
  20. var defaults = {
  21. customValidators: {},
  22. customSanitizers: {},
  23. errorFormatter: function(param, msg, value, location) {
  24. return {
  25. location,
  26. param: param,
  27. msg: msg,
  28. value: value
  29. };
  30. }
  31. };
  32. _.defaults(options, defaults);
  33. /**
  34. * Initializes a sanitizer
  35. *
  36. * @class
  37. * @param {(string|string[])} param path to property to sanitize
  38. * @param {[type]} req request to sanitize
  39. * @param {[string]} locations request property to find value
  40. */
  41. function Sanitizer(param, req, locations) {
  42. this.values = locations.map(function(location) {
  43. return _.get(req[location], param);
  44. });
  45. this.req = req;
  46. this.param = param;
  47. this.locations = locations;
  48. utils.mapAndExtend(
  49. getCustomMethods(req, options).sanitizers,
  50. Sanitizer.prototype,
  51. utils.makeSanitizer
  52. );
  53. return this;
  54. }
  55. /**
  56. * validate an object using a schema, using following format:
  57. *
  58. * {
  59. * paramName: {
  60. * validatorName: true,
  61. * validator2Name: true
  62. * }
  63. * }
  64. *
  65. * Pass options or a custom error message:
  66. *
  67. * {
  68. * paramName: {
  69. * validatorName: {
  70. * options: ['', ''],
  71. * errorMessage: 'An Error Message'
  72. * }
  73. * }
  74. * }
  75. *
  76. * @method validateSchema
  77. * @param {Object} schema schema of validations
  78. * @param {Request} req request to attach validation errors
  79. * @param {string} loc request property to find value (body, params, query, etc.)
  80. * @return {object[]} array of errors
  81. */
  82. function validateSchema(schema, req, loc, contexts) {
  83. checkSchema(schema, loc, (field, locations, message) => chainCreator(
  84. field,
  85. locations,
  86. message,
  87. contexts,
  88. getCustomMethods(req, options)
  89. ));
  90. }
  91. /**
  92. * Error formatter delegator to the legacy format
  93. * @param {*} error
  94. */
  95. function errorFormatter({ param, msg, value, location }) {
  96. return options.errorFormatter(param, msg, value, location);
  97. }
  98. // _.set sanitizers as prototype methods on corresponding chains
  99. _.forEach(validator, function(method, methodName) {
  100. if (methodName.match(/^to/) || _.includes(additionalSanitizers, methodName)) {
  101. Sanitizer.prototype[methodName] = utils.makeSanitizer(methodName, validator);
  102. }
  103. });
  104. utils.mapAndExtend(options.customSanitizers, Sanitizer.prototype, utils.makeSanitizer);
  105. return function(req, res, next) {
  106. const contexts = [];
  107. const runContexts = () => contextRunner(contexts, req);
  108. var locations = ['body', 'params', 'query', 'cookies'];
  109. // Extend existing validators. Fixes bug #341
  110. const customMethods = getCustomMethods(req, options);
  111. req._validationErrors = [];
  112. req._asyncValidationErrors = [];
  113. req.validationErrors = function(mapped) {
  114. runContexts();
  115. var result = validationResult(req).formatWith(errorFormatter);
  116. if (result.isEmpty()) {
  117. return false;
  118. }
  119. return mapped ? result.mapped() : result.array();
  120. };
  121. req.asyncValidationErrors = function(mapped) {
  122. runContexts();
  123. return Promise.all(req._asyncValidationErrors).then(() => {
  124. if (req._validationErrors.length > 0) {
  125. return Promise.reject(req.validationErrors(mapped, true));
  126. }
  127. return Promise.resolve();
  128. });
  129. };
  130. req.getValidationResult = function() {
  131. runContexts();
  132. return Promise.all(req._asyncValidationErrors).then(() => {
  133. return validationResult(req).formatWith(errorFormatter);
  134. });
  135. };
  136. locations.forEach(function(location) {
  137. /**
  138. * @name req.sanitizeQuery
  139. * @see sanitize
  140. * @param param
  141. */
  142. /**
  143. * @name req.sanitizeParams
  144. * @see sanitize
  145. * @param param
  146. */
  147. /**
  148. * @name req.sanitizeBody
  149. * @see sanitize
  150. * @param param
  151. */
  152. req['sanitize' + _.capitalize(location)] = function(param) {
  153. return new Sanitizer(param, req, [location]);
  154. };
  155. });
  156. req.sanitizeHeaders = function(param) {
  157. if (param === 'referrer') {
  158. param = 'referer';
  159. }
  160. return new Sanitizer(param.toLowerCase(), req, ['headers']);
  161. };
  162. req.sanitize = function(param) {
  163. return new Sanitizer(param, req, locations);
  164. };
  165. locations.forEach(function(location) {
  166. /**
  167. * @name req.checkQuery
  168. * @see check
  169. * @param param
  170. * @param [failMsg]
  171. */
  172. /**
  173. * @name req.checkParams
  174. * @see check
  175. * @param param
  176. * @param [failMsg]
  177. */
  178. /**
  179. * @name req.checkBody
  180. * @see check
  181. * @param param
  182. * @param [failMsg]
  183. */
  184. /**
  185. * @name req.checkCookies
  186. * @see check
  187. * @param param
  188. * @param [failMsg]
  189. */
  190. req['check' + _.capitalize(location)] = function(param, failMsg) {
  191. if (_.isPlainObject(param)) {
  192. return validateSchema(param, req, [location], contexts);
  193. }
  194. return chainCreator(param, location, failMsg, contexts, customMethods);
  195. };
  196. });
  197. req.checkHeaders = function(param, failMsg) {
  198. if (_.isPlainObject(param)) {
  199. return validateSchema(param, req, ['headers'], contexts);
  200. }
  201. if (param === 'referrer') {
  202. param = 'referer';
  203. }
  204. return chainCreator(param.toLowerCase(), 'headers', failMsg, contexts, customMethods);
  205. };
  206. req.check = function(param, failMsg) {
  207. if (_.isPlainObject(param)) {
  208. return validateSchema(param, req, ['params', 'query', 'body', 'headers', 'cookies'], contexts);
  209. }
  210. return chainCreator(param, allLocations, failMsg, contexts, customMethods);
  211. };
  212. req.filter = req.sanitize;
  213. req.assert = req.check;
  214. req.validate = req.check;
  215. next();
  216. };
  217. };
  218. module.exports = expressValidator;
  219. module.exports.validator = validator;
  220. module.exports.utils = utils;