mirror of
https://github.com/jambonz/sbc-sip-sidecar.git
synced 2026-08-19 09:40:25 +00:00
Feat/auth trunk register (#141)
* allow REGISTER for auth trunks * use db-helpers * bump db-helpers
This commit is contained in:
@@ -44,7 +44,7 @@ const Srf = require('drachtio-srf');
|
||||
const srf = new Srf();
|
||||
const StatsCollector = require('@jambonz/stats-collector');
|
||||
const stats = new StatsCollector(logger);
|
||||
const { initLocals, rejectIpv4, checkCache, checkAccountLimits } = require('./lib/middleware');
|
||||
const { initLocals, rejectIpv4, checkCache, checkAccountLimits, enforceDeviceLimits } = require('./lib/middleware');
|
||||
const responseTime = require('drachtio-mw-response-time');
|
||||
const regParser = require('drachtio-mw-registration-parser');
|
||||
const Registrar = require('@jambonz/mw-registrar');
|
||||
@@ -65,7 +65,8 @@ const {
|
||||
lookupCarrierBySid,
|
||||
lookupSystemInformation,
|
||||
updateCarrierBySid,
|
||||
lookupAccountBySid
|
||||
lookupAccountBySid,
|
||||
lookupAuthCarriersForAccountAndSP
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: JAMBONES_MYSQL_HOST,
|
||||
user: JAMBONES_MYSQL_USER,
|
||||
@@ -125,7 +126,8 @@ srf.locals = {
|
||||
updateSipGatewayBySid,
|
||||
lookupCarrierBySid,
|
||||
lookupSystemInformation,
|
||||
updateCarrierBySid
|
||||
updateCarrierBySid,
|
||||
lookupAuthCarriersForAccountAndSP
|
||||
},
|
||||
realtimeDbHelpers: {
|
||||
client,
|
||||
@@ -267,7 +269,8 @@ srf.use('register', [
|
||||
regParser,
|
||||
checkCache,
|
||||
checkAccountLimits,
|
||||
digestChallenge]);
|
||||
digestChallenge,
|
||||
enforceDeviceLimits]);
|
||||
|
||||
srf.use('options', [
|
||||
initLocals
|
||||
|
||||
+50
-11
@@ -65,9 +65,8 @@ const checkCache = async(req, res, next) => {
|
||||
|
||||
const checkAccountLimits = async(req, res, next) => {
|
||||
const {logger} = req.locals;
|
||||
const {lookupAccountBySipRealm, lookupAccountCapacitiesBySid} = req.srf.locals.dbHelpers;
|
||||
const {lookupAccountBySipRealm, lookupAuthCarriersForAccountAndSP} = req.srf.locals.dbHelpers;
|
||||
const {realm} = req.locals;
|
||||
const {registrar, writeAlerts, AlertType} = req.srf.locals;
|
||||
try {
|
||||
const account = await lookupAccountBySipRealm(realm);
|
||||
if (account && !account.is_active) {
|
||||
@@ -77,10 +76,19 @@ const checkAccountLimits = async(req, res, next) => {
|
||||
}});
|
||||
}
|
||||
if (account) {
|
||||
/* if the account has auth trunk(s) configured, we validate REGISTER credentials against
|
||||
those trunk credentials rather than storing a device registration (see digestChallenge) */
|
||||
const auth_trunks = await lookupAuthCarriersForAccountAndSP(
|
||||
account.account_sid,
|
||||
account.service_provider_sid
|
||||
);
|
||||
req.locals = {
|
||||
...req.locals,
|
||||
account,
|
||||
account_sid: account.account_sid,
|
||||
service_provider_sid: account.service_provider_sid,
|
||||
webhook_secret: account.webhook_secret,
|
||||
...(auth_trunks?.length && {auth_trunks}),
|
||||
...(account.registration_hook && {
|
||||
registration_hook_url: account.registration_hook.url,
|
||||
registration_hook_method: account.registration_hook.method,
|
||||
@@ -96,9 +104,39 @@ const checkAccountLimits = async(req, res, next) => {
|
||||
return res.send(403);
|
||||
}
|
||||
|
||||
if ('unregister' === req.registration.type || !JAMBONES_HOSTING) return next();
|
||||
next();
|
||||
} catch (err) {
|
||||
logger.error({err, realm}, 'checkAccountLimits: error looking up account');
|
||||
// if we can not reach the db for some reason, allow the registration to proceed
|
||||
if (err.message?.includes('connect ECONNREFUSED')) {
|
||||
return next();
|
||||
}
|
||||
res.send(500);
|
||||
}
|
||||
};
|
||||
|
||||
/* only check limits on the jambonz hosted platform */
|
||||
/**
|
||||
* Enforce per-account device registration limits.
|
||||
*
|
||||
* Runs after digestChallenge so we know how the request authenticated: REGISTERs that
|
||||
* validated against an auth trunk are not devices (and are never stored), so they bypass
|
||||
* the limit entirely. Only client/device registrations are counted and limited.
|
||||
*/
|
||||
const enforceDeviceLimits = async(req, res, next) => {
|
||||
const {logger, realm, account} = req.locals;
|
||||
const {lookupAccountCapacitiesBySid} = req.srf.locals.dbHelpers;
|
||||
const {registrar, writeAlerts, AlertType} = req.srf.locals;
|
||||
|
||||
/* only check limits on the jambonz hosted platform, and never on unregister */
|
||||
if ('unregister' === req.registration.type || !JAMBONES_HOSTING) return next();
|
||||
|
||||
/* auth trunks are not devices and are never stored, so they don't count against device limits */
|
||||
if (req.authorization?.grant?.auth_trunk) {
|
||||
logger.debug('enforceDeviceLimits: authenticated as auth trunk, skipping device limit check');
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
const {account_sid} = account;
|
||||
const capacities = await lookupAccountCapacitiesBySid(account_sid);
|
||||
const limit_calls = capacities.find((c) => c.category == 'voice_call_session');
|
||||
@@ -108,30 +146,30 @@ const checkAccountLimits = async(req, res, next) => {
|
||||
debug(`call capacity: ${limit_calls.quantity}, device capacity: ${limit_registrations}`);
|
||||
|
||||
if (0 === limit_registrations) {
|
||||
logger.info({account_sid}, 'checkAccountLimits: device calling not allowed for this account');
|
||||
logger.info({account_sid}, 'enforceDeviceLimits: device calling not allowed for this account');
|
||||
writeAlerts({
|
||||
alert_type: AlertType.ACCOUNT_DEVICE_LIMIT,
|
||||
account_sid,
|
||||
count: 0
|
||||
}).catch((err) => logger.info({err}, 'checkAccountLimits: error writing alert'));
|
||||
}).catch((err) => logger.info({err}, 'enforceDeviceLimits: error writing alert'));
|
||||
|
||||
return res.send(503, 'Max Devices Registered');
|
||||
}
|
||||
|
||||
const deviceCount = await registrar.getCountOfUsers(realm);
|
||||
if (deviceCount > limit_registrations + 1) {
|
||||
logger.info({account_sid}, 'checkAccountLimits: registration rejected due to limits');
|
||||
logger.info({account_sid}, 'enforceDeviceLimits: registration rejected due to limits');
|
||||
writeAlerts({
|
||||
alert_type: AlertType.ACCOUNT_DEVICE_LIMIT,
|
||||
account_sid,
|
||||
count: limit_registrations
|
||||
}).catch((err) => logger.info({err}, 'checkAccountLimits: error writing alert'));
|
||||
}).catch((err) => logger.info({err}, 'enforceDeviceLimits: error writing alert'));
|
||||
return res.send(503, 'Max Devices Registered');
|
||||
}
|
||||
logger.debug(`checkAccountLimits - passed: devices registered ${deviceCount}, limit is ${limit_registrations}`);
|
||||
logger.debug(`enforceDeviceLimits - passed: devices registered ${deviceCount}, limit is ${limit_registrations}`);
|
||||
next();
|
||||
} catch (err) {
|
||||
logger.error({err, realm}, 'checkAccountLimits: error checking account limits');
|
||||
logger.error({err, realm}, 'enforceDeviceLimits: error checking account limits');
|
||||
// if we can not reach the db for some reason, allow the registration to proceed
|
||||
if (err.message?.includes('connect ECONNREFUSED')) {
|
||||
return next();
|
||||
@@ -144,5 +182,6 @@ module.exports = {
|
||||
initLocals,
|
||||
rejectIpv4,
|
||||
checkCache,
|
||||
checkAccountLimits
|
||||
checkAccountLimits,
|
||||
enforceDeviceLimits
|
||||
};
|
||||
|
||||
+20
-1
@@ -7,13 +7,32 @@ function handler({logger}) {
|
||||
return async(req, res) => {
|
||||
logger.debug(`received ${req.method} from ${req.protocol}/${req.source_address}:${req.source_port}`);
|
||||
|
||||
if ('register' === req.registration.type && '0' !== req.registration.expires) await register(logger, req, res);
|
||||
/* if credentials were validated against an auth trunk, acknowledge without storing a registration */
|
||||
if (req.authorization?.grant?.auth_trunk) {
|
||||
await acknowledgeAuthTrunk(logger, req, res);
|
||||
}
|
||||
else if ('register' === req.registration.type && '0' !== req.registration.expires) {
|
||||
await register(logger, req, res);
|
||||
}
|
||||
else await unregister(logger, req, res);
|
||||
|
||||
req.srf.endSession(req);
|
||||
};
|
||||
}
|
||||
|
||||
async function acknowledgeAuthTrunk(logger, req, res) {
|
||||
const {auth_trunk} = req.authorization.grant;
|
||||
const expires = req.registration.expires;
|
||||
logger.debug({voip_carrier_sid: auth_trunk.voip_carrier_sid, name: auth_trunk.name},
|
||||
'REGISTER validated against auth trunk credentials; responding OK without storing registration');
|
||||
res.send(200, {
|
||||
headers: {
|
||||
'Contact': req.get('Contact'),
|
||||
'Expires': expires
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function register(logger, req, res) {
|
||||
try {
|
||||
const registrar = req.srf.locals.registrar;
|
||||
|
||||
Generated
+1
-1
@@ -10,7 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helpers": "^7.26.10",
|
||||
"@jambonz/db-helpers": "^0.9.20",
|
||||
"@jambonz/db-helpers": "^0.9.21",
|
||||
"@jambonz/digest-utils": "^0.0.9",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.21",
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
"homepage": "https://github.com/jambonz/sbc-sip-sidecar#readme",
|
||||
"dependencies": {
|
||||
"@babel/helpers": "^7.26.10",
|
||||
"@jambonz/db-helpers": "^0.9.20",
|
||||
"@jambonz/db-helpers": "^0.9.21",
|
||||
"@jambonz/digest-utils": "^0.0.9",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.21",
|
||||
|
||||
Reference in New Issue
Block a user