Compare commits

..

1 Commits

Author SHA1 Message Date
Dave Horton
f66aabba53 handle PUT of VoipCarrier that is associated to a service provider 2021-06-18 12:13:38 -04:00
53 changed files with 287 additions and 14075 deletions

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -8,7 +8,7 @@
"jsx": false,
"modules": false
},
"ecmaVersion": 2020
"ecmaVersion": 2018
},
"plugins": ["promise"],
"rules": {

View File

@@ -1,51 +0,0 @@
name: Docker
on:
push:
# Publish `main` as Docker `latest` image.
branches:
- main
# Publish `v1.2.3` tags as releases.
tags:
- v*
env:
IMAGE_NAME: db-create
jobs:
push:
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- name: Build image
run: docker build . --file Dockerfile.db-create --tag $IMAGE_NAME
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "main" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION

View File

@@ -1,51 +0,0 @@
name: Docker
on:
push:
# Publish `main` as Docker `latest` image.
branches:
- main
# Publish `v1.2.3` tags as releases.
tags:
- v*
env:
IMAGE_NAME: api-server
jobs:
push:
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- name: Build image
run: docker build . --file Dockerfile --tag $IMAGE_NAME
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "main" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION

6
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Logs
logs
*.log
package-lock.json
# Runtime data
pids
@@ -42,7 +43,4 @@ create_db.sql
.vscode
.env.*
.env
test/postgres-data
db/remove-account.sh
.env

View File

@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run jslint

View File

@@ -1,10 +1,16 @@
FROM node:17
FROM node:alpine as builder
RUN apk update && apk add --no-cache python make g++
WORKDIR /opt/app/
COPY package.json ./
RUN npm install
RUN npm prune
FROM node:alpine as app
WORKDIR /opt/app
COPY . /opt/app
COPY --from=builder /opt/app/node_modules ./node_modules
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
CMD [ "npm", "start" ]
CMD [ "npm", "start" ]

View File

@@ -1,10 +0,0 @@
FROM node:17
WORKDIR /opt/app/
COPY package.json ./
RUN npm install
RUN npm prune
COPY . /opt/app
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
CMD [ "npm", "run", "upgrade-db" ]

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Drachtio Communications Services, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

13
app.js
View File

@@ -9,8 +9,6 @@ const opts = Object.assign({
const logger = require('pino')(opts);
const express = require('express');
const app = express();
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const passport = require('passport');
const routes = require('./lib/routes');
@@ -91,17 +89,6 @@ const unless = (paths, middleware) => {
};
};
const limiter = rateLimit({
windowMs: (process.env.RATE_LIMIT_WINDOWS_MINS || 5) * 60 * 1000, // 5 minutes
max: process.env.RATE_LIMIT_MAX_PER_WINDOW || 600, // Limit each IP to 600 requests per `window`
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
app.use(limiter);
app.use(helmet());
app.use(helmet.hidePoweredBy());
app.use(passport.initialize());
app.use(cors());
app.use(express.urlencoded({extended: true}));
app.use(unless(['/stripe'], express.json()));

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env node
const crypto = require('crypto');
const {promisePool} = require('../lib/db');
const sql = 'INSERT INTO beta_invite_codes (invite_code) VALUES (?);';
const rand_string = (n) => {
if (n <= 0) {
return '';
}
var rs = '';
try {
rs = crypto.randomBytes(Math.ceil(n/2)).toString('hex').slice(0,n);
/* note: could do this non-blocking, but still might fail */
}
catch (ex) {
/* known exception cause: depletion of entropy info for randomBytes */
console.error('Exception generating random string: ' + ex);
/* weaker random fallback */
rs = '';
var r = n % 8, q = (n - r) / 8, i;
for (i = 0; i < q; i++) {
rs += Math.random().toString(16).slice(2);
}
if (r > 0) {
rs += Math.random().toString(16).slice(2, i);
}
}
return rs;
};
const doIt = async(len) => {
for (let i = 0; i < 50; i++) {
const val = rand_string(len).toUpperCase();
await promisePool.execute(sql, [val]);
}
process.exit(0);
};
doIt(6);

View File

@@ -20,16 +20,12 @@ DROP TABLE IF EXISTS lcr_routes;
DROP TABLE IF EXISTS predefined_sip_gateways;
DROP TABLE IF EXISTS predefined_smpp_gateways;
DROP TABLE IF EXISTS predefined_carriers;
DROP TABLE IF EXISTS account_offers;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS schema_version;
DROP TABLE IF EXISTS api_keys;
DROP TABLE IF EXISTS sbc_addresses;
@@ -152,20 +148,6 @@ predefined_carrier_sid CHAR(36) NOT NULL,
PRIMARY KEY (predefined_sip_gateway_sid)
);
CREATE TABLE predefined_smpp_gateways
(
predefined_smpp_gateway_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. ',
port INTEGER NOT NULL DEFAULT 2775 COMMENT 'smpp signaling port',
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound SMS from the gateway',
outbound BOOLEAN NOT NULL COMMENT 'i',
netmask INTEGER NOT NULL DEFAULT 32,
is_primary BOOLEAN NOT NULL DEFAULT 1,
use_tls BOOLEAN DEFAULT 0,
predefined_carrier_sid CHAR(36) NOT NULL,
PRIMARY KEY (predefined_smpp_gateway_sid)
);
CREATE TABLE products
(
product_sid CHAR(36) NOT NULL UNIQUE ,
@@ -192,11 +174,6 @@ stripe_product_id VARCHAR(56) NOT NULL,
PRIMARY KEY (account_offer_sid)
);
CREATE TABLE schema_version
(
version VARCHAR(16)
);
CREATE TABLE api_keys
(
api_key_sid CHAR(36) NOT NULL UNIQUE ,
@@ -365,7 +342,7 @@ CREATE TABLE webhooks
(
webhook_sid CHAR(36) NOT NULL UNIQUE ,
url VARCHAR(1024) NOT NULL,
method ENUM("GET","POST","WS") NOT NULL DEFAULT 'POST',
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
username VARCHAR(255),
password VARCHAR(255),
PRIMARY KEY (webhook_sid)
@@ -418,10 +395,6 @@ disable_cdrs BOOLEAN NOT NULL DEFAULT 0,
trial_end_date DATETIME,
deactivated_reason VARCHAR(255),
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
subspace_client_id VARCHAR(255),
subspace_client_secret VARCHAR(255),
subspace_sip_teleport_id VARCHAR(255),
subspace_sip_teleport_destinations VARCHAR(255),
PRIMARY KEY (account_sid)
) COMMENT='An enterprise that uses the platform for comm services';
@@ -447,10 +420,6 @@ CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefin
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
ALTER TABLE predefined_sip_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
CREATE INDEX predefined_smpp_gateway_sid_idx ON predefined_smpp_gateways (predefined_smpp_gateway_sid);
CREATE INDEX predefined_carrier_sid_idx ON predefined_smpp_gateways (predefined_carrier_sid);
ALTER TABLE predefined_smpp_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk_1 (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
CREATE INDEX product_sid_idx ON products (product_sid);
CREATE INDEX account_product_sid_idx ON account_products (account_product_sid);
CREATE INDEX account_subscription_sid_idx ON account_products (account_subscription_sid);
@@ -576,4 +545,4 @@ ALTER TABLE accounts ADD FOREIGN KEY queue_event_hook_sid_idxfk (queue_event_hoo
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=0;
SET FOREIGN_KEY_CHECKS=1;

File diff suppressed because one or more lines are too long

View File

@@ -1,27 +1,24 @@
#!/usr/bin/env node
console.log('reset_admin_password');
const {promisePool} = require('../lib/db');
const { v4: uuidv4 } = require('uuid');
const uuidv4 = require('uuid/v4');
const {generateHashedPassword} = require('../lib/utils/password-utils');
const sqlInsert = `INSERT into users
(user_sid, name, email, hashed_password, force_change, provider, email_validated)
values (?, ?, ?, ?, ?, ?, ?)
`;
const sqlInsertAdminToken = `INSERT into api_keys
(api_key_sid, token)
values (?, ?)`;
const sqlQueryAccount = 'SELECT * from accounts LEFT JOIN api_keys ON api_keys.account_sid = accounts.account_sid';
const sqlAddAccountToken = `INSERT into api_keys (api_key_sid, token, account_sid)
const sqlChangeAdminToken = `UPDATE api_keys set token = ?
WHERE account_sid IS NULL
AND service_provider_sid IS NULL`;
const sqlQueryAccount = 'SELECT * from accounts LIMIT 1';
const sqlAddAccountAdminToken = `INSERT into api_keys (api_key_sid, token, account_sid)
VALUES (?, ?, ?)`;
const password = process.env.JAMBONES_ADMIN_INITIAL_PASSWORD || 'admin';
console.log(`reset_admin_password, initial admin password is ${password}`);
const doIt = async() => {
const passwordHash = await generateHashedPassword(password);
const passwordHash = await generateHashedPassword('admin');
const sid = uuidv4();
await promisePool.execute('DELETE from users where name = "admin"');
await promisePool.execute('DELETE from api_keys where account_sid is null and service_provider_sid is null');
await promisePool.execute(sqlInsert,
await promisePool.execute(sqlInsert,
[
sid,
'admin',
@@ -32,16 +29,16 @@ const doIt = async() => {
1
]
);
await promisePool.execute(sqlInsertAdminToken, [uuidv4(), uuidv4()]);
/* reset admin token */
const uuid = uuidv4();
await promisePool.query(sqlChangeAdminToken, [uuid]);
/* create admin token for single account */
const [r] = await promisePool.query({sql: sqlQueryAccount, nestTables: true});
if (1 === r.length && r[0].api_keys.api_key_sid === null) {
const api_key_sid = uuidv4();
const token = uuidv4();
const {account_sid} = r[0].accounts;
await promisePool.execute(sqlAddAccountToken, [api_key_sid, token, account_sid]);
}
const api_key_sid = uuidv4();
const token = uuidv4();
const [r] = await promisePool.query(sqlQueryAccount);
await promisePool.execute(sqlAddAccountAdminToken, [api_key_sid, token, r[0].account_sid]);
process.exit(0);
};

View File

@@ -44,23 +44,12 @@ insert into predefined_carriers (predefined_carrier_sid, name, requires_static_i
requires_register, register_username, register_password,
register_sip_realm, tech_prefix, inbound_auth_username, inbound_auth_password, diversion)
VALUES
('17479288-bb9f-421a-89d1-f4ac57af1dca', 'TelecomsXChange', 0, 0, 0, NULL, NULL, NULL, 'your-tech-prefix', NULL, NULL, NULL),
('7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', 'Twilio', 0, 1, 0, '<your-twilio-credential-username>', '<your-twilio-credential-password>', NULL, NULL, NULL, NULL, NULL),
('032d90d5-39e8-41c0-b807-9c88cffba65c', 'Voxbone', 0, 1, 0, '<your-voxbone-outbound-username>', '<your-voxbone-outbound-password>', NULL, NULL, NULL, NULL, '<your-voxbone-DID>'),
('17479288-bb9f-421a-89d1-f4ac57af1dca', 'Peerless Network (US)', 1, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
('bdf70650-5328-47aa-b3d0-47cb219d9c6e', '382 Communications (US)', 1, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
('e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'Simwood', 0, 1, 0, '<your-simwood-auth-trunk-username>', '<your-simwood-auth-trunk-password>', NULL, NULL, NULL, NULL, NULL);
-- TelecomXchange gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('c9c3643e-9a83-4b78-b172-9c09d911bef5', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.213', 32, 5060, 1, 0),
('3b5b7fa5-4e61-4423-b921-05c3283b2101', '17479288-bb9f-421a-89d1-f4ac57af1dca', 'sip01.TelecomsXChange.com', 32, 5060, 0, 1);
insert into predefined_smpp_gateways (predefined_smpp_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('9b72467a-cfe3-491f-80bf-652c38e666b9', '17479288-bb9f-421a-89d1-f4ac57af1dca', 'smpp01.telecomsxchange.com', 32, 2776, 0, 1),
('d22883b9-f124-4a89-bab2-4487cf783f64', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.11', 32, 2775, 1, 0),
('fdcf7f1e-1f5f-487b-afb3-c0f75ed0aa3d', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.213', 32, 2775, 1, 0);
-- twilio gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
@@ -85,6 +74,19 @@ VALUES
('7bae60b3-4237-4baa-a711-30ea3bce19d8', '032d90d5-39e8-41c0-b807-9c88cffba65c', '185.47.148.45', 32, 5060, 1, 0),
('bc933522-18a2-47d8-9ae4-9faa8de4e927', '032d90d5-39e8-41c0-b807-9c88cffba65c', 'outbound.voxbone.com', 32, 5060, 0, 1);
-- Peerless gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('4e23f698-a70a-4616-9bf0-c9dd5ab123af', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.54.182', 32, 5060, 1, 0),
('e5c71c18-0511-41b8-bed9-1ba061bbcf10', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.52.192', 32, 5060, 0, 1),
('226c7471-2f4f-440f-8525-37fd0512bd8b', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.54.185', 32, 5060, 0, 1);
-- 382com gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('23e4c250-8578-4d88-99b5-a7941a58e26f', 'bdf70650-5328-47aa-b3d0-47cb219d9c6e', '64.125.111.10', 32, 5060, 1, 0),
('c726d435-c9a7-4c37-b891-775990a54638', 'bdf70650-5328-47aa-b3d0-47cb219d9c6e', '64.124.67.11', 32, 5060, 0, 1);
-- simwood gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
@@ -97,5 +99,4 @@ VALUES
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 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

@@ -49,12 +49,6 @@ VALUES
('c9c3643e-9a83-4b78-b172-9c09d911bef5', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.213', 32, 5060, 1, 0),
('3b5b7fa5-4e61-4423-b921-05c3283b2101', '17479288-bb9f-421a-89d1-f4ac57af1dca', 'sip01.TelecomsXChange.com', 32, 5060, 0, 1);
insert into predefined_smpp_gateways (predefined_smpp_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('9b72467a-cfe3-491f-80bf-652c38e666b9', '17479288-bb9f-421a-89d1-f4ac57af1dca', 'smpp01.telecomsxchange.com', 32, 2776, 0, 1),
('d22883b9-f124-4a89-bab2-4487cf783f64', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.11', 32, 2775, 1, 0),
('fdcf7f1e-1f5f-487b-afb3-c0f75ed0aa3d', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.213', 32, 2775, 1, 0);
-- twilio gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
@@ -79,6 +73,19 @@ VALUES
('7bae60b3-4237-4baa-a711-30ea3bce19d8', '032d90d5-39e8-41c0-b807-9c88cffba65c', '185.47.148.45', 32, 5060, 1, 0),
('bc933522-18a2-47d8-9ae4-9faa8de4e927', '032d90d5-39e8-41c0-b807-9c88cffba65c', 'outbound.voxbone.com', 32, 5060, 0, 1);
-- Peerless gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('4e23f698-a70a-4616-9bf0-c9dd5ab123af', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.54.182', 32, 5060, 1, 0),
('e5c71c18-0511-41b8-bed9-1ba061bbcf10', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.52.192', 32, 5060, 0, 1),
('226c7471-2f4f-440f-8525-37fd0512bd8b', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.54.185', 32, 5060, 0, 1);
-- 382com gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('23e4c250-8578-4d88-99b5-a7941a58e26f', 'bdf70650-5328-47aa-b3d0-47cb219d9c6e', '64.125.111.10', 32, 5060, 1, 0),
('c726d435-c9a7-4c37-b891-775990a54638', 'bdf70650-5328-47aa-b3d0-47cb219d9c6e', '64.124.67.11', 32, 5060, 0, 1);
-- simwood gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES

View File

@@ -27,12 +27,6 @@ VALUES
('c9c3643e-9a83-4b78-b172-9c09d911bef5', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.213', 32, 5060, 1, 0),
('3b5b7fa5-4e61-4423-b921-05c3283b2101', '17479288-bb9f-421a-89d1-f4ac57af1dca', 'sip01.TelecomsXChange.com', 32, 5060, 0, 1);
insert into predefined_smpp_gateways (predefined_smpp_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('9b72467a-cfe3-491f-80bf-652c38e666b9', '17479288-bb9f-421a-89d1-f4ac57af1dca', 'smpp01.telecomsxchange.com', 32, 2776, 0, 1),
('d22883b9-f124-4a89-bab2-4487cf783f64', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.11', 32, 2775, 1, 0),
('fdcf7f1e-1f5f-487b-afb3-c0f75ed0aa3d', '17479288-bb9f-421a-89d1-f4ac57af1dca', '174.136.44.213', 32, 2775, 1, 0);
-- twilio gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
@@ -57,6 +51,19 @@ VALUES
('7bae60b3-4237-4baa-a711-30ea3bce19d8', '032d90d5-39e8-41c0-b807-9c88cffba65c', '185.47.148.45', 32, 5060, 1, 0),
('bc933522-18a2-47d8-9ae4-9faa8de4e927', '032d90d5-39e8-41c0-b807-9c88cffba65c', 'outbound.voxbone.com', 32, 5060, 0, 1);
-- Peerless gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('4e23f698-a70a-4616-9bf0-c9dd5ab123af', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.54.182', 32, 5060, 1, 0),
('e5c71c18-0511-41b8-bed9-1ba061bbcf10', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.52.192', 32, 5060, 0, 1),
('226c7471-2f4f-440f-8525-37fd0512bd8b', '17479288-bb9f-421a-89d1-f4ac57af1dca', '208.79.54.185', 32, 5060, 0, 1);
-- 382com gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES
('23e4c250-8578-4d88-99b5-a7941a58e26f', 'bdf70650-5328-47aa-b3d0-47cb219d9c6e', '64.125.111.10', 32, 5060, 1, 0),
('c726d435-c9a7-4c37-b891-775990a54638', 'bdf70650-5328-47aa-b3d0-47cb219d9c6e', '64.124.67.11', 32, 5060, 0, 1);
-- simwood gateways
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
VALUES

View File

@@ -1,74 +0,0 @@
#!/usr/bin/env node
const assert = require('assert');
const mysql = require('mysql2/promise');
const {readFile} = require('fs/promises');
const {execSync} = require('child_process');
const {version:desiredVersion} = require('../package.json');
const logger = require('pino')();
logger.info(`upgrade-jambonz-db: desired version ${desiredVersion}`);
assert.ok(process.env.JAMBONES_MYSQL_HOST, 'missing env JAMBONES_MYSQL_HOST');
assert.ok(process.env.JAMBONES_MYSQL_DATABASE, 'missing env JAMBONES_MYSQL_DATABASE');
assert.ok(process.env.JAMBONES_MYSQL_PASSWORD, 'missing env JAMBONES_MYSQL_PASSWORD');
assert.ok(process.env.JAMBONES_MYSQL_USER, 'missing env JAMBONES_MYSQL_USER');
const opts = {
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
password: process.env.JAMBONES_MYSQL_PASSWORD,
database: process.env.JAMBONES_MYSQL_DATABASE,
port: process.env.JAMBONES_MYSQL_PORT || 3306,
multipleStatements: true
};
const doIt = async() => {
let connection;
try {
logger.info({opts}, 'connecting to mysql database..');
connection = await mysql.createConnection(opts);
} catch (err) {
logger.error({err}, 'Error connecting to database with provided env vars');
process.exit(1);
}
try {
/* does the schema exist at all ? */
const [r] = await connection.execute('SELECT version from schema_version');
if (r.length) {
//TODO: check against desired version and perform upgrades
logger.info(`current version is ${r[0].version}, no upgrade will be performed`);
await connection.end();
return;
}
} catch (err) {
}
try {
await createSchema(connection);
await seedDatabase(connection);
logger.info('reset admin password..');
execSync(`${__dirname}/../db/reset_admin_password.js`);
await connection.query(`INSERT into schema_version (version) values('${desiredVersion}')`);
logger.info('database install/upgrade complete.');
await connection.end();
} catch (err) {
logger.error({err}, 'Error seeding database');
process.exit(1);
}
};
const createSchema = async(connection) => {
logger.info('reading schema..');
const sql = await readFile(`${__dirname}/../db/jambones-sql.sql`, {encoding: 'utf8'});
logger.info('creating schema..');
await connection.query(sql);
};
const seedDatabase = async(connection) => {
const sql = await readFile(`${__dirname}/../db/seed-production-database-open-source.sql`, {encoding: 'utf8'});
logger.info('seeding data..');
await connection.query(sql);
};
doIt();

View File

@@ -2,15 +2,12 @@ const debug = require('debug')('jambonz:api-server');
const Model = require('./model');
const {getMysqlConnection} = require('../db');
const {promisePool} = require('../db');
const { v4: uuid } = require('uuid');
const uuid = require('uuid').v4;
const {encrypt} = require('../utils/encrypt-decrypt');
const retrieveSql = `SELECT * from accounts acc
LEFT JOIN webhooks AS rh
ON acc.registration_hook_sid = rh.webhook_sid
LEFT JOIN webhooks AS qh
ON acc.queue_event_hook_sid = qh.webhook_sid`;
ON acc.registration_hook_sid = rh.webhook_sid`;
const insertPendingAccountSubscriptionSql = `INSERT account_subscriptions
(account_subscription_sid, account_sid, pending, stripe_subscription_id,
@@ -58,23 +55,12 @@ AND pending = 0`;
function transmogrifyResults(results) {
return results.map((row) => {
const obj = row.acc;
/* registration hook */
if (row.rh && Object.keys(row.rh).length && row.rh.url !== null) {
Object.assign(obj, {registration_hook: row.rh});
delete obj.registration_hook.webhook_sid;
}
else obj.registration_hook = null;
delete obj.registration_hook_sid;
/* queue event hook */
if (row.qh && Object.keys(row.qh).length && row.qh.url !== null) {
Object.assign(obj, {queue_event_hook: row.qh});
delete obj.queue_event_hook.webhook_sid;
}
else obj.queue_event_hook = null;
delete obj.queue_event_hook_sid;
return obj;
});
}
@@ -262,10 +248,6 @@ Account.fields = [
name: 'sip_realm',
type: 'string',
},
{
name: 'queue_event_hook_sid',
type: 'string',
},
{
name: 'registration_hook_sid',
type: 'string',
@@ -297,23 +279,7 @@ Account.fields = [
{
name: 'disable_cdrs',
type: 'number',
},
{
name: 'subspace_client_id',
type: 'string',
},
{
name: 'subspace_client_secret',
type: 'string',
},
{
name: 'subspace_sip_teleport_id',
type: 'string',
},
{
name: 'subspace_sip_teleport_destinations',
type: 'string',
},
}
];
module.exports = Account;

View File

@@ -1,5 +1,5 @@
const Emitter = require('events');
const { v4: uuidv4 } = require('uuid');
const uuidv4 = require('uuid/v4');
const assert = require('assert');
const {getMysqlConnection} = require('../db');
const {DbErrorBadRequest} = require('../utils/errors');

View File

@@ -1,38 +1,45 @@
const Model = require('./model');
const {promisePool} = require('../db');
const {getMysqlConnection} = require('../db');
const sql = 'SELECT * from phone_numbers WHERE account_sid = ?';
const sqlSP = `SELECT *
FROM phone_numbers
WHERE account_sid IN
(
SELECT account_sid
FROM accounts
WHERE service_provider_sid = ?
)`;
class PhoneNumber extends Model {
constructor() {
super();
}
static async retrieveAll(account_sid) {
static retrieveAll(account_sid) {
if (!account_sid) return super.retrieveAll();
const [rows] = await promisePool.query(sql, account_sid);
return rows;
}
static async retrieveAllForSP(service_provider_sid) {
const [rows] = await promisePool.query(sqlSP, service_provider_sid);
return rows;
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
conn.query(sql, account_sid, (err, results, fields) => {
conn.release();
if (err) return reject(err);
resolve(results);
});
});
});
}
/**
* retrieve a phone number
* retrieve an application
*/
static async retrieve(sid, account_sid) {
static retrieve(sid, account_sid) {
if (!account_sid) return super.retrieve(sid);
const [rows] = await promisePool.query(`${sql} AND phone_number_sid = ?`, [account_sid, sid]);
return rows;
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
conn.query(`${sql} AND phone_number_sid = ?`, [account_sid, sid], (err, results, fields) => {
conn.release();
if (err) return reject(err);
resolve(results);
});
});
});
}
}
PhoneNumber.table = 'phone_numbers';

View File

@@ -8,40 +8,17 @@ const ApiKey = require('../../models/api-key');
const ServiceProvider = require('../../models/service-provider');
const {deleteDnsRecords} = require('../../utils/dns-utils');
const {deleteCustomer} = require('../../utils/stripe-utils');
const { v4: uuidv4 } = require('uuid');
const uuidv4 = require('uuid/v4');
const snakeCase = require('../../utils/snake-case');
const sysError = require('../error');
const {promisePool} = require('../../db');
const {hasAccountPermissions, parseAccountSid, enableSubspace, disableSubspace} = require('./utils');
const {hasAccountPermissions, parseAccountSid} = require('./utils');
const short = require('short-uuid');
const VoipCarrier = require('../../models/voip-carrier');
const translator = short();
let idx = 0;
const getFsUrl = async(logger, retrieveSet, setName) => {
if (process.env.K8S) return `http://${process.env.K8S_FEATURE_SERVER_SERVICE_NAME}:3000/v1/createCall`;
try {
const fs = await retrieveSet(setName);
if (0 === fs.length) {
logger.info('No available feature servers to handle createCall API request');
return ;
}
const ip = stripPort(fs[idx++ % fs.length]);
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
return `http://${ip}:3000/v1/createCall`;
} catch (err) {
logger.error({err}, 'getFsUrl: error retreving feature servers from redis');
}
};
const stripPort = (hostport) => {
const arr = /^(.*):(.*)$/.exec(hostport);
if (arr) return arr[1];
return hostport;
};
router.use('/:sid/SpeechCredentials', hasAccountPermissions, require('./speech-credentials'));
router.use('/:sid/RecentCalls', hasAccountPermissions, require('./recent-calls'));
router.use('/:sid/Alerts', hasAccountPermissions, require('./alerts'));
@@ -114,11 +91,7 @@ function validateUpdateCall(opts) {
'child_call_hook',
'call_status',
'listen_status',
'conf_hold_status',
'conf_mute_status',
'mute_status',
'sip_request'
]
'mute_status']
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
switch (count) {
@@ -131,7 +104,6 @@ function validateUpdateCall(opts) {
break;
case 2:
if (opts.call_hook && opts.child_call_hook) break;
else if (opts.conf_hold_status && opts.waitHook) break;
// eslint-disable-next-line no-fallthrough
default:
throw new DbErrorBadRequest('multiple options are not allowed in updateCall');
@@ -146,16 +118,6 @@ function validateUpdateCall(opts) {
if (opts.mute_status && !['mute', 'unmute'].includes(opts.mute_status)) {
throw new DbErrorBadRequest('invalid mute_status');
}
if (opts.conf_hold_status && !['hold', 'unhold'].includes(opts.conf_hold_status)) {
throw new DbErrorBadRequest('invalid conf_hold_status');
}
if (opts.conf_mute_status && !['mute', 'unmute'].includes(opts.conf_mute_status)) {
throw new DbErrorBadRequest('invalid conf_mute_status');
}
if (opts.sip_request &&
(!opts.sip_request.method && !opts.sip_request.content_type || !opts.sip_request.content_type)) {
throw new DbErrorBadRequest('sip_request requires content_type and content properties');
}
}
function validateTo(to) {
@@ -250,7 +212,7 @@ async function validateCreateCall(logger, sid, req) {
async function validateCreateMessage(logger, sid, req) {
const obj = req.body;
logger.debug({payload: req.body}, 'validateCreateMessage');
//const {lookupAccountByPhoneNumber} = req.app.locals;
if (req.user.account_sid !== sid) {
throw new DbErrorBadRequest(`unauthorized createMessage request for account ${sid}`);
@@ -291,9 +253,6 @@ async function validateAdd(req) {
if (req.body.registration_hook && typeof req.body.registration_hook !== 'object') {
throw new DbErrorBadRequest('\'registration_hook\' must be an object when adding an account');
}
if (req.body.queue_event_hook && typeof req.body.queue_event_hook !== 'object') {
throw new DbErrorBadRequest('\'queue_event_hook\' must be an object when adding an account');
}
}
async function validateUpdate(req, sid) {
if (req.user.hasAccountAuth && req.user.account_sid !== sid) {
@@ -332,12 +291,12 @@ router.post('/', async(req, res) => {
await validateAdd(req);
// create webhooks if provided
const obj = {...req.body, webhook_secret: secret};
for (const prop of ['registration_hook', 'queue_event_hook']) {
if (obj[prop] && obj[prop].url && obj[prop].url.length > 0) {
const obj = Object.assign({webhook_secret: secret}, req.body);
for (const prop of ['registration_hook']) {
if (obj[prop]) {
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
delete obj[prop];
}
delete obj[prop];
}
logger.debug(`Attempting to add account ${JSON.stringify(obj)}`);
@@ -394,58 +353,6 @@ router.get('/:sid/WebhookSecret', async(req, res) => {
}
});
router.post('/:sid/SubspaceTeleport', async(req, res) => {
const logger = req.app.locals.logger;
try {
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
const results = await Account.retrieve(req.params.sid, service_provider_sid);
if (results.length === 0) return res.status(404).end();
const {subspace_client_id, subspace_client_secret} = results[0];
const {destination} = req.body;
const arr = /^(.*):\d+$/.exec(destination);
const dest = arr ? `sip:${arr[1]}` : `sip:${destination}`;
const teleport = await enableSubspace({
subspace_client_id,
subspace_client_secret,
destination: dest
});
logger.info({destination, teleport}, 'SubspaceTeleport - create teleport');
await Account.update(req.params.sid, {
subspace_sip_teleport_id: teleport.id,
subspace_sip_teleport_destinations: JSON.stringify(teleport.teleport_entry_points)//hacky
});
return res.status(200).json({
subspace_sip_teleport_id: teleport.id,
subspace_sip_teleport_destinations: teleport.teleport_entry_points
});
}
catch (err) {
sysError(logger, res, err);
}
});
router.delete('/:sid/SubspaceTeleport', async(req, res) => {
const logger = req.app.locals.logger;
try {
const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null;
const results = await Account.retrieve(req.params.sid, service_provider_sid);
if (results.length === 0) return res.status(404).end();
const {subspace_client_id, subspace_client_secret, subspace_sip_teleport_id} = results[0];
await disableSubspace({subspace_client_id, subspace_client_secret, subspace_sip_teleport_id});
await Account.update(req.params.sid, {
subspace_sip_teleport_id: null,
subspace_sip_teleport_destinations: null
});
return res.sendStatus(204);
}
catch (err) {
sysError(logger, res, err);
}
});
/* update */
router.put('/:sid', async(req, res) => {
const sid = req.params.sid;
@@ -454,14 +361,12 @@ router.put('/:sid', async(req, res) => {
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['registration_hook', 'queue_event_hook']) {
if (prop in obj) {
if (null === obj[prop] || !obj[prop].url || 0 === obj[prop].url.length) {
obj[`${prop}_sid`] = null;
}
else if (typeof obj[prop] === 'object') {
if (null !== obj.registration_hook) {
for (const prop of ['registration_hook']) {
if (prop in obj && Object.keys(obj[prop]).length) {
if ('webhook_sid' in obj[prop]) {
const sid = obj[prop]['webhook_sid'];
delete obj[prop]['webhook_sid'];
await Webhook.update(sid, obj[prop]);
}
else {
@@ -469,35 +374,30 @@ router.put('/:sid', async(req, res) => {
obj[`${prop}_sid`] = sid;
}
}
else {
obj[`${prop}_sid`] = null;
}
delete obj[prop];
}
}
await validateUpdate(req, sid);
if (Object.keys(obj).length) {
let orphanedRegHook, orphanedQueueHook;
let orphanedHook;
if (null === obj.registration_hook) {
const results = await Account.retrieve(sid);
if (results.length && results[0].registration_hook_sid) orphanedRegHook = results[0].registration_hook_sid;
if (results.length && results[0].registration_hook_sid) orphanedHook = results[0].registration_hook_sid;
obj.registration_hook_sid = null;
delete obj.registration_hook;
}
if (null === obj.queue_event_hook) {
const results = await Account.retrieve(sid);
if (results.length && results[0].queue_event_hook_sid) orphanedQueueHook = results[0].queue_event_hook_sid;
obj.queue_event_hook_sid = null;
}
delete obj.registration_hook;
delete obj.queue_event_hook;
logger.info({obj}, `about to update Account ${sid}`);
const rowsAffected = await Account.update(sid, obj);
if (rowsAffected === 0) {
return res.status(404).end();
}
if (orphanedRegHook) {
await Webhook.remove(orphanedRegHook);
}
if (orphanedQueueHook) {
await Webhook.remove(orphanedQueueHook);
if (orphanedHook) {
await Webhook.remove(orphanedHook);
}
}
@@ -590,11 +490,18 @@ router.post('/:sid/Calls', async(req, res) => {
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
const {retrieveSet, logger} = req.app.locals;
const serviceUrl = await getFsUrl(logger, retrieveSet, setName);
if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480);
try {
const fs = await retrieveSet(setName);
if (0 === fs.length) {
logger.info('No available feature servers to handle createCall API request');
return res.json({msg: 'no available feature servers at this time'}).status(500);
}
const ip = fs[idx++ % fs.length];
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
const serviceUrl = `http://${ip}:3000/v1/createCall`;
await validateCreateCall(logger, sid, req);
logger.debug({payload: req.body}, `sending createCall API request to to ${ip}`);
updateLastUsed(logger, sid, req).catch((err) => {});
request({
url: serviceUrl,
@@ -603,11 +510,11 @@ router.post('/:sid/Calls', async(req, res) => {
body: Object.assign(req.body, {account_sid: sid})
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createCall POST to ${serviceUrl}`);
logger.error(err, `Error sending createCall POST to ${ip}`);
return res.sendStatus(500);
}
if (response.statusCode !== 201) {
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${serviceUrl}`);
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${ip}`);
return res.sendStatus(500);
}
res.status(201).json(body);
@@ -730,8 +637,14 @@ router.post('/:sid/Messages', async(req, res) => {
const {retrieveSet, logger} = req.app.locals;
try {
const serviceUrl = await getFsUrl(logger, retrieveSet, setName);
if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480);
const fs = await retrieveSet(setName);
if (0 === fs.length) {
logger.info('No available feature servers to handle createMessage API request');
return res.json({msg: 'no available feature servers at this time'}).status(500);
}
const ip = fs[idx++ % fs.length];
logger.info({fs}, `feature servers available for createMessage API request, selecting ${ip}`);
const serviceUrl = `http://${ip}:3000/v1/createMessage/${account_sid}`;
await validateCreateMessage(logger, account_sid, req);
const payload = {
@@ -739,7 +652,7 @@ router.post('/:sid/Messages', async(req, res) => {
account_sid,
...req.body
};
logger.debug({payload}, `sending createMessage API request to to ${serviceUrl}`);
logger.debug({payload}, `sending createMessage API request to to ${ip}`);
updateLastUsed(logger, account_sid, req).catch(() => {});
request({
url: serviceUrl,
@@ -748,12 +661,12 @@ router.post('/:sid/Messages', async(req, res) => {
body: payload
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createMessage POST to ${serviceUrl}`);
logger.error(err, `Error sending createMessage POST to ${ip}`);
return res.sendStatus(500);
}
if (response.statusCode !== 200) {
logger.error({statusCode: response.statusCode}, `Non-success response returned by createMessage ${serviceUrl}`);
return body ? res.status(response.statusCode).json(body) : res.sendStatus(response.statusCode);
return res.sendStatus(response.statusCode);
}
res.status(201).json(body);
});

View File

@@ -1,10 +1,9 @@
const router = require('express').Router();
const {DbErrorBadRequest} = require('../../utils/errors');
const PredefinedCarrier = require('../../models/predefined-carrier');
const VoipCarrier = require('../../models/voip-carrier');
const SipGateway = require('../../models/sip-gateway');
const SmppGateway = require('../../models/smpp-gateway');
const {parseServiceProviderSid} = require('./utils');
const short = require('short-uuid');
const {promisePool} = require('../../db');
const sysError = require('../error');
@@ -14,9 +13,7 @@ AND name = ?`;
const sqlSelectCarrierByNameForSP = `SELECT * FROM voip_carriers
WHERE service_provider_sid = ?
AND name = ?`;
const sqlSelectTemplateSipGateways = `SELECT * FROM predefined_sip_gateways
WHERE predefined_carrier_sid = ?`;
const sqlSelectTemplateSmppGateways = `SELECT * FROM predefined_smpp_gateways
const sqlSelectTemplateGateways = `SELECT * FROM predefined_sip_gateways
WHERE predefined_carrier_sid = ?`;
@@ -43,38 +40,26 @@ router.post('/:sid', async(req, res) => {
await promisePool.query(sqlSelectCarrierByNameForSP, [service_provider_sid, template.name]);
if (r2.length > 0) {
template.name = `${template.name}-${short.generate()}`;
logger.info({account_sid}, `Failed to add carrier with name ${template.name}, carrier of that name exists`);
throw new DbErrorBadRequest(`A carrier with name ${template.name} already exists`);
}
/* retrieve all the sip gateways */
const [r3] = await promisePool.query(sqlSelectTemplateSipGateways, template.predefined_carrier_sid);
logger.debug({r3}, `retrieved template sip gateways for ${template.name}`);
/* retrieve all the smpp gateways */
const [r4] = await promisePool.query(sqlSelectTemplateSmppGateways, template.predefined_carrier_sid);
logger.debug({r4}, `retrieved template smpp gateways for ${template.name}`);
/* retrieve all the gateways */
const [r3] = await promisePool.query(sqlSelectTemplateGateways, template.predefined_carrier_sid);
logger.debug({r3}, `retrieved template gateways for ${template.name}`);
/* add a voip_carrier */
// eslint-disable-next-line no-unused-vars
const {requires_static_ip, predefined_carrier_sid, ...obj} = template;
const uuid = await VoipCarrier.make({...obj, account_sid, service_provider_sid});
/* add all the sipp gateways */
/* add all the gateways */
for (const gw of r3) {
// eslint-disable-next-line no-unused-vars
const {predefined_carrier_sid, predefined_sip_gateway_sid, ...obj} = gw;
logger.debug({obj}, 'adding sip gateway');
logger.debug({obj}, 'adding gateway');
await SipGateway.make({...obj, voip_carrier_sid: uuid});
}
/* add all the smpp gateways */
for (const gw of r4) {
// eslint-disable-next-line no-unused-vars
const {predefined_carrier_sid, predefined_smpp_gateway_sid, ...obj} = gw;
logger.debug({obj}, 'adding smpp gateway');
await SmppGateway.make({...obj, voip_carrier_sid: uuid});
}
logger.debug({sid: uuid}, 'Successfully added carrier from predefined list');
res.status(201).json({sid: uuid});
} catch (err) {

View File

@@ -3,7 +3,8 @@ const {DbErrorBadRequest} = require('../../utils/errors');
const ApiKey = require('../../models/api-key');
const Account = require('../../models/account');
const decorate = require('./decorate');
const { v4: uuidv4 } = require('uuid');
const uuidv4 = require('uuid/v4');
const assert = require('assert');
const sysError = require('../error');
const preconditions = {
'add': validateAddToken,
@@ -70,7 +71,10 @@ async function validateDeleteToken(req, sid) {
router.post('/', async(req, res) => {
const logger = req.app.locals.logger;
try {
await validateAddToken(req);
if ('add' in preconditions) {
assert(typeof preconditions.add === 'function');
await preconditions.add(req);
}
const uuid = await ApiKey.make(req.body);
res.status(201).json({sid: uuid, token: req.body.token});
} catch (err) {

View File

@@ -1,7 +1,7 @@
const router = require('express').Router();
const sysError = require('../error');
const {DbErrorBadRequest} = require('../../utils/errors');
const {getHomerApiKey, getHomerSipTrace, getHomerPcap} = require('../../utils/homer-utils');
const parseAccountSid = (url) => {
const arr = /Accounts\/([^\/]*)/.exec(url);
if (arr) return arr[1];
@@ -34,42 +34,4 @@ router.get('/', async(req, res) => {
}
});
router.get('/:call_id', async(req, res) => {
const {logger} = req.app.locals;
try {
const token = await getHomerApiKey(logger);
if (!token) return res.sendStatus(400, {msg: 'Failed to get Homer API token; check server config'});
const obj = await getHomerSipTrace(logger, token, req.params.call_id);
if (!obj) {
logger.info(`/RecentCalls: unable to get sip traces from Homer for ${req.params.call_id}`);
return res.sendStatus(404);
}
res.status(200).json(obj);
} catch (err) {
logger.error({err}, '/RecentCalls error retrieving sip traces from homer');
res.sendStatus(err.statusCode || 500);
}
});
router.get('/:call_id/pcap', async(req, res) => {
const {logger} = req.app.locals;
try {
const token = await getHomerApiKey(logger);
if (!token) return res.sendStatus(400, {msg: 'getHomerApiKey: Failed to get Homer API token; check server config'});
const stream = await getHomerPcap(logger, token, [req.params.call_id]);
if (!stream) {
logger.info(`getHomerApiKey: unable to get sip traces from Homer for ${req.params.call_id}`);
return res.sendStatus(404);
}
res.set({
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename=callid-${req.params.call_id}.pcap`
});
stream.pipe(res);
} catch (err) {
logger.error({err}, 'getHomerApiKey error retrieving sip traces from homer');
res.sendStatus(err.statusCode || 500);
}
});
module.exports = router;

View File

@@ -4,7 +4,7 @@ const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/er
const {promisePool} = require('../../db');
const {doGithubAuth, doGoogleAuth, doLocalAuth} = require('../../utils/oauth-utils');
const {validateEmail} = require('../../utils/email-utils');
const { v4: uuid } = require('uuid');
const uuid = require('uuid').v4;
const short = require('short-uuid');
const translator = short();
const jwt = require('jsonwebtoken');
@@ -20,13 +20,6 @@ values (?, ?, ?, ?, ?, 0, 'local', ?)`;
const insertAccountSql = `INSERT into accounts
(account_sid, service_provider_sid, name, is_active, webhook_secret, trial_end_date)
values (?, ?, ?, ?, ?, CURDATE() + INTERVAL 21 DAY)`;
const insertWebookSql = `INSERT INTO webhooks (webhook_sid, url, method)
VALUES (?, ?, ?)`;
const insertApplicationSql = `INSERT INTO applications
(application_sid, account_sid, name, call_hook_sid, call_status_hook_sid,
speech_synthesis_vendor, speech_synthesis_language, speech_synthesis_voice,
speech_recognizer_vendor, speech_recognizer_language)
VALUES (?,?,?,?,?,?,?,?,?,?)`;
const queryRootDomainSql = `SELECT root_domain
FROM service_providers
WHERE service_providers.service_provider_sid = ?`;
@@ -156,7 +149,7 @@ router.post('/', async(req, res) => {
const user = await doGoogleAuth(logger, req.body);
logger.info({user}, 'retrieved user details from google');
Object.assign(userProfile, {
name: user.name || user.email,
name: user.name,
email: user.email,
email_validated: user.verified_email,
picture: user.picture,
@@ -288,22 +281,6 @@ router.post('/', async(req, res) => {
userProfile.provider_userid);
}
/* add hello-world and dial-time as starter applications */
const callStatusSid = uuid();
const helloWordSid = uuid();
const dialTimeSid = uuid();
/* 3 webhooks */
await promisePool.execute(insertWebookSql, [callStatusSid, 'https://public-apps.jambonz.us/call-status', 'POST']);
await promisePool.execute(insertWebookSql, [helloWordSid, 'https://public-apps.jambonz.us/hello-world', 'POST']);
await promisePool.execute(insertWebookSql, [dialTimeSid, 'https://public-apps.jambonz.us/dial-time', 'POST']);
/* 2 applications */
await promisePool.execute(insertApplicationSql, [uuid(), userProfile.account_sid, 'hello world',
helloWordSid, callStatusSid, 'google', 'en-US', 'en-US-Wavenet-C', 'google', 'en-US']);
await promisePool.execute(insertApplicationSql, [uuid(), userProfile.account_sid, 'dial time clock',
dialTimeSid, callStatusSid, 'google', 'en-US', 'en-US-Wavenet-C', 'google', 'en-US']);
Object.assign(userProfile, {
pristine: true,
is_active: req.body.provider !== 'local',

View File

@@ -1,41 +1,20 @@
const router = require('express').Router();
const {promisePool} = require('../../db');
const {DbErrorUnprocessableRequest} = require('../../utils/errors');
const Webhook = require('../../models/webhook');
const ServiceProvider = require('../../models/service-provider');
const Account = require('../../models/account');
const VoipCarrier = require('../../models/voip-carrier');
const Application = require('../../models/application');
const PhoneNumber = require('../../models/phone-number');
const {hasServiceProviderPermissions, parseServiceProviderSid} = require('./utils');
const sysError = require('../error');
const decorate = require('./decorate');
const preconditions = {
'delete': noActiveAccounts
};
const sqlDeleteSipGateways = `DELETE from sip_gateways
WHERE voip_carrier_sid IN (
SELECT voip_carrier_sid
FROM voip_carriers
WHERE service_provider_sid = ?
)`;
const sqlDeleteSmppGateways = `DELETE from smpp_gateways
WHERE voip_carrier_sid IN (
SELECT voip_carrier_sid
FROM voip_carriers
WHERE service_provider_sid = ?
)`;
/* can not delete a service provider if it has any active accounts */
async function noActiveAccounts(req, sid) {
const activeAccounts = await ServiceProvider.getForeignKeyReferences('accounts.service_provider_sid', sid);
if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts');
/* ok we can delete -- no active accounts. remove carriers and speech credentials */
await promisePool.execute('DELETE from speech_credentials WHERE service_provider_sid = ?', [sid]);
await promisePool.query(sqlDeleteSipGateways, [sid]);
await promisePool.query(sqlDeleteSmppGateways, [sid]);
await promisePool.query('DELETE from voip_carriers WHERE service_provider_sid = ?', [sid]);
}
decorate(router, ServiceProvider, ['delete'], preconditions);
@@ -52,26 +31,6 @@ router.get('/:sid/Accounts', async(req, res) => {
sysError(logger, res, err);
}
});
router.get('/:sid/Applications', async(req, res) => {
const logger = req.app.locals.logger;
try {
const service_provider_sid = parseServiceProviderSid(req);
const results = await Application.retrieveAll(service_provider_sid);
res.status(200).json(results);
} catch (err) {
sysError(logger, res, err);
}
});
router.get('/:sid/PhoneNumbers', async(req, res) => {
const logger = req.app.locals.logger;
try {
const service_provider_sid = parseServiceProviderSid(req);
const results = await PhoneNumber.retrieveAllForSP(service_provider_sid);
res.status(200).json(results);
} catch (err) {
sysError(logger, res, err);
}
});
router.get('/:sid/VoipCarriers', async(req, res) => {
const logger = req.app.locals.logger;
try {

View File

@@ -2,7 +2,7 @@ const router = require('express').Router();
const {promisePool} = require('../../db');
const {DbErrorBadRequest} = require('../../utils/errors');
const {createDnsRecords, deleteDnsRecords} = require('../../utils/dns-utils');
const { v4: uuid } = require('uuid');
const uuid = require('uuid').v4;
const sysError = require('../error');
const insertDnsRecords = `INSERT INTO dns_records
(dns_record_sid, account_sid, record_type, record_id)

View File

@@ -1,5 +1,5 @@
const router = require('express').Router();
const SmppGateway = require('../../models/smpp-gateway');
const SmppGateway = require('../../models/smpp_gateway');
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
const decorate = require('./decorate');
const sysError = require('../error');

View File

@@ -1,41 +1,19 @@
const router = require('express').Router();
const request = require('request');
const getProvider = require('../../utils/sms-provider');
const { v4: uuidv4 } = require('uuid');
const uuidv4 = require('uuid/v4');
const sysError = require('../error');
let idx = 0;
const getFsUrl = async(logger, retrieveSet, setName, provider) => {
if (process.env.K8S) return `http://${process.env.K8S_FEATURE_SERVER_SERVICE_NAME}:3000/v1/messaging/${provider}`;
try {
const fs = await retrieveSet(setName);
if (0 === fs.length) {
logger.info('No available feature servers to handle createCall API request');
return ;
}
const ip = stripPort(fs[idx++ % fs.length]);
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
return `http://${ip}:3000/v1/messaging/${provider}`;
} catch (err) {
logger.error({err}, 'getFsUrl: error retreving feature servers from redis');
}
};
const stripPort = (hostport) => {
const arr = /^(.*):(.*)$/.exec(hostport);
if (arr) return arr[1];
return hostport;
};
const doSendResponse = async(res, respondFn, body) => {
async function doSendResponse(res, respondFn, body) {
if (typeof respondFn === 'number') res.sendStatus(respondFn);
else if (typeof respondFn !== 'function') res.sendStatus(200);
else {
const payload = await respondFn(body);
res.status(200).json(payload);
}
};
}
router.post('/:provider', async(req, res) => {
const provider = req.params.provider;
@@ -89,8 +67,17 @@ router.post('/:provider', async(req, res) => {
}
try {
const serviceUrl = await getFsUrl(logger, retrieveSet, setName, provider);
if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480);
const fs = await retrieveSet(setName);
if (0 === fs.length) {
logger.info('No available feature servers to handle createCall API request');
return res
.json({
msg: 'no available feature servers at this time'
})
.status(480);
}
const ip = fs[idx++ % fs.length];
const serviceUrl = `http://${ip}:3000/v1/messaging/${provider}`;
const messageSid = uuidv4();
const payload = await Promise.resolve(filterFn({messageSid}, req.body));
@@ -126,7 +113,7 @@ router.post('/:provider', async(req, res) => {
logger.debug({body: req.body, payload}, 'filtered incoming SMS');
logger.info({payload, url: serviceUrl}, `sending incomingSms API request to FS at ${serviceUrl}`);
logger.info({payload, url: serviceUrl}, `sending incomingSms API request to FS at ${ip}`);
request({
url: serviceUrl,
@@ -136,7 +123,7 @@ router.post('/:provider', async(req, res) => {
},
async(err, response, body) => {
if (err) {
logger.error(err, `Error sending incomingSms POST to ${serviceUrl}`);
logger.error(err, `Error sending incomingSms POST to ${ip}`);
return res.sendStatus(500);
}
if (200 === response.statusCode) {
@@ -144,7 +131,7 @@ router.post('/:provider', async(req, res) => {
logger.info({body}, 'sending response to provider for incomingSMS');
return doSendResponse(res, respondFn, body);
}
logger.error({statusCode: response.statusCode}, `Non-success response returned by incomingSms ${serviceUrl}`);
logger.error({statusCode: response.statusCode}, `Non-success response returned by incomingSms ${ip}`);
return res.sendStatus(500);
});
} catch (err) {

View File

@@ -1,75 +1,20 @@
const router = require('express').Router();
const assert = require('assert');
const SpeechCredential = require('../../models/speech-credential');
const sysError = require('../error');
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
const {parseAccountSid, parseServiceProviderSid} = require('./utils');
const {DbErrorUnprocessableRequest} = require('../../utils/errors');
const {DbErrorUnprocessableRequest, DbErrorBadRequest} = require('../../utils/errors');
const {
testGoogleTts,
testGoogleStt,
testAwsTts,
testAwsStt,
testMicrosoftStt,
testMicrosoftTts,
testWellSaidTts
testAwsStt
} = require('../../utils/speech-utils');
const encryptCredential = (obj) => {
const {
vendor,
service_key,
access_key_id,
secret_access_key,
aws_region,
api_key,
region
} = obj;
switch (vendor) {
case 'google':
assert(service_key, 'invalid json key: service_key is required');
try {
const o = JSON.parse(service_key);
assert(o.client_email && o.private_key, 'invalid google service account key');
}
catch (err) {
assert(false, 'invalid google service account key - not JSON');
}
return encrypt(service_key);
case 'aws':
assert(access_key_id, 'invalid aws speech credential: access_key_id is required');
assert(secret_access_key, 'invalid aws speech credential: secret_access_key is required');
assert(aws_region, 'invalid aws speech credential: aws_region is required');
const awsData = JSON.stringify({aws_region, access_key_id, secret_access_key});
return encrypt(awsData);
case 'microsoft':
assert(region, 'invalid azure speech credential: region is required');
assert(api_key, 'invalid azure speech credential: api_key is required');
const azureData = JSON.stringify({region, api_key});
return encrypt(azureData);
case 'wellsaid':
assert(api_key, 'invalid wellsaid speech credential: api_key is required');
const wsData = JSON.stringify({api_key});
return encrypt(wsData);
default:
assert(false, `invalid or missing vendor: ${vendor}`);
}
};
router.post('/', async(req, res) => {
const logger = req.app.locals.logger;
const {
use_for_stt,
use_for_tts,
vendor,
} = req.body;
const account_sid = req.user.account_sid || req.body.account_sid;
const {use_for_stt, use_for_tts, vendor, service_key, access_key_id, secret_access_key, aws_region} = req.body;
const {account_sid} = req.user;
let service_provider_sid;
if (!account_sid) {
if (!req.user.hasServiceProviderAuth) {
@@ -79,7 +24,30 @@ router.post('/', async(req, res) => {
service_provider_sid = parseServiceProviderSid(req);
}
try {
const encrypted_credential = encryptCredential(req.body);
let encrypted_credential;
if (vendor === 'google') {
let obj;
if (!service_key) throw new DbErrorBadRequest('invalid json key: service_key is required');
try {
obj = JSON.parse(service_key);
if (!obj.client_email || !obj.private_key) {
throw new DbErrorBadRequest('invalid google service account key');
}
}
catch (err) {
throw new DbErrorBadRequest('invalid google service account key - not JSON');
}
encrypted_credential = encrypt(service_key);
}
else if (vendor === 'aws') {
const data = JSON.stringify({
aws_region: aws_region || 'us-east-1',
access_key_id,
secret_access_key
});
encrypted_credential = encrypt(data);
}
else throw new DbErrorBadRequest(`invalid speech vendor ${vendor}`);
const uuid = await SpeechCredential.make({
account_sid,
service_provider_sid,
@@ -110,24 +78,12 @@ router.get('/', async(req, res) => {
res.status(200).json(creds.map((c) => {
const {credential, ...obj} = c;
if ('google' === obj.vendor) {
obj.service_key = JSON.parse(decrypt(credential));
obj.service_key = decrypt(credential);
}
else if ('aws' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
const o = decrypt(credential);
obj.access_key_id = o.access_key_id;
obj.secret_access_key = o.secret_access_key;
obj.aws_region = o.aws_region;
logger.info({obj, o}, 'retrieving aws speech credential');
}
else if ('microsoft' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key;
obj.region = o.region;
logger.info({obj, o}, 'retrieving azure speech credential');
}
else if ('wellsaid' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key;
}
return obj;
}));
@@ -153,12 +109,6 @@ router.get('/:sid', async(req, res) => {
const o = JSON.parse(decrypt(credential));
obj.access_key_id = o.access_key_id;
obj.secret_access_key = o.secret_access_key;
obj.aws_region = o.aws_region;
}
else if ('microsoft' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key;
obj.region = o.region;
}
res.status(200).json(obj);
} catch (err) {
@@ -201,11 +151,6 @@ router.put('/:sid', async(req, res) => {
obj.use_for_stt = use_for_stt;
}
/* update the credential if provided */
try {
obj.credential = encryptCredential(req.body);
} catch (err) {}
const rowsAffected = await SpeechCredential.update(sid, obj);
if (rowsAffected === 0) {
return res.sendStatus(404);
@@ -294,42 +239,6 @@ router.get('/:sid/test', async(req, res) => {
}
}
}
else if (cred.vendor === 'microsoft') {
const {api_key, region} = credential;
if (cred.use_for_tts) {
try {
await testMicrosoftTts(logger, {api_key, region});
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
if (cred.use_for_stt) {
try {
await testMicrosoftStt(logger, {api_key, region});
results.stt.status = 'ok';
SpeechCredential.sttTestResult(sid, true);
} catch (err) {
results.stt = {status: 'fail', reason: err.message};
SpeechCredential.sttTestResult(sid, false);
}
}
}
else if (cred.vendor === 'wellsaid') {
const {api_key} = credential;
if (cred.use_for_tts) {
try {
await testWellSaidTts(logger, {api_key});
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {
results.tts = {status: 'fail', reason: err.message};
SpeechCredential.ttsTestResult(sid, false);
}
}
}
res.status(200).json(results);
} catch (err) {
sysError(logger, res, err);

View File

@@ -1,5 +1,4 @@
const { v4: uuid } = require('uuid');
const bent = require('bent');
const uuid = require('uuid').v4;
const Account = require('../../models/account');
const {promisePool} = require('../../db');
const {cancelSubscription, detachPaymentMethod} = require('../../utils/stripe-utils');
@@ -10,8 +9,6 @@ values (?, ?)`;
const replaceOldSubscriptionSql = `UPDATE account_subscriptions
SET effective_end_date = CURRENT_TIMESTAMP, change_reason = ?
WHERE account_subscription_sid = ?`;
//const request = require('request');
//require('request-debug')(request);
const setupFreeTrial = async(logger, account_sid, isReturningUser) => {
const sid = uuid();
@@ -77,13 +74,12 @@ const createTestCdrs = async(writeCdrs, account_sid) => {
for (let i = 0 ; i < points; i++) {
const attempted_at = new Date(start.getTime() + (i * increment));
const failed = 0 === i % 5;
const sip_callid = `685cd008-0a66-4974-b37a-bdd6d9a3c4a-${i % 2}`;
data.push({
call_sid: 'b6f48929-8e86-4d62-ae3b-64fb574d91f6',
from: '15083084809',
to: '18882349999',
answered: !failed,
sip_callid,
sip_callid: '685cd008-0a66-4974-b37a-bdd6d9a3c4aa@192.168.1.100',
sip_status: 200,
duration: failed ? 0 : 45,
attempted_at: attempted_at.getTime(),
@@ -171,108 +167,6 @@ const hasServiceProviderPermissions = (req, res, next) => {
});
};
const checkLimits = async(req, res, next) => {
const logger = req.app.locals.logger;
if (process.env.APPLY_JAMBONZ_DB_LIMITS && req.user.hasScope('account')) {
const account_sid = req.user.account_sid;
const url = req.originalUrl;
let sql;
let limit;
if (/Applications/.test(url)) {
limit = 50;
sql = 'SELECT count(*) as count from applications where account_sid = ?';
}
else if (/VoipCarriers/.test(url)) {
limit = 10;
sql = 'SELECT count(*) as count from voip_carriers where account_sid = ?';
}
else if (/SipGateways/.test(url)) {
limit = 150;
sql = `SELECT count(*) as count
from sip_gateways
where voip_carrier_sid IN (
SELECT voip_carrier_sid from voip_carriers
where account_sid = ?
)`;
}
else if (/PhoneNumbers/.test(url)) {
limit = 200;
sql = 'SELECT count(*) as count from phone_numbers where account_sid = ?';
}
else if (/SpeechCredentials/.test(url)) {
limit = 10;
sql = 'SELECT count(*) as count from speech_credentials where account_sid = ?';
}
else if (/ApiKeys/.test(url)) {
limit = 10;
sql = 'SELECT count(*) as count from api_keys where account_sid = ?';
}
if (sql) {
try {
const [r] = await promisePool.execute(sql, [account_sid]);
if (r[0].count >= limit) {
res.status(422).json({
status: 'fail',
message: `exceeded limits - you have created ${r.count} instances of this resource`
});
return;
}
} catch (err) {
logger.error({err}, 'Error checking limits');
}
}
}
next();
};
const getSubspaceJWT = async(id, secret) => {
const postJwt = bent('https://id.subspace.com', 'POST', 'json', 200);
const jwt = await postJwt('/oauth/token',
{
client_id: id,
client_secret: secret,
audience: 'https://api.subspace.com/',
grant_type: 'client_credentials',
}
);
return jwt.access_token;
};
const enableSubspace = async(opts) => {
const {subspace_client_id, subspace_client_secret, destination} = opts;
const accessToken = await getSubspaceJWT(subspace_client_id, subspace_client_secret);
const postTeleport = bent('https://api.subspace.com', 'POST', 'json', 200);
const teleport = await postTeleport('/v1/sipteleport',
{
name: 'Jambonz',
destination,
status: 'ENABLED'
},
{
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
}
);
return teleport;
};
const disableSubspace = async(opts) => {
const {subspace_client_id, subspace_client_secret, subspace_sip_teleport_id} = opts;
const accessToken = await getSubspaceJWT(subspace_client_id, subspace_client_secret);
const relativeUrl = `/v1/sipteleport/${subspace_sip_teleport_id}`;
const deleteTeleport = bent('https://api.subspace.com', 'DELETE', 'json', 200);
await deleteTeleport(relativeUrl, {},
{
Authorization: `Bearer ${accessToken}`
}
);
return;
};
module.exports = {
setupFreeTrial,
createTestCdrs,
@@ -280,8 +174,5 @@ module.exports = {
parseAccountSid,
parseServiceProviderSid,
hasAccountPermissions,
hasServiceProviderPermissions,
checkLimits,
enableSubspace,
disableSubspace
hasServiceProviderPermissions
};

View File

@@ -5,17 +5,9 @@ const path = require('path');
const swaggerDocument = YAML.load(path.resolve(__dirname, '../swagger/swagger.yaml'));
const api = require('./api');
const stripe = require('./stripe');
const {checkLimits} = require('./api/utils');
const routes = express.Router();
routes.post([
'/v1/Applications',
'/v1/VoipCarriers',
'/v1/SipGateways',
'/v1/PhoneNumbers',
'/v1/Accounts'
], checkLimits);
routes.use('/v1', api);
routes.use('/stripe', stripe);
routes.use('/swagger', swaggerUi.serve);

View File

@@ -1958,7 +1958,7 @@ paths:
put:
summary: update tenant
operationId: putTenant
operationId: updateAccount
requestBody:
content:
application/json:
@@ -2004,9 +2004,6 @@ paths:
registration_hook:
$ref: '#/components/schemas/Webhook'
description: authentication webhook for registration
queue_event_hook:
$ref: '#/components/schemas/Webhook'
description: webhook called when members join or leave a queue
service_provider_sid:
type: string
format: uuid
@@ -2638,56 +2635,6 @@ paths:
- duration
404:
description: account not found
/Accounts/{AccountSid}/RecentCalls/{CallId}:
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
- name: CallId
in: path
required: true
schema:
type: string
get:
summary: retrieve sip trace detail for a call
operationId: getRecentCallTrace
responses:
200:
description: retrieve sip trace data
content:
application/json:
schema:
type: object
404:
description: account or call not found
/Accounts/{AccountSid}/RecentCalls/{CallId}/pcap:
parameters:
- name: AccountSid
in: path
required: true
schema:
type: string
format: uuid
- name: CallId
in: path
required: true
schema:
type: string
get:
summary: retrieve pcap for a call
operationId: getRecentCallTrace
responses:
200:
description: retrieve sip trace data
content:
application/octet-stream:
schema:
type: object
404:
description: account or call not found
/Accounts/{AccountSid}/Alerts:
parameters:
- name: AccountSid
@@ -3102,16 +3049,6 @@ paths:
enum:
- completed
- no-answer
conf_mute_status:
type: string
enum:
- mute
- unmute
conf_hold_status:
type: string
enum:
- hold
- unhold
listen_status:
type: string
enum:
@@ -3123,21 +3060,8 @@ paths:
- mute
- unmute
whisper:
$ref: '#/components/schemas/Webhook'
sip_request:
type: object
properties:
method:
type: string
content_type:
type: string
content:
type: string
headers:
type: object
$ref: '#/components/schemas/Webhook'
responses:
200:
description: Accepted
202:
description: Accepted
400:
@@ -3186,22 +3110,6 @@ paths:
example: 2531329f-fb09-4ef7-887e-84e648214436
providerResponse:
type: string
480:
description: temporary failure
content:
application/json:
schema:
required:
- sid
properties:
sid:
type: string
format: uuid
example: 2531329f-fb09-4ef7-887e-84e648214436
message:
type: string
smpp_err_code:
type: string
400:
description: bad request
components:
@@ -3724,8 +3632,6 @@ components:
type: string
registration_hook_sid:
type: string
queue_event_hook_sid:
type: string
device_calling_application_sid:
type: string
is_active:

View File

@@ -1,5 +1,5 @@
const crypto = require('crypto');
const algorithm = process.env.LEGACY_CRYPTO ? 'aes-256-ctr' : 'aes-256-cbc';
const algorithm = 'aes-256-ctr';
const iv = crypto.randomBytes(16);
const secretKey = crypto.createHash('sha256')
.update(String(process.env.JWT_SECRET))

View File

@@ -1,93 +0,0 @@
const debug = require('debug')('jambonz:api-server');
const bent = require('bent');
const basicAuth = (apiKey) => {
const header = `Bearer ${apiKey}`;
return {Authorization: header};
};
const postJSON = bent(process.env.HOMER_BASE_URL || 'http://127.0.0.1', 'POST', 'json', 200, 201);
const postPcap = bent(process.env.HOMER_BASE_URL || 'http://127.0.0.1', 'POST', 200, {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
});
const SEVEN_DAYS_IN_MS = (1000 * 3600 * 24 * 7);
const getHomerApiKey = async(logger) => {
if (!process.env.HOMER_BASE_URL || !process.env.HOMER_USERNAME || !process.env.HOMER_PASSWORD) {
logger.debug('getHomerApiKey: Homer integration not installed');
}
try {
const obj = await postJSON('/api/v3/auth', {
username: process.env.HOMER_USERNAME,
password: process.env.HOMER_PASSWORD
});
debug(obj);
logger.debug({obj}, `getHomerApiKey for user ${process.env.HOMER_USERNAME}`);
return obj.token;
} catch (err) {
debug(err);
logger.info({err}, `getHomerApiKey: Error retrieving apikey for user ${process.env.HOMER_USERNAME}`);
}
};
const getHomerSipTrace = async(logger, apiKey, callId) => {
if (!process.env.HOMER_BASE_URL || !process.env.HOMER_USERNAME || !process.env.HOMER_PASSWORD) {
logger.debug('getHomerSipTrace: Homer integration not installed');
}
try {
const now = Date.now();
const obj = await postJSON('/api/v3/call/transaction', {
param: {
transaction: {
call: true
},
search: {
'1_call': {
callid: [callId]
}
},
},
timestamp: {
from: now - SEVEN_DAYS_IN_MS,
to: now
}
}, basicAuth(apiKey));
return obj;
} catch (err) {
logger.info({err}, `getHomerSipTrace: Error retrieving messages for callid ${callId}`);
}
};
const getHomerPcap = async(logger, apiKey, callIds) => {
if (!process.env.HOMER_BASE_URL || !process.env.HOMER_USERNAME || !process.env.HOMER_PASSWORD) {
logger.debug('getHomerPcap: Homer integration not installed');
}
try {
const now = Date.now();
const stream = await postPcap('/api/v3/export/call/messages/pcap', {
param: {
transaction: {
call: true
},
search: {
'1_call': {
callid: callIds
}
},
},
timestamp: {
from: now - SEVEN_DAYS_IN_MS,
to: now
}
}, basicAuth(apiKey));
return stream;
} catch (err) {
logger.info({err}, `getHomerPcap: Error retrieving messages for callid ${callIds}`);
}
};
module.exports = {
getHomerApiKey,
getHomerSipTrace,
getHomerPcap
};

View File

@@ -2,7 +2,6 @@ const ttsGoogle = require('@google-cloud/text-to-speech');
const sttGoogle = require('@google-cloud/speech').v1p1beta1;
const Polly = require('aws-sdk/clients/polly');
const AWS = require('aws-sdk');
const bent = require('bent');
const fs = require('fs');
const testGoogleTts = async(logger, credentials) => {
@@ -53,59 +52,9 @@ const testAwsStt = (logger, credentials) => {
});
};
const testMicrosoftTts = async(logger, credentials) => {
const {api_key, region} = credentials;
if (!api_key) throw new Error('testMicrosoftTts: credentials are missing api_key');
if (!region) throw new Error('testMicrosoftTts: credentials are missing region');
try {
const getJSON = bent('json', {
'Ocp-Apim-Subscription-Key': api_key
});
const response = await getJSON(`https://${region}.tts.speech.microsoft.com/cognitiveservices/voices/list`);
return response;
} catch (err) {
logger.info({err}, `testMicrosoftTts - failed to list voices for region ${region}`);
throw err;
}
};
const testMicrosoftStt = async(logger, credentials) => {
//TODO
return true;
};
const testWellSaidTts = async(logger, credentials) => {
const {api_key} = credentials;
try {
const post = bent('https://api.wellsaidlabs.com', 'POST', 'buffer', {
'X-Api-Key': api_key,
'Accept': 'audio/mpeg',
'Content-Type': 'application/json'
});
const mp3 = await post('/v1/tts/stream', {
text: 'Hello, world',
speaker_id: '3'
});
return mp3;
} catch (err) {
logger.info({err}, 'testWellSaidTts returned error');
throw err;
}
};
const testWellSaidStt = async(logger, credentials) => {
//TODO
return true;
};
module.exports = {
testGoogleTts,
testGoogleStt,
testAwsTts,
testWellSaidTts,
testAwsStt,
testMicrosoftTts,
testMicrosoftStt,
testWellSaidStt,
testAwsStt
};

View File

@@ -7,7 +7,7 @@ assert.ok(process.env.STRIPE_BASE_URL || process.env.NODE_ENV === 'test',
'missing env STRIPE_BASE_URL for billing operations');
const bent = require('bent');
const formurlencoded = require('form-urlencoded');
const formurlencoded = require('form-urlencoded').default;
const qs = require('qs');
const toBase64 = (str) => Buffer.from(str || '', 'utf8').toString('base64');
const basicAuth = () => {

12299
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,14 @@
{
"name": "jambonz-api-server",
"version": "v0.7.5",
"version": "1.2.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "NODE_ENV=test APPLY_JAMBONZ_DB_LIMITS=1 JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/ ",
"test": "NODE_ENV=test JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/ ",
"integration-test": "NODE_ENV=test JAMBONES_TIME_SERIES_HOST=127.0.0.1 AWS_REGION='us-east-1' JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/serve-integration.js",
"upgrade-db": "node ./db/upgrade-jambonz-db.js",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib",
"prepare": "husky install"
"jslint": "eslint app.js lib"
},
"author": "Dave Horton",
"license": "MIT",
@@ -18,41 +16,39 @@
"url": "https://github.com/jambonz/jambonz-api-server.git"
},
"dependencies": {
"@google-cloud/speech": "^4.10.0",
"@google-cloud/text-to-speech": "^3.4.0",
"@jambonz/db-helpers": "^0.6.16",
"@jambonz/realtimedb-helpers": "^0.4.24",
"@jambonz/time-series": "^0.1.6",
"@google-cloud/speech": "^4.2.0",
"@google-cloud/text-to-speech": "^3.1.3",
"@jambonz/db-helpers": "^0.6.12",
"@jambonz/realtimedb-helpers": "^0.4.3",
"@jambonz/time-series": "^0.1.5",
"argon2-ffi": "^2.0.0",
"aws-sdk": "^2.1073.0",
"aws-sdk": "^2.839.0",
"bent": "^7.3.12",
"cors": "^2.8.5",
"debug": "^4.3.3",
"express": "^4.17.2",
"express-rate-limit": "^6.3.0",
"form-data": "^2.5.1",
"form-urlencoded": "^6.0.5",
"helmet": "^5.0.2",
"debug": "^4.3.1",
"express": "^4.17.1",
"form-data": "^2.3.3",
"form-urlencoded": "^4.2.1",
"google-libphonenumber": "^3.2.15",
"jsonwebtoken": "^8.5.1",
"mailgun.js": "^3.7.3",
"microsoft-cognitiveservices-speech-sdk": "^1.19.0",
"mysql2": "^2.3.3",
"passport": "^0.5.2",
"mailgun.js": "^3.3.0",
"mysql2": "^2.2.5",
"passport": "^0.4.1",
"passport-http-bearer": "^1.0.1",
"pino": "^5.17.0",
"qs": "^6.7.0",
"request": "^2.88.2",
"request-debug": "^0.2.0",
"short-uuid": "^4.1.0",
"stripe": "^8.196.0",
"swagger-ui-express": "^4.3.0",
"uuid": "^8.3.2",
"stripe": "^8.138.0",
"swagger-ui-express": "^4.1.6",
"uuid": "^3.4.0",
"yamljs": "^0.3.0"
},
"devDependencies": {
"eslint": "^7.17.0",
"eslint-plugin-promise": "^4.2.1",
"husky": "7.0.4",
"nyc": "^15.1.0",
"request": "^2.88.2",
"request-promise-native": "^1.0.9",
"tape": "^5.2.2"
}

View File

@@ -154,10 +154,6 @@ test('account tests', async(t) => {
registration_hook: {
url: 'http://example.com/reg2',
method: 'get'
},
queue_event_hook: {
url: 'http://example.com/q',
method: 'post'
}
}
});

View File

@@ -1,16 +1,7 @@
version: '3'
networks:
jambonz-api:
driver: bridge
ipam:
config:
- subnet: 172.58.0.0/16
services:
mysql:
platform: linux/x86_64
image: mysql:5.7
ports:
- "3360:3306"
@@ -19,11 +10,7 @@ services:
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "127.0.0.1", "--protocol", "tcp"]
timeout: 5s
retries: 10
networks:
jambonz-api:
ipv4_address: 172.58.0.2
retries: 10
redis:
image: redis:5-alpine
ports:
@@ -31,102 +18,8 @@ services:
depends_on:
mysql:
condition: service_healthy
networks:
jambonz-api:
ipv4_address: 172.58.0.3
influxdb:
platform: linux/x86_64
image: influxdb:1.8
image: influxdb:1.8-alpine
ports:
- "8086:8086"
networks:
jambonz-api:
ipv4_address: 172.58.0.4
db:
image: postgres:11-alpine
restart: always
environment:
POSTGRES_PASSWORD: homerSeven
POSTGRES_USER: root
expose:
- 5432
restart: unless-stopped
volumes:
- ./postgresql/init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh
- ./postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "psql -h 'localhost' -U 'root' -c '\\l'"]
interval: 3s
timeout: 3s
retries: 60
networks:
jambonz-api:
ipv4_address: 172.58.0.5
heplify-server:
image: sipcapture/heplify-server
container_name: heplify-server
ports:
- "9069:9060"
- "9060:9060/udp"
- "9061:9061/tcp"
command:
- './heplify-server'
environment:
- "HEPLIFYSERVER_HEPADDR=0.0.0.0:9060"
- "HEPLIFYSERVER_HEPTCPADDR=0.0.0.0:9061"
- "HEPLIFYSERVER_DBDRIVER=postgres"
- "HEPLIFYSERVER_DBSHEMA=homer7"
- "HEPLIFYSERVER_DBADDR=db:5432"
- "HEPLIFYSERVER_DBUSER=root"
- "HEPLIFYSERVER_DBPASS=homerSeven"
- "HEPLIFYSERVER_DBDATATABLE=homer_data"
- "HEPLIFYSERVER_DBROTATE=true"
- "HEPLIFYSERVER_LOGLVL=debug"
- "HEPLIFYSERVER_LOGSTD=true"
- "HEPLIFYSERVER_DBDROPDAYS=7"
- "HEPLIFYSERVER_ALEGIDS=X-CID"
restart: unless-stopped
depends_on:
db:
condition: service_healthy
networks:
jambonz-api:
ipv4_address: 172.58.0.6
homer-webapp:
container_name: homer-webapp
image: sipcapture/webapp
environment:
- "DB_HOST=db"
- "DB_USER=root"
- "DB_PASS=homerSeven"
ports:
- "9090:80"
expose:
- 80
restart: unless-stopped
volumes:
- ./bootstrap:/app/bootstrap
depends_on:
db:
condition: service_healthy
networks:
jambonz-api:
ipv4_address: 172.58.0.7
drachtio:
container_name: drachtio
image: drachtio/drachtio-server:latest
command: drachtio --contact "sip:*;transport=udp" --loglevel debug --sofia-loglevel 9 --homer 172.58.0.6:9060 --homer-id 10
networks:
jambonz-api:
ipv4_address: 172.58.0.8
depends_on:
db:
condition: service_healthy

View File

@@ -1,45 +0,0 @@
const test = require('tape') ;
const noopLogger = {debug: () => {}, info: () => {}, error: () => {}};
const fs = require('fs');
test('homer tests', async(t, done) => {
//const {getHomerApiKey, getHomerSipTrace, getHomerPcap} = require('../lib/utils/homer-utils');
if (process.env.HOMER_BASE_URL && process.env.HOMER_USERNAME && process.env.HOMER_PASSWORD) {
try {
/* get a token */
/*
let token = await getHomerApiKey(noopLogger);
console.log(token);
t.ok(token, 'successfully created an api key for homer');
const result = await getHomerSipTrace(noopLogger, token, '224f0f24-69aa-123a-eaa6-0ea24be4d211');
console.log(`got trace: ${JSON.stringify(result)}`);
var writeStream = fs.createWriteStream('./call.pcap');
const stream = await getHomerPcap(noopLogger, token, ['224f0f24-69aa-123a-eaa6-0ea24be4d211']);
stream.pipe(writeStream);
stream.on('end', () => {
console.log('finished writing');
done();
});
*/
let result = await request.get('/RecentCalls/224f0f24-69aa-123a-eaa6-0ea24be4d211', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
body: {
service_provider_sid,
account_sid,
tenant_fqdn: 'foo.bar.baz'
}
});
t.ok(result.statusCode === 201, 'successfully added ms teams tenant');
}
catch (err) {
console.error(err);
t.end(err);
}
}
});

View File

@@ -13,5 +13,4 @@ require('./ms-teams');
require('./speech-credentials');
require('./recent-calls');
require('./webapp_tests');
//require('./homer');
require('./docker_stop');

View File

@@ -1,6 +1,7 @@
const bent = require('bent');
const getJSON = bent('GET', 200);
const request = require('request');
require('request-debug')(request);
const test = async() => {
request.get('https://api.github.com/user', {

View File

@@ -1,6 +0,0 @@
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE DATABASE homer_config;
EOSQL

View File

@@ -66,22 +66,6 @@ test('recent calls tests', async(t) => {
json: true,
});
/* pull sip traces and pcap from homer */
/*
result = await request.get(`/Accounts/${account_sid}/RecentCalls/224f0f24-69aa-123a-eaa6-0ea24be4d211`, {
auth: authUser,
json: true
});
console.log(result);
const writeStream = fs.createWriteStream('./call.pcap');
const ret = await request.get(`/Accounts/${account_sid}/RecentCalls/224f0f24-69aa-123a-eaa6-0ea24be4d211/pcap`, {
auth: authUser,
resolveWithFullResponse: true
});
writeStream.write(ret.body);
*/
await deleteObjectBySid(request, '/Accounts', account_sid);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAC with media">
<send retrans="500">
<![CDATA[
INVITE sip:+15083871234@echo.sip.jambonz.org SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:15083871234@echo.sip.jambonz.org>
Call-ID: 685cd008-0a66-4974-b37a-bdd6d9a3c4a-0
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[local_ip_type] [local_ip]
t=0 0
m=audio [auto_media_port] RTP/AVP 8 101
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-11,16
]]>
</send>
<recv response="100" optional="true">
</recv>
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
<!-- are saved and used for following messages sent. Useful to test -->
<!-- against stateful SIP proxies/B2BUAs. -->
<recv response="503" rtd="true" crlf="true">
</recv>
<!-- Packet lost can be simulated in any send/recv message by -->
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
<send>
<![CDATA[
ACK sip:15083871234@echo.sip.jambonz.org SIP/2.0
[last_Via]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:15083871234@echo.sip.jambonz.org>[peer_tag_param]
Call-ID: 685cd008-0a66-4974-b37a-bdd6d9a3c4a-0
CSeq: 1 ACK
Max-Forwards: 70
Subject: uac-pcap-carrier-max-call-limit
Content-Length: 0
]]>
</send>
<!-- definition of the response time repartition table (unit is ms) -->
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
<!-- definition of the call length repartition table (unit is ms) -->
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
</scenario>

View File

@@ -1,5 +1,4 @@
const exec = require('child_process').exec ;
const { sippUac } = require('./sipp')('test_jambonz-api');
let stopping = false;
process.on('SIGINT', async() => {
@@ -67,14 +66,6 @@ const resetAdminPassword = () => {
});
};
const generateSipTrace = async() => {
try {
await sippUac('uac.xml', '172.58.0.30');
} catch (err) {
console.log(err);
}
};
const stopDocker = () => {
return new Promise((resolve, reject) => {
console.log('stopping docker network..')
@@ -90,7 +81,6 @@ startDocker()
.then(createSchema)
.then(seedDb)
.then(resetAdminPassword)
.then(generateSipTrace)
.then(() => {
console.log('ready for testing!');
require('..');

View File

@@ -1,68 +0,0 @@
const { spawn } = require('child_process');
const debug = require('debug')('jambonz:ci');
let network;
const obj = {};
let output = '';
let idx = 1;
function clearOutput() {
output = '';
}
function addOutput(str) {
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) < 128) output += str.charAt(i);
}
}
module.exports = (networkName) => {
network = networkName ;
return obj;
};
obj.output = () => {
return output;
};
obj.sippUac = (file, bindAddress) => {
const cmd = 'docker';
const args = [
'run', '--rm', '--net', `${network}`,
'-v', `${__dirname}/scenarios:/tmp/scenarios`,
'drachtio/sipp', 'sipp', '-sf', `/tmp/scenarios/${file}`,
'-m', '1',
'-sleep', '250ms',
'-nostdin',
'-cid_str', `%u-%p@%s-${idx++}`,
'drachtio'
];
if (bindAddress) args.splice(4, 0, '--ip', bindAddress);
//console.log(args.join(' '));
clearOutput();
return new Promise((resolve, reject) => {
const child_process = spawn(cmd, args, {stdio: ['inherit', 'pipe', 'pipe']});
child_process.on('exit', (code, signal) => {
if (code === 0) {
return resolve();
}
console.log(`sipp exited with non-zero code ${code} signal ${signal}`);
reject(code);
});
child_process.on('error', (error) => {
console.log(`error spawing child process for docker: ${args}`);
});
child_process.stdout.on('data', (data) => {
debug(`stderr: ${data}`);
addOutput(data.toString());
});
child_process.stderr.on('data', (data) => {
debug(`stderr: ${data}`);
addOutput(data.toString());
});
});
};

View File

@@ -7,7 +7,6 @@ const request = require('request-promise-native').defaults({
baseUrl: 'http://127.0.0.1:3000/v1'
});
const {createServiceProvider, createAccount, deleteObjectBySid} = require('./utils');
const { noopLogger } = require('@jambonz/realtimedb-helpers/lib/utils');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
@@ -110,64 +109,10 @@ test('speech credentials tests', async(t) => {
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
/* add a credential for microsoft */
if (process.env.MICROSOFT_API_KEY && process.env.MICROSOFT_REGION) {
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'microsoft',
use_for_tts: true,
api_key: process.env.MICROSOFT_API_KEY,
region: process.env.MICROSOFT_REGION
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential');
const ms_sid = result.body.sid;
/* test the speech credential */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}/test`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
});
console.log(JSON.stringify(result));
}
/* add a credential for wellsaid */
if (process.env.WELLSAID_API_KEY) {
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
body: {
vendor: 'wellsaid',
use_for_tts: true,
api_key: process.env.WELLSAID_API_KEY
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential');
const ms_sid = result.body.sid;
/* test the speech credential */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}/test`, {
resolveWithFullResponse: true,
auth: authUser,
json: true,
});
console.log(JSON.stringify(result));
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
}
await deleteObjectBySid(request, '/Accounts', account_sid);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
//t.end();
}
catch (err) {

View File

@@ -1,4 +1,5 @@
const { v4: uuid } = require('uuid');
const bytesToUuid = require("uuid/lib/bytesToUuid");
const uuid = require('uuid').v4;
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
const authAdmin = {bearer: ADMIN_TOKEN};