All files / platform/packages/validator/src/Middleware Validator.js

100% Statements 47/47
96.97% Branches 32/33
100% Functions 6/6
100% Lines 47/47

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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267                      1x 1x 1x                                 421x                                       420x 195x     225x 225x   225x 225x           225x 225x                                                 420x 6x                   414x       414x           414x 413x 413x           414x                   414x 368x             46x 41x 41x               5x                             374x 4x   370x                                   43x 3x                               421x   421x 1x         420x         420x         420x                     420x 415x 41x         41x                       374x 373x 2x         2x           371x       1x  
'use strict';
 
/*
 * adonis-validator
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
const { resolver } = require('@adonisjs/fold');
const _ = require('lodash');
const CE = require('../Exceptions');
 
/**
 * The middleware to validate requests using a custom
 * validator.
 *
 * This middleware is wrapped inside `Route.validator()` method
 * which automatically pushes this middleware in the route
 * middleware stack
 *
 * @namespace Adonis/Middleware/ValidatorMiddleware
 *
 * @class ValidatorMiddleware
 * @constructor
 */
class ValidatorMiddleware {
  constructor(Validator) {
    this.Validator = Validator;
  }
 
  /**
   * Sanitize user data based upon validator sanitization
   * rules
   *
   * @method _sanitizeData
   *
   * @param  {Object}      request
   * @param  {Object}      validatorInstance
   *
   * @return {void}
   *
   * @private
   */
  _sanitizeData(request, validatorInstance) {
    /**
     * Skip sanitization when there are no rules defined
     */
    if (!validatorInstance.sanitizationRules || !_.size(validatorInstance.sanitizationRules)) {
      return true;
    }
 
    const qsData = request.get();
    const sanitizedQSData = this.Validator.sanitize(qsData, validatorInstance.sanitizationRules);
 
    const data = request.post();
    const sanitizedData = this.Validator.sanitize(data, validatorInstance.sanitizationRules);
 
    /**
     * Here we mutate the actual request body and query params, this is required since there is
     * no point keep the actual data when we really want to sanitize it.
     */
    request._qs = _.merge({}, qsData, sanitizedQSData);
    request.body = _.merge({}, data, sanitizedData);
  }
 
  /**
   * Runs validations on the current request data using
   * the validator instance.
   *
   * @method _runValidations
   * @async
   *
   * @param  {Object}        ctx
   * @param  {Object}        validatorInstance
   *
   * @return {Boolean}
   *
   * @throws {ValidationException} If validation fails and there is no `fails` method
   *                               on the validatorInstance
   *
   * @private
   */
  async _runValidations(request, validatorInstance) {
    /**
     * Skip validation when there are no rules
     * defined
     */
    if (!validatorInstance.rules || !_.size(validatorInstance.rules)) {
      return true;
    }
 
    /**
     * The validation method to be used. If there is a
     * property called `validateAll` on the validator
     * instance, then validateAll otherwise not.
     *
     * @type {Function}
     */
    const validate = validatorInstance.validateAll
      ? this.Validator.validateAll
      : this.Validator.validate;
 
    let data = validatorInstance.data;
 
    /**
     * Merge request body and files when custom data object is
     * not defined
     */
    if (!data) {
      const files = typeof request.files === 'function' ? request.files() : {};
      data = Object.assign({}, request.all(), files);
    }
 
    /**
     * Run validations
     */
    const validation = await validate(
      data,
      validatorInstance.rules,
      validatorInstance.messages,
      validatorInstance.formatter,
    );
 
    /**
     * Validation passed
     */
    if (!validation.fails()) {
      return true;
    }
 
    /**
     * If validator has a method to handle messages, then
     * call the method.
     */
    if (typeof validatorInstance.fails === 'function') {
      await validatorInstance.fails(validation.messages());
      return false;
    }
 
    /**
     * Finally throw the validation messages
     *
     * @type {Error}
     */
    throw CE.ValidationException.validationFailed(validation.messages());
  }
 
  /**
   * Calls the validator authorize method when it exists
   *
   * @method _authorize
   *
   * @param  {Object}   validatorInstance
   *
   * @return {Boolean}
   *
   * @private
   */
  _authorize(validatorInstance) {
    if (typeof validatorInstance.authorize !== 'function') {
      return true;
    }
    return validatorInstance.authorize();
  }
 
  /**
   * Ends the response when it's pending and the end-user
   * has not made any response so far.
   *
   * @method _endResponseIfCan
   *
   * @param  {Object}          response
   * @param  {String}          message
   * @param  {Number}          status
   *
   * @return {void}
   *
   * @private
   */
  _endResponseIfCan(response, message, status) {
    if ((!response.lazyBody.content || !response.lazyBody.method) && response.isPending) {
      response.status(status).send(message);
    }
  }
 
  /**
   * Handle method executed by adonis middleware chain
   *
   * @method handle
   *
   * @param  {Object}   ctx
   * @param  {Function} next
   * @param  {Array}   validator
   *
   * @return {void}
   */
  async handle(ctx, next, validator) {
    validator = validator instanceof Array === true ? validator[0] : validator;
 
    if (!validator) {
      throw new Error(
        "Cannot validate request without a validator. Make sure to call Route.validator('validatorPath')",
      );
    }
 
    const validatorInstance = resolver.resolve(validator);
 
    /**
     * Set request ctx on the validator
     */
    validatorInstance.ctx = ctx;
 
    /**
     * Sanitize request data if there are sanitization rules
     */
    this._sanitizeData(ctx.request, validatorInstance);
 
    /**
     * Validate the request. This method should handle the request
     * response, since the middleware chain has been stopped.
     *
     * If this method doesn't handles the response, then a generic
     * response is made
     *
     * @type {void}
     */
    const validate = await this._runValidations(ctx.request, validatorInstance);
    if (!validate) {
      this._endResponseIfCan(
        ctx.response,
        'Validation failed. Make sure to handle it inside validator.fails method',
        400,
      );
      return;
    }
 
    /**
     * Authorize the request. This method should return true to
     * authorize the request.
     *
     * Make response or throw an exception to reject the request.
     *
     * Returning false from the method will result in a generic
     * error message
     */
    const authorized = await this._authorize(validatorInstance);
    if (!authorized) {
      this._endResponseIfCan(
        ctx.response,
        'Unauthorized request. Make sure to handle it inside validator.authorize method',
        401,
      );
      return;
    }
 
    /**
     * All good, so continue
     */
    await next();
  }
}
 
module.exports = ValidatorMiddleware;