128 lines
4.0 KiB
JavaScript
128 lines
4.0 KiB
JavaScript
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; |