# All files / middleware validation.js

0% Statements 0/24
0% Branches 0/18
0% Functions 0/5
0% Lines 0/23

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

     /**
     * Input Validation Middleware
     *
     * Comprehensive validation rules using express-validator
     * for all API endpoints to ensure data integrity and security
     */

    const { body, param, query, validationResult } = require('express-validator');
    const validator = require('validator');

    /**
     * Middleware to handle validation results
     * Returns 400 with validation errors if validation fails
     */
    const handleValidationErrors = (req, res, next) => {
      const errors = validationResult(req);

      if (!errors.isEmpty()) {
        const formattedErrors = errors.array().map(err => ({
          field: err.path || err.param,
          message: err.msg,
          value: err.value
        }));

        return res.status(400).json({
          success: false,
          error: 'Validation failed',
          details: formattedErrors
        });
      }

      next();
    };

    /**
     * Common validation rules that can be reused
     */
    const commonValidations = {
      // Username validation
      username: body('username')
        .trim()
        .notEmpty().withMessage('Username is required')
        .isLength({ min: 3, max: 50 }).withMessage('Username must be between 3 and 50 characters')
        .matches(/^[a-zA-Z0-9_]+$/).withMessage('Username can only contain letters, numbers, and underscores'),

      // Password validation
      password: body('password')
        .notEmpty().withMessage('Password is required')
        .isLength({ min: 6 }).withMessage('Password must be at least 6 characters long'),

      // Email validation
      email: body('email')
        .optional()
        .trim()
        .isEmail().withMessage('Invalid email format')
        .normalizeEmail(),

      // Phone number validation
      phone: body('phone')
        .optional()
        .trim()
        .matches(/^[\d\s\-\+\(\)]+$/).withMessage('Invalid phone number format')
        .isLength({ min: 10, max: 20 }).withMessage('Phone number must be between 10 and 20 characters'),

      // Text/Message validation
      message: body('message')
        .trim()
        .notEmpty().withMessage('Message is required')
        .isLength({ max: 5000 }).withMessage('Message must not exceed 5000 characters'),

      // ID parameter validation
      id: param('id')
        .isInt({ min: 1 }).withMessage('Invalid ID format'),

      // Pagination
      page: query('page')
        .optional()
        .isInt({ min: 1 }).withMessage('Page must be a positive integer')
        .toInt(),

      limit: query('limit')
        .optional()
        .isInt({ min: 1, max: 100 }).withMessage('Limit must be between 1 and 100')
        .toInt()
    };

    /**
     * Custom sanitization middleware
     * Removes potentially dangerous characters from inputs
     */
    const sanitizeInput = (req, res, next) => {
      // Sanitize body
      if (req.body && typeof req.body === 'object') {
        Object.keys(req.body).forEach(key => {
          if (typeof req.body[key] === 'string') {
            // Remove null bytes
            req.body[key] = req.body[key].replace(/\0/g, '');

            // For fields that shouldn't contain HTML, strip tags
            if (!['message', 'answer', 'description'].includes(key)) {
              req.body[key] = validator.stripLow(req.body[key]);
            }
          }
        });
      }

      // Sanitize query parameters
      if (req.query && typeof req.query === 'object') {
        Object.keys(req.query).forEach(key => {
          if (typeof req.query[key] === 'string') {
            req.query[key] = req.query[key].replace(/\0/g, '');
            req.query[key] = validator.stripLow(req.query[key]);
          }
        });
      }

      next();
    };

    module.exports = {
      // Validation result handler
      handleValidationErrors,

      // Common validations
      commonValidations,

      // Sanitization
      sanitizeInput
    };