mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bf68b6a9b | ||
|
|
69046ab5d2 | ||
|
|
3f1e756467 | ||
|
|
4201ebbe9c | ||
|
|
e02db2e025 | ||
|
|
dd79813229 | ||
|
|
1aa28e8ba0 | ||
|
|
15f2d92f71 | ||
|
|
6ef40a648c |
@@ -140,6 +140,11 @@ router.put('/:sid/VoipCarriers/:voip_carrier_sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
|
||||
try {
|
||||
if (process.env.JAMBONES_ADMIN_CARRIER == 1 && (!req.user.hasScope('service_provider')
|
||||
&& !req.user.hasScope('admin'))) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
|
||||
const sid = parseVoipCarrierSid(req);
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
@@ -159,6 +164,10 @@ router.post('/:sid/VoipCarriers', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const payload = req.body;
|
||||
try {
|
||||
if (process.env.JAMBONES_ADMIN_CARRIER == 1 && (!req.user.hasScope('service_provider')
|
||||
|| !!req.user.hasScope('admin'))) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
const account_sid = parseAccountSid(req);
|
||||
await validateRequest(req, account_sid);
|
||||
// Set the service_provder_sid to the relevent value for the account
|
||||
@@ -298,7 +307,9 @@ function validateUpdateCall(opts) {
|
||||
'tag',
|
||||
'dtmf',
|
||||
'conferenceParticipantAction',
|
||||
'dub'
|
||||
'dub',
|
||||
'boostAudioSignal',
|
||||
'media_path'
|
||||
]
|
||||
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
|
||||
|
||||
@@ -362,6 +373,9 @@ function validateUpdateCall(opts) {
|
||||
throw new DbErrorBadRequest('conferenceParticipantAction requires tag property when action is \'coach\'');
|
||||
}
|
||||
}
|
||||
if (opts.media_path && !['no-media', 'partial-media', 'full-media'].includes(opts.media_path)) {
|
||||
throw new DbErrorBadRequest('invalid media_path');
|
||||
}
|
||||
}
|
||||
|
||||
function validateTo(to) {
|
||||
|
||||
@@ -101,6 +101,20 @@ async function validateUpdate(req, sid) {
|
||||
if (req.body.call_status_hook && typeof req.body.call_hook !== 'object') {
|
||||
throw new DbErrorBadRequest('\'call_status_hook\' must be an object when updating an application');
|
||||
}
|
||||
|
||||
let urlError;
|
||||
if (req.body.call_hook) {
|
||||
urlError = await isInvalidUrl(req.body.call_hook.url);
|
||||
if (urlError) {
|
||||
throw new DbErrorBadRequest(`call_hook ${urlError}`);
|
||||
}
|
||||
}
|
||||
if (req.body.call_status_hook) {
|
||||
urlError = await isInvalidUrl(req.body.call_status_hook.url);
|
||||
if (urlError) {
|
||||
throw new DbErrorBadRequest(`call_status_hook ${urlError}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function validateDelete(req, sid) {
|
||||
@@ -290,9 +304,6 @@ router.put('/:sid', async(req, res) => {
|
||||
obj[`${prop}_sid`] = sid;
|
||||
}
|
||||
}
|
||||
else {
|
||||
obj[`${prop}_sid`] = null;
|
||||
}
|
||||
delete obj[prop];
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ const hasWhitespace = (str) => /\s/.test(str);
|
||||
/* check for required fields when adding */
|
||||
async function validateAdd(req) {
|
||||
try {
|
||||
if (process.env.JAMBONES_ADMIN_CARRIER == 1 && (!req.user.hasScope('service_provider')
|
||||
&& !req.user.hasScope('admin'))) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
|
||||
/* account level user can only act on carriers associated to his/her account */
|
||||
if (req.user.hasAccountAuth) {
|
||||
req.body.account_sid = req.user.account_sid;
|
||||
|
||||
@@ -45,6 +45,12 @@ const validate = async(req, sid) => {
|
||||
const {netmask, ipv4, inbound, outbound} = req.body;
|
||||
let voip_carrier_sid;
|
||||
|
||||
if (process.env.JAMBONES_ADMIN_CARRIER == 1 && (!req.user.hasScope('service_provider')
|
||||
&& !req.user.hasScope('admin'))) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
|
||||
|
||||
if (sid) {
|
||||
const gateway = await lookupSipGatewayBySid(sid);
|
||||
if (!gateway) throw new DbErrorBadRequest('invalid sip_gateway_sid');
|
||||
|
||||
@@ -17,6 +17,7 @@ const {
|
||||
} = require('../../utils/stripe-utils');
|
||||
const {setupFreeTrial} = require('./utils');
|
||||
const sysError = require('../error');
|
||||
const Product = require('../../models/product');
|
||||
const actions = [
|
||||
'upgrade-to-paid',
|
||||
'downgrade-to-free',
|
||||
@@ -24,6 +25,8 @@ const actions = [
|
||||
'update-quantities'
|
||||
];
|
||||
|
||||
const MIN_VOICE_CALL_SESSION_QUANTITY = 5;
|
||||
|
||||
const handleError = async(logger, method, res, err) => {
|
||||
if ('StatusError' === err.name) {
|
||||
const text = await err.text();
|
||||
@@ -146,6 +149,22 @@ const upgradeToPaidPlan = async(req, res) => {
|
||||
|
||||
await handleSubscriptionOutcome(req, res, subscription);
|
||||
};
|
||||
|
||||
const validateProductQuantities = async(products) => {
|
||||
const availableProducts = await Product.retrieveAll();
|
||||
const voiceCallSessionsProductSid =
|
||||
availableProducts.find((p) => p.category === 'voice_call_session')?.product_sid;
|
||||
if (voiceCallSessionsProductSid) {
|
||||
const invalid = products.find((p) => {
|
||||
return (p.product_sid === voiceCallSessionsProductSid &&
|
||||
(typeof p.quantity !== 'number' || p.quantity < MIN_VOICE_CALL_SESSION_QUANTITY));
|
||||
});
|
||||
if (invalid) {
|
||||
throw new DbErrorBadRequest('invalid voice call session value, minimum is ' +
|
||||
MIN_VOICE_CALL_SESSION_QUANTITY);
|
||||
}
|
||||
}
|
||||
};
|
||||
const downgradeToFreePlan = async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {account_sid} = req.user;
|
||||
@@ -291,11 +310,11 @@ router.post('/', async(req, res) => {
|
||||
if ('update-payment-method' === action && typeof payment_method_id !== 'string') {
|
||||
throw new DbErrorBadRequest('missing payment_method_id');
|
||||
}
|
||||
if ('upgrade-to-paid' === action && (!Array.isArray(products) || 0 === products.length)) {
|
||||
throw new DbErrorBadRequest('missing products');
|
||||
}
|
||||
if ('update-quantities' === action && (!Array.isArray(products) || 0 === products.length)) {
|
||||
throw new DbErrorBadRequest('missing products');
|
||||
if (['update-quantities', 'upgrade-to-paid'].includes(action)) {
|
||||
if ((!Array.isArray(products) || 0 === products.length)) {
|
||||
throw new DbErrorBadRequest('missing products');
|
||||
}
|
||||
await validateProductQuantities(products);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
|
||||
@@ -9,6 +9,11 @@ const { parseVoipCarrierSid } = require('./utils');
|
||||
const validate = async(req) => {
|
||||
const {lookupAppBySid, lookupAccountBySid} = req.app.locals;
|
||||
|
||||
if (process.env.JAMBONES_ADMIN_CARRIER == 1 && (!req.user.hasScope('service_provider')
|
||||
&& !req.user.hasScope('admin'))) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
|
||||
/* account level user can only act on carriers associated to his/her account */
|
||||
if (req.user.hasAccountAuth) {
|
||||
req.body.account_sid = req.user.account_sid;
|
||||
@@ -45,6 +50,12 @@ const validateUpdate = async(req, sid) => {
|
||||
|
||||
const validateDelete = async(req, sid) => {
|
||||
const {lookupCarrierBySid} = req.app.locals;
|
||||
if (process.env.JAMBONES_ADMIN_CARRIER == 1 && (!req.user.hasScope('service_provider')
|
||||
&& !req.user.hasScope('admin'))) {
|
||||
throw new DbErrorBadRequest('insufficient privileges');
|
||||
}
|
||||
|
||||
|
||||
if (req.user.hasAccountAuth) {
|
||||
/* can only update carriers for the user's account */
|
||||
const carrier = await lookupCarrierBySid(sid);
|
||||
|
||||
@@ -13,6 +13,10 @@ const handleInvoicePaymentSucceeded = async(logger, obj) => {
|
||||
const sub = await retrieveSubscription(logger, subscription);
|
||||
if ('active' === sub.status) {
|
||||
const {account_sid} = sub.metadata;
|
||||
if (!account_sid) {
|
||||
logger.info({subscription}, `handleInvoicePaymentSucceeded: received subscription ${sub.id} without account_sid`);
|
||||
return;
|
||||
}
|
||||
if (await Account.activateSubscription(logger, account_sid, sub.id,
|
||||
'subscription_create' === obj.billing_reason ? 'upgrade to paid plan' : 'change plan details')) {
|
||||
logger.info(`handleInvoicePaymentSucceeded: activated subscription for account ${account_sid}`);
|
||||
@@ -35,6 +39,10 @@ const handleInvoicePaymentFailed = async(logger, obj) => {
|
||||
const sub = await retrieveSubscription(logger, subscription);
|
||||
logger.debug({obj}, `payment for ${obj.billing_reason} failed, subscription status is ${sub.status}`);
|
||||
const {account_sid} = sub.metadata;
|
||||
if (!account_sid) {
|
||||
logger.info({subscription}, `handleInvoicePaymentFailed: received subscription ${sub.id} without account_sid`);
|
||||
return;
|
||||
}
|
||||
if (await Account.deactivateSubscription(logger, account_sid, 'payment failed')) {
|
||||
logger.info(`handleInvoicePaymentFailed: deactivated subscription for account ${account_sid}`);
|
||||
}
|
||||
|
||||
@@ -50,18 +50,15 @@ function isObscureKey(bucketCredentials) {
|
||||
service_key = '',
|
||||
connection_string = ''
|
||||
} = bucketCredentials || {};
|
||||
let pattern;
|
||||
// Pattern matches: 4-6 any characters followed by one or more X's
|
||||
const pattern = /^.{4,6}X+$/;
|
||||
switch (vendor) {
|
||||
case 'aws_s3':
|
||||
case 's3_compatible':
|
||||
pattern = /^([A-Za-z0-9]{4,6}X+$)/;
|
||||
return pattern.test(secret_access_key);
|
||||
case 'azure':
|
||||
pattern = /^([A-Za-z0-9:]{4,6}X+$)/;
|
||||
return pattern.test(connection_string);
|
||||
|
||||
case 'google': {
|
||||
pattern = /^([A-Za-z0-9]{4,6}X+$)/;
|
||||
let {private_key} = JSON.parse(service_key);
|
||||
const key_header = '-----BEGIN PRIVATE KEY-----\n';
|
||||
private_key = private_key.slice(key_header.length, private_key.length);
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -21,7 +21,7 @@
|
||||
"@jambonz/lamejs": "^1.2.2",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.15",
|
||||
"@jambonz/speech-utils": "^0.2.26",
|
||||
"@jambonz/speech-utils": "^0.2.27",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/verb-specifications": "^0.0.122",
|
||||
"@soniox/soniox-node": "^1.2.2",
|
||||
@@ -4178,9 +4178,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/speech-utils": {
|
||||
"version": "0.2.26",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.26.tgz",
|
||||
"integrity": "sha512-gL5PBd2zSDCxAgz5cIdiLgkhqGEkW5UizF93BV8xcpMZuzIuLhsr2WlIMY3L3vVpFSx8AtZ9XdgPtxIOqI+FHw==",
|
||||
"version": "0.2.27",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.27.tgz",
|
||||
"integrity": "sha512-wx0VKq2Gy8+pNx/RwMJjvaG7HT1/pezlgKkUfL0a4DoXxwIoglVyc/2SPUMf9yZ5xKSifYxcGoGSKfor9f9EbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"23": "^0.0.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@jambonz/lamejs": "^1.2.2",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.15",
|
||||
"@jambonz/speech-utils": "^0.2.26",
|
||||
"@jambonz/speech-utils": "^0.2.27",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/verb-specifications": "^0.0.122",
|
||||
"@soniox/soniox-node": "^1.2.2",
|
||||
|
||||
Reference in New Issue
Block a user