UNPKG

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