# All files / middleware security.js

0% Statements 0/34
0% Branches 0/24
0% Functions 0/8
0% Lines 0/34

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

     /**
     * Security Middleware
     * Rate limiting, input sanitization, and security headers
     *
     * @module middleware/security
     */

    const rateLimit = require('express-rate-limit');
    const validator = require('validator');
    const { logger } = require('../config/logger');

    /**
     * API Rate Limiter
     * General rate limiting for all API endpoints
     */
    const apiLimiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 500, // Limit each IP to 500 requests per windowMs (increased from 100)
      message: 'Too many requests from this IP, please try again later.',
      standardHeaders: true,
      legacyHeaders: false,
      handler: (req, res) => {
        logger.warn(`Rate limit exceeded for IP: ${req.ip}`);
        res.status(429).json({
          success: false,
          error: 'Too many requests, please try again later.',
        });
      },
    });

    /**
     * Authentication Rate Limiter
     * Stricter rate limiting for authentication endpoints
     */
    const authLimiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 5, // Limit each IP to 5 login requests per windowMs
      message: 'Too many login attempts, please try again later.',
      standardHeaders: true,
      legacyHeaders: false,
      skipSuccessfulRequests: true,
      handler: (req, res) => {
        logger.warn(`Auth rate limit exceeded for IP: ${req.ip}`);
        res.status(429).json({
          success: false,
          error: 'Too many login attempts, please try again in 15 minutes.',
        });
      },
    });

    /**
     * Input Sanitization Middleware
     * Sanitizes all user inputs to prevent XSS attacks
     */
    function sanitizeInput(req, res, next) {
      try {
        // Sanitize body
        if (req.body && typeof req.body === 'object') {
          req.body = sanitizeObject(req.body);
        }

        // Sanitize query parameters
        if (req.query && typeof req.query === 'object') {
          req.query = sanitizeObject(req.query);
        }

        // Sanitize URL parameters
        if (req.params && typeof req.params === 'object') {
          req.params = sanitizeObject(req.params);
        }

        next();
      } catch (error) {
        logger.error('Error in input sanitization', { error: error.message });
        next(error);
      }
    }

    /**
     * Recursively sanitize object properties
     */
    function sanitizeObject(obj) {
      const sanitized = {};

      for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
          const value = obj[key];

          if (typeof value === 'string') {
            // Escape HTML and trim whitespace
            sanitized[key] = validator.escape(value.trim());
          } else if (typeof value === 'object' && value !== null) {
            // Recursively sanitize nested objects
            sanitized[key] = Array.isArray(value)
              ? value.map(item => typeof item === 'string' ? validator.escape(item.trim()) : item)
              : sanitizeObject(value);
          } else {
            sanitized[key] = value;
          }
        }
      }

      return sanitized;
    }

    /**
     * Validate email format
     */
    function isValidEmail(email) {
      return validator.isEmail(email);
    }

    /**
     * Validate phone number format
     */
    function isValidPhone(phone) {
      return validator.isMobilePhone(phone, 'any', { strictMode: false });
    }

    /**
     * Validate URL format
     */
    function isValidURL(url) {
      return validator.isURL(url);
    }

    module.exports = {
      apiLimiter,
      authLimiter,
      sanitizeInput,
      isValidEmail,
      isValidPhone,
      isValidURL,
    };