support auth trunk for incoming call (#213)

* support auth trunk for incoming call

* wip

* wip

* wip

* update digest-utils version

* update sql file from api-server

* update sql file from api-server

* wip
This commit is contained in:
Hoan Luu Huu
2025-10-24 04:00:34 +07:00
committed by GitHub
parent b2868842ad
commit 9beba4330a
4 changed files with 82 additions and 8 deletions

13
app.js
View File

@@ -130,7 +130,8 @@ const {
wasOriginatedFromCarrier,
getApplicationForDidAndCarrier,
getOutboundGatewayForRefer,
getApplicationBySid
getApplicationBySid,
lookupAuthCarriersForAccountAndSP
} = require('./lib/db-utils')(srf, logger);
srf.locals = {
...srf.locals,
@@ -139,7 +140,8 @@ srf.locals = {
getApplicationForDidAndCarrier,
getOutboundGatewayForRefer,
getFeatureServer: require('./lib/fs-tracking')(srf, logger),
getApplicationBySid
getApplicationBySid,
lookupAuthCarriersForAccountAndSP
};
const activeCallIds = srf.locals.activeCallIds;
@@ -148,7 +150,8 @@ const {
handleSipRec,
identifyAccount,
checkLimits,
challengeDeviceCalls
challengeDeviceCalls,
identifyAuthTrunk
} = require('./lib/middleware')(srf, logger);
const CallSession = require('./lib/call-session');
@@ -236,7 +239,9 @@ srf.use('invite', [
handleSipRec,
identifyAccount,
checkLimits,
challengeDeviceCalls
challengeDeviceCalls,
// challengeDeviceCalls will detect auth_trunk or device calls, identifyAuthTrunk have to be after that
identifyAuthTrunk
]);
srf.invite((req, res) => {

View File

@@ -65,6 +65,16 @@ AND vc.is_active = 1
AND vc.register_sip_realm = ?
AND vc.register_username = ?`;
const sqlSelectAuthCarriersForAccountAndSP = `
SELECT * FROM voip_carriers
WHERE trunk_type = 'auth'
AND is_active = 1
AND (
(account_sid = ?)
OR
(service_provider_sid = ? AND account_sid IS NULL)
)`;
const sqlSelectGatewaysByVoipCarrierSids = `
SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
vc.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto
@@ -579,12 +589,33 @@ module.exports = (srf, logger) => {
return failure;
};
/**
* Retrieves voip_carriers with trunk_type 'auth' that belong to either:
* 1. The specified account (account_sid matches), OR
* 2. The service provider but with null account_sid (shared across service provider)
*
* @param {string} account_sid - The SID of the account
* @param {string} service_provider_sid - The SID of the service provider
* @returns {Promise<Array>} Array of voip_carrier records matching the criteria
* @throws {Error} Database errors or other unexpected errors
*/
const lookupAuthCarriersForAccountAndSP = async(account_sid, service_provider_sid) => {
try {
const [rows] = await pp.query(sqlSelectAuthCarriersForAccountAndSP, [account_sid, service_provider_sid]);
return rows;
} catch (err) {
logger.error({err, account_sid, service_provider_sid}, 'lookupAuthCarriersForAccountAndSP');
throw err;
}
};
return {
wasOriginatedFromCarrier,
getApplicationForDidAndCarrier,
getApplicationForDidAndCarriers,
getOutboundGatewayForRefer,
getSPForAccount,
getApplicationBySid
getApplicationBySid,
lookupAuthCarriersForAccountAndSP
};
};

View File

@@ -28,9 +28,9 @@ module.exports = function(srf, logger) {
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid,
queryCallLimits
queryCallLimits,
} = srf.locals.dbHelpers;
const {stats, writeCdrs} = srf.locals;
const {stats, writeCdrs, lookupAuthCarriersForAccountAndSP, getApplicationForDidAndCarrier} = srf.locals;
const initLocals = (req, res, next) => {
const callId = req.get('Call-ID');
@@ -191,6 +191,10 @@ module.exports = function(srf, logger) {
res.send(404);
return req.srf.endSession(req);
}
const auth_trunks = await lookupAuthCarriersForAccountAndSP(
account.account_sid,
account.service_provider_sid
);
/* if this is a dedicated SBC (static IP) only take calls for that account's sip realm */
if (process.env.SBC_ACCOUNT_SID && account.account_sid !== process.env.SBC_ACCOUNT_SID) {
@@ -214,6 +218,7 @@ module.exports = function(srf, logger) {
registration_hook_username: account.registration_hook.username,
registration_hook_password: account.registration_hook.password
}),
...(auth_trunks?.length && {auth_trunks}),
...req.locals
};
}
@@ -365,6 +370,38 @@ module.exports = function(srf, logger) {
}
};
const identifyAuthTrunk = async(req, res, next) => {
try {
if (req.authorization) {
const {grant} = req.authorization;
if (grant && grant.status === 'ok' && grant.auth_trunk) {
// we have successfully authenticated the call for an auth_trunk
const application_sid = await getApplicationForDidAndCarrier(req, grant.auth_trunk.voip_carrier_sid);
req.locals = {
...req.locals,
originator: 'trunk',
carrier: grant.auth_trunk.name,
gateway: grant.auth_trunk,
voip_carrier_sid: grant.auth_trunk.voip_carrier_sid,
application_sid: application_sid || grant.auth_trunk.application_sid,
};
// as call from auth carrier, clean req.authorization that impact on legacy logic for authenticated user
delete req.authorization;
logger.debug({callId: req.locals.callId, auth_trunk: grant.auth_trunk.name},
'identifyAuthTrunk: call authenticated for auth trunk');
}
}
next();
} catch (err) {
stats.increment('sbc.terminations', ['sipStatus:500']);
logger.error(err, `${req.get('Call-ID')} Error challenging auth trunk`);
res.send(500);
req.srf.endSession(req);
}
};
const challengeDeviceCalls = async(req, res, next) => {
try {
/* TODO: check if this is a gateway that we have an ACL for */
@@ -383,6 +420,7 @@ module.exports = function(srf, logger) {
handleSipRec,
challengeDeviceCalls,
identifyAccount,
identifyAuthTrunk,
checkLimits
};
};

View File

@@ -748,4 +748,4 @@ ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (devic
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=0;
SET FOREIGN_KEY_CHECKS=0;