Compare commits

...

50 Commits

Author SHA1 Message Date
Dave Horton
cd935999a6 bump version 2022-02-09 15:42:52 -05:00
Dave Horton
863d7a02c8 update to latest @jambonz/realtimedb-helpers with support for redis username / password auth 2022-02-09 15:30:18 -05:00
Dave Horton
45c023e374 add helmet middleware 2022-02-03 07:26:41 -05:00
Snyk bot
f634ca4076 fix: upgrade stripe from 8.195.0 to 8.196.0 (#32)
Snyk has created this PR to upgrade stripe from 8.195.0 to 8.196.0.

See this package in npm:
https://www.npmjs.com/package/stripe

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-02-02 22:57:08 -05:00
Dave Horton
654f93b30c update deps 2022-02-01 21:03:49 -05:00
Snyk bot
280aaef120 fix: upgrade express from 4.17.1 to 4.17.2 (#31)
Snyk has created this PR to upgrade express from 4.17.1 to 4.17.2.

See this package in npm:
https://www.npmjs.com/package/express

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-02-01 20:51:26 -05:00
Dave Horton
e6931574e3 fix bug from prev commit 2022-02-01 20:46:41 -05:00
Dave Horton
1bd21cb39d update to uuid@8.3.2 2022-02-01 20:42:10 -05:00
Dave Horton
480e1155f3 0.7.2 version 2022-01-28 09:16:51 -05:00
Dave Horton
75e7c1058b lint 2022-01-27 15:31:11 -05:00
Dave Horton
a5e4fafda4 minor 2022-01-27 15:26:09 -05:00
Dave Horton
2e041df6e4 update deps 2022-01-27 09:04:54 -05:00
Snyk bot
3ee82d6c8c fix: upgrade passport from 0.5.0 to 0.5.2 (#24)
Snyk has created this PR to upgrade passport from 0.5.0 to 0.5.2.

See this package in npm:
https://www.npmjs.com/package/passport

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-27 08:33:19 -05:00
Snyk bot
d396c5b252 fix: upgrade aws-sdk from 2.1048.0 to 2.1049.0 (#26)
Snyk has created this PR to upgrade aws-sdk from 2.1048.0 to 2.1049.0.

See this package in npm:
https://www.npmjs.com/package/aws-sdk

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-27 08:32:04 -05:00
Snyk bot
daef0ee215 fix: upgrade form-urlencoded from 6.0.4 to 6.0.5 (#27)
Snyk has created this PR to upgrade form-urlencoded from 6.0.4 to 6.0.5.

See this package in npm:
https://www.npmjs.com/package/form-urlencoded

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-27 08:30:45 -05:00
Snyk bot
1d168e93e1 fix: package.json & package-lock.json to reduce vulnerabilities (#28)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-SWAGGERUIDIST-2314884
2022-01-27 08:30:30 -05:00
dependabot[bot]
605a0e762f Bump node-fetch from 2.6.1 to 2.6.7 (#30)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-27 08:30:16 -05:00
Dave Horton
c9bf943656 initial changes to support wellsaid tts (#29) 2022-01-27 08:17:51 -05:00
Snyk bot
3aac11560a fix: upgrade mysql2 from 2.2.5 to 2.3.3 (#20)
Snyk has created this PR to upgrade mysql2 from 2.2.5 to 2.3.3.

See this package in npm:
https://www.npmjs.com/package/mysql2

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-23 19:53:19 -05:00
Snyk bot
bb4a20a375 fix: upgrade form-data from 2.3.3 to 2.5.1 (#22)
Snyk has created this PR to upgrade form-data from 2.3.3 to 2.5.1.

See this package in npm:
https://www.npmjs.com/package/form-data

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-23 19:52:55 -05:00
Snyk bot
45c4c626f2 fix: upgrade @google-cloud/text-to-speech from 3.2.2 to 3.4.0 (#19)
Snyk has created this PR to upgrade @google-cloud/text-to-speech from 3.2.2 to 3.4.0.

See this package in npm:
https://www.npmjs.com/package/@google-cloud/text-to-speech

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-23 19:52:39 -05:00
Snyk bot
65e6d75f72 fix: upgrade debug from 4.3.1 to 4.3.3 (#23)
Snyk has created this PR to upgrade debug from 4.3.1 to 4.3.3.

See this package in npm:
https://www.npmjs.com/package/debug

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-23 19:52:15 -05:00
Snyk bot
c6bb273aa0 fix: upgrade aws-sdk from 2.929.0 to 2.1048.0 (#14)
Snyk has created this PR to upgrade aws-sdk from 2.929.0 to 2.1048.0.

See this package in npm:
https://www.npmjs.com/package/aws-sdk

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-22 20:34:31 -05:00
Snyk bot
256b295be1 fix: upgrade stripe from 8.155.0 to 8.195.0 (#15)
Snyk has created this PR to upgrade stripe from 8.155.0 to 8.195.0.

See this package in npm:
https://www.npmjs.com/package/stripe

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-22 20:34:14 -05:00
Snyk bot
693ba51339 fix: upgrade mailgun.js from 3.4.0 to 3.7.3 (#16)
Snyk has created this PR to upgrade mailgun.js from 3.4.0 to 3.7.3.

See this package in npm:
https://www.npmjs.com/package/mailgun.js

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-22 20:33:58 -05:00
Snyk bot
531366ee58 fix: upgrade @google-cloud/speech from 4.5.2 to 4.9.0 (#17)
Snyk has created this PR to upgrade @google-cloud/speech from 4.5.2 to 4.9.0.

See this package in npm:
https://www.npmjs.com/package/@google-cloud/speech

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-22 20:33:14 -05:00
Snyk bot
a36604029c fix: upgrade @jambonz/realtimedb-helpers from 0.4.3 to 0.4.14 (#18)
Snyk has created this PR to upgrade @jambonz/realtimedb-helpers from 0.4.3 to 0.4.14.

See this package in npm:
https://www.npmjs.com/package/@jambonz/realtimedb-helpers

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/b7e09765-19b2-4aa5-90ba-10432e250041?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-01-22 20:32:36 -05:00
Dave Horton
63a88844aa add package-lock.json 2022-01-21 22:04:30 -05:00
Dave Horton
24f6833493 fix test on mac 2022-01-21 09:58:49 -05:00
Dave Horton
4119d766d5 Dockerfile change 2021-12-21 20:51:02 -05:00
Dave Horton
936a9da887 dockerfile change 2021-12-21 09:52:27 -05:00
Dave Horton
77098f273d bump version 2021-12-21 09:43:27 -05:00
Dave Horton
e27b5a39a6 Dockerfile 2021-12-17 13:49:59 -05:00
Dave Horton
66872494f9 added docker publish 2021-12-13 14:27:23 -05:00
Dave Horton
4557b32804 added docker publish 2021-12-13 14:18:34 -05:00
Dave Horton
e55fe77171 version bump 2021-12-13 09:54:53 -05:00
Dave Horton
0fd87a732f need to provide status ENABLED when creating a subspace teleport 2021-12-08 20:11:15 -05:00
Dave Horton
f6d358d3df Subspace (#12)
* changes for subspace (via nimbleape)

* changes from more testing

* working api to subspace

* more subspace fixes

* further subspace fixes
2021-12-07 07:40:50 -05:00
Dave Horton
19a55a5774 add env LEGACY_CRYPTO 2021-11-29 09:02:28 -05:00
Dave Horton
f1d7dcc6d2 initial changes for microsoft speech support (#11)
* initial changes for microsoft speech support

* remove very wordy log message
2021-11-17 20:50:26 -05:00
Dave Horton
bc8ff644db db-upgrade job exits with non-zero error code if fail to connect to db 2021-11-08 13:14:23 -05:00
Dave Horton
fa6acef02a logging to db init job 2021-11-08 12:49:11 -05:00
Dave Horton
8117f77955 Dockerfile to init/upgrade the database, and associated changes 2021-11-08 10:54:57 -05:00
Dave Horton
4bf79fe42b changed Dockerfile 2021-11-04 12:52:28 -04:00
Dave Horton
3d879b5ac9 version bump 2021-11-03 13:50:47 -04:00
Dave Horton
f882a0e3c8 update for some vulnerabilities 2021-11-02 16:19:37 -04:00
Dave Horton
0d18a097fb bump version 2021-10-21 13:08:50 -04:00
Dave Horton
91119c6971 bump version 2021-10-21 13:01:00 -04:00
Dave Horton
f3c4b89897 Merge branch 'main' of github.com:jambonz/jambonz-api-server into main 2021-10-21 11:34:29 -04:00
Dave Horton
bda5e69cbb fix stripe bug 2021-10-20 08:48:30 -04:00
27 changed files with 12896 additions and 91 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
node_modules

View 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
View 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

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
# Logs
logs
*.log
package-lock.json
# Runtime data
pids

View File

@@ -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
View 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" ]

5
app.js
View File

@@ -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()));

View File

@@ -28,6 +28,8 @@ 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;
@@ -190,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 ,
@@ -411,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';
@@ -565,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;

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
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)

74
db/upgrade-jambonz-db.js Normal file
View 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();

View File

@@ -2,7 +2,8 @@ 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
@@ -296,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;

View File

@@ -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');

View File

@@ -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'));
@@ -365,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;
@@ -515,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);
@@ -661,9 +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);
}
let ip = fs[idx++ % fs.length];
const arr = /^(.*):\d+$/.exec(ip);
if (arr) ip = arr[1];
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);

View File

@@ -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,

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 uuid = require('uuid').v4;
const { v4: uuid } = require('uuid');
const short = require('short-uuid');
const translator = short();
const jwt = require('jsonwebtoken');

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 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)

View File

@@ -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;

View File

@@ -8,12 +8,25 @@ 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 {
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) {
@@ -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);

View File

@@ -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();
@@ -224,6 +227,52 @@ const checkLimits = async(req, res, next) => {
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,
@@ -232,5 +281,7 @@ module.exports = {
parseServiceProviderSid,
hasAccountPermissions,
hasServiceProviderPermissions,
checkLimits
checkLimits,
enableSubspace,
disableSubspace
};

View File

@@ -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))

View File

@@ -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,
};

12259
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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 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 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,29 +17,32 @@
"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": "^6.0.4",
"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",
"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": {

View File

@@ -10,6 +10,7 @@ networks:
services:
mysql:
platform: linux/x86_64
image: mysql:5.7
ports:
- "3360:3306"
@@ -35,7 +36,8 @@ services:
ipv4_address: 172.58.0.3
influxdb:
image: influxdb:1.8-alpine
platform: linux/x86_64
image: influxdb:1.8
ports:
- "8086:8086"
networks:

View File

@@ -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) {

View File

@@ -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};