mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
72 Commits
v0.6.3
...
v0.7.3-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd935999a6 | ||
|
|
863d7a02c8 | ||
|
|
45c023e374 | ||
|
|
f634ca4076 | ||
|
|
654f93b30c | ||
|
|
280aaef120 | ||
|
|
e6931574e3 | ||
|
|
1bd21cb39d | ||
|
|
480e1155f3 | ||
|
|
75e7c1058b | ||
|
|
a5e4fafda4 | ||
|
|
2e041df6e4 | ||
|
|
3ee82d6c8c | ||
|
|
d396c5b252 | ||
|
|
daef0ee215 | ||
|
|
1d168e93e1 | ||
|
|
605a0e762f | ||
|
|
c9bf943656 | ||
|
|
3aac11560a | ||
|
|
bb4a20a375 | ||
|
|
45c4c626f2 | ||
|
|
65e6d75f72 | ||
|
|
c6bb273aa0 | ||
|
|
256b295be1 | ||
|
|
693ba51339 | ||
|
|
531366ee58 | ||
|
|
a36604029c | ||
|
|
63a88844aa | ||
|
|
24f6833493 | ||
|
|
4119d766d5 | ||
|
|
936a9da887 | ||
|
|
77098f273d | ||
|
|
e27b5a39a6 | ||
|
|
66872494f9 | ||
|
|
4557b32804 | ||
|
|
e55fe77171 | ||
|
|
0fd87a732f | ||
|
|
f6d358d3df | ||
|
|
19a55a5774 | ||
|
|
f1d7dcc6d2 | ||
|
|
bc8ff644db | ||
|
|
fa6acef02a | ||
|
|
8117f77955 | ||
|
|
4bf79fe42b | ||
|
|
3d879b5ac9 | ||
|
|
f882a0e3c8 | ||
|
|
0d18a097fb | ||
|
|
91119c6971 | ||
|
|
f3c4b89897 | ||
|
|
bda5e69cbb | ||
|
|
82b48e20df | ||
|
|
21ffad6c8d | ||
|
|
e125491d5a | ||
|
|
8411570668 | ||
|
|
d4e297578f | ||
|
|
eac0e3b820 | ||
|
|
1013f3f222 | ||
|
|
5350f7bea0 | ||
|
|
446cc57e09 | ||
|
|
9525cf5a36 | ||
|
|
43393a2e4a | ||
|
|
a06bba60e6 | ||
|
|
318a8f0822 | ||
|
|
ecdf9898f8 | ||
|
|
e0bacb55e7 | ||
|
|
0eb365ea58 | ||
|
|
f7fcbd4c7c | ||
|
|
bc3b5bb1dc | ||
|
|
a5a759940b | ||
|
|
6c01d28288 | ||
|
|
a3b9727d64 | ||
|
|
ac4ea4b265 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
51
.github/workflows/docker-publish-dbcreate.yml
vendored
Normal file
51
.github/workflows/docker-publish-dbcreate.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
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
|
||||
51
.github/workflows/docker-publish.yml
vendored
Normal file
51
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
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
6
.gitignore
vendored
@@ -1,7 +1,6 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
package-lock.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
@@ -43,4 +42,7 @@ create_db.sql
|
||||
.vscode
|
||||
|
||||
.env.*
|
||||
.env
|
||||
.env
|
||||
|
||||
test/postgres-data
|
||||
db/remove-account.sh
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -1,16 +1,10 @@
|
||||
FROM node:alpine as builder
|
||||
RUN apk update && apk add --no-cache python make g++
|
||||
FROM node:17
|
||||
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" ]
|
||||
10
Dockerfile.db-create
Normal file
10
Dockerfile.db-create
Normal file
@@ -0,0 +1,10 @@
|
||||
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
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
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.
|
||||
5
app.js
5
app.js
@@ -9,6 +9,7 @@ const opts = Object.assign({
|
||||
const logger = require('pino')(opts);
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const helmet = require('helmet');
|
||||
const cors = require('cors');
|
||||
const passport = require('passport');
|
||||
const routes = require('./lib/routes');
|
||||
@@ -88,7 +89,9 @@ const unless = (paths, middleware) => {
|
||||
return middleware(req, res, next);
|
||||
};
|
||||
};
|
||||
|
||||
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()));
|
||||
|
||||
@@ -20,12 +20,16 @@ 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;
|
||||
@@ -148,6 +152,20 @@ 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 ,
|
||||
@@ -174,6 +192,11 @@ 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 ,
|
||||
@@ -395,6 +418,10 @@ 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';
|
||||
|
||||
@@ -420,6 +447,10 @@ 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);
|
||||
@@ -545,4 +576,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=1;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
215
db/jambones.sqs
215
db/jambones.sqs
File diff suppressed because one or more lines are too long
@@ -1,24 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
console.log('reset_admin_password');
|
||||
const {promisePool} = require('../lib/db');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
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 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)
|
||||
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)
|
||||
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('admin');
|
||||
const passwordHash = await generateHashedPassword(password);
|
||||
const sid = uuidv4();
|
||||
await promisePool.execute('DELETE from users where name = "admin"');
|
||||
await promisePool.execute(sqlInsert,
|
||||
await promisePool.execute('DELETE from api_keys where account_sid is null and service_provider_sid is null');
|
||||
await promisePool.execute(sqlInsert,
|
||||
[
|
||||
sid,
|
||||
'admin',
|
||||
@@ -29,17 +32,15 @@ const doIt = async() => {
|
||||
1
|
||||
]
|
||||
);
|
||||
|
||||
/* reset admin token */
|
||||
const uuid = uuidv4();
|
||||
await promisePool.query(sqlChangeAdminToken, [uuid]);
|
||||
await promisePool.execute(sqlInsertAdminToken, [uuidv4(), uuidv4()]);
|
||||
|
||||
/* create admin token for single account */
|
||||
const api_key_sid = uuidv4();
|
||||
const token = uuidv4();
|
||||
const [r] = await promisePool.query(sqlQueryAccount);
|
||||
if (r.length > 0) {
|
||||
await promisePool.execute(sqlAddAccountAdminToken, [api_key_sid, token, r[0].account_sid]);
|
||||
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]);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
|
||||
@@ -44,12 +44,23 @@ 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
|
||||
@@ -74,19 +85,6 @@ 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
|
||||
@@ -99,4 +97,5 @@ 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;
|
||||
|
||||
@@ -49,6 +49,12 @@ 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
|
||||
|
||||
@@ -27,6 +27,12 @@ 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
|
||||
|
||||
74
db/upgrade-jambonz-db.js
Normal file
74
db/upgrade-jambonz-db.js
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/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();
|
||||
|
||||
@@ -2,12 +2,15 @@ const debug = require('debug')('jambonz:api-server');
|
||||
const Model = require('./model');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const {promisePool} = require('../db');
|
||||
const uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
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`;
|
||||
ON acc.registration_hook_sid = rh.webhook_sid
|
||||
LEFT JOIN webhooks AS qh
|
||||
ON acc.queue_event_hook_sid = qh.webhook_sid`;
|
||||
|
||||
const insertPendingAccountSubscriptionSql = `INSERT account_subscriptions
|
||||
(account_subscription_sid, account_sid, pending, stripe_subscription_id,
|
||||
@@ -55,12 +58,23 @@ 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;
|
||||
});
|
||||
}
|
||||
@@ -248,6 +262,10 @@ Account.fields = [
|
||||
name: 'sip_realm',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'queue_event_hook_sid',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'registration_hook_sid',
|
||||
type: 'string',
|
||||
@@ -279,7 +297,23 @@ 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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Emitter = require('events');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const assert = require('assert');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const {DbErrorBadRequest} = require('../utils/errors');
|
||||
|
||||
@@ -8,17 +8,23 @@ 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 uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const snakeCase = require('../../utils/snake-case');
|
||||
const sysError = require('../error');
|
||||
const {promisePool} = require('../../db');
|
||||
const {hasAccountPermissions, parseAccountSid} = require('./utils');
|
||||
const {hasAccountPermissions, parseAccountSid, enableSubspace, disableSubspace} = require('./utils');
|
||||
const short = require('short-uuid');
|
||||
const VoipCarrier = require('../../models/voip-carrier');
|
||||
const translator = short();
|
||||
|
||||
let idx = 0;
|
||||
|
||||
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'));
|
||||
@@ -91,6 +97,8 @@ function validateUpdateCall(opts) {
|
||||
'child_call_hook',
|
||||
'call_status',
|
||||
'listen_status',
|
||||
'conf_hold_status',
|
||||
'conf_mute_status',
|
||||
'mute_status']
|
||||
.reduce((acc, prop) => (opts[prop] ? ++acc : acc), 0);
|
||||
|
||||
@@ -104,6 +112,7 @@ 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');
|
||||
@@ -118,6 +127,12 @@ 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');
|
||||
}
|
||||
}
|
||||
|
||||
function validateTo(to) {
|
||||
@@ -212,7 +227,7 @@ async function validateCreateCall(logger, sid, req) {
|
||||
|
||||
async function validateCreateMessage(logger, sid, req) {
|
||||
const obj = req.body;
|
||||
//const {lookupAccountByPhoneNumber} = req.app.locals;
|
||||
logger.debug({payload: req.body}, 'validateCreateMessage');
|
||||
|
||||
if (req.user.account_sid !== sid) {
|
||||
throw new DbErrorBadRequest(`unauthorized createMessage request for account ${sid}`);
|
||||
@@ -253,6 +268,9 @@ 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) {
|
||||
@@ -291,12 +309,12 @@ router.post('/', async(req, res) => {
|
||||
await validateAdd(req);
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({webhook_secret: secret}, req.body);
|
||||
for (const prop of ['registration_hook']) {
|
||||
if (obj[prop]) {
|
||||
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) {
|
||||
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
|
||||
delete obj[prop];
|
||||
}
|
||||
delete obj[prop];
|
||||
}
|
||||
|
||||
logger.debug(`Attempting to add account ${JSON.stringify(obj)}`);
|
||||
@@ -353,6 +371,58 @@ 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;
|
||||
@@ -361,12 +431,14 @@ router.put('/:sid', async(req, res) => {
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
if (null !== obj.registration_hook) {
|
||||
for (const prop of ['registration_hook']) {
|
||||
if (prop in obj && Object.keys(obj[prop]).length) {
|
||||
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 ('webhook_sid' in obj[prop]) {
|
||||
const sid = obj[prop]['webhook_sid'];
|
||||
delete obj[prop]['webhook_sid'];
|
||||
await Webhook.update(sid, obj[prop]);
|
||||
}
|
||||
else {
|
||||
@@ -374,30 +446,35 @@ 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 orphanedHook;
|
||||
let orphanedRegHook, orphanedQueueHook;
|
||||
if (null === obj.registration_hook) {
|
||||
const results = await Account.retrieve(sid);
|
||||
if (results.length && results[0].registration_hook_sid) orphanedHook = results[0].registration_hook_sid;
|
||||
if (results.length && results[0].registration_hook_sid) orphanedRegHook = results[0].registration_hook_sid;
|
||||
obj.registration_hook_sid = null;
|
||||
delete obj.registration_hook;
|
||||
}
|
||||
logger.info({obj}, `about to update Account ${sid}`);
|
||||
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;
|
||||
|
||||
const rowsAffected = await Account.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
if (orphanedHook) {
|
||||
await Webhook.remove(orphanedHook);
|
||||
if (orphanedRegHook) {
|
||||
await Webhook.remove(orphanedRegHook);
|
||||
}
|
||||
if (orphanedQueueHook) {
|
||||
await Webhook.remove(orphanedQueueHook);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +573,7 @@ router.post('/:sid/Calls', async(req, res) => {
|
||||
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];
|
||||
const ip = stripPort(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);
|
||||
@@ -642,7 +719,7 @@ router.post('/:sid/Messages', async(req, res) => {
|
||||
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];
|
||||
const ip = stripPort(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);
|
||||
@@ -666,7 +743,7 @@ router.post('/:sid/Messages', async(req, res) => {
|
||||
}
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error({statusCode: response.statusCode}, `Non-success response returned by createMessage ${serviceUrl}`);
|
||||
return res.sendStatus(response.statusCode);
|
||||
return body ? res.status(response.statusCode).json(body) : res.sendStatus(response.statusCode);
|
||||
}
|
||||
res.status(201).json(body);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 {promisePool} = require('../../db');
|
||||
const sysError = require('../error');
|
||||
@@ -13,7 +14,9 @@ AND name = ?`;
|
||||
const sqlSelectCarrierByNameForSP = `SELECT * FROM voip_carriers
|
||||
WHERE service_provider_sid = ?
|
||||
AND name = ?`;
|
||||
const sqlSelectTemplateGateways = `SELECT * FROM predefined_sip_gateways
|
||||
const sqlSelectTemplateSipGateways = `SELECT * FROM predefined_sip_gateways
|
||||
WHERE predefined_carrier_sid = ?`;
|
||||
const sqlSelectTemplateSmppGateways = `SELECT * FROM predefined_smpp_gateways
|
||||
WHERE predefined_carrier_sid = ?`;
|
||||
|
||||
|
||||
@@ -44,22 +47,35 @@ router.post('/:sid', async(req, res) => {
|
||||
throw new DbErrorBadRequest(`A carrier with name ${template.name} already exists`);
|
||||
}
|
||||
|
||||
/* retrieve all the gateways */
|
||||
const [r3] = await promisePool.query(sqlSelectTemplateGateways, template.predefined_carrier_sid);
|
||||
logger.debug({r3}, `retrieved template gateways for ${template.name}`);
|
||||
/* 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}`);
|
||||
|
||||
/* 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 gateways */
|
||||
/* add all the sipp 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 gateway');
|
||||
logger.debug({obj}, 'adding sip 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) {
|
||||
|
||||
@@ -3,7 +3,7 @@ const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const ApiKey = require('../../models/api-key');
|
||||
const Account = require('../../models/account');
|
||||
const decorate = require('./decorate');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const sysError = require('../error');
|
||||
const preconditions = {
|
||||
'add': validateAddToken,
|
||||
|
||||
@@ -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,4 +34,42 @@ 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;
|
||||
|
||||
@@ -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 uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
const short = require('short-uuid');
|
||||
const translator = short();
|
||||
const jwt = require('jsonwebtoken');
|
||||
@@ -156,7 +156,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,
|
||||
name: user.name || user.email,
|
||||
email: user.email,
|
||||
email_validated: user.verified_email,
|
||||
picture: user.picture,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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');
|
||||
@@ -12,11 +13,29 @@ 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);
|
||||
|
||||
@@ -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 uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
const sysError = require('../error');
|
||||
const insertDnsRecords = `INSERT INTO dns_records
|
||||
(dns_record_sid, account_sid, record_type, record_id)
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const router = require('express').Router();
|
||||
const request = require('request');
|
||||
const getProvider = require('../../utils/sms-provider');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const sysError = require('../error');
|
||||
let idx = 0;
|
||||
|
||||
|
||||
@@ -8,13 +8,26 @@ const {
|
||||
testGoogleTts,
|
||||
testGoogleStt,
|
||||
testAwsTts,
|
||||
testAwsStt
|
||||
testAwsStt,
|
||||
testMicrosoftStt,
|
||||
testMicrosoftTts,
|
||||
testWellSaidTts
|
||||
} = require('../../utils/speech-utils');
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
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;
|
||||
const {
|
||||
use_for_stt,
|
||||
use_for_tts,
|
||||
vendor,
|
||||
service_key,
|
||||
access_key_id,
|
||||
secret_access_key,
|
||||
aws_region,
|
||||
api_key,
|
||||
region
|
||||
} = req.body;
|
||||
const account_sid = req.user.account_sid || req.body.account_sid;
|
||||
let service_provider_sid;
|
||||
if (!account_sid) {
|
||||
if (!req.user.hasServiceProviderAuth) {
|
||||
@@ -47,6 +60,19 @@ router.post('/', async(req, res) => {
|
||||
});
|
||||
encrypted_credential = encrypt(data);
|
||||
}
|
||||
else if (vendor === 'microsoft') {
|
||||
const data = JSON.stringify({
|
||||
region,
|
||||
api_key
|
||||
});
|
||||
encrypted_credential = encrypt(data);
|
||||
}
|
||||
else if (vendor === 'wellsaid') {
|
||||
const data = JSON.stringify({
|
||||
api_key
|
||||
});
|
||||
encrypted_credential = encrypt(data);
|
||||
}
|
||||
else throw new DbErrorBadRequest(`invalid speech vendor ${vendor}`);
|
||||
const uuid = await SpeechCredential.make({
|
||||
account_sid,
|
||||
@@ -85,6 +111,15 @@ router.get('/', async(req, res) => {
|
||||
obj.access_key_id = o.access_key_id;
|
||||
obj.secret_access_key = o.secret_access_key;
|
||||
}
|
||||
else if ('microsoft' === obj.vendor) {
|
||||
const o = decrypt(credential);
|
||||
obj.api_key = o.api_key;
|
||||
obj.region = o.region;
|
||||
}
|
||||
else if ('wellsaid' === obj.vendor) {
|
||||
const o = decrypt(credential);
|
||||
obj.api_key = o.api_key;
|
||||
}
|
||||
return obj;
|
||||
}));
|
||||
} catch (err) {
|
||||
@@ -110,6 +145,11 @@ router.get('/:sid', async(req, res) => {
|
||||
obj.access_key_id = o.access_key_id;
|
||||
obj.secret_access_key = o.secret_access_key;
|
||||
}
|
||||
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) {
|
||||
sysError(logger, res, err);
|
||||
@@ -239,6 +279,42 @@ 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);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
const bent = require('bent');
|
||||
const Account = require('../../models/account');
|
||||
const {promisePool} = require('../../db');
|
||||
const {cancelSubscription, detachPaymentMethod} = require('../../utils/stripe-utils');
|
||||
@@ -9,6 +10,8 @@ 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();
|
||||
@@ -74,12 +77,13 @@ 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: '685cd008-0a66-4974-b37a-bdd6d9a3c4aa@192.168.1.100',
|
||||
sip_callid,
|
||||
sip_status: 200,
|
||||
duration: failed ? 0 : 45,
|
||||
attempted_at: attempted_at.getTime(),
|
||||
@@ -167,6 +171,108 @@ 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,
|
||||
@@ -174,5 +280,8 @@ module.exports = {
|
||||
parseAccountSid,
|
||||
parseServiceProviderSid,
|
||||
hasAccountPermissions,
|
||||
hasServiceProviderPermissions
|
||||
hasServiceProviderPermissions,
|
||||
checkLimits,
|
||||
enableSubspace,
|
||||
disableSubspace
|
||||
};
|
||||
|
||||
@@ -5,9 +5,17 @@ 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);
|
||||
|
||||
@@ -1958,7 +1958,7 @@ paths:
|
||||
|
||||
put:
|
||||
summary: update tenant
|
||||
operationId: updateAccount
|
||||
operationId: putTenant
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
@@ -2004,6 +2004,9 @@ 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
|
||||
@@ -2635,6 +2638,56 @@ 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
|
||||
@@ -3049,6 +3102,13 @@ paths:
|
||||
enum:
|
||||
- completed
|
||||
- no-answer
|
||||
conf_mute:
|
||||
type: boolean
|
||||
conf_status:
|
||||
type: string
|
||||
enum:
|
||||
- hold
|
||||
- unhold
|
||||
listen_status:
|
||||
type: string
|
||||
enum:
|
||||
@@ -3110,6 +3170,22 @@ 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:
|
||||
@@ -3632,6 +3708,8 @@ components:
|
||||
type: string
|
||||
registration_hook_sid:
|
||||
type: string
|
||||
queue_event_hook_sid:
|
||||
type: string
|
||||
device_calling_application_sid:
|
||||
type: string
|
||||
is_active:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const crypto = require('crypto');
|
||||
const algorithm = 'aes-256-ctr';
|
||||
const algorithm = process.env.LEGACY_CRYPTO ? 'aes-256-ctr' : 'aes-256-cbc';
|
||||
const iv = crypto.randomBytes(16);
|
||||
const secretKey = crypto.createHash('sha256')
|
||||
.update(String(process.env.JWT_SECRET))
|
||||
|
||||
93
lib/utils/homer-utils.js
Normal file
93
lib/utils/homer-utils.js
Normal file
@@ -0,0 +1,93 @@
|
||||
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
|
||||
};
|
||||
@@ -2,6 +2,7 @@ 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) => {
|
||||
@@ -52,9 +53,59 @@ 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,
|
||||
testAwsStt
|
||||
testWellSaidTts,
|
||||
testAwsStt,
|
||||
testMicrosoftTts,
|
||||
testMicrosoftStt,
|
||||
testWellSaidStt,
|
||||
};
|
||||
|
||||
@@ -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').default;
|
||||
const formurlencoded = require('form-urlencoded');
|
||||
const qs = require('qs');
|
||||
const toBase64 = (str) => Buffer.from(str || '', 'utf8').toString('base64');
|
||||
const basicAuth = () => {
|
||||
|
||||
12259
package-lock.json
generated
Normal file
12259
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "jambonz-api-server",
|
||||
"version": "1.2.0",
|
||||
"version": "v0.7.3",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"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/ ",
|
||||
"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=info 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"
|
||||
},
|
||||
@@ -16,39 +17,39 @@
|
||||
"url": "https://github.com/jambonz/jambonz-api-server.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@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",
|
||||
"argon2-ffi": "^2.0.0",
|
||||
"aws-sdk": "^2.839.0",
|
||||
"aws-sdk": "^2.1066.0",
|
||||
"bent": "^7.3.12",
|
||||
"cors": "^2.8.5",
|
||||
"debug": "^4.3.1",
|
||||
"express": "^4.17.1",
|
||||
"form-data": "^2.3.3",
|
||||
"form-urlencoded": "^4.2.1",
|
||||
"google-libphonenumber": "^3.2.15",
|
||||
"debug": "^4.3.3",
|
||||
"express": "^4.17.2",
|
||||
"form-data": "^2.5.1",
|
||||
"form-urlencoded": "^6.0.5",
|
||||
"helmet": "^5.0.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mailgun.js": "^3.3.0",
|
||||
"mysql2": "^2.2.5",
|
||||
"passport": "^0.4.1",
|
||||
"mailgun.js": "^3.7.3",
|
||||
"microsoft-cognitiveservices-speech-sdk": "^1.19.0",
|
||||
"mysql2": "^2.3.3",
|
||||
"passport": "^0.5.2",
|
||||
"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.138.0",
|
||||
"swagger-ui-express": "^4.1.6",
|
||||
"uuid": "^3.4.0",
|
||||
"stripe": "^8.196.0",
|
||||
"swagger-ui-express": "^4.3.0",
|
||||
"uuid": "^8.3.2",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"nyc": "^15.1.0",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.9",
|
||||
"tape": "^5.2.2"
|
||||
}
|
||||
|
||||
@@ -154,6 +154,10 @@ test('account tests', async(t) => {
|
||||
registration_hook: {
|
||||
url: 'http://example.com/reg2',
|
||||
method: 'get'
|
||||
},
|
||||
queue_event_hook: {
|
||||
url: 'http://example.com/q',
|
||||
method: 'post'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
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"
|
||||
@@ -10,7 +19,11 @@ services:
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "127.0.0.1", "--protocol", "tcp"]
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
retries: 10
|
||||
networks:
|
||||
jambonz-api:
|
||||
ipv4_address: 172.58.0.2
|
||||
|
||||
redis:
|
||||
image: redis:5-alpine
|
||||
ports:
|
||||
@@ -18,8 +31,102 @@ services:
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
|
||||
networks:
|
||||
jambonz-api:
|
||||
ipv4_address: 172.58.0.3
|
||||
|
||||
influxdb:
|
||||
image: influxdb:1.8-alpine
|
||||
platform: linux/x86_64
|
||||
image: influxdb:1.8
|
||||
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
|
||||
|
||||
|
||||
|
||||
45
test/homer.js
Normal file
45
test/homer.js
Normal file
@@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,4 +13,5 @@ require('./ms-teams');
|
||||
require('./speech-credentials');
|
||||
require('./recent-calls');
|
||||
require('./webapp_tests');
|
||||
//require('./homer');
|
||||
require('./docker_stop');
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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', {
|
||||
|
||||
6
test/postgresql/init-user-db.sh
Executable file
6
test/postgresql/init-user-db.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
CREATE DATABASE homer_config;
|
||||
EOSQL
|
||||
@@ -66,6 +66,22 @@ 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);
|
||||
|
||||
|
||||
68
test/scenarios/uac.xml
Normal file
68
test/scenarios/uac.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const exec = require('child_process').exec ;
|
||||
const { sippUac } = require('./sipp')('test_jambonz-api');
|
||||
let stopping = false;
|
||||
|
||||
process.on('SIGINT', async() => {
|
||||
@@ -66,6 +67,14 @@ 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..')
|
||||
@@ -81,6 +90,7 @@ startDocker()
|
||||
.then(createSchema)
|
||||
.then(seedDb)
|
||||
.then(resetAdminPassword)
|
||||
.then(generateSipTrace)
|
||||
.then(() => {
|
||||
console.log('ready for testing!');
|
||||
require('..');
|
||||
|
||||
68
test/sipp.js
Normal file
68
test/sipp.js
Normal file
@@ -0,0 +1,68 @@
|
||||
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());
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -7,6 +7,7 @@ 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);
|
||||
@@ -109,10 +110,64 @@ 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) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const bytesToUuid = require("uuid/lib/bytesToUuid");
|
||||
const uuid = require('uuid').v4;
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
|
||||
const authAdmin = {bearer: ADMIN_TOKEN};
|
||||
|
||||
Reference in New Issue
Block a user