const config = require('config'); const logger = require('../utils/logger'); /** * Request Validation Middleware - Equivalent to Kamailio REQINIT route * Performs initial security and validation checks on ALL incoming requests */ class RequestValidator { constructor() { this.maxForwards = config.get('security.maxForwards'); } /** * Validate Max-Forwards header (equivalent to kamailio.cfg:370-373) * Prevents infinite routing loops and excessive hops */ validateMaxForwards(req, res, next) { const maxForwards = req.get('Max-Forwards'); if (maxForwards === undefined || parseInt(maxForwards) <= 0) { logger.warn('[REQINIT] Invalid Max-Forwards header from %s:%s', req.source_address, req.source_port); res.send(483, 'Too Many Hops'); return; } // Decrement Max-Forwards (equivalent to mf_process_maxfwd_header) req.set('Max-Forwards', parseInt(maxForwards) - 1); next(); } /** * Basic SIP message validation (equivalent to kamailio.cfg:379-382) * Validates required SIP headers and message structure */ validateSipMessage(req, res, next) { // Check required headers const requiredHeaders = ['From', 'To', 'Call-ID', 'CSeq', 'Via']; for (const header of requiredHeaders) { if (!req.get(header)) { logger.warn('[REQINIT] Missing required header: %s from %s:%s', header, req.source_address, req.source_port); res.send(400, 'Bad Request - Missing ' + header); return; } } // Validate Request-URI for non-REGISTER requests if (req.method !== 'REGISTER' && (!req.uri || !req.uri.user)) { logger.warn('[REQINIT] Invalid R-URI user part from %s:%s', req.source_address, req.source_port); res.send(484, 'Address Incomplete'); return; } next(); } /** * Enhanced logging for incoming requests (equivalent to kamailio.cfg:157-169) * Provides visibility into all SIP traffic */ logIncomingRequest(req, res, next) { const method = req.method; const sourceIp = req.source_address; const sourcePort = req.source_port; const callId = req.get('Call-ID'); const fromUser = req.getParsedHeader('from').uri.user; const toUser = req.getParsedHeader('to').uri.user; let logMessage; switch (method) { case 'REGISTER': logMessage = `[REGISTER] Registration request from ${sourceIp}:${sourcePort} | User: ${fromUser} | Call-ID: ${callId}`; logger.info(logMessage); break; case 'INVITE': logMessage = `[INVITE] INVITE: ${fromUser} -> ${toUser} | Call-ID: ${callId}`; logger.info(logMessage); break; case 'BYE': logMessage = `[BYE] BYE: Call-ID: ${callId}`; logger.info(logMessage); break; case 'ACK': logMessage = `[ACK] ACK: Call-ID: ${callId}`; logger.info(logMessage); break; case 'CANCEL': logMessage = `[CANCEL] CANCEL: Call-ID: ${callId}`; logger.info(logMessage); break; default: logMessage = `[DEBUG] Other method: ${method} from ${sourceIp}:${sourcePort}`; logger.debug(logMessage); } next(); } /** * Detect and handle retransmissions (equivalent to kamailio.cfg:219-231) */ handleRetransmissions(req, res, next) { // This will be handled by Drachtio's built-in transaction management // We just add logging for visibility const callId = req.get('Call-ID'); const cseq = req.get('CSeq'); logger.debug('[RETRANS] Processing request: %s | Call-ID: %s | CSeq: %s', req.method, callId, cseq); next(); } /** * Main middleware function that chains all validation steps */ middleware() { return (req, res, next) => { this.logIncomingRequest(req, res, () => { this.handleRetransmissions(req, res, () => { this.validateMaxForwards(req, res, () => { this.validateSipMessage(req, res, next); }); }); }); }; } } module.exports = RequestValidator;