Compare commits

...

10 Commits

Author SHA1 Message Date
EgleH
d4f2be3dc1 Add check for users to delete function (#104)
* add check for users to delete function

* fix typo

* fix active admin check

---------

Co-authored-by: eglehelms <e.helms@cognigy.com>
2023-02-08 07:16:29 -05:00
Dave Horton
a46c24b3aa change applications.app_json to TEXT as it could hold large strings 2023-02-03 16:34:11 -05:00
AFelix53
f5c833720a feat: added twilio gateways (#99) 2023-01-30 10:16:27 -05:00
EgleH
4d2cc15de4 Bug/speech creds get all with no sp sid (#102)
* backwards compatibility

* fetch account and sp speech remove duplicates

* fix retrieval of SP credentials associated to an account level user

* update gh actions

---------

Co-authored-by: eglehelms <e.helms@cognigy.com>
Co-authored-by: Dave Horton <daveh@beachdognet.com>
2023-01-30 10:06:16 -05:00
Hoan Luu Huu
019599741a feat: add app_json to applications endpoint (#100)
* added phone_numbers.app_json column

* modify prev commit to put app_json on applications table

* feat: add app_json to applications endpoint

---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
Co-authored-by: Quan HL <quanluuhoang8@gmail.com>
2023-01-28 10:02:04 -05:00
EgleH
f2c2623b28 Speech should always be attached to an SP (#98)
* user will always be attached to SP, thus always provide SP sid

* add another fallback for service_provider_sid

* fix the email and username check in user creation that was crashing the server

* not allow same names for shared and account carriers

Co-authored-by: eglehelms <e.helms@cognigy.com>
2023-01-26 07:53:29 -05:00
EgleH
6c494786c8 add check to not allow the same password to be user as new (#97)
Co-authored-by: eglehelms <e.helms@cognigy.com>
2023-01-23 07:59:33 -05:00
EgleH
80ee1d06d7 Return scoped carriers and speech creds in /SP GET call (#96)
* return scoped carriers and speech creds in /SP GET call

* apply review comments

Co-authored-by: eglehelms <e.helms@cognigy.com>
2023-01-19 10:53:37 -05:00
EgleH
274377960e Allow falsy values for force_change and Is_active in PUT user call (#95)
* allow falsy values

* apply review comments

Co-authored-by: eglehelms <e.helms@cognigy.com>
2023-01-19 10:39:57 -05:00
Charles Chance
02bba9d981 Update Simwood gateways (#92) 2023-01-12 07:48:47 -05:00
21 changed files with 1078 additions and 57 deletions

View File

@@ -6,10 +6,10 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: lts/*
- run: npm install
- run: npm run jslint
- run: npm test

View File

@@ -20,7 +20,7 @@ jobs:
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build image
run: docker build . --file Dockerfile.db-create --tag $IMAGE_NAME

View File

@@ -20,7 +20,7 @@ jobs:
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build image
run: docker build . --file Dockerfile --tag $IMAGE_NAME

View File

@@ -45,8 +45,6 @@ VALUES
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);

View File

@@ -435,6 +435,7 @@ account_sid CHAR(36) COMMENT 'account that this application belongs to (if null,
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
app_json TEXT,
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
speech_synthesis_voice VARCHAR(64),
@@ -647,4 +648,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=1;
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -1841,8 +1841,8 @@
<name><![CDATA[predefined_carriers]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>47.00</x>
<y>1103.00</y>
<x>35.00</x>
<y>1194.00</y>
</location>
<size>
<width>302.00</width>
@@ -2187,7 +2187,7 @@
</location>
<size>
<width>345.00</width>
<height>300.00</height>
<height>320.00</height>
</size>
<zorder>0</zorder>
<SQLField>
@@ -2281,6 +2281,11 @@
<objectComment><![CDATA[webhook to call for inbound SMS/MMS ]]></objectComment>
<uid><![CDATA[4690A98A-2F67-4205-A5C1-D9523F6B3282]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[app_json]]></name>
<type><![CDATA[TEXT]]></type>
<uid><![CDATA[B3BB24D3-DDA7-418F-A8CE-FD0E3E8B25C7]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[speech_synthesis_vendor]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
@@ -2711,10 +2716,10 @@
<SourceSidebarWidth><![CDATA[0.000000]]></SourceSidebarWidth>
<SQLEditorFileFormatVersion><![CDATA[4]]></SQLEditorFileFormatVersion>
<uid><![CDATA[58C99A00-06C9-478C-A667-C63842E088F3]]></uid>
<windowHeight><![CDATA[870.000000]]></windowHeight>
<windowHeight><![CDATA[917.000000]]></windowHeight>
<windowLocationX><![CDATA[0.000000]]></windowLocationX>
<windowLocationY><![CDATA[74.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{432, 18.5}]]></windowScrollOrigin>
<windowLocationY><![CDATA[27.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{383, 243.5}]]></windowScrollOrigin>
<windowWidth><![CDATA[1512.000000]]></windowWidth>
</SQLDocumentInfo>
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>

View File

@@ -98,10 +98,8 @@ VALUES
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);

View File

@@ -68,6 +68,9 @@ VALUES
('d2ccfcb1-9198-4fe9-a0ca-6e49395837c4', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.172.60.0', 30, 5060, 1, 0),
('6b1d0032-4430-41f1-87c6-f22233d394ef', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.244.51.0', 30, 5060, 1, 0),
('0de40217-8bd5-4aa8-a9fd-1994282953c6', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.192', 30, 5060, 1, 0),
('48b108e3-1ce7-4f18-a4cb-e41e63688bdf', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.193', 30, 5060, 1, 0),
('d9131a69-fe44-4c2a-ba82-4adc81f628dd', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.194', 30, 5060, 1, 0),
('34a6a311-4bd6-49ca-aa77-edd3cb92c6e1', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.195', 30, 5060, 1, 0),
('37bc0b20-b53c-4c31-95a6-f82b1c3713e3', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '35.156.191.128', 30, 5060, 1, 0),
('39791f4e-b612-4882-a37e-e92711a39f3f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.65.63.192', 30, 5060, 1, 0),
('81a0c8cb-a33e-42da-8f20-99083da6f02f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.252.254.64', 30, 5060, 1, 0),
@@ -92,10 +95,8 @@ VALUES
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -72,10 +72,8 @@ VALUES
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -59,6 +59,7 @@ const sql = {
'ALTER TABLE `voip_carriers` ADD COLUMN `register_public_ip_in_contact` BOOLEAN NOT NULL DEFAULT false'
],
'8000': [
'ALTER TABLE `applications` ADD COLUMN `app_json` TEXT',
'ALTER TABLE voip_carriers CHANGE register_public_domain_in_contact register_public_ip_in_contact BOOLEAN',
'alter table phone_numbers modify number varchar(132) NOT NULL UNIQUE',
`CREATE TABLE permissions

View File

@@ -85,9 +85,6 @@ VALUES
-- simwood gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '149.91.14.0', 24, 5060, 1, 0),
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '154.51.137.96', 27, 5060, 1, 0),
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '78.40.245.160', 27, 5060, 1, 0),
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.136.24', 29, 5060, 1, 0),
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.136.28', 28, 5060, 1, 0),
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.48', 28, 5060, 1, 0),
@@ -98,7 +95,7 @@ VALUES
('b6ae6240-55ac-4c11-892f-a71b2155ea60', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.0', 26, 5060, 1, 0),
('5a976337-164b-408e-8748-d8bfb4bd5d76', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.0', 26, 5060, 1, 0),
('ed0434ca-7f26-4624-9523-0419d0d2924d', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.0', 26, 5060, 1, 0),
('d1a594c2-c14f-4ead-b621-96129bc87886', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.224.0', 24, 5060, 1, 0),
('6bfb55e5-e248-48dc-a104-4f3eedd7d7de', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.0', 24, 5060, 1, 0),
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);

View File

@@ -8,9 +8,7 @@ const short = require('short-uuid');
const {promisePool} = require('../../db');
const sysError = require('../error');
const sqlSelectCarrierByName = `SELECT * FROM voip_carriers
WHERE account_sid = ?
AND name = ?`;
const sqlSelectCarrierByNameForSP = `SELECT * FROM voip_carriers
WHERE service_provider_sid = ?
AND name = ?`;
@@ -25,18 +23,20 @@ router.post('/:sid', async(req, res) => {
const {sid } = req.params;
let service_provider_sid;
const {account_sid} = req.user;
if (!account_sid) {
service_provider_sid = parseServiceProviderSid(req);
} else {
service_provider_sid = req.user.service_provider_sid;
}
try {
const [template] = await PredefinedCarrier.retrieve(sid);
logger.debug({template}, `Retrieved template carrier for sid ${sid}`);
if (!template) return res.sendStatus(404);
/* make sure not to add the same carrier twice */
const [r2] = account_sid ?
await promisePool.query(sqlSelectCarrierByName, [account_sid, template.name]) :
await promisePool.query(sqlSelectCarrierByNameForSP, [service_provider_sid, template.name]);
const [r2] = await promisePool.query(sqlSelectCarrierByNameForSP, [service_provider_sid, template.name]);
if (r2.length > 0) {
template.name = `${template.name}-${short.generate()}`;

View File

@@ -6,6 +6,7 @@ const Webhook = require('../../models/webhook');
const {promisePool} = require('../../db');
const decorate = require('./decorate');
const sysError = require('../error');
const jambonzAppJsonValidator = require('../../utils/jambonz-app-json-validation');
const preconditions = {
'add': validateAdd,
'update': validateUpdate
@@ -76,6 +77,12 @@ router.post('/', async(req, res) => {
}
}
// validate app json if required
if (obj['app_json']) {
const app_json = JSON.parse(obj['app_json']);
jambonzAppJsonValidator(logger, app_json);
}
const uuid = await Application.make(obj);
res.status(201).json({sid: uuid});
} catch (err) {
@@ -179,6 +186,12 @@ router.put('/:sid', async(req, res) => {
delete obj[prop];
}
// validate app json if required
if (obj['app_json']) {
const app_json = JSON.parse(obj['app_json']);
jambonzAppJsonValidator(logger, app_json);
}
const rowsAffected = await Application.update(sid, obj);
if (rowsAffected === 0) {
return res.status(404).end();

View File

@@ -12,7 +12,7 @@ const {hasServiceProviderPermissions, parseServiceProviderSid} = require('./util
const sysError = require('../error');
const decorate = require('./decorate');
const preconditions = {
'delete': noActiveAccounts
'delete': noActiveAccountsOrUsers
};
const sqlDeleteSipGateways = `DELETE from sip_gateways
WHERE voip_carrier_sid IN (
@@ -59,12 +59,20 @@ function validateUpdate(req) {
}
/* can not delete a service provider if it has any active accounts */
async function noActiveAccounts(req, sid) {
async function noActiveAccountsOrUsers(req, sid) {
if (!req.user.hasAdminAuth) {
throw new DbErrorForbidden('only admin users can delete a service provider');
}
const activeAccounts = await ServiceProvider.getForeignKeyReferences('accounts.service_provider_sid', sid);
const activeUsers = await ServiceProvider.getForeignKeyReferences('users.service_provider_sid', sid);
if (activeAccounts > 0 && activeUsers > 0) throw new DbErrorUnprocessableRequest(
'cannot delete service provider with active accounts or users'
);
if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts');
if (activeUsers > 0) throw new DbErrorUnprocessableRequest(
'cannot delete service provider with active service provider level users'
);
/* ok we can delete -- no active accounts. remove carriers and speech credentials */
await promisePool.execute('DELETE from speech_credentials WHERE service_provider_sid = ?', [sid]);
@@ -127,8 +135,13 @@ router.get('/:sid/VoipCarriers', async(req, res) => {
try {
await validateRetrieve(req);
const service_provider_sid = parseServiceProviderSid(req);
const results = await VoipCarrier.retrieveAllForSP(service_provider_sid);
res.status(200).json(results);
const carriers = await VoipCarrier.retrieveAllForSP(service_provider_sid);
if (req.user.hasScope('account')) {
return res.status(200).json(carriers.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid));
}
res.status(200).json(carriers);
} catch (err) {
sysError(logger, res, err);
}

View File

@@ -1,5 +1,6 @@
const router = require('express').Router();
const assert = require('assert');
const Account = require('../../models/account');
const SpeechCredential = require('../../models/speech-credential');
const sysError = require('../error');
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
@@ -118,13 +119,13 @@ router.post('/', async(req, res) => {
vendor,
} = req.body;
const account_sid = req.user.account_sid || req.body.account_sid;
let service_provider_sid;
const service_provider_sid = req.user.service_provider_sid ||
req.body.service_provider_sid || parseServiceProviderSid(req);
if (!account_sid) {
if (!req.user.hasServiceProviderAuth && !req.user.hasAdminAuth) {
logger.error('POST /SpeechCredentials invalid credentials');
return res.sendStatus(403);
}
service_provider_sid = parseServiceProviderSid(req);
}
try {
const encrypted_credential = encryptCredential(req.body);
@@ -146,14 +147,20 @@ router.post('/', async(req, res) => {
* retrieve all speech credentials for an account
*/
router.get('/', async(req, res) => {
let service_provider_sid;
const account_sid = parseAccountSid(req);
if (!account_sid) service_provider_sid = parseServiceProviderSid(req);
const account_sid = parseAccountSid(req) || req.user.account_sid;
const service_provider_sid = parseServiceProviderSid(req) || req.user.service_provider_sid;
const logger = req.app.locals.logger;
try {
const creds = account_sid ?
await SpeechCredential.retrieveAll(account_sid) :
await SpeechCredential.retrieveAllForSP(service_provider_sid);
const credsAccount = account_sid ? await SpeechCredential.retrieveAll(account_sid) : [];
const credsSP = service_provider_sid ?
await SpeechCredential.retrieveAllForSP(service_provider_sid) :
await SpeechCredential.retrieveAllForSP((await Account.retrieve(account_sid))[0].service_provider_sid);
// filter out duplicates and discard those from other non-matching accounts
let creds = [...new Set([...credsAccount, ...credsSP].map((c) => JSON.stringify(c)))].map((c) => JSON.parse(c));
if (req.user.hasScope('account')) {
creds = creds.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid);
}
res.status(200).json(creds.map((c) => {
const {credential, ...obj} = c;

View File

@@ -285,6 +285,11 @@ router.put('/:user_sid', async(req, res) => {
//debug(`PUT /Users/:sid pwd ${old_password} does not match hash ${old_hashed_password}`);
return res.sendStatus(403);
}
if (old_password === new_password) {
throw new Error('new password cannot be your old password');
}
const passwordHash = await generateHashedPassword(new_password);
//debug(`updating hashed_password to ${passwordHash}`);
const r = await promisePool.execute(updateSql, [passwordHash, user_sid]);
@@ -305,12 +310,12 @@ router.put('/:user_sid', async(req, res) => {
if (0 === r.changedRows) throw new Error('database update failed');
}
if (is_active) {
if (typeof is_active !== 'undefined') {
const r = await promisePool.execute('UPDATE users SET is_active = ? WHERE user_sid = ?', [is_active, user_sid]);
if (0 === r.changedRows) throw new Error('database update failed');
}
if (force_change) {
if (typeof force_change !== 'undefined') {
const r = await promisePool.execute(
'UPDATE users SET force_change = ? WHERE user_sid = ?',
[force_change, user_sid]);
@@ -368,9 +373,16 @@ router.post('/', async(req, res) => {
try {
const email = allUsers.find((e) => e.email === payload.email);
const name = allUsers.find((e) => e.name === payload.name);
if (name) {
logger.debug({payload}, 'user with this username already exists');
return res.status(422).json({msg: 'user with this username already exists'});
}
if (email) {
logger.debug({payload}, 'user with this email already exists');
res.status(422).json({msg: 'user with this email already exists'});
return res.status(422).json({msg: 'user with this email already exists'});
}
if (decodedJwt.scope === 'admin') {
@@ -412,7 +424,8 @@ router.delete('/:user_sid', async(req, res) => {
const user = await User.retrieve(user_sid);
try {
if (decodedJwt.scope === 'admin' && activeAdminUsers.length === 1) {
if (decodedJwt.scope === 'admin' && !user.account_sid && !user.service_provider_sid &&
activeAdminUsers.length === 1) {
throw new Error('cannot delete this admin user - there are no other active admin users');
}

View File

@@ -3382,6 +3382,9 @@ paths:
messaging_hook:
$ref: '#/components/schemas/Webhook'
description: application webhook to handle inbound SMS/MMS messages
app_json:
type: string
description: Voice Application Json, call_hook will not be invoked if app_json is provided
speech_synthesis_vendor:
type: string
speech_synthesis_voice:

View File

@@ -0,0 +1,112 @@
const {DbErrorBadRequest} = require('../utils/errors');
const assert = require('assert');
const _specData = require('./specs');
const specs = new Map();
for (const key in _specData) { specs.set(key, _specData[key]); }
function normalizeJambones(logger, obj) {
if (!Array.isArray(obj)) throw new DbErrorBadRequest('malformed jambonz payload: must be array');
const document = [];
for (const tdata of obj) {
if (typeof tdata !== 'object') throw new DbErrorBadRequest('malformed jambonz payload: must be array of objects');
if ('verb' in tdata) {
// {verb: 'say', text: 'foo..bar'..}
const name = tdata.verb;
const o = {};
Object.keys(tdata)
.filter((k) => k !== 'verb')
.forEach((k) => o[k] = tdata[k]);
const o2 = {};
o2[name] = o;
document.push(o2);
}
else if (Object.keys(tdata).length === 1) {
// {'say': {..}}
document.push(tdata);
}
else {
logger.info(tdata, 'malformed jambonz payload: missing verb property');
throw new DbErrorBadRequest('malformed jambonz payload: missing verb property');
}
}
logger.debug({ document }, `normalizeJambones: returning document with ${document.length} tasks`);
return document;
}
function validate(logger, obj) {
normalizeJambones(logger, obj).map((tdata) => {
const keys = Object.keys(tdata);
const name = keys[0];
const data = tdata[name];
validateVerb(name, data, logger);
});
}
function validateVerb(name, data, logger) {
logger.debug(`validating ${name} with data ${JSON.stringify(data)}`);
// validate the instruction is supported
if (!specs.has(name)) throw new DbErrorBadRequest(`invalid instruction: ${name}`);
// check type of each element and make sure required elements are present
const specData = specs.get(name);
let required = specData.required || [];
for (const dKey in data) {
if (dKey in specData.properties) {
const dVal = data[dKey];
const dSpec = specData.properties[dKey];
logger.debug(`Task:validate validating property ${dKey} with value ${JSON.stringify(dVal)}`);
if (typeof dSpec === 'string' && dSpec === 'array') {
if (!Array.isArray(dVal)) throw new DbErrorBadRequest(`${name}: property ${dKey} is not an array`);
}
else if (typeof dSpec === 'string' && dSpec.includes('|')) {
const types = dSpec.split('|').map((t) => t.trim());
if (!types.includes(typeof dVal) && !(types.includes('array') && Array.isArray(dVal))) {
throw new DbErrorBadRequest(`${name}: property ${dKey} has invalid data type, must be one of ${types}`);
}
}
else if (typeof dSpec === 'string' && ['number', 'string', 'object', 'boolean'].includes(dSpec)) {
// simple types
if (typeof dVal !== specData.properties[dKey]) {
throw new DbErrorBadRequest(`${name}: property ${dKey} has invalid data type`);
}
}
else if (Array.isArray(dSpec) && dSpec[0].startsWith('#')) {
const name = dSpec[0].slice(1);
for (const item of dVal) {
validate(name, item);
}
}
else if (typeof dSpec === 'object') {
// complex types
const type = dSpec.type;
assert.ok(['number', 'string', 'object', 'boolean'].includes(type),
`invalid or missing type in spec ${JSON.stringify(dSpec)}`);
if (type === 'string' && dSpec.enum) {
assert.ok(Array.isArray(dSpec.enum), `enum must be an array ${JSON.stringify(dSpec.enum)}`);
if (!dSpec.enum.includes(dVal)) {
throw new DbErrorBadRequest(`invalid value ${dVal} must be one of ${dSpec.enum}`);
}
}
}
else if (typeof dSpec === 'string' && dSpec.startsWith('#')) {
// reference to another datatype (i.e. nested type)
const name = dSpec.slice(1);
//const obj = {};
//obj[name] = dVal;
validate(name, dVal);
}
else {
assert.ok(0, `invalid spec ${JSON.stringify(dSpec)}`);
}
required = required.filter((item) => item !== dKey);
}
else if (dKey === '_') {
/* no op: allow arbitrary info to be carried here, used by conference e.g in transfer */
}
else throw new DbErrorBadRequest(`${name}: unknown property ${dKey}`);
}
if (required.length > 0) throw new DbErrorBadRequest(`${name}: missing value for ${required}`);
}
module.exports = validate;

759
lib/utils/specs.json Normal file
View File

@@ -0,0 +1,759 @@
{
"sip:decline": {
"properties": {
"id": "string",
"status": "number",
"reason": "string",
"headers": "object"
},
"required": [
"status"
]
},
"sip:request": {
"properties": {
"id": "string",
"method": "string",
"body": "string",
"headers": "object",
"actionHook": "object|string"
},
"required": [
"method"
]
},
"sip:refer": {
"properties": {
"id": "string",
"referTo": "string",
"referredBy": "string",
"headers": "object",
"actionHook": "object|string",
"eventHook": "object|string"
},
"required": [
"referTo"
]
},
"config": {
"properties": {
"id": "string",
"synthesizer": "#synthesizer",
"recognizer": "#recognizer",
"bargeIn": "#bargeIn",
"record": "#recordOptions",
"amd": "#amd",
"notifyEvents": "boolean"
},
"required": []
},
"bargeIn": {
"properties": {
"enable": "boolean",
"sticky": "boolean",
"actionHook": "object|string",
"input": "array",
"finishOnKey": "string",
"numDigits": "number",
"minDigits": "number",
"maxDigits": "number",
"interDigitTimeout": "number",
"dtmfBargein": "boolean",
"minBargeinWordCount": "number"
},
"required": [
"enable"
]
},
"dequeue": {
"properties": {
"id": "string",
"name": "string",
"actionHook": "object|string",
"timeout": "number",
"beep": "boolean"
},
"required": [
"name"
]
},
"enqueue": {
"properties": {
"id": "string",
"name": "string",
"actionHook": "object|string",
"waitHook": "object|string",
"_": "object"
},
"required": [
"name"
]
},
"leave": {
"properties": {
"id": "string"
}
},
"hangup": {
"properties": {
"id": "string",
"headers": "object"
},
"required": [
]
},
"play": {
"properties": {
"id": "string",
"url": "string|array",
"loop": "number|string",
"earlyMedia": "boolean",
"seekOffset": "number|string",
"timeoutSecs": "number|string",
"actionHook": "object|string"
},
"required": [
"url"
]
},
"say": {
"properties": {
"id": "string",
"text": "string|array",
"loop": "number|string",
"synthesizer": "#synthesizer",
"earlyMedia": "boolean",
"disableTtsCache": "boolean"
},
"required": [
"text"
]
},
"gather": {
"properties": {
"id": "string",
"actionHook": "object|string",
"finishOnKey": "string",
"input": "array",
"numDigits": "number",
"minDigits": "number",
"maxDigits": "number",
"interDigitTimeout": "number",
"partialResultHook": "object|string",
"speechTimeout": "number",
"listenDuringPrompt": "boolean",
"dtmfBargein": "boolean",
"bargein": "boolean",
"minBargeinWordCount": "number",
"timeout": "number",
"recognizer": "#recognizer",
"play": "#play",
"say": "#say"
},
"required": [
]
},
"conference": {
"properties": {
"id": "string",
"name": "string",
"beep": "boolean",
"startConferenceOnEnter": "boolean",
"endConferenceOnExit": "boolean",
"maxParticipants": "number",
"joinMuted": "boolean",
"actionHook": "object|string",
"waitHook": "object|string",
"statusEvents": "array",
"statusHook": "object|string",
"enterHook": "object|string",
"record": "#record"
},
"required": [
"name"
]
},
"dial": {
"properties": {
"id": "string",
"actionHook": "object|string",
"answerOnBridge": "boolean",
"callerId": "string",
"confirmHook": "object|string",
"referHook": "object|string",
"dialMusic": "string",
"dtmfCapture": "object",
"dtmfHook": "object|string",
"headers": "object",
"listen": "#listen",
"target": ["#target"],
"timeLimit": "number",
"timeout": "number",
"proxy": "string",
"transcribe": "#transcribe",
"amd": "#amd"
},
"required": [
"target"
]
},
"dialogflow": {
"properties": {
"id": "string",
"credentials": "object|string",
"project": "string",
"environment": "string",
"region": {
"type": "string",
"enum": ["europe-west1", "europe-west2", "australia-southeast1", "asia-northeast1"]
},
"lang": "string",
"actionHook": "object|string",
"eventHook": "object|string",
"events": "[string]",
"welcomeEvent": "string",
"welcomeEventParams": "object",
"noInputTimeout": "number",
"noInputEvent": "string",
"passDtmfAsTextInput": "boolean",
"thinkingMusic": "string",
"tts": "#synthesizer",
"bargein": "boolean"
},
"required": [
"project",
"credentials",
"lang"
]
},
"dtmf": {
"properties": {
"id": "string",
"dtmf": "string",
"duration": "number"
},
"required": [
"dtmf"
]
},
"lex": {
"properties": {
"id": "string",
"botId": "string",
"botAlias": "string",
"credentials": "object",
"region": "string",
"locale": "string",
"intent": "#lexIntent",
"welcomeMessage": "string",
"metadata": "object",
"bargein": "boolean",
"passDtmf": "boolean",
"actionHook": "object|string",
"eventHook": "object|string",
"noInputTimeout": "number",
"tts": "#synthesizer"
},
"required": [
"botId",
"botAlias",
"region",
"credentials"
]
},
"listen": {
"properties": {
"id": "string",
"actionHook": "object|string",
"auth": "#auth",
"finishOnKey": "string",
"maxLength": "number",
"metadata": "object",
"mixType": {
"type": "string",
"enum": ["mono", "stereo", "mixed"]
},
"passDtmf": "boolean",
"playBeep": "boolean",
"sampleRate": "number",
"timeout": "number",
"transcribe": "#transcribe",
"url": "string",
"wsAuth": "#auth",
"earlyMedia": "boolean"
},
"required": [
"url"
]
},
"message": {
"properties": {
"id": "string",
"carrier": "string",
"account_sid": "string",
"message_sid": "string",
"to": "string",
"from": "string",
"text": "string",
"media": "string|array",
"actionHook": "object|string"
},
"required": [
"to",
"from"
]
},
"pause": {
"properties": {
"id": "string",
"length": "number"
},
"required": [
"length"
]
},
"rasa": {
"properties": {
"id": "string",
"url": "string",
"recognizer": "#recognizer",
"tts": "#synthesizer",
"prompt": "string",
"actionHook": "object|string",
"eventHook": "object|string"
},
"required": [
"url"
]
},
"record": {
"properties": {
"path": "string"
},
"required": [
"path"
]
},
"recordOptions": {
"properties": {
"action": {
"type": "string",
"enum": ["startCallRecording", "stopCallRecording", "pauseCallRecording", "resumeCallRecording"]
},
"recordingID": "string",
"siprecServerURL": "string"
},
"required": [
"action"
]
},
"redirect": {
"properties": {
"id": "string",
"actionHook": "object|string"
},
"required": [
"actionHook"
]
},
"rest:dial": {
"properties": {
"id": "string",
"account_sid": "string",
"application_sid": "string",
"call_hook": "object|string",
"call_status_hook": "object|string",
"from": "string",
"fromHost": "string",
"speech_synthesis_vendor": "string",
"speech_synthesis_voice": "string",
"speech_synthesis_language": "string",
"speech_recognizer_vendor": "string",
"speech_recognizer_language": "string",
"tag": "object",
"to": "#target",
"headers": "object",
"timeout": "number"
},
"required": [
"call_hook",
"from",
"to"
]
},
"tag": {
"properties": {
"id": "string",
"data": "object"
},
"required": [
"data"
]
},
"transcribe": {
"properties": {
"id": "string",
"transcriptionHook": "string",
"recognizer": "#recognizer",
"earlyMedia": "boolean"
},
"required": [
"recognizer"
]
},
"target": {
"properties": {
"type": {
"type": "string",
"enum": ["phone", "sip", "user", "teams"]
},
"confirmHook": "object|string",
"method": {
"type": "string",
"enum": ["GET", "POST"]
},
"headers": "object",
"from": "#dialFrom",
"name": "string",
"number": "string",
"sipUri": "string",
"auth": "#auth",
"vmail": "boolean",
"tenant": "string",
"trunk": "string",
"overrideTo": "string"
},
"required": [
"type"
]
},
"dialFrom": {
"properties": {
"user": "string",
"host": "string"
},
"required": [
]
},
"auth": {
"properties": {
"username": "string",
"password": "string"
},
"required": [
"username",
"password"
]
},
"synthesizer": {
"properties": {
"vendor": {
"type": "string",
"enum": ["google", "aws", "polly", "microsoft", "nuance", "ibm", "default"]
},
"language": "string",
"voice": "string",
"engine": {
"type": "string",
"enum": ["standard", "neural"]
},
"gender": {
"type": "string",
"enum": ["MALE", "FEMALE", "NEUTRAL"]
}
},
"required": [
"vendor"
]
},
"recognizer": {
"properties": {
"vendor": {
"type": "string",
"enum": ["google", "aws", "microsoft", "nuance", "deepgram", "ibm", "default"]
},
"language": "string",
"vad": "#vad",
"hints": "array",
"hintsBoost": "number",
"altLanguages": "array",
"profanityFilter": "boolean",
"interim": "boolean",
"singleUtterance": "boolean",
"dualChannel": "boolean",
"separateRecognitionPerChannel": "boolean",
"punctuation": "boolean",
"enhancedModel": "boolean",
"words": "boolean",
"diarization": "boolean",
"diarizationMinSpeakers": "number",
"diarizationMaxSpeakers": "number",
"interactionType": {
"type": "string",
"enum": [
"unspecified",
"discussion",
"presentation",
"phone_call",
"voicemail",
"voice_search",
"voice_command",
"dictation"
]
},
"naicsCode": "number",
"identifyChannels": "boolean",
"vocabularyName": "string",
"vocabularyFilterName": "string",
"filterMethod": {
"type": "string",
"enum": [
"remove",
"mask",
"tag"
]
},
"model": "string",
"outputFormat": {
"type": "string",
"enum": [
"simple",
"detailed"
]
},
"profanityOption": {
"type": "string",
"enum": [
"masked",
"removed",
"raw"
]
},
"requestSnr": "boolean",
"initialSpeechTimeoutMs": "number",
"azureServiceEndpoint": "string",
"azureSttEndpointId": "string",
"asrDtmfTerminationDigit": "string",
"asrTimeout": "number",
"nuanceOptions": "#nuanceOptions",
"deepgramOptions": "#deepgramOptions",
"ibmOptions": "#ibmOptions"
},
"required": [
"vendor"
]
},
"ibmOptions": {
"properties": {
"sttApiKey": "string",
"sttRegion": "string",
"ttsApiKey": "string",
"ttsRegion": "string",
"instanceId": "string",
"model": "string",
"languageCustomizationId": "string",
"acousticCustomizationId": "string",
"baseModelVersion": "string",
"watsonMetadata": "string",
"watsonLearningOptOut": "boolean"
},
"required": [
]
},
"deepgramOptions": {
"properties": {
"apiKey": "string",
"tier": {
"type": "string",
"enum": [
"enhanced",
"base"
]
},
"model": {
"type": "string",
"enum": [
"general",
"meeting",
"phonecall",
"voicemail",
"finance",
"conversationalai",
"video",
"custom"
]
},
"customModel": "string",
"version": "string",
"punctuate": "boolean",
"profanityFilter": "boolean",
"redact": {
"type": "string",
"enum": [
"pci",
"numbers",
"true",
"ssn"
]
},
"diarize": "boolean",
"diarizeVersion": "string",
"ner": "boolean",
"multichannel": "boolean",
"alternatives": "number",
"numerals": "boolean",
"search": "array",
"replace": "array",
"keywords": "array",
"endpointing": "boolean",
"vadTurnoff": "number",
"tag": "string"
}
},
"nuanceOptions": {
"properties": {
"clientId": "string",
"secret": "string",
"kryptonEndpoint": "string",
"topic": "string",
"utteranceDetectionMode": {
"type": "string",
"enum": [
"single",
"multiple",
"disabled"
]
},
"punctuation": "boolean",
"profanityFilter": "boolean",
"includeTokenization": "boolean",
"discardSpeakerAdaptation": "boolean",
"suppressCallRecording": "boolean",
"maskLoadFailures": "boolean",
"suppressInitialCapitalization": "boolean",
"allowZeroBaseLmWeight": "boolean",
"filterWakeupWord": "boolean",
"resultType": {
"type": "string",
"enum": [
"final",
"partial",
"immutable_partial"
]
},
"noInputTimeoutMs": "number",
"recognitionTimeoutMs": "number",
"utteranceEndSilenceMs": "number",
"maxHypotheses": "number",
"speechDomain": "string",
"formatting": "#formatting",
"clientData": "object",
"userId": "string",
"speechDetectionSensitivity": "number",
"resources": ["#resource"]
},
"required": [
]
},
"resource": {
"properties": {
"externalReference": "#resourceReference",
"inlineWordset": "string",
"builtin": "string",
"inlineGrammar": "string",
"wakeupWord": "[string]",
"weightName": {
"type": "string",
"enum": [
"defaultWeight",
"lowest",
"low",
"medium",
"high",
"highest"
]
},
"weightValue": "number",
"reuse": {
"type": "string",
"enum": [
"undefined_reuse",
"low_reuse",
"high_reuse"
]
}
},
"required": [
]
},
"resourceReference": {
"properties": {
"type": {
"type": "string",
"enum": [
"undefined_resource_type",
"wordset",
"compiled_wordset",
"domain_lm",
"speaker_profile",
"grammar",
"settings"
]
},
"uri": "string",
"maxLoadFailures": "boolean",
"requestTimeoutMs": "number",
"headers": "object"
},
"required": [
]
},
"formatting": {
"properties": {
"scheme": "string",
"options": "object"
},
"required": [
"scheme",
"options"
]
},
"lexIntent": {
"properties": {
"name": "string",
"slots": "object"
},
"required": [
"name"
]
},
"vad": {
"properties": {
"enable": "boolean",
"voiceMs": "number",
"mode": "number"
},
"required": [
"enable"
]
},
"amd": {
"properties": {
"actionHook": "object|string",
"thresholdWordCount": "number",
"timers": "#amdTimers",
"recognizer": "#recognizer"
},
"required": [
"actionHook"
]
},
"amdTimers": {
"properties": {
"noSpeechTimeoutMs": "number",
"decisionTimeoutMs": "number",
"toneTimeoutMs": "number",
"greetingCompletionTimeoutMs": "number"
}
}
}

View File

@@ -23,6 +23,37 @@ test('application tests', async(t) => {
const service_provider_sid = await createServiceProvider(request);
const phone_number_sid = await createPhoneNumber(request, voip_carrier_sid);
const account_sid = await createAccount(request, service_provider_sid);
/* add an invalid application app_json */
result = await request.post('/Applications', {
resolveWithFullResponse: true,
simple: false,
auth: authAdmin,
json: true,
body: {
name: 'daveh',
account_sid,
call_hook: {
url: 'http://example.com'
},
call_status_hook: {
url: 'http://example.com/status',
method: 'POST'
},
messaging_hook: {
url: 'http://example.com/sms'
},
app_json : '[\
{\
"verb": "play",\
"timeoutSecs": 10,\
"seekOffset": 8000,\
"actionHook": "/play/action"\
}\
]'
}
});
t.ok(result.statusCode === 400, 'Cant create application with invalid app_josn');
/* add an application */
result = await request.post('/Applications', {
@@ -41,7 +72,16 @@ test('application tests', async(t) => {
},
messaging_hook: {
url: 'http://example.com/sms'
}
},
app_json : '[\
{\
"verb": "play",\
"url": "https://example.com/example.mp3",\
"timeoutSecs": 10,\
"seekOffset": 8000,\
"actionHook": "/play/action"\
}\
]'
}
});
t.ok(result.statusCode === 201, 'successfully created application');
@@ -62,6 +102,9 @@ test('application tests', async(t) => {
});
t.ok(result.name === 'daveh' , 'successfully retrieved application by sid');
t.ok(result.messaging_hook.url === 'http://example.com/sms' , 'successfully retrieved messaging_hook from application');
let app_json = JSON.parse(result.app_json);
t.ok(app_json[0].verb === 'play', 'successfully retrieved app_json from application')
/* update applications */
result = await request.put(`/Applications/${sid}`, {
@@ -74,7 +117,15 @@ test('application tests', async(t) => {
},
messaging_hook: {
url: 'http://example2.com/mms'
}
},
app_json : '[\
{\
"verb": "hangup",\
"headers": {\
"X-Reason" : "maximum call duration exceeded"\
}\
}\
]'
}
});
t.ok(result.statusCode === 204, 'successfully updated application');
@@ -85,6 +136,57 @@ test('application tests', async(t) => {
json: true,
});
t.ok(result.messaging_hook.url === 'http://example2.com/mms' , 'successfully updated messaging_hook');
app_json = JSON.parse(result.app_json);
t.ok(app_json[0].verb === 'hangup', 'successfully updated app_json from application')
/* remove applications app_json*/
result = await request.put(`/Applications/${sid}`, {
auth: authAdmin,
json: true,
resolveWithFullResponse: true,
body: {
call_hook: {
url: 'http://example2.com'
},
messaging_hook: {
url: 'http://example2.com/mms'
},
app_json : null
}
});
t.ok(result.statusCode === 204, 'successfully updated application');
/* validate messaging hook was updated */
result = await request.get(`/Applications/${sid}`, {
auth: authAdmin,
json: true,
});
t.ok(result.app_json == undefined, 'successfully removed app_json from application')
/* Update invalid applications app_json*/
result = await request.put(`/Applications/${sid}`, {
auth: authAdmin,
json: true,
resolveWithFullResponse: true,
simple: false,
body: {
call_hook: {
url: 'http://example2.com'
},
messaging_hook: {
url: 'http://example2.com/mms'
},
app_json : '[\
{\
"verb": "play",\
"timeoutSecs": 10,\
"seekOffset": 8000,\
"actionHook": "/play/action"\
}\
]'
}
});
t.ok(result.statusCode === 400, 'Cant update invalid application app_json');
/* assign phone number to application */
result = await request.put(`/PhoneNumbers/${phone_number_sid}`, {

View File

@@ -86,7 +86,7 @@ test('service provider tests', async(t) => {
auth: authAdmin,
json: true,
});
console.log(JSON.stringify(result));
//console.log(JSON.stringify(result));
t.ok(result.length === 2 , 'successfully queried all service providers');
/* query one service providers */