diff --git a/app.js b/app.js index e95cfc7..ef28d5f 100644 --- a/app.js +++ b/app.js @@ -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) => { diff --git a/lib/db-utils.js b/lib/db-utils.js index 86638d8..5f2d415 100644 --- a/lib/db-utils.js +++ b/lib/db-utils.js @@ -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 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 }; }; diff --git a/lib/middleware.js b/lib/middleware.js index e2b2acb..123ba9c 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -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 }; }; diff --git a/test/db/jambones-sql.sql b/test/db/jambones-sql.sql index 6961e73..67ad019 100644 --- a/test/db/jambones-sql.sql +++ b/test/db/jambones-sql.sql @@ -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; \ No newline at end of file