mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
Compare commits
73 Commits
v0.7.7
...
v0.8.2-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb705fe808 | ||
|
|
789a0ba3ff | ||
|
|
27cb7c471a | ||
|
|
39260f0b47 | ||
|
|
75a2b42d65 | ||
|
|
518a9163fb | ||
|
|
5fb4bd7bd1 | ||
|
|
409ad68123 | ||
|
|
17afb7102a | ||
|
|
6e7cb9b332 | ||
|
|
34f83e323c | ||
|
|
00af458cb3 | ||
|
|
389017a5c4 | ||
|
|
c4cc6c51ee | ||
|
|
aea7388ba0 | ||
|
|
3d86292a90 | ||
|
|
08962fe7ba | ||
|
|
e573f6ab06 | ||
|
|
4934e2a1ca | ||
|
|
cc384995ea | ||
|
|
d4506fb8fa | ||
|
|
042a2c37dc | ||
|
|
6da1903dee | ||
|
|
10009d903e | ||
|
|
69a72c5e43 | ||
|
|
f7f3881d70 | ||
|
|
4d48c6946c | ||
|
|
5b48fc8a07 | ||
|
|
f46be95551 | ||
|
|
d4f2be3dc1 | ||
|
|
a46c24b3aa | ||
|
|
f5c833720a | ||
|
|
4d2cc15de4 | ||
|
|
019599741a | ||
|
|
f2c2623b28 | ||
|
|
6c494786c8 | ||
|
|
80ee1d06d7 | ||
|
|
274377960e | ||
|
|
02bba9d981 | ||
|
|
642a6615a0 | ||
|
|
b0f317b545 | ||
|
|
43d991ef1c | ||
|
|
5ab1ad9056 | ||
|
|
37062fa720 | ||
|
|
e42634d726 | ||
|
|
5a9e22df5e | ||
|
|
e75eae4e24 | ||
|
|
2000e7de90 | ||
|
|
317a094b3e | ||
|
|
86953b9524 | ||
|
|
c6fd24bc13 | ||
|
|
21a81c224f | ||
|
|
59445d62cc | ||
|
|
4a78c5c1fc | ||
|
|
89f25d7eda | ||
|
|
49491fe1c4 | ||
|
|
add9f2cb99 | ||
|
|
dd2176bf89 | ||
|
|
fadbe116c2 | ||
|
|
02e3358c8f | ||
|
|
7bb78a9a2a | ||
|
|
5e070324ae | ||
|
|
5be286d3db | ||
|
|
0fc8500361 | ||
|
|
ee5e25bb8d | ||
|
|
1b67d5f89d | ||
|
|
46eee0cc60 | ||
|
|
8d303390b7 | ||
|
|
35214a04dc | ||
|
|
110c4ed0d8 | ||
|
|
7890de8c8f | ||
|
|
b65dc7080c | ||
|
|
9d0be0f8e1 |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -1,17 +1,15 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: lts/*
|
||||
- run: npm install
|
||||
- run: npm run jslint
|
||||
- run: npm test
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile.db-create --tag $IMAGE_NAME
|
||||
|
||||
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile --tag $IMAGE_NAME
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
run-tests.sh
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 node:18.8.0-alpine as base
|
||||
FROM --platform=linux/amd64 node:18.14.1-alpine3.16 as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
@@ -20,4 +20,4 @@ ARG NODE_ENV
|
||||
|
||||
ENV NODE_ENV $NODE_ENV
|
||||
|
||||
CMD [ "node", "app.js" ]
|
||||
CMD [ "node", "app.js" ]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 node:18.8.0-alpine as base
|
||||
FROM --platform=linux/amd64 node:18.14.1-alpine3.16 as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
|
||||
47
README.md
47
README.md
@@ -1,29 +1,44 @@
|
||||
# jambonz-api-server 
|
||||
|
||||
Jambones REST API server.
|
||||
Jambones REST API server of the jambones platform.
|
||||
|
||||
## Configuration
|
||||
|
||||
This process requires the following environment variables to be set.
|
||||
Configuration is provided via environment variables:
|
||||
|
||||
```
|
||||
JAMBONES_MYSQL_HOST
|
||||
JAMBONES_MYSQL_USER
|
||||
JAMBONES_MYSQL_PASSWORD
|
||||
JAMBONES_MYSQL_DATABASE
|
||||
JAMBONES_MYSQL_CONNECTION_LIMIT # defaults to 10
|
||||
JAMBONES_REDIS_HOST
|
||||
JAMBONES_REDIS_PORT
|
||||
JAMBONES_LOGLEVEL # defaults to info
|
||||
JAMBONES_API_VERSION # defaults to v1
|
||||
HTTP_PORT # defaults to 3000
|
||||
```
|
||||
| variable | meaning | required?|
|
||||
|----------|----------|---------|
|
||||
|JWT_SECRET| secret for signing JWT token |yes|
|
||||
|JWT_EXPIRES_IN| expiration time for JWT token(in minutes) |no|
|
||||
|ENCRYPTION_SECRET| secret for credential encryption(JWT_SECRET is deprecated) |yes|
|
||||
|HTTP_PORT| tcp port to listen on for API requests from jambonz-api-server |no|
|
||||
|JAMBONES_LOGLEVEL| log level for application, 'info' or 'debug' |no|
|
||||
|JAMBONES_MYSQL_HOST| mysql host |yes|
|
||||
|JAMBONES_MYSQL_USER| mysql username |yes|
|
||||
|JAMBONES_MYSQL_PASSWORD| mysql password |yes|
|
||||
|JAMBONES_MYSQL_DATABASE| mysql data |yes|
|
||||
|JAMBONES_MYSQL_PORT| mysql port |no|
|
||||
|JAMBONES_MYSQL_CONNECTION_LIMIT| mysql connection limit |no|
|
||||
|JAMBONES_REDIS_HOST| redis host |yes|
|
||||
|JAMBONES_REDIS_PORT| redis port |no|
|
||||
|RATE_LIMIT_WINDOWS_MINS| rate limit window |no|
|
||||
|RATE_LIMIT_MAX_PER_WINDOW| number of requests per window |no|
|
||||
|JAMBONES_TRUST_PROXY| trust proxies, must be a number |no|
|
||||
|JAMBONES_API_VERSION| api version |no|
|
||||
|JAMBONES_TIME_SERIES_HOST| influxdb host |yes|
|
||||
|JAMBONES_CLUSTER_ID| cluster id |no|
|
||||
|HOMER_BASE_URL| HOMER URL |no|
|
||||
|HOMER_USERNAME| HOMER username |no|
|
||||
|HOMER_PASSWORD| HOMER password |no|
|
||||
|K8S| service running as kubernetes service |no|
|
||||
|K8S_FEATURE_SERVER_SERVICE_NAME| feature server name(required for K8S) |no|
|
||||
|K8S_FEATURE_SERVER_SERVICE_PORT| feature server port(required for K8S) |no|
|
||||
|
||||
#### Database dependency
|
||||
A mysql database is used to store long-lived objects such as Accounts, Applications, etc. To create the database schema, use or review the scripts in the 'db' folder, particularly:
|
||||
- [create_db.sql](db/create_db.sql), which creates the database and associated user (you may want to edit the username and password),
|
||||
- [jambones-sql.sql](db/jambones-sql.sql), which creates the schema,
|
||||
- [create-admin-token.sql](db/create-admin-token.sql), which creates an admin-level auth token that can be used for testing/exercising the API.
|
||||
- [seed-production-database-open-source.sql](db/seed-production-database-open-source.sql), which seeds the database with initial dataset(accounts, permissions, api keys, applications etc).
|
||||
- [create-admin-user.sql](db/create-admin-user.sql), which creates admin user with password set to "admin". The password will be forced to change after the first login.
|
||||
|
||||
> Note: due to the dependency on the npmjs [mysql](https://www.npmjs.com/package/mysql) package, the mysql database must be configured to use sql [native authentication](https://medium.com/@crmcmullen/how-to-run-mysql-8-0-with-native-password-authentication-502de5bac661).
|
||||
|
||||
|
||||
14
app.js
14
app.js
@@ -21,6 +21,9 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
|
||||
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
||||
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
||||
assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
|
||||
assert.ok(process.env.ENCRYPTION_SECRET || process.env.JWT_SECRET, 'missing ENCRYPTION_SECRET env var');
|
||||
assert.ok(process.env.JWT_SECRET, 'missing JWT_SECRET env var');
|
||||
|
||||
const {
|
||||
queryCdrs,
|
||||
queryCdrsSP,
|
||||
@@ -40,9 +43,15 @@ const {
|
||||
retrieveSet,
|
||||
addKey,
|
||||
retrieveKey,
|
||||
deleteKey
|
||||
deleteKey,
|
||||
} = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
host: process.env.JAMBONES_REDIS_HOST,
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
const {
|
||||
getTtsVoices
|
||||
} = require('@jambonz/speech-utils')({
|
||||
host: process.env.JAMBONES_REDIS_HOST,
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
const {
|
||||
@@ -78,6 +87,7 @@ app.locals = {
|
||||
addKey,
|
||||
retrieveKey,
|
||||
deleteKey,
|
||||
getTtsVoices,
|
||||
lookupAppBySid,
|
||||
lookupAccountBySid,
|
||||
lookupAccountByPhoneNumber,
|
||||
|
||||
@@ -45,8 +45,6 @@ VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
|
||||
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
|
||||
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
|
||||
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
10
db/create-admin-user.sql
Normal file
10
db/create-admin-user.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
/* hashed password is "admin" */
|
||||
insert into users (user_sid, name, email, hashed_password, force_change, provider, email_validated)
|
||||
values ('12c80508-edf9-4b22-8d09-55abd02648eb', 'admin', 'joe@foo.bar', '$argon2i$v=19$m=65536,t=3,p=4$c2FsdHNhbHRzYWx0c2FsdA$x5OO6gXFXS25oqUU2JvbYqrSgRxBujNUJBq6xv9EgjM', 1, 'local', 1);
|
||||
|
||||
insert into user_permissions (user_permissions_sid, user_sid, permission_sid)
|
||||
values ('8919e0dc-4d69-4de5-be56-a121598d9093', '12c80508-edf9-4b22-8d09-55abd02648eb', 'ffbc342a-546a-11ed-bdc3-0242ac120002');
|
||||
insert into user_permissions (user_permissions_sid, user_sid, permission_sid)
|
||||
values ('d6fdf064-0a65-4b17-8b10-5500e956a159', '12c80508-edf9-4b22-8d09-55abd02648eb', 'ffbc3a10-546a-11ed-bdc3-0242ac120002');
|
||||
insert into user_permissions (user_permissions_sid, user_sid, permission_sid)
|
||||
values ('f68185dd-0486-4767-a77d-a0b84c1b236e' ,'12c80508-edf9-4b22-8d09-55abd02648eb', 'ffbc3c5e-546a-11ed-bdc3-0242ac120002');
|
||||
@@ -22,6 +22,10 @@ DROP TABLE IF EXISTS lcr_routes;
|
||||
|
||||
DROP TABLE IF EXISTS password_settings;
|
||||
|
||||
DROP TABLE IF EXISTS user_permissions;
|
||||
|
||||
DROP TABLE IF EXISTS permissions;
|
||||
|
||||
DROP TABLE IF EXISTS predefined_sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS predefined_smpp_gateways;
|
||||
@@ -79,7 +83,7 @@ CREATE TABLE account_limits
|
||||
(
|
||||
account_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (account_limits_sid)
|
||||
);
|
||||
@@ -145,6 +149,14 @@ require_digit BOOLEAN NOT NULL DEFAULT false,
|
||||
require_special_character BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE TABLE permissions
|
||||
(
|
||||
permission_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(32) NOT NULL UNIQUE ,
|
||||
description VARCHAR(255),
|
||||
PRIMARY KEY (permission_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE predefined_carriers
|
||||
(
|
||||
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -254,7 +266,7 @@ CREATE TABLE service_provider_limits
|
||||
(
|
||||
service_provider_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36) NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (service_provider_limits_sid)
|
||||
);
|
||||
@@ -348,6 +360,14 @@ register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
CREATE TABLE user_permissions
|
||||
(
|
||||
user_permissions_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
user_sid CHAR(36) NOT NULL,
|
||||
permission_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (user_permissions_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE smpp_gateways
|
||||
(
|
||||
smpp_gateway_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -365,7 +385,7 @@ PRIMARY KEY (smpp_gateway_sid)
|
||||
CREATE TABLE phone_numbers
|
||||
(
|
||||
phone_number_sid CHAR(36) UNIQUE ,
|
||||
number VARCHAR(32) NOT NULL UNIQUE ,
|
||||
number VARCHAR(132) NOT NULL UNIQUE ,
|
||||
voip_carrier_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
@@ -415,6 +435,7 @@ account_sid CHAR(36) COMMENT 'account that this application belongs to (if null,
|
||||
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
|
||||
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
|
||||
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||
app_json TEXT,
|
||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
speech_synthesis_voice VARCHAR(64),
|
||||
@@ -481,6 +502,7 @@ ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid)
|
||||
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
||||
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX permission_sid_idx ON permissions (permission_sid);
|
||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
|
||||
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
|
||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
|
||||
@@ -561,6 +583,12 @@ ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_
|
||||
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX user_permissions_sid_idx ON user_permissions (user_permissions_sid);
|
||||
CREATE INDEX user_sid_idx ON user_permissions (user_sid);
|
||||
ALTER TABLE user_permissions ADD FOREIGN KEY user_sid_idxfk (user_sid) REFERENCES users (user_sid) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE user_permissions ADD FOREIGN KEY permission_sid_idxfk (permission_sid) REFERENCES permissions (permission_sid);
|
||||
|
||||
CREATE INDEX smpp_gateway_sid_idx ON smpp_gateways (smpp_gateway_sid);
|
||||
CREATE INDEX voip_carrier_sid_idx ON smpp_gateways (voip_carrier_sid);
|
||||
ALTER TABLE smpp_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
@@ -620,4 +648,4 @@ ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (devic
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
201
db/jambones.sqs
201
db/jambones.sqs
File diff suppressed because one or more lines are too long
@@ -12,6 +12,9 @@ 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 sqlInsertPermissions = `
|
||||
INSERT into user_permissions (user_permissions_sid, user_sid, permission_sid)
|
||||
VALUES (?,?,?)`;
|
||||
|
||||
const password = process.env.JAMBONES_ADMIN_INITIAL_PASSWORD || 'admin';
|
||||
console.log(`reset_admin_password, initial admin password is ${password}`);
|
||||
@@ -21,6 +24,7 @@ const doIt = async() => {
|
||||
const sid = uuidv4();
|
||||
await promisePool.execute('DELETE from users where name = "admin"');
|
||||
await promisePool.execute('DELETE from api_keys where account_sid is null and service_provider_sid is null');
|
||||
|
||||
await promisePool.execute(sqlInsert,
|
||||
[
|
||||
sid,
|
||||
@@ -34,6 +38,12 @@ const doIt = async() => {
|
||||
);
|
||||
await promisePool.execute(sqlInsertAdminToken, [uuidv4(), uuidv4()]);
|
||||
|
||||
/* assign all permissions to the admin user */
|
||||
const [p] = await promisePool.query('SELECT * from permissions');
|
||||
for (const perm of p) {
|
||||
await promisePool.execute(sqlInsertPermissions, [uuidv4(), sid, perm.permission_sid]);
|
||||
}
|
||||
|
||||
/* create admin token for single account */
|
||||
const [r] = await promisePool.query({sql: sqlQueryAccount, nestTables: true});
|
||||
if (1 === r.length && r[0].api_keys.api_key_sid === null) {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
-- create standard permissions
|
||||
insert into permissions (permission_sid, name, description)
|
||||
values
|
||||
('ffbc342a-546a-11ed-bdc3-0242ac120002', 'VIEW_ONLY', 'Can view data but not make changes'),
|
||||
('ffbc3a10-546a-11ed-bdc3-0242ac120002', 'PROVISION_SERVICES', 'Can provision services'),
|
||||
('ffbc3c5e-546a-11ed-bdc3-0242ac120002', 'PROVISION_USERS', 'Can provision users');
|
||||
|
||||
insert into sbc_addresses (sbc_address_sid, ipv4, port)
|
||||
values('f6567ae1-bf97-49af-8931-ca014b689995', '52.55.111.178', 5060);
|
||||
insert into sbc_addresses (sbc_address_sid, ipv4, port)
|
||||
@@ -91,10 +98,8 @@ VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
|
||||
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
|
||||
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
|
||||
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
-- create standard permissions
|
||||
insert into permissions (permission_sid, name, description)
|
||||
values
|
||||
('ffbc342a-546a-11ed-bdc3-0242ac120002', 'VIEW_ONLY', 'Can view data but not make changes'),
|
||||
('ffbc3a10-546a-11ed-bdc3-0242ac120002', 'PROVISION_SERVICES', 'Can provision services'),
|
||||
('ffbc3c5e-546a-11ed-bdc3-0242ac120002', 'PROVISION_USERS', 'Can provision users');
|
||||
|
||||
-- create one service provider and account
|
||||
insert into api_keys (api_key_sid, token)
|
||||
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', '38700987-c7a4-4685-a5bb-af378f9734de');
|
||||
@@ -61,6 +68,9 @@ VALUES
|
||||
('d2ccfcb1-9198-4fe9-a0ca-6e49395837c4', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.172.60.0', 30, 5060, 1, 0),
|
||||
('6b1d0032-4430-41f1-87c6-f22233d394ef', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.244.51.0', 30, 5060, 1, 0),
|
||||
('0de40217-8bd5-4aa8-a9fd-1994282953c6', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.192', 30, 5060, 1, 0),
|
||||
('48b108e3-1ce7-4f18-a4cb-e41e63688bdf', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.193', 30, 5060, 1, 0),
|
||||
('d9131a69-fe44-4c2a-ba82-4adc81f628dd', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.194', 30, 5060, 1, 0),
|
||||
('34a6a311-4bd6-49ca-aa77-edd3cb92c6e1', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.171.127.195', 30, 5060, 1, 0),
|
||||
('37bc0b20-b53c-4c31-95a6-f82b1c3713e3', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '35.156.191.128', 30, 5060, 1, 0),
|
||||
('39791f4e-b612-4882-a37e-e92711a39f3f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.65.63.192', 30, 5060, 1, 0),
|
||||
('81a0c8cb-a33e-42da-8f20-99083da6f02f', '7d509a18-bbff-4c5d-b21e-b99bf8f8c49a', '54.252.254.64', 30, 5060, 1, 0),
|
||||
@@ -85,10 +95,8 @@ VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
|
||||
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
|
||||
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
|
||||
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
-- create standard permissions
|
||||
insert into permissions (permission_sid, name, description)
|
||||
values
|
||||
('ffbc342a-546a-11ed-bdc3-0242ac120002', 'VIEW_ONLY', 'Can view data but not make changes'),
|
||||
('ffbc3a10-546a-11ed-bdc3-0242ac120002', 'PROVISION_SERVICES', 'Can provision services'),
|
||||
('ffbc3c5e-546a-11ed-bdc3-0242ac120002', 'PROVISION_USERS', 'Can provision users');
|
||||
|
||||
-- create one service provider
|
||||
insert into service_providers (service_provider_sid, name, description, root_domain)
|
||||
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'sip.jambonz.xyz', 'jambonz.xyz service provider', 'sip.jambonz.xyz');
|
||||
@@ -65,10 +72,8 @@ VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.77', 32, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.140.77', 32, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.77', 32, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.141.77', 32, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.77', 32, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.34', 32, 5060, 1, 0),
|
||||
('e7447e7e-2c7d-4738-ab53-097c187236ff', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.143.66', 32, 5060, 1, 0),
|
||||
('38d8520a-527f-4f8e-8456-f9dfca742561', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.77', 32, 5060, 1, 0),
|
||||
('834f8b0c-d4c2-4f3e-93d9-cf307995eedd', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.88', 32, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
|
||||
@@ -57,6 +57,36 @@ const sql = {
|
||||
'ALTER TABLE `voip_carriers` ADD COLUMN `register_from_user` VARCHAR(128)',
|
||||
'ALTER TABLE `voip_carriers` ADD COLUMN `register_from_domain` VARCHAR(256)',
|
||||
'ALTER TABLE `voip_carriers` ADD COLUMN `register_public_ip_in_contact` BOOLEAN NOT NULL DEFAULT false'
|
||||
],
|
||||
'8000': [
|
||||
'ALTER TABLE `applications` ADD COLUMN `app_json` TEXT',
|
||||
'ALTER TABLE voip_carriers CHANGE register_public_domain_in_contact register_public_ip_in_contact BOOLEAN',
|
||||
'alter table phone_numbers modify number varchar(132) NOT NULL UNIQUE',
|
||||
`CREATE TABLE permissions
|
||||
(
|
||||
permission_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(32) NOT NULL UNIQUE ,
|
||||
description VARCHAR(255),
|
||||
PRIMARY KEY (permission_sid)
|
||||
)`,
|
||||
`CREATE TABLE user_permissions
|
||||
(
|
||||
user_permissions_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
user_sid CHAR(36) NOT NULL,
|
||||
permission_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (user_permissions_sid)
|
||||
)`,
|
||||
`CREATE TABLE password_settings
|
||||
(
|
||||
min_password_length INTEGER NOT NULL DEFAULT 8,
|
||||
require_digit BOOLEAN NOT NULL DEFAULT false,
|
||||
require_special_character BOOLEAN NOT NULL DEFAULT false
|
||||
)`,
|
||||
'CREATE INDEX user_permissions_sid_idx ON user_permissions (user_permissions_sid)',
|
||||
'CREATE INDEX user_sid_idx ON user_permissions (user_sid)',
|
||||
'ALTER TABLE user_permissions ADD FOREIGN KEY user_sid_idxfk (user_sid) REFERENCES users (user_sid) ON DELETE CASCADE',
|
||||
'ALTER TABLE user_permissions ADD FOREIGN KEY permission_sid_idxfk (permission_sid) REFERENCES permissions (permission_sid)',
|
||||
'ALTER TABLE `users` ADD COLUMN `is_active` BOOLEAN NOT NULL default true',
|
||||
]
|
||||
};
|
||||
|
||||
@@ -85,6 +115,7 @@ const doIt = async() => {
|
||||
|
||||
if (val < 7006) upgrades.push(...sql['7006']);
|
||||
if (val < 7007) upgrades.push(...sql['7007']);
|
||||
if (val < 8000) upgrades.push(...sql['8000']);
|
||||
|
||||
// perform all upgrades
|
||||
logger.info({upgrades}, 'applying schema upgrades..');
|
||||
|
||||
@@ -85,9 +85,6 @@ VALUES
|
||||
-- simwood gateways
|
||||
insert into predefined_sip_gateways (predefined_sip_gateway_sid, predefined_carrier_sid, ipv4, netmask, port, inbound, outbound)
|
||||
VALUES
|
||||
('91cb050f-9826-4ac9-b736-84a10372a9fe', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '149.91.14.0', 24, 5060, 1, 0),
|
||||
('58700fad-98bf-4d31-b61e-888c54911b35', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '154.51.137.96', 27, 5060, 1, 0),
|
||||
('d020fd9e-7fdb-4bca-ae0d-e61b38142873', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '78.40.245.160', 27, 5060, 1, 0),
|
||||
('441fd2e7-c845-459c-963d-6e917063ed9a', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.136.24', 29, 5060, 1, 0),
|
||||
('1e0d3e80-9973-4184-9bec-07ae564f983f', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.136.28', 28, 5060, 1, 0),
|
||||
('e56ec745-5f37-443f-afb4-7bbda31ae7ac', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.140.48', 28, 5060, 1, 0),
|
||||
@@ -98,7 +95,7 @@ VALUES
|
||||
('b6ae6240-55ac-4c11-892f-a71b2155ea60', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.142.0', 26, 5060, 1, 0),
|
||||
('5a976337-164b-408e-8748-d8bfb4bd5d76', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '185.63.143.0', 26, 5060, 1, 0),
|
||||
('ed0434ca-7f26-4624-9523-0419d0d2924d', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '178.22.139.0', 26, 5060, 1, 0),
|
||||
('d1a594c2-c14f-4ead-b621-96129bc87886', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.224.0', 24, 5060, 1, 0),
|
||||
('6bfb55e5-e248-48dc-a104-4f3eedd7d7de', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', '172.86.225.0', 24, 5060, 1, 0),
|
||||
('5f431d42-48e4-44ce-a311-d946f0b475b6', 'e6fb301a-1af0-4fb8-a1f6-f65530c6e1c6', 'out.simwood.com', 32, 5060, 0, 1);
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const sql = `
|
||||
function makeStrategy(logger, retrieveKey) {
|
||||
return new Strategy(
|
||||
async function(token, done) {
|
||||
logger.debug(`validating with token ${token}`);
|
||||
//logger.debug(`validating with token ${token}`);
|
||||
jwt.verify(token, process.env.JWT_SECRET, async(err, decoded) => {
|
||||
if (err) {
|
||||
if (err.name === 'TokenExpiredError') {
|
||||
@@ -35,20 +35,21 @@ function makeStrategy(logger, retrieveKey) {
|
||||
debug(err);
|
||||
logger.info({err}, 'Error checking blacklist for jwt');
|
||||
}
|
||||
const {user_sid, account_sid, email, name} = decoded;
|
||||
//logger.debug({user_sid, account_sid}, 'successfully validated jwt');
|
||||
const scope = ['account'];
|
||||
const {user_sid, service_provider_sid, account_sid, email, name, scope, permissions} = decoded;
|
||||
const user = {
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
user_sid,
|
||||
jwt: token,
|
||||
email,
|
||||
name,
|
||||
hasScope: (s) => s === 'account',
|
||||
hasAdminAuth: false,
|
||||
hasServiceProviderAuth: false,
|
||||
hasAccountAuth: true
|
||||
permissions,
|
||||
hasScope: (s) => s === scope,
|
||||
hasAdminAuth: scope === 'admin',
|
||||
hasServiceProviderAuth: scope === 'service_provider',
|
||||
hasAccountAuth: scope === 'account'
|
||||
};
|
||||
logger.debug({user}, 'successfully validated jwt');
|
||||
return done(null, user, {scope});
|
||||
}
|
||||
});
|
||||
@@ -75,26 +76,30 @@ const checkApiTokens = (logger, token, done) => {
|
||||
}
|
||||
|
||||
// found api key
|
||||
const scope = [];
|
||||
let scope;
|
||||
//const scope = [];
|
||||
if (results[0].account_sid === null && results[0].service_provider_sid === null) {
|
||||
scope.push.apply(scope, ['admin', 'service_provider', 'account']);
|
||||
//scope.push.apply(scope, ['admin', 'service_provider', 'account']);
|
||||
scope = 'admin';
|
||||
}
|
||||
else if (results[0].service_provider_sid) {
|
||||
scope.push.apply(scope, ['service_provider', 'account']);
|
||||
//scope.push.apply(scope, ['service_provider', 'account']);
|
||||
scope = 'service_provider';
|
||||
}
|
||||
else {
|
||||
scope.push('account');
|
||||
//scope.push('account');
|
||||
scope = 'account';
|
||||
}
|
||||
|
||||
const user = {
|
||||
account_sid: results[0].account_sid,
|
||||
service_provider_sid: results[0].service_provider_sid,
|
||||
hasScope: (s) => scope.includes(s),
|
||||
hasAdminAuth: scope.length === 3,
|
||||
hasServiceProviderAuth: scope.includes('service_provider'),
|
||||
hasAccountAuth: scope.includes('account') && !scope.includes('service_provider')
|
||||
hasScope: (s) => s === scope,
|
||||
hasAdminAuth: scope === 'admin',
|
||||
hasServiceProviderAuth: scope === 'service_provider',
|
||||
hasAccountAuth: scope === 'account'
|
||||
};
|
||||
logger.info(user, `successfully validated with scope ${scope}`);
|
||||
logger.debug({user}, `successfully validated with scope ${scope}`);
|
||||
return done(null, user, {scope});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ class Model extends Emitter {
|
||||
if (pk.name in obj) throw new DbErrorBadRequest(`primary key ${pk.name} is immutable`);
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(`UPDATE ${this.table} SET ? WHERE ${pk.name} = '${sid}'`, obj, (err, results, fields) => {
|
||||
conn.query(`UPDATE ${this.table} SET ? WHERE ${pk.name} = ?`, [obj, sid], (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results.affectedRows);
|
||||
|
||||
70
lib/models/password-settings.js
Normal file
70
lib/models/password-settings.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const {promisePool} = require('../db');
|
||||
|
||||
class PasswordSettings {
|
||||
|
||||
/**
|
||||
* Retrieve object from database
|
||||
*/
|
||||
static async retrieve() {
|
||||
const [r] = await promisePool.execute(`SELECT * FROM ${this.table}`);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update object into the database
|
||||
*/
|
||||
static async update(obj) {
|
||||
let sql = `UPDATE ${this.table} SET `;
|
||||
const values = [];
|
||||
const keys = Object.keys(obj);
|
||||
this.fields.forEach(({name}) => {
|
||||
if (keys.includes(name)) {
|
||||
sql = sql + `${name} = ?,`;
|
||||
values.push(obj[name]);
|
||||
}
|
||||
});
|
||||
if (values.length) {
|
||||
sql = sql.slice(0, -1);
|
||||
await promisePool.execute(sql, values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* insert object into the database
|
||||
*/
|
||||
static async make(obj) {
|
||||
let params = '', marks = '';
|
||||
const values = [];
|
||||
const keys = Object.keys(obj);
|
||||
this.fields.forEach(({name}) => {
|
||||
if (keys.includes(name)) {
|
||||
params = params + `${name},`;
|
||||
marks = marks + '?,';
|
||||
values.push(obj[name]);
|
||||
}
|
||||
});
|
||||
if (values.length) {
|
||||
params = `(${params.slice(0, -1)})`;
|
||||
marks = `values(${marks.slice(0, -1)})`;
|
||||
return await promisePool.execute(`INSERT into ${this.table} ${params} ${marks}`, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PasswordSettings.table = 'password_settings';
|
||||
PasswordSettings.fields = [
|
||||
{
|
||||
name: 'min_password_length',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'require_digit',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'require_special_character',
|
||||
type: 'number'
|
||||
}
|
||||
];
|
||||
module.exports = PasswordSettings;
|
||||
117
lib/models/user.js
Normal file
117
lib/models/user.js
Normal file
@@ -0,0 +1,117 @@
|
||||
const Model = require('./model');
|
||||
const {promisePool} = require('../db');
|
||||
const sqlAll = `
|
||||
SELECT u.user_sid, u.name, u.email, u.account_sid, u.service_provider_sid, u.is_active,
|
||||
u.force_change, u.phone, u.pending_email, u.provider, u.provider_userid,
|
||||
u.email_activation_code, u.email_validated,
|
||||
sp.name as service_provider_name, acc.name as account_name
|
||||
FROM users u
|
||||
LEFT JOIN service_providers as sp ON u.service_provider_sid = sp.service_provider_sid
|
||||
LEFT JOIN accounts acc ON u.account_sid = acc.account_sid
|
||||
`;
|
||||
const sqlAccount = `
|
||||
SELECT u.user_sid, u.name, u.email, u.account_sid, u.service_provider_sid, u.is_active,
|
||||
u.force_change, u.phone, u.pending_email, u.provider, u.provider_userid,
|
||||
u.email_activation_code, u.email_validated,
|
||||
sp.name as service_provider_name, acc.name as account_name
|
||||
FROM users u
|
||||
LEFT JOIN service_providers as sp ON u.service_provider_sid = sp.service_provider_sid
|
||||
LEFT JOIN accounts acc ON u.account_sid = acc.account_sid
|
||||
WHERE u.account_sid = ?
|
||||
`;
|
||||
const sqlSP = `
|
||||
SELECT u.user_sid, u.name, u.email, u.account_sid, u.service_provider_sid, u.is_active,
|
||||
u.force_change, u.phone, u.pending_email, u.provider, u.provider_userid,
|
||||
u.email_activation_code, u.email_validated,
|
||||
sp.name as service_provider_name, acc.name as account_name
|
||||
FROM users u
|
||||
LEFT JOIN service_providers as sp ON u.service_provider_sid = sp.service_provider_sid
|
||||
LEFT JOIN accounts acc ON u.account_sid = acc.account_sid
|
||||
WHERE u.service_provider_sid = ?
|
||||
`;
|
||||
|
||||
class User extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static async retrieveAll() {
|
||||
const [rows] = await promisePool.query(sqlAll);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async retrieveAllForAccount(account_sid) {
|
||||
const [rows] = await promisePool.query(sqlAccount, [account_sid]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
static async retrieveAllForServiceProvider(service_provider_sid) {
|
||||
const [rows] = await promisePool.query(sqlSP, [service_provider_sid]);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
User.table = 'users';
|
||||
User.fields = [
|
||||
{
|
||||
name: 'user_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'pending_email',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'hashed_password',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'account_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'service_provider_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'force_change',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'provider',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'provider_userid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'email_activation_code',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'email_validated',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
name: 'is_active',
|
||||
type: 'number'
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = User;
|
||||
@@ -1,6 +1,6 @@
|
||||
const router = require('express').Router();
|
||||
const request = require('request');
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const {DbErrorBadRequest, DbErrorForbidden, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const Account = require('../../models/account');
|
||||
const Application = require('../../models/application');
|
||||
const Webhook = require('../../models/webhook');
|
||||
@@ -45,6 +45,38 @@ const stripPort = (hostport) => {
|
||||
return hostport;
|
||||
};
|
||||
|
||||
const validateUpdateForCarrier = async(req) => {
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
|
||||
if (req.user.hasScope('admin')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
if (account_sid === req.user.account_sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions to update account');
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
const [r] = await promisePool.execute(
|
||||
'SELECT service_provider_sid from accounts WHERE account_sid = ?', [account_sid]
|
||||
);
|
||||
|
||||
if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions to update account');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
router.use('/:sid/SpeechCredentials', hasAccountPermissions, require('./speech-credentials'));
|
||||
router.use('/:sid/RecentCalls', hasAccountPermissions, require('./recent-calls'));
|
||||
router.use('/:sid/Alerts', hasAccountPermissions, require('./alerts'));
|
||||
@@ -72,6 +104,20 @@ router.get('/:sid/VoipCarriers', async(req, res) => {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
router.put('/:sid/VoipCarriers/:voip_carrier_sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateUpdateForCarrier(req);
|
||||
const rowsAffected = await VoipCarrier.update(req.params.voip_carrier_sid, req.body);
|
||||
if (rowsAffected === 0) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:sid/VoipCarriers', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const payload = req.body;
|
||||
@@ -315,10 +361,20 @@ async function validateUpdate(req, sid) {
|
||||
|
||||
if (req.user.service_provider_sid && !req.user.hasScope('admin')) {
|
||||
const result = await Account.retrieve(sid);
|
||||
if (!result || result.length === 0) {
|
||||
throw new DbErrorBadRequest(`account not found for sid ${sid}`);
|
||||
}
|
||||
if (result[0].service_provider_sid !== req.user.service_provider_sid) {
|
||||
throw new DbErrorUnprocessableRequest('cannot update account from different service provider');
|
||||
}
|
||||
}
|
||||
if (req.user.hasScope('admin')) {
|
||||
/* check to be sure that the account_sid exists */
|
||||
const result = await Account.retrieve(sid);
|
||||
if (!result || result.length === 0) {
|
||||
throw new DbErrorBadRequest(`account not found for sid ${sid}`);
|
||||
}
|
||||
}
|
||||
if (req.body.service_provider_sid) throw new DbErrorBadRequest('service_provider_sid may not be modified');
|
||||
}
|
||||
async function validateDelete(req, sid) {
|
||||
@@ -746,11 +802,11 @@ router.put('/:sid/Calls/:callSid', async(req, res) => {
|
||||
* create a new Message
|
||||
*/
|
||||
router.post('/:sid/Messages', async(req, res) => {
|
||||
const account_sid = parseAccountSid(req);
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
const {retrieveSet, logger} = req.app.locals;
|
||||
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
const serviceUrl = await getFsUrl(logger, retrieveSet, setName);
|
||||
if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480);
|
||||
await validateCreateMessage(logger, account_sid, req);
|
||||
|
||||
@@ -8,9 +8,7 @@ const short = require('short-uuid');
|
||||
const {promisePool} = require('../../db');
|
||||
const sysError = require('../error');
|
||||
|
||||
const sqlSelectCarrierByName = `SELECT * FROM voip_carriers
|
||||
WHERE account_sid = ?
|
||||
AND name = ?`;
|
||||
|
||||
const sqlSelectCarrierByNameForSP = `SELECT * FROM voip_carriers
|
||||
WHERE service_provider_sid = ?
|
||||
AND name = ?`;
|
||||
@@ -25,22 +23,20 @@ router.post('/:sid', async(req, res) => {
|
||||
const {sid } = req.params;
|
||||
let service_provider_sid;
|
||||
const {account_sid} = req.user;
|
||||
if (!account_sid) {
|
||||
if (!req.user.hasScope('service_provider')) {
|
||||
logger.error({user: req.user}, 'invalid creds');
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
service_provider_sid = parseServiceProviderSid(req);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!account_sid) {
|
||||
service_provider_sid = parseServiceProviderSid(req);
|
||||
} else {
|
||||
service_provider_sid = req.user.service_provider_sid;
|
||||
}
|
||||
|
||||
const [template] = await PredefinedCarrier.retrieve(sid);
|
||||
logger.debug({template}, `Retrieved template carrier for sid ${sid}`);
|
||||
if (!template) return res.sendStatus(404);
|
||||
|
||||
/* make sure not to add the same carrier twice */
|
||||
const [r2] = account_sid ?
|
||||
await promisePool.query(sqlSelectCarrierByName, [account_sid, template.name]) :
|
||||
await promisePool.query(sqlSelectCarrierByNameForSP, [service_provider_sid, template.name]);
|
||||
const [r2] = await promisePool.query(sqlSelectCarrierByNameForSP, [service_provider_sid, template.name]);
|
||||
|
||||
if (r2.length > 0) {
|
||||
template.name = `${template.name}-${short.generate()}`;
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
const router = require('express').Router();
|
||||
const sysError = require('../error');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const { parseServiceProviderSid } = require('./utils');
|
||||
|
||||
const parseAccountSid = (url) => {
|
||||
const arr = /Accounts\/([^\/]*)/.exec(url);
|
||||
if (arr) return arr[1];
|
||||
};
|
||||
|
||||
const parseServiceProviderSid = (url) => {
|
||||
const arr = /ServiceProviders\/([^\/]*)/.exec(url);
|
||||
if (arr) return arr[1];
|
||||
};
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const {logger, queryAlerts, queryAlertsSP} = req.app.locals;
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,7 @@ const Webhook = require('../../models/webhook');
|
||||
const {promisePool} = require('../../db');
|
||||
const decorate = require('./decorate');
|
||||
const sysError = require('../error');
|
||||
const { validate } = require('@jambonz/verb-specifications');
|
||||
const preconditions = {
|
||||
'add': validateAdd,
|
||||
'update': validateUpdate
|
||||
@@ -76,6 +77,16 @@ router.post('/', async(req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// validate app json if required
|
||||
if (obj['app_json']) {
|
||||
const app_json = JSON.parse(obj['app_json']);
|
||||
try {
|
||||
validate(logger, app_json);
|
||||
} catch (err) {
|
||||
throw new DbErrorBadRequest(err);
|
||||
}
|
||||
}
|
||||
|
||||
const uuid = await Application.make(obj);
|
||||
res.status(201).json({sid: uuid});
|
||||
} catch (err) {
|
||||
@@ -179,6 +190,16 @@ router.put('/:sid', async(req, res) => {
|
||||
delete obj[prop];
|
||||
}
|
||||
|
||||
// validate app json if required
|
||||
if (obj['app_json']) {
|
||||
const app_json = JSON.parse(obj['app_json']);
|
||||
try {
|
||||
validate(logger, app_json);
|
||||
} catch (err) {
|
||||
throw new DbErrorBadRequest(err);
|
||||
}
|
||||
}
|
||||
|
||||
const rowsAffected = await Application.update(sid, obj);
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const { BadRequestError, DbErrorBadRequest, DbErrorUnprocessableRequest } = require('../../utils/errors');
|
||||
|
||||
function sysError(logger, res, err) {
|
||||
if (err instanceof BadRequestError) {
|
||||
logger.info(err, err.message);
|
||||
return res.status(400).json({msg: 'Bad request'});
|
||||
}
|
||||
if (err instanceof DbErrorBadRequest) {
|
||||
logger.info(err, 'invalid client request');
|
||||
return res.status(400).json({msg: err.message});
|
||||
|
||||
@@ -7,16 +7,16 @@ const isAdminScope = (req, res, next) => {
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
};
|
||||
const isAdminOrSPScope = (req, res, next) => {
|
||||
if (req.user.hasScope('admin') || req.user.hasScope('service_provider')) return next();
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
};
|
||||
// const isAdminOrSPScope = (req, res, next) => {
|
||||
// if (req.user.hasScope('admin') || req.user.hasScope('service_provider')) return next();
|
||||
// res.status(403).json({
|
||||
// status: 'fail',
|
||||
// message: 'insufficient privileges'
|
||||
// });
|
||||
// };
|
||||
|
||||
api.use('/BetaInviteCodes', isAdminScope, require('./beta-invite-codes'));
|
||||
api.use('/ServiceProviders', isAdminOrSPScope, require('./service-providers'));
|
||||
api.use('/ServiceProviders', require('./service-providers'));
|
||||
api.use('/VoipCarriers', require('./voip-carriers'));
|
||||
api.use('/Webhooks', require('./webhooks'));
|
||||
api.use('/SipGateways', require('./sip-gateways'));
|
||||
@@ -44,6 +44,7 @@ api.use('/Subscriptions', require('./subscriptions'));
|
||||
api.use('/Invoices', require('./invoices'));
|
||||
api.use('/InviteCodes', require('./invite-codes'));
|
||||
api.use('/PredefinedCarriers', require('./predefined-carriers'));
|
||||
api.use('/PasswordSettings', require('./password-settings'));
|
||||
|
||||
// messaging
|
||||
api.use('/Smpps', require('./smpps')); // our smpp server info
|
||||
|
||||
@@ -28,16 +28,18 @@ router.post('/', async(req, res) => {
|
||||
category,
|
||||
quantity
|
||||
} = req.body;
|
||||
const account_sid = parseAccountSid(req);
|
||||
let service_provider_sid;
|
||||
if (!account_sid) {
|
||||
if (!req.user.hasServiceProviderAuth) {
|
||||
logger.error('POST /SpeechCredentials invalid credentials');
|
||||
return res.send(403);
|
||||
}
|
||||
service_provider_sid = parseServiceProviderSid(req);
|
||||
}
|
||||
|
||||
try {
|
||||
let service_provider_sid;
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (!account_sid) {
|
||||
if (!req.user.hasServiceProviderAuth && !req.user.hasAdminAuth) {
|
||||
logger.error('POST /SpeechCredentials invalid credentials');
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
service_provider_sid = parseServiceProviderSid(req);
|
||||
}
|
||||
|
||||
let uuid;
|
||||
if (account_sid) {
|
||||
const existing = (await AccountLimits.retrieve(account_sid) || [])
|
||||
@@ -80,10 +82,11 @@ router.post('/', async(req, res) => {
|
||||
*/
|
||||
router.get('/', async(req, res) => {
|
||||
let service_provider_sid;
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (!account_sid) service_provider_sid = parseServiceProviderSid(req);
|
||||
const logger = req.app.locals.logger;
|
||||
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (!account_sid) service_provider_sid = parseServiceProviderSid(req);
|
||||
const limits = account_sid ?
|
||||
await AccountLimits.retrieve(account_sid) :
|
||||
await ServiceProviderLimits.retrieve(service_provider_sid);
|
||||
@@ -99,10 +102,11 @@ router.get('/', async(req, res) => {
|
||||
|
||||
router.delete('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const account_sid = parseAccountSid(req);
|
||||
const {category} = req.query;
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
|
||||
try {
|
||||
const account_sid = parseAccountSid(req);
|
||||
const {category} = req.query;
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
if (account_sid) {
|
||||
if (category) {
|
||||
await promisePool.execute(sqlDeleteAccountLimitsByCategory, [account_sid, category]);
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
const router = require('express').Router();
|
||||
const {getMysqlConnection} = require('../../db');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const {verifyPassword} = require('../../utils/password-utils');
|
||||
|
||||
const {promisePool} = require('../../db');
|
||||
const Account = require('../../models/account');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const sysError = require('../error');
|
||||
const retrievePemissionsSql = `
|
||||
SELECT p.name
|
||||
FROM permissions p, user_permissions up
|
||||
WHERE up.permission_sid = p.permission_sid
|
||||
AND up.user_sid = ?
|
||||
`;
|
||||
const retrieveSql = 'SELECT * from users where name = ?';
|
||||
const tokenSql = 'SELECT token from api_keys where account_sid IS NULL AND service_provider_sid IS NULL';
|
||||
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {username, password} = req.body;
|
||||
if (!username || !password) {
|
||||
@@ -14,48 +23,63 @@ router.post('/', (req, res) => {
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
try {
|
||||
const [r] = await promisePool.query(retrieveSql, username);
|
||||
if (r.length === 0) {
|
||||
logger.info(`Failed login attempt for user ${username}`);
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
logger.info({r}, 'successfully retrieved user account');
|
||||
const isCorrect = await verifyPassword(r[0].hashed_password, password);
|
||||
if (!isCorrect) return res.sendStatus(403);
|
||||
const force_change = !!r[0].force_change;
|
||||
const [t] = await promisePool.query(tokenSql);
|
||||
if (t.length === 0) {
|
||||
logger.error('Database has no admin token provisioned...run reset_admin_password');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
conn.query(retrieveSql, [username], async(err, results) => {
|
||||
conn.release();
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (0 === results.length) {
|
||||
logger.info(`Failed login attempt for user ${username}`);
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
logger.info({results}, 'successfully retrieved account');
|
||||
const isCorrect = await verifyPassword(results[0].hashed_password, password);
|
||||
if (!isCorrect) return res.sendStatus(403);
|
||||
|
||||
const force_change = !!results[0].force_change;
|
||||
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
conn.query(tokenSql, (err, tokenResults) => {
|
||||
conn.release();
|
||||
if (err) {
|
||||
logger.error({err}, 'Error getting db connection');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
if (0 === tokenResults.length) {
|
||||
logger.error('Database has no admin token provisioned...run reset_admin_password');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
res.json({user_sid: results[0].user_sid, force_change, token: tokenResults[0].token});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
const [p] = await promisePool.query(retrievePemissionsSql, r[0].user_sid);
|
||||
const permissions = p.map((x) => x.name);
|
||||
const obj = {user_sid: r[0].user_sid, scope: 'admin', force_change, permissions};
|
||||
if (r[0].service_provider_sid && r[0].account_sid) {
|
||||
const account = await Account.retrieve(r[0].account_sid);
|
||||
const service_provider = await ServiceProvider.retrieve(r[0].service_provider_sid);
|
||||
obj.scope = 'account';
|
||||
obj.service_provider_sid = r[0].service_provider_sid;
|
||||
obj.account_sid = r[0].account_sid;
|
||||
obj.account_name = account[0].name;
|
||||
obj.service_provider_name = service_provider[0].name;
|
||||
}
|
||||
else if (r[0].service_provider_sid) {
|
||||
const service_provider = await ServiceProvider.retrieve(r[0].service_provider_sid);
|
||||
obj.scope = 'service_provider';
|
||||
obj.service_provider_sid = r[0].service_provider_sid;
|
||||
obj.service_provider_name = service_provider[0].name;
|
||||
}
|
||||
const payload = {
|
||||
scope: obj.scope,
|
||||
permissions,
|
||||
...(obj.service_provider_sid && {
|
||||
service_provider_sid: obj.service_provider_sid,
|
||||
service_provider_name: obj.service_provider_name
|
||||
}),
|
||||
...(obj.account_sid && {
|
||||
account_sid: obj.account_sid,
|
||||
account_name: obj.account_name,
|
||||
service_provider_name: obj.service_provider_name
|
||||
}),
|
||||
user_sid: obj.user_sid
|
||||
};
|
||||
const token = jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: parseInt(process.env.JWT_EXPIRES_IN || 60) * 60 }
|
||||
);
|
||||
res.json({token, ...obj});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
45
lib/routes/api/password-settings.js
Normal file
45
lib/routes/api/password-settings.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const router = require('express').Router();
|
||||
const sysError = require('../error');
|
||||
const PasswordSettings = require('../../models/password-settings');
|
||||
const { DbErrorBadRequest } = require('../../utils/errors');
|
||||
|
||||
const validate = (obj) => {
|
||||
if (obj.min_password_length && (
|
||||
obj.min_password_length < 8 ||
|
||||
obj.min_password_length > 20
|
||||
)) {
|
||||
throw new DbErrorBadRequest('invalid min_password_length property: should be between 8-20');
|
||||
}
|
||||
};
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
if (!req.user.hasAdminAuth) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
validate(req.body);
|
||||
const [existing] = (await PasswordSettings.retrieve() || []);
|
||||
if (existing) {
|
||||
await PasswordSettings.update(req.body);
|
||||
} else {
|
||||
await PasswordSettings.make(req.body);
|
||||
}
|
||||
res.status(201).json({});
|
||||
}
|
||||
catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const [results] = (await PasswordSettings.retrieve() || []);
|
||||
return res.status(200).json(results || {min_password_length: 8});
|
||||
}
|
||||
catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
module.exports = router;
|
||||
@@ -347,7 +347,7 @@ router.post('/', async(req, res) => {
|
||||
account_sid: userProfile.account_sid,
|
||||
email: userProfile.email,
|
||||
name: userProfile.name
|
||||
}, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
}, process.env.JWT_SECRET, { expiresIn: parseInt(process.env.JWT_EXPIRES_IN || 60) * 60 });
|
||||
|
||||
logger.debug({
|
||||
user_sid: userProfile.user_sid,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const router = require('express').Router();
|
||||
const {promisePool} = require('../../db');
|
||||
const {DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const {DbErrorUnprocessableRequest, DbErrorForbidden} = require('../../utils/errors');
|
||||
const Webhook = require('../../models/webhook');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const Account = require('../../models/account');
|
||||
@@ -12,7 +12,7 @@ const {hasServiceProviderPermissions, parseServiceProviderSid} = require('./util
|
||||
const sysError = require('../error');
|
||||
const decorate = require('./decorate');
|
||||
const preconditions = {
|
||||
'delete': noActiveAccounts
|
||||
'delete': noActiveAccountsOrUsers
|
||||
};
|
||||
const sqlDeleteSipGateways = `DELETE from sip_gateways
|
||||
WHERE voip_carrier_sid IN (
|
||||
@@ -27,10 +27,72 @@ WHERE voip_carrier_sid IN (
|
||||
WHERE service_provider_sid = ?
|
||||
)`;
|
||||
|
||||
/* only admin users can add a service provider */
|
||||
function validateAdd(req) {
|
||||
if (!req.user.hasAdminAuth) {
|
||||
throw new DbErrorForbidden('only admin users can add a service provider');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateRetrieve(req) {
|
||||
try {
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
|
||||
if (req.user.hasScope('admin')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
if (service_provider_sid === req.user.service_provider_sid) return ;
|
||||
}
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
/* allow account users to retrieve service provider data from parent SP */
|
||||
const sid = req.user.account_sid;
|
||||
const [r] = await promisePool.execute('SELECT service_provider_sid from accounts WHERE account_sid = ?', [sid]);
|
||||
if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions to update service provider');
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function validateUpdate(req) {
|
||||
try {
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
|
||||
if (req.user.hasScope('admin')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
if (service_provider_sid === req.user.service_provider_sid) return;
|
||||
}
|
||||
|
||||
throw new DbErrorForbidden('insufficient permissions to update service provider');
|
||||
} catch (error) {
|
||||
console.log('Passing forward the error received');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/* can not delete a service provider if it has any active accounts */
|
||||
async function noActiveAccounts(req, sid) {
|
||||
async function noActiveAccountsOrUsers(req, sid) {
|
||||
if (!req.user.hasAdminAuth) {
|
||||
throw new DbErrorForbidden('only admin users can delete a service provider');
|
||||
}
|
||||
const activeAccounts = await ServiceProvider.getForeignKeyReferences('accounts.service_provider_sid', sid);
|
||||
const activeUsers = await ServiceProvider.getForeignKeyReferences('users.service_provider_sid', sid);
|
||||
if (activeAccounts > 0 && activeUsers > 0) throw new DbErrorUnprocessableRequest(
|
||||
'cannot delete service provider with active accounts or users'
|
||||
);
|
||||
|
||||
if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts');
|
||||
if (activeUsers > 0) throw new DbErrorUnprocessableRequest(
|
||||
'cannot delete service provider with active service provider level users'
|
||||
);
|
||||
|
||||
/* ok we can delete -- no active accounts. remove carriers and speech credentials */
|
||||
await promisePool.execute('DELETE from speech_credentials WHERE service_provider_sid = ?', [sid]);
|
||||
@@ -43,14 +105,18 @@ decorate(router, ServiceProvider, ['delete'], preconditions);
|
||||
|
||||
router.use('/:sid/RecentCalls', hasServiceProviderPermissions, require('./recent-calls'));
|
||||
router.use('/:sid/Alerts', hasServiceProviderPermissions, require('./alerts'));
|
||||
router.use('/:sid/SpeechCredentials', hasServiceProviderPermissions, require('./speech-credentials'));
|
||||
router.use('/:sid/SpeechCredentials', require('./speech-credentials'));
|
||||
router.use('/:sid/Limits', hasServiceProviderPermissions, require('./limits'));
|
||||
router.use('/:sid/PredefinedCarriers', hasServiceProviderPermissions, require('./add-from-predefined-carrier'));
|
||||
router.get('/:sid/Accounts', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateRetrieve(req);
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const results = await Account.retrieveAll(service_provider_sid);
|
||||
let results = await Account.retrieveAll(service_provider_sid);
|
||||
if (req.user.hasScope('account')) {
|
||||
results = results.filter((r) => r.account_sid === req.user.account_sid);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -59,8 +125,12 @@ router.get('/:sid/Accounts', async(req, res) => {
|
||||
router.get('/:sid/Applications', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateRetrieve(req);
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const results = await Application.retrieveAll(service_provider_sid);
|
||||
let results = await Application.retrieveAll(service_provider_sid);
|
||||
if (req.user.hasScope('account')) {
|
||||
results = results.filter((r) => r.account_sid === req.user.account_sid);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -69,8 +139,12 @@ router.get('/:sid/Applications', async(req, res) => {
|
||||
router.get('/:sid/PhoneNumbers', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateRetrieve(req);
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const results = await PhoneNumber.retrieveAllForSP(service_provider_sid);
|
||||
let results = await PhoneNumber.retrieveAllForSP(service_provider_sid);
|
||||
if (req.user.hasScope('account')) {
|
||||
results = results.filter((r) => r.account_sid === req.user.account_sid);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -79,9 +153,15 @@ router.get('/:sid/PhoneNumbers', async(req, res) => {
|
||||
router.get('/:sid/VoipCarriers', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
await validateRetrieve(req);
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const results = await VoipCarrier.retrieveAllForSP(service_provider_sid);
|
||||
res.status(200).json(results);
|
||||
const carriers = await VoipCarrier.retrieveAllForSP(service_provider_sid);
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
return res.status(200).json(carriers.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid));
|
||||
}
|
||||
|
||||
res.status(200).json(carriers);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -89,6 +169,7 @@ router.get('/:sid/VoipCarriers', async(req, res) => {
|
||||
router.post('/:sid/VoipCarriers', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
validateUpdate(req);
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const uuid = await VoipCarrier.make({...req.body, service_provider_sid});
|
||||
res.status(201).json({sid: uuid});
|
||||
@@ -99,6 +180,7 @@ router.post('/:sid/VoipCarriers', async(req, res) => {
|
||||
router.put('/:sid/VoipCarriers/:voip_carrier_sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
validateUpdate(req);
|
||||
const rowsAffected = await VoipCarrier.update(req.params.voip_carrier_sid, req.body);
|
||||
if (rowsAffected === 0) {
|
||||
return res.sendStatus(404);
|
||||
@@ -108,21 +190,15 @@ router.put('/:sid/VoipCarriers/:voip_carrier_sid', async(req, res) => {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
router.get(':sid/Acccounts', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
const results = await Account.retrieveAll(service_provider_sid);
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
router.get('/:sid/ApiKeys', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {sid} = req.params;
|
||||
try {
|
||||
const results = await ApiKey.retrieveAllForSP(sid);
|
||||
await validateRetrieve(req);
|
||||
let results = await ApiKey.retrieveAllForSP(sid);
|
||||
if (req.user.hasScope('account')) {
|
||||
results = results.filter((r) => r.account_sid === req.user.account_sid);
|
||||
}
|
||||
res.status(200).json(results);
|
||||
await ApiKey.updateLastUsed(sid);
|
||||
} catch (err) {
|
||||
@@ -134,7 +210,7 @@ router.get('/:sid/ApiKeys', async(req, res) => {
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
|
||||
validateAdd(req);
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['registration_hook']) {
|
||||
@@ -157,6 +233,12 @@ router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await ServiceProvider.retrieveAll();
|
||||
logger.debug({results, user: req.user}, 'ServiceProvider.retrieveAll');
|
||||
if (req.user.hasScope('service_provider') || req.user.hasScope('account')) {
|
||||
logger.debug(`Filtering results for ${req.user.service_provider_sid}`);
|
||||
return res.status(200).json(results.filter((e) => req.user.service_provider_sid === e.service_provider_sid));
|
||||
}
|
||||
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -178,9 +260,11 @@ router.get('/:sid', async(req, res) => {
|
||||
|
||||
/* update */
|
||||
router.put('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
validateUpdate(req);
|
||||
const sid = req.params.sid;
|
||||
|
||||
// create webhooks if provided
|
||||
const obj = Object.assign({}, req.body);
|
||||
for (const prop of ['registration_hook']) {
|
||||
@@ -189,15 +273,14 @@ router.put('/:sid', async(req, res) => {
|
||||
const sid = obj[prop]['webhook_sid'];
|
||||
delete obj[prop]['webhook_sid'];
|
||||
await Webhook.update(sid, obj[prop]);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const sid = await Webhook.make(obj[prop]);
|
||||
obj[`${prop}_sid`] = sid;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
obj[`${prop}_sid`] = null;
|
||||
}
|
||||
|
||||
delete obj[prop];
|
||||
}
|
||||
|
||||
@@ -205,6 +288,7 @@ router.put('/:sid', async(req, res) => {
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
|
||||
@@ -68,7 +68,7 @@ router.post('/', async(req, res) => {
|
||||
const token = jwt.sign({
|
||||
user_sid: userProfile.user_sid,
|
||||
account_sid: userProfile.account_sid
|
||||
}, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
}, process.env.JWT_SECRET, { expiresIn: parseInt(process.env.JWT_EXPIRES_IN || 60) * 60 });
|
||||
|
||||
logger.debug({
|
||||
user_sid: userProfile.user_sid,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const router = require('express').Router();
|
||||
const assert = require('assert');
|
||||
const Account = require('../../models/account');
|
||||
const SpeechCredential = require('../../models/speech-credential');
|
||||
const sysError = require('../error');
|
||||
const {decrypt, encrypt} = require('../../utils/encrypt-decrypt');
|
||||
@@ -12,7 +13,13 @@ const {
|
||||
testAwsStt,
|
||||
testMicrosoftStt,
|
||||
testMicrosoftTts,
|
||||
testWellSaidTts
|
||||
testWellSaidTts,
|
||||
testNuanceStt,
|
||||
testNuanceTts,
|
||||
testDeepgramStt,
|
||||
testSonioxStt,
|
||||
testIbmTts,
|
||||
testIbmStt
|
||||
} = require('../../utils/speech-utils');
|
||||
|
||||
const obscureKey = (key) => {
|
||||
@@ -35,10 +42,23 @@ const encryptCredential = (obj) => {
|
||||
aws_region,
|
||||
api_key,
|
||||
region,
|
||||
client_id,
|
||||
secret,
|
||||
nuance_tts_uri,
|
||||
nuance_stt_uri,
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
custom_stt_endpoint,
|
||||
tts_api_key,
|
||||
tts_region,
|
||||
stt_api_key,
|
||||
stt_region,
|
||||
riva_server_uri,
|
||||
instance_id,
|
||||
custom_stt_url,
|
||||
custom_tts_url,
|
||||
auth_token = ''
|
||||
} = obj;
|
||||
|
||||
switch (vendor) {
|
||||
@@ -78,28 +98,61 @@ const encryptCredential = (obj) => {
|
||||
const wsData = JSON.stringify({api_key});
|
||||
return encrypt(wsData);
|
||||
|
||||
case 'nuance':
|
||||
const checked = (client_id && secret) || (nuance_tts_uri || nuance_stt_uri);
|
||||
assert(checked, 'invalid nuance speech credential: either entered client id and\
|
||||
secret or entered a nuance_tts_uri or nuance_stt_uri');
|
||||
const nuanceData = JSON.stringify({client_id, secret, nuance_tts_uri, nuance_stt_uri});
|
||||
return encrypt(nuanceData);
|
||||
|
||||
case 'deepgram':
|
||||
assert(api_key, 'invalid deepgram speech credential: api_key is required');
|
||||
const deepgramData = JSON.stringify({api_key});
|
||||
return encrypt(deepgramData);
|
||||
|
||||
case 'ibm':
|
||||
const ibmData = JSON.stringify({tts_api_key, tts_region, stt_api_key, stt_region, instance_id});
|
||||
return encrypt(ibmData);
|
||||
|
||||
case 'nvidia':
|
||||
assert(riva_server_uri, 'invalid riva server uri: riva_server_uri is required');
|
||||
const nvidiaData = JSON.stringify({ riva_server_uri });
|
||||
return encrypt(nvidiaData);
|
||||
|
||||
case 'soniox':
|
||||
assert(api_key, 'invalid soniox speech credential: api_key is required');
|
||||
const sonioxData = JSON.stringify({api_key});
|
||||
return encrypt(sonioxData);
|
||||
|
||||
default:
|
||||
assert(false, `invalid or missing vendor: ${vendor}`);
|
||||
if (vendor.startsWith('custom:')) {
|
||||
const customData = JSON.stringify({auth_token, custom_stt_url, custom_tts_url});
|
||||
return encrypt(customData);
|
||||
}
|
||||
else assert(false, `invalid or missing vendor: ${vendor}`);
|
||||
}
|
||||
};
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {
|
||||
use_for_stt,
|
||||
use_for_tts,
|
||||
vendor,
|
||||
} = req.body;
|
||||
const account_sid = req.user.account_sid || req.body.account_sid;
|
||||
let service_provider_sid;
|
||||
if (!account_sid) {
|
||||
if (!req.user.hasServiceProviderAuth) {
|
||||
logger.error('POST /SpeechCredentials invalid credentials');
|
||||
return res.send(403);
|
||||
}
|
||||
service_provider_sid = parseServiceProviderSid(req);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
use_for_stt,
|
||||
use_for_tts,
|
||||
vendor,
|
||||
} = req.body;
|
||||
const account_sid = req.user.account_sid || req.body.account_sid;
|
||||
const service_provider_sid = req.user.service_provider_sid ||
|
||||
req.body.service_provider_sid || parseServiceProviderSid(req);
|
||||
|
||||
if (!account_sid) {
|
||||
if (!req.user.hasServiceProviderAuth && !req.user.hasAdminAuth) {
|
||||
logger.error('POST /SpeechCredentials invalid credentials');
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
}
|
||||
|
||||
const encrypted_credential = encryptCredential(req.body);
|
||||
const uuid = await SpeechCredential.make({
|
||||
account_sid,
|
||||
@@ -119,14 +172,21 @@ router.post('/', async(req, res) => {
|
||||
* retrieve all speech credentials for an account
|
||||
*/
|
||||
router.get('/', async(req, res) => {
|
||||
let service_provider_sid;
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (!account_sid) service_provider_sid = parseServiceProviderSid(req);
|
||||
const logger = req.app.locals.logger;
|
||||
|
||||
try {
|
||||
const creds = account_sid ?
|
||||
await SpeechCredential.retrieveAll(account_sid) :
|
||||
await SpeechCredential.retrieveAllForSP(service_provider_sid);
|
||||
const account_sid = parseAccountSid(req) || req.user.account_sid;
|
||||
const service_provider_sid = parseServiceProviderSid(req) || req.user.service_provider_sid;
|
||||
const credsAccount = account_sid ? await SpeechCredential.retrieveAll(account_sid) : [];
|
||||
const credsSP = service_provider_sid ?
|
||||
await SpeechCredential.retrieveAllForSP(service_provider_sid) :
|
||||
await SpeechCredential.retrieveAllForSP((await Account.retrieve(account_sid))[0].service_provider_sid);
|
||||
|
||||
// filter out duplicates and discard those from other non-matching accounts
|
||||
let creds = [...new Set([...credsAccount, ...credsSP].map((c) => JSON.stringify(c)))].map((c) => JSON.parse(c));
|
||||
if (req.user.hasScope('account')) {
|
||||
creds = creds.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid);
|
||||
}
|
||||
|
||||
res.status(200).json(creds.map((c) => {
|
||||
const {credential, ...obj} = c;
|
||||
@@ -160,6 +220,36 @@ router.get('/', async(req, res) => {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if ('nuance' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.client_id = o.client_id;
|
||||
obj.secret = o.secret ? obscureKey(o.secret) : null;
|
||||
}
|
||||
else if ('deepgram' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if ('ibm' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.tts_api_key = obscureKey(o.tts_api_key);
|
||||
obj.tts_region = o.tts_region;
|
||||
obj.stt_api_key = obscureKey(o.stt_api_key);
|
||||
obj.stt_region = o.stt_region;
|
||||
obj.instance_id = o.instance_id;
|
||||
} else if ('nvidia' == obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.riva_server_uri = o.riva_server_uri;
|
||||
}
|
||||
else if ('soniox' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if (obj.vendor.startsWith('custom:')) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.auth_token = obscureKey(o.auth_token);
|
||||
obj.custom_stt_url = o.custom_stt_url;
|
||||
obj.custom_tts_url = o.custom_tts_url;
|
||||
}
|
||||
return obj;
|
||||
}));
|
||||
} catch (err) {
|
||||
@@ -205,6 +295,38 @@ router.get('/:sid', async(req, res) => {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if ('nuance' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.client_id = o.client_id;
|
||||
obj.secret = o.secret ? obscureKey(o.secret) : null;
|
||||
obj.nuance_tts_uri = o.nuance_tts_uri;
|
||||
obj.nuance_stt_uri = o.nuance_stt_uri;
|
||||
}
|
||||
else if ('deepgram' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if ('ibm' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.tts_api_key = obscureKey(o.tts_api_key);
|
||||
obj.tts_region = o.tts_region;
|
||||
obj.stt_api_key = obscureKey(o.stt_api_key);
|
||||
obj.stt_region = o.stt_region;
|
||||
obj.instance_id = o.instance_id;
|
||||
} else if ('nvidia' == obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.riva_server_uri = o.riva_server_uri;
|
||||
}
|
||||
else if ('soniox' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = obscureKey(o.api_key);
|
||||
}
|
||||
else if (obj.vendor.startsWith('custom:')) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.auth_token = obscureKey(o.auth_token);
|
||||
obj.custom_stt_url = o.custom_stt_url;
|
||||
obj.custom_tts_url = o.custom_tts_url;
|
||||
}
|
||||
res.status(200).json(obj);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
@@ -234,7 +356,8 @@ router.put('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const {use_for_tts, use_for_stt, region, aws_region} = req.body;
|
||||
const {use_for_tts, use_for_stt, region, aws_region, stt_region, tts_region,
|
||||
riva_server_uri, nuance_tts_uri, nuance_stt_uri} = req.body;
|
||||
if (typeof use_for_tts === 'undefined' && typeof use_for_stt === 'undefined') {
|
||||
throw new DbErrorUnprocessableRequest('use_for_tts and use_for_stt are the only updateable fields');
|
||||
}
|
||||
@@ -267,7 +390,12 @@ router.put('/:sid', async(req, res) => {
|
||||
use_custom_tts,
|
||||
custom_tts_endpoint,
|
||||
use_custom_stt,
|
||||
custom_stt_endpoint
|
||||
custom_stt_endpoint,
|
||||
stt_region,
|
||||
tts_region,
|
||||
riva_server_uri,
|
||||
nuance_stt_uri,
|
||||
nuance_tts_uri
|
||||
};
|
||||
logger.info({o, newCred}, 'updating speech credential with this new credential');
|
||||
obj.credential = encryptCredential(newCred);
|
||||
@@ -319,7 +447,8 @@ router.get('/:sid/test', async(req, res) => {
|
||||
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testGoogleTts(logger, credential);
|
||||
const {getTtsVoices} = req.app.locals;
|
||||
await testGoogleTts(logger, getTtsVoices, credential);
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
@@ -341,8 +470,9 @@ router.get('/:sid/test', async(req, res) => {
|
||||
}
|
||||
else if (cred.vendor === 'aws') {
|
||||
if (cred.use_for_tts) {
|
||||
const {getTtsVoices} = req.app.locals;
|
||||
try {
|
||||
await testAwsTts(logger, {
|
||||
await testAwsTts(logger, getTtsVoices, {
|
||||
accessKeyId: credential.access_key_id,
|
||||
secretAccessKey: credential.secret_access_key,
|
||||
region: credential.aws_region || process.env.AWS_REGION
|
||||
@@ -419,7 +549,106 @@ router.get('/:sid/test', async(req, res) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cred.vendor === 'nuance') {
|
||||
const {getTtsVoices} = req.app.locals;
|
||||
|
||||
const {
|
||||
client_id,
|
||||
secret,
|
||||
nuance_tts_uri,
|
||||
nuance_stt_uri
|
||||
} = credential;
|
||||
if (cred.use_for_tts) {
|
||||
try {
|
||||
await testNuanceTts(logger, getTtsVoices, {
|
||||
client_id,
|
||||
secret,
|
||||
nuance_tts_uri
|
||||
});
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
logger.error({err}, 'error testing nuance tts');
|
||||
const reason = err.statusCode === 401 ?
|
||||
'invalid client_id or secret' :
|
||||
(err.message || 'error accessing nuance tts service with provided credentials');
|
||||
results.tts = {status: 'fail', reason};
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testNuanceStt(logger, {client_id, secret, nuance_stt_uri});
|
||||
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 === 'deepgram') {
|
||||
const {api_key} = credential;
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testDeepgramStt(logger, {api_key});
|
||||
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 === 'ibm') {
|
||||
const {getTtsVoices} = req.app.locals;
|
||||
|
||||
if (cred.use_for_tts) {
|
||||
const {tts_api_key, tts_region} = credential;
|
||||
try {
|
||||
await testIbmTts(logger, getTtsVoices, {
|
||||
tts_api_key,
|
||||
tts_region
|
||||
});
|
||||
results.tts.status = 'ok';
|
||||
SpeechCredential.ttsTestResult(sid, true);
|
||||
} catch (err) {
|
||||
logger.error({err}, 'error testing ibm tts');
|
||||
const reason = err.statusCode === 401 ?
|
||||
'invalid api_key or region' :
|
||||
(err.message || 'error accessing ibm tts service with provided credentials');
|
||||
results.tts = {status: 'fail', reason};
|
||||
SpeechCredential.ttsTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
if (cred.use_for_stt) {
|
||||
const {stt_api_key, stt_region, instance_id} = credential;
|
||||
try {
|
||||
await testIbmStt(logger, {stt_region, stt_api_key, instance_id});
|
||||
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 === 'soniox') {
|
||||
const {api_key} = credential;
|
||||
if (cred.use_for_stt) {
|
||||
try {
|
||||
await testSonioxStt(logger, {api_key});
|
||||
results.stt.status = 'ok';
|
||||
SpeechCredential.sttTestResult(sid, true);
|
||||
} catch (err) {
|
||||
results.stt = {status: 'fail', reason: err.message};
|
||||
SpeechCredential.sttTestResult(sid, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json(results);
|
||||
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//const assert = require('assert');
|
||||
//const debug = require('debug')('jambonz:api-server');
|
||||
const router = require('express').Router();
|
||||
const User = require('../../models/user');
|
||||
const request = require('request');
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const {generateHashedPassword, verifyPassword} = require('../../utils/password-utils');
|
||||
const {promisePool} = require('../../db');
|
||||
const {validatePasswordSettings} = require('./utils');
|
||||
const {decrypt} = require('../../utils/encrypt-decrypt');
|
||||
const sysError = require('../error');
|
||||
const retrieveMyDetails = `SELECT *
|
||||
@@ -11,6 +12,11 @@ FROM users user
|
||||
JOIN accounts AS account ON account.account_sid = user.account_sid
|
||||
LEFT JOIN service_providers as sp ON account.service_provider_sid = sp.service_provider_sid
|
||||
WHERE user.user_sid = ?`;
|
||||
const retrieveMyDetails2 = `SELECT *
|
||||
FROM users user
|
||||
LEFT JOIN accounts AS account ON account.account_sid = user.account_sid
|
||||
LEFT JOIN service_providers as sp ON sp.service_provider_sid = user.service_provider_sid
|
||||
WHERE user.user_sid = ?`;
|
||||
const retrieveSql = 'SELECT * from users where user_sid = ?';
|
||||
const retrieveProducts = `SELECT *
|
||||
FROM account_products
|
||||
@@ -22,109 +28,250 @@ AND account_subscriptions.pending=0`;
|
||||
const updateSql = 'UPDATE users set hashed_password = ?, force_change = false WHERE user_sid = ?';
|
||||
const retrieveStaticIps = 'SELECT * FROM account_static_ips WHERE account_sid = ?';
|
||||
|
||||
const validateRequest = async(user_sid, payload) => {
|
||||
const {old_password, new_password, name, email, email_activation_code} = payload;
|
||||
const validateRequest = async(user_sid, req) => {
|
||||
const payload = req.body;
|
||||
const {
|
||||
old_password,
|
||||
new_password,
|
||||
initial_password,
|
||||
name,
|
||||
email,
|
||||
email_activation_code,
|
||||
force_change,
|
||||
is_active
|
||||
} = payload;
|
||||
|
||||
const [r] = await promisePool.query(retrieveSql, user_sid);
|
||||
if (r.length === 0) return null;
|
||||
if (r.length === 0) {
|
||||
throw new DbErrorBadRequest('Invalid request: user_sid does not exist');
|
||||
}
|
||||
const user = r[0];
|
||||
|
||||
/* it is not allowed for anyone to promote a user to a higher level of authority */
|
||||
if (null === payload.account_sid || null === payload.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('Invalid request: user may not be promoted');
|
||||
}
|
||||
|
||||
if (req.user.hasAccountAuth) {
|
||||
/* account user may not change modify account_sid or service_provider_sid */
|
||||
if ('account_sid' in payload && payload.account_sid !== user.account_sid) {
|
||||
throw new DbErrorBadRequest('Invalid request: user may not be promoted or moved to another account');
|
||||
}
|
||||
if ('service_provider_sid' in payload && payload.service_provider_sid !== user.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('Invalid request: user may not be promoted or moved to another service provider');
|
||||
}
|
||||
}
|
||||
if (req.user.hasServiceProviderAuth) {
|
||||
if ('service_provider_sid' in payload && payload.service_provider_sid !== user.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('Invalid request: user may not be promoted or moved to another service provider');
|
||||
}
|
||||
}
|
||||
if ('account_sid' in payload) {
|
||||
const [r] = await promisePool.query('SELECT * FROM accounts WHERE account_sid = ?', payload.account_sid);
|
||||
if (r.length === 0) throw new DbErrorBadRequest('Invalid request: account_sid does not exist');
|
||||
const {service_provider_sid} = r[0];
|
||||
if (service_provider_sid !== user.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('Invalid request: user may not be moved to another service provider');
|
||||
}
|
||||
}
|
||||
|
||||
if (initial_password) {
|
||||
await validatePasswordSettings(initial_password);
|
||||
}
|
||||
|
||||
if ((old_password && !new_password) || (new_password && !old_password)) {
|
||||
throw new DbErrorBadRequest('new_password and old_password both required');
|
||||
}
|
||||
if (new_password) {
|
||||
await validatePasswordSettings(new_password);
|
||||
}
|
||||
if (new_password && name) throw new DbErrorBadRequest('can not change name and password simultaneously');
|
||||
if (new_password && user.provider !== 'local') {
|
||||
throw new DbErrorBadRequest('can not change password when using oauth2');
|
||||
}
|
||||
|
||||
if ((email && !email_activation_code) || (email_activation_code && !email)) {
|
||||
if (email_activation_code && !email) {
|
||||
throw new DbErrorBadRequest('email and email_activation_code both required');
|
||||
}
|
||||
if (!name && !new_password && !email) throw new DbErrorBadRequest('no updates requested');
|
||||
if (!name && !new_password && !email && !initial_password && !force_change && !is_active)
|
||||
throw new DbErrorBadRequest('no updates requested');
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
|
||||
let usersList;
|
||||
try {
|
||||
let results;
|
||||
if (req.user.hasAdminAuth) {
|
||||
results = await User.retrieveAll();
|
||||
}
|
||||
else if (req.user.hasAccountAuth) {
|
||||
results = await User.retrieveAllForAccount(req.user.account_sid, true);
|
||||
}
|
||||
else if (req.user.hasServiceProviderAuth) {
|
||||
results = await User.retrieveAllForServiceProvider(req.user.service_provider_sid, true);
|
||||
}
|
||||
|
||||
if (results.length === 0) throw new Error('failure retrieving users list');
|
||||
|
||||
usersList = results.map((user) => {
|
||||
const {
|
||||
user_sid,
|
||||
name,
|
||||
email,
|
||||
force_change,
|
||||
is_active,
|
||||
account_sid,
|
||||
service_provider_sid,
|
||||
account_name,
|
||||
service_provider_name
|
||||
} = user;
|
||||
let scope;
|
||||
if (account_sid && service_provider_sid) {
|
||||
scope = 'account';
|
||||
} else if (service_provider_sid) {
|
||||
scope = 'service_provider';
|
||||
} else {
|
||||
scope = 'admin';
|
||||
}
|
||||
|
||||
const obj = {
|
||||
user_sid,
|
||||
name,
|
||||
email,
|
||||
scope,
|
||||
force_change,
|
||||
is_active,
|
||||
...(account_sid && {account_sid}),
|
||||
...(account_name && {account_name}),
|
||||
...(service_provider_sid && {service_provider_sid}),
|
||||
...(service_provider_name && {service_provider_name})
|
||||
};
|
||||
return obj;
|
||||
});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
res.status(200).json(usersList);
|
||||
});
|
||||
|
||||
router.get('/me', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {user_sid} = req.user;
|
||||
|
||||
if (!user_sid) return res.sendStatus(403);
|
||||
|
||||
let payload;
|
||||
try {
|
||||
const [r] = await promisePool.query({sql: retrieveMyDetails, nestTables: true}, user_sid);
|
||||
logger.debug(r, 'retrieved user details');
|
||||
const payload = r[0];
|
||||
const {user, account, sp} = payload;
|
||||
['hashed_password', 'salt', 'phone_activation_code', 'email_activation_code', 'account_sid'].forEach((prop) => {
|
||||
delete user[prop];
|
||||
});
|
||||
['email_validated', 'phone_validated', 'force_change'].forEach((prop) => user[prop] = !!user[prop]);
|
||||
['is_active'].forEach((prop) => account[prop] = !!account[prop]);
|
||||
account.root_domain = sp.root_domain;
|
||||
delete payload.sp;
|
||||
|
||||
/* get api keys */
|
||||
const [keys] = await promisePool.query('SELECT * from api_keys WHERE account_sid = ?', account.account_sid);
|
||||
payload.api_keys = keys.map((k) => {
|
||||
return {
|
||||
api_key_sid: k.api_key_sid,
|
||||
//token: k.token.replace(/.(?=.{4,}$)/g, '*'),
|
||||
token: k.token,
|
||||
last_used: k.last_used,
|
||||
created_at: k.created_at
|
||||
};
|
||||
});
|
||||
|
||||
/* get products */
|
||||
const [products] = await promisePool.query({sql: retrieveProducts, nestTables: true}, account.account_sid);
|
||||
if (!products.length || !products[0].account_subscriptions) {
|
||||
throw new Error('account is missing a subscription');
|
||||
}
|
||||
const account_subscription = products[0].account_subscriptions;
|
||||
payload.subscription = {
|
||||
status: 'active',
|
||||
account_subscription_sid: account_subscription.account_subscription_sid,
|
||||
start_date: account_subscription.effective_start_date,
|
||||
products: products.map((prd) => {
|
||||
return {
|
||||
name: prd.products.name,
|
||||
units: prd.products.unit_label,
|
||||
quantity: prd.account_products.quantity
|
||||
};
|
||||
})
|
||||
};
|
||||
if (account_subscription.pending) {
|
||||
Object.assign(payload.subscription, {
|
||||
status: 'suspended',
|
||||
suspend_reason: account_subscription.pending_reason
|
||||
if (process.env.JAMBONES_HOSTING) {
|
||||
const [r] = await promisePool.query({sql: retrieveMyDetails, nestTables: true}, user_sid);
|
||||
logger.debug(r, 'retrieved user details');
|
||||
payload = r[0];
|
||||
const {user, account, sp} = payload;
|
||||
['hashed_password', 'salt', 'phone_activation_code', 'email_activation_code', 'account_sid'].forEach((prop) => {
|
||||
delete user[prop];
|
||||
});
|
||||
}
|
||||
const {
|
||||
last4,
|
||||
exp_month,
|
||||
exp_year,
|
||||
card_type,
|
||||
stripe_statement_descriptor
|
||||
} = account_subscription;
|
||||
if (last4) {
|
||||
const real_last4 = decrypt(last4);
|
||||
Object.assign(payload.subscription, {
|
||||
last4: real_last4,
|
||||
['email_validated', 'phone_validated', 'force_change'].forEach((prop) => user[prop] = !!user[prop]);
|
||||
['is_active'].forEach((prop) => account[prop] = !!account[prop]);
|
||||
account.root_domain = sp.root_domain;
|
||||
delete payload.sp;
|
||||
|
||||
/* get api keys */
|
||||
const [keys] = await promisePool.query('SELECT * from api_keys WHERE account_sid = ?', account.account_sid);
|
||||
payload.api_keys = keys.map((k) => {
|
||||
return {
|
||||
api_key_sid: k.api_key_sid,
|
||||
//token: k.token.replace(/.(?=.{4,}$)/g, '*'),
|
||||
token: k.token,
|
||||
last_used: k.last_used,
|
||||
created_at: k.created_at
|
||||
};
|
||||
});
|
||||
|
||||
/* get products */
|
||||
const [products] = await promisePool.query({sql: retrieveProducts, nestTables: true}, account.account_sid);
|
||||
if (!products.length || !products[0].account_subscriptions) {
|
||||
throw new Error('account is missing a subscription');
|
||||
}
|
||||
const account_subscription = products[0].account_subscriptions;
|
||||
payload.subscription = {
|
||||
status: 'active',
|
||||
account_subscription_sid: account_subscription.account_subscription_sid,
|
||||
start_date: account_subscription.effective_start_date,
|
||||
products: products.map((prd) => {
|
||||
return {
|
||||
name: prd.products.name,
|
||||
units: prd.products.unit_label,
|
||||
quantity: prd.account_products.quantity
|
||||
};
|
||||
})
|
||||
};
|
||||
if (account_subscription.pending) {
|
||||
Object.assign(payload.subscription, {
|
||||
status: 'suspended',
|
||||
suspend_reason: account_subscription.pending_reason
|
||||
});
|
||||
}
|
||||
const {
|
||||
last4,
|
||||
exp_month,
|
||||
exp_year,
|
||||
card_type,
|
||||
statement_descriptor: stripe_statement_descriptor
|
||||
stripe_statement_descriptor
|
||||
} = account_subscription;
|
||||
if (last4) {
|
||||
const real_last4 = decrypt(last4);
|
||||
Object.assign(payload.subscription, {
|
||||
last4: real_last4,
|
||||
exp_month,
|
||||
exp_year,
|
||||
card_type,
|
||||
statement_descriptor: stripe_statement_descriptor
|
||||
});
|
||||
}
|
||||
|
||||
/* get static ips */
|
||||
const [static_ips] = await promisePool.query(retrieveStaticIps, account.account_sid);
|
||||
payload.static_ips = static_ips.map((r) => r.public_ipv4);
|
||||
}
|
||||
else {
|
||||
const [r] = await promisePool.query({sql: retrieveMyDetails2, nestTables: true}, user_sid);
|
||||
logger.debug(r, 'retrieved user details');
|
||||
payload = r[0];
|
||||
const {user} = payload;
|
||||
['hashed_password', 'salt', 'phone_activation_code', 'email_activation_code'].forEach((prop) => {
|
||||
delete user[prop];
|
||||
});
|
||||
['email_validated', 'phone_validated', 'force_change'].forEach((prop) => user[prop] = !!user[prop]);
|
||||
}
|
||||
logger.debug({payload}, 'returning user details');
|
||||
res.json(payload);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:user_sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {user_sid} = req.params;
|
||||
|
||||
try {
|
||||
const [user] = await User.retrieve(user_sid);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {hashed_password, ...rest} = user;
|
||||
if (!user) throw new Error('failure retrieving user');
|
||||
|
||||
if (req.user.hasAdminAuth ||
|
||||
req.user.hasAccountAuth && req.user.account_sid === user.account_sid ||
|
||||
req.user.hasServiceProviderAuth && req.user.service_provider_sid === user.service_provider_sid) {
|
||||
res.status(200).json(rest);
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
}
|
||||
|
||||
/* get static ips */
|
||||
const [static_ips] = await promisePool.query(retrieveStaticIps, account.account_sid);
|
||||
payload.static_ips = static_ips.map((r) => r.public_ipv4);
|
||||
|
||||
logger.debug({payload}, 'returning user details');
|
||||
|
||||
res.json(payload);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -133,12 +280,32 @@ router.get('/me', async(req, res) => {
|
||||
router.put('/:user_sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {user_sid} = req.params;
|
||||
const {old_password, new_password, name, email, email_activation_code} = req.body;
|
||||
const user = await User.retrieve(user_sid);
|
||||
const {hasAccountAuth, hasServiceProviderAuth, hasAdminAuth} = req.user;
|
||||
const {
|
||||
old_password,
|
||||
new_password,
|
||||
initial_password,
|
||||
email_activation_code,
|
||||
email,
|
||||
name,
|
||||
is_active,
|
||||
force_change,
|
||||
account_sid,
|
||||
service_provider_sid
|
||||
} = req.body;
|
||||
|
||||
if (req.user.user_sid && req.user.user_sid !== user_sid) return res.sendStatus(403);
|
||||
//if (req.user.user_sid && req.user.user_sid !== user_sid) return res.sendStatus(403);
|
||||
|
||||
if (!hasAdminAuth &&
|
||||
!(hasAccountAuth && req.user.account_sid === user[0].account_sid) &&
|
||||
!(hasServiceProviderAuth && req.user.service_provider_sid === user[0].service_provider_sid) &&
|
||||
(req.user.user_sid && req.user.user_sid !== user_sid)) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await validateRequest(user_sid, req.body);
|
||||
const user = await validateRequest(user_sid, req);
|
||||
if (!user) return res.sendStatus(404);
|
||||
|
||||
if (new_password) {
|
||||
@@ -149,6 +316,11 @@ router.put('/:user_sid', async(req, res) => {
|
||||
//debug(`PUT /Users/:sid pwd ${old_password} does not match hash ${old_hashed_password}`);
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
if (old_password === new_password) {
|
||||
throw new Error('new password cannot be your old password');
|
||||
}
|
||||
|
||||
const passwordHash = await generateHashedPassword(new_password);
|
||||
//debug(`updating hashed_password to ${passwordHash}`);
|
||||
const r = await promisePool.execute(updateSql, [passwordHash, user_sid]);
|
||||
@@ -160,10 +332,51 @@ router.put('/:user_sid', async(req, res) => {
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
|
||||
if (email) {
|
||||
if (initial_password) {
|
||||
const passwordHash = await generateHashedPassword(initial_password);
|
||||
const r = await promisePool.execute(
|
||||
'UPDATE users SET email = ?, email_activation_code = ?, email_validated = 0 WHERE user_sid = ?',
|
||||
[email, email_activation_code, user_sid]);
|
||||
'UPDATE users SET hashed_password = ? WHERE user_sid = ?',
|
||||
[passwordHash, user_sid]
|
||||
);
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
|
||||
if (typeof is_active !== 'undefined') {
|
||||
const r = await promisePool.execute('UPDATE users SET is_active = ? WHERE user_sid = ?', [is_active, user_sid]);
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
|
||||
if (typeof force_change !== 'undefined') {
|
||||
const r = await promisePool.execute(
|
||||
'UPDATE users SET force_change = ? WHERE user_sid = ?',
|
||||
[force_change, user_sid]);
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
|
||||
if (account_sid || account_sid === null) {
|
||||
const r = await promisePool.execute(
|
||||
'UPDATE users SET account_sid = ? WHERE user_sid = ?',
|
||||
[account_sid, user_sid]);
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
|
||||
if (service_provider_sid || service_provider_sid === null) {
|
||||
const r = await promisePool.execute(
|
||||
'UPDATE users SET service_provider_sid = ? WHERE user_sid = ?',
|
||||
[service_provider_sid, user_sid]);
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
|
||||
if (email) {
|
||||
if (email_activation_code) {
|
||||
const r = await promisePool.execute(
|
||||
'UPDATE users SET email = ?, email_activation_code = ?, email_validated = 0 WHERE user_sid = ?',
|
||||
[email, email_activation_code, user_sid]);
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
}
|
||||
const r = await promisePool.execute(
|
||||
'UPDATE users SET email = ? WHERE user_sid = ?',
|
||||
[email, user_sid]);
|
||||
if (0 === r.changedRows) throw new Error('database update failed');
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
@@ -176,5 +389,96 @@ router.put('/:user_sid', async(req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const passwordHash = await generateHashedPassword(req.body.initial_password);
|
||||
const payload = {
|
||||
...req.body,
|
||||
provider: 'local',
|
||||
hashed_password: passwordHash,
|
||||
};
|
||||
const allUsers = await User.retrieveAll();
|
||||
delete payload.initial_password;
|
||||
|
||||
try {
|
||||
if (req.body.initial_password) {
|
||||
await validatePasswordSettings(req.body.initial_password);
|
||||
}
|
||||
const email = allUsers.find((e) => e.email === payload.email);
|
||||
const name = allUsers.find((e) => e.name === payload.name);
|
||||
|
||||
if (name) {
|
||||
logger.debug({payload}, 'user with this username already exists');
|
||||
return res.status(422).json({msg: 'user with this username already exists'});
|
||||
}
|
||||
|
||||
if (email) {
|
||||
logger.debug({payload}, 'user with this email already exists');
|
||||
return res.status(422).json({msg: 'user with this email already exists'});
|
||||
}
|
||||
|
||||
if (req.user.hasAdminAuth) {
|
||||
logger.debug({payload}, 'POST /users');
|
||||
const uuid = await User.make(payload);
|
||||
res.status(201).json({user_sid: uuid});
|
||||
}
|
||||
else if (req.user.hasAccountAuth) {
|
||||
logger.debug({payload}, 'POST /users');
|
||||
const uuid = await User.make({
|
||||
...payload,
|
||||
account_sid: req.user.account_sid,
|
||||
});
|
||||
res.status(201).json({user_sid: uuid});
|
||||
}
|
||||
else if (req.user.hasServiceProviderAuth) {
|
||||
logger.debug({payload}, 'POST /users');
|
||||
const uuid = await User.make({
|
||||
...payload,
|
||||
service_provider_sid: req.user.service_provider_sid,
|
||||
});
|
||||
res.status(201).json({user_sid: uuid});
|
||||
}
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:user_sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const {user_sid} = req.params;
|
||||
const allUsers = await User.retrieveAll();
|
||||
const activeAdminUsers = allUsers.filter((e) => !e.account_sid && !e.service_provider_sid && e.is_active);
|
||||
const user = await User.retrieve(user_sid);
|
||||
|
||||
try {
|
||||
if (req.user.hasAdminAuth && activeAdminUsers.length === 1) {
|
||||
throw new Error('cannot delete this admin user - there are no other active admin users');
|
||||
}
|
||||
|
||||
if (req.user.hasAdminAuth ||
|
||||
(req.user.hasAccountAuth && req.user.account_sid === user[0].account_sid) ||
|
||||
(req.user.hasServiceProviderAuth && req.user.service_provider_sid === user[0].service_provider_sid)) {
|
||||
await User.remove(user_sid);
|
||||
//logout user after self-delete
|
||||
if (req.user.user_sid === user_sid) {
|
||||
request({
|
||||
url:'http://localhost:3000/v1/logout',
|
||||
method: 'POST',
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
logger.error(err, 'could not log out user');
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
logger.debug({user}, 'user deleted and logged out');
|
||||
});
|
||||
}
|
||||
return res.sendStatus(204);
|
||||
} else {
|
||||
throw new DbErrorBadRequest('invalid request');
|
||||
}
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const { v4: uuid } = require('uuid');
|
||||
const { v4: uuid, validate } = require('uuid');
|
||||
const bent = require('bent');
|
||||
const Account = require('../../models/account');
|
||||
const {promisePool} = require('../../db');
|
||||
const {cancelSubscription, detachPaymentMethod} = require('../../utils/stripe-utils');
|
||||
const freePlans = require('../../utils/free_plans');
|
||||
const { BadRequestError, DbErrorBadRequest } = require('../../utils/errors');
|
||||
const insertAccountSubscriptionSql = `INSERT INTO account_subscriptions
|
||||
(account_subscription_sid, account_sid)
|
||||
values (?, ?)`;
|
||||
@@ -139,36 +140,76 @@ const createTestAlerts = async(writeAlerts, AlertType, account_sid) => {
|
||||
|
||||
const parseServiceProviderSid = (req) => {
|
||||
const arr = /ServiceProviders\/([^\/]*)/.exec(req.originalUrl);
|
||||
if (arr) return arr[1];
|
||||
if (arr) {
|
||||
const sid = arr[1];
|
||||
const sid_validation = validate(sid);
|
||||
if (!sid_validation) {
|
||||
throw new BadRequestError('invalid service_provider_sid format');
|
||||
}
|
||||
|
||||
return arr[1];
|
||||
}
|
||||
};
|
||||
|
||||
const parseAccountSid = (req) => {
|
||||
const arr = /Accounts\/([^\/]*)/.exec(req.originalUrl);
|
||||
if (arr) return arr[1];
|
||||
if (arr) {
|
||||
const sid = arr[1];
|
||||
const sid_validation = validate(sid);
|
||||
if (!sid_validation) {
|
||||
throw new BadRequestError('invalid account_sid format');
|
||||
}
|
||||
|
||||
return arr[1];
|
||||
}
|
||||
};
|
||||
|
||||
const hasAccountPermissions = (req, res, next) => {
|
||||
if (req.user.hasScope('admin')) return next();
|
||||
if (req.user.hasScope('account')) {
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (account_sid === req.user.account_sid) return next();
|
||||
try {
|
||||
if (req.user.hasScope('admin')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.user.hasScope('account')) {
|
||||
const account_sid = parseAccountSid(req);
|
||||
if (account_sid === req.user.account_sid) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
};
|
||||
|
||||
const hasServiceProviderPermissions = (req, res, next) => {
|
||||
if (req.user.hasScope('admin')) return next();
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
if (service_provider_sid === req.user.service_provider_sid) return next();
|
||||
try {
|
||||
if (req.user.hasScope('admin')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.user.hasScope('service_provider')) {
|
||||
const service_provider_sid = parseServiceProviderSid(req);
|
||||
if (service_provider_sid === req.user.service_provider_sid) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
};
|
||||
|
||||
const checkLimits = async(req, res, next) => {
|
||||
@@ -273,6 +314,31 @@ const disableSubspace = async(opts) => {
|
||||
return;
|
||||
};
|
||||
|
||||
const validatePasswordSettings = async(password) => {
|
||||
const sql = 'SELECT * from password_settings';
|
||||
const [rows] = await promisePool.execute(sql);
|
||||
const specialChars = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/;
|
||||
const numbers = /[0-9]+/;
|
||||
if (rows.length === 0) {
|
||||
if (password.length < 8 || password.length > 20) {
|
||||
throw new DbErrorBadRequest('password length must be between 8 and 20');
|
||||
}
|
||||
} else {
|
||||
if (rows[0].min_password_length && password.length < rows[0].min_password_length) {
|
||||
throw new DbErrorBadRequest(`password must be at least ${rows[0].min_password_length} characters long`);
|
||||
}
|
||||
|
||||
if (rows[0].require_digit === 1 && !numbers.test(password)) {
|
||||
throw new DbErrorBadRequest('password must contain at least one digit');
|
||||
}
|
||||
|
||||
if (rows[0].require_special_character === 1 && !specialChars.test(password)) {
|
||||
throw new DbErrorBadRequest('password must contain at least one special character');
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
setupFreeTrial,
|
||||
createTestCdrs,
|
||||
@@ -283,5 +349,6 @@ module.exports = {
|
||||
hasServiceProviderPermissions,
|
||||
checkLimits,
|
||||
enableSubspace,
|
||||
disableSubspace
|
||||
disableSubspace,
|
||||
validatePasswordSettings
|
||||
};
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest, DbErrorForbidden} = require('../utils/errors');
|
||||
const {
|
||||
BadRequestError,
|
||||
DbErrorBadRequest,
|
||||
DbErrorUnprocessableRequest,
|
||||
DbErrorForbidden
|
||||
} = require('../utils/errors');
|
||||
|
||||
function sysError(logger, res, err) {
|
||||
if (err instanceof BadRequestError) {
|
||||
logger.info(err, err.message);
|
||||
return res.status(400).json({msg: 'Bad request'});
|
||||
}
|
||||
if (err instanceof DbErrorBadRequest) {
|
||||
logger.info(err, 'invalid client request');
|
||||
return res.status(400).json({msg: err.message});
|
||||
|
||||
@@ -10,7 +10,34 @@ info:
|
||||
version: 1.0.0
|
||||
servers:
|
||||
- url: /v1
|
||||
description: development server
|
||||
description: jambonz API server
|
||||
tags:
|
||||
- name: Authentication
|
||||
description: Authentication operations
|
||||
- name: Accounts
|
||||
description: Accounts operations
|
||||
- name: Users
|
||||
description: Users operations
|
||||
- name: Applications
|
||||
description: Applications operations
|
||||
- name: Phone Numbers
|
||||
description: Phone Numbers operations
|
||||
- name: Api Keys
|
||||
description: Api Keys operations
|
||||
- name: Service Providers
|
||||
description: Service Providers operations
|
||||
- name: SBCs
|
||||
description: SBCs operations
|
||||
- name: Voip Carriers
|
||||
description: Voip Carriers operations
|
||||
- name: Sip Gateways
|
||||
description: Sip Gateways operations
|
||||
- name: Smpp Gateways
|
||||
description: Smpp Gateways operations
|
||||
- name: Webhooks
|
||||
description: Webhooks operations
|
||||
- name: Microsoft Teams Tenants
|
||||
description: Microsoft Teams Tenants operations
|
||||
paths:
|
||||
/BetaInviteCodes:
|
||||
post:
|
||||
@@ -79,6 +106,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: add a VoiPCarrier to an account based on PredefinedCarrier template
|
||||
operationId: createVoipCarrierFromTemplate
|
||||
responses:
|
||||
@@ -96,6 +125,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Sbcs:
|
||||
post:
|
||||
tags:
|
||||
- SBCs
|
||||
summary: add an SBC address
|
||||
operationId: createSbc
|
||||
requestBody:
|
||||
@@ -134,7 +165,9 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
summary: retrieve public IP addresses of the jambonz Sbcs
|
||||
tags:
|
||||
- SBCs
|
||||
summary: retrieve public IP addresses of the jambonz SBCs
|
||||
operationId: listSbcs
|
||||
parameters:
|
||||
- in: query
|
||||
@@ -171,7 +204,9 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
summary: delete sbc address
|
||||
tags:
|
||||
- SBCs
|
||||
summary: delete SBC address
|
||||
operationId: deleteSbcAddress
|
||||
responses:
|
||||
200:
|
||||
@@ -240,6 +275,8 @@ paths:
|
||||
|
||||
/ApiKeys:
|
||||
post:
|
||||
tags:
|
||||
- Api Keys
|
||||
summary: create an api key
|
||||
operationId: createApikey
|
||||
requestBody:
|
||||
@@ -277,7 +314,7 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Apikeys/{ApiKeySid}:
|
||||
/ApiKeys/{ApiKeySid}:
|
||||
parameters:
|
||||
- name: ApiKeySid
|
||||
in: path
|
||||
@@ -285,6 +322,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- Api Keys
|
||||
summary: delete api key
|
||||
operationId: deleteApiKey
|
||||
responses:
|
||||
@@ -294,6 +333,8 @@ paths:
|
||||
description: api key or account not found
|
||||
/signin:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: sign in using email and password
|
||||
operationId: loginUser
|
||||
requestBody:
|
||||
@@ -335,6 +376,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/logout:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: log out and deactivate jwt
|
||||
operationId: logoutUser
|
||||
responses:
|
||||
@@ -348,6 +391,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/forgot-password:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: send link to reset password
|
||||
operationId: forgotPassword
|
||||
requestBody:
|
||||
@@ -377,6 +422,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/change-password:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: changePassword
|
||||
operationId: changePassword
|
||||
requestBody:
|
||||
@@ -408,6 +455,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/register:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: create a new user and account
|
||||
operationId: registerUser
|
||||
requestBody:
|
||||
@@ -515,6 +564,30 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Users:
|
||||
get:
|
||||
tags:
|
||||
- Users
|
||||
summary: list all users
|
||||
operationId: listUsers
|
||||
responses:
|
||||
200:
|
||||
description: list of users
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type:
|
||||
array
|
||||
items:
|
||||
$ref: '#/components/schemas/Users'
|
||||
403:
|
||||
description: unauthorized
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Users/{UserSid}:
|
||||
parameters:
|
||||
- name: UserSid
|
||||
@@ -524,7 +597,47 @@ paths:
|
||||
explode: false
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
tags:
|
||||
- Users
|
||||
summary: retrieve user information
|
||||
operationId: getUser
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
is_active:
|
||||
type: boolean
|
||||
force_change:
|
||||
type: boolean
|
||||
scope:
|
||||
type: string
|
||||
permissions:
|
||||
type: array
|
||||
responses:
|
||||
204:
|
||||
description: user information
|
||||
403:
|
||||
description: user information
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
put:
|
||||
tags:
|
||||
- Users
|
||||
summary: update user information
|
||||
operationId: updateUser
|
||||
requestBody:
|
||||
@@ -545,11 +658,19 @@ paths:
|
||||
new_password:
|
||||
type: string
|
||||
description: new password
|
||||
is_active:
|
||||
type: boolean
|
||||
force_change:
|
||||
type: boolean
|
||||
scope:
|
||||
type: string
|
||||
permissions:
|
||||
type: array
|
||||
responses:
|
||||
204:
|
||||
description: user updated
|
||||
403:
|
||||
description: password change failed
|
||||
description: user update failed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -560,8 +681,69 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
post:
|
||||
tags:
|
||||
- Users
|
||||
summary: create a new user
|
||||
operationId: createUser
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
is_active:
|
||||
type: boolean
|
||||
force_change:
|
||||
type: boolean
|
||||
scope:
|
||||
type: string
|
||||
permissions:
|
||||
type: array
|
||||
old_password:
|
||||
type: string
|
||||
description: existing password, which is to be replaced
|
||||
responses:
|
||||
204:
|
||||
description: user created
|
||||
403:
|
||||
description: user creation failed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
delete:
|
||||
tags:
|
||||
- Users
|
||||
summary: delete a user
|
||||
operationId: deleteUser
|
||||
responses:
|
||||
204:
|
||||
description: user deleted
|
||||
404:
|
||||
description: user not found
|
||||
403:
|
||||
description: unauthorized
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Users/me:
|
||||
get:
|
||||
tags:
|
||||
- Users
|
||||
summary: retrieve details about logged-in user and associated account
|
||||
operationId: getMyDetails
|
||||
responses:
|
||||
@@ -727,6 +909,8 @@ paths:
|
||||
|
||||
/ActivationCode:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: send an activation code to the user
|
||||
operationId: sendActivationCode
|
||||
requestBody:
|
||||
@@ -772,6 +956,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: validate an activation code
|
||||
operationId: validateActivationCode
|
||||
requestBody:
|
||||
@@ -851,6 +1037,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
tags:
|
||||
- Webhooks
|
||||
summary: retrieve webhook
|
||||
operationId: getWebhook
|
||||
responses:
|
||||
@@ -871,6 +1059,8 @@ paths:
|
||||
|
||||
/VoipCarriers:
|
||||
post:
|
||||
tags:
|
||||
- Voip Carriers
|
||||
summary: create voip carrier
|
||||
operationId: createVoipCarrier
|
||||
requestBody:
|
||||
@@ -958,6 +1148,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Voip Carriers
|
||||
summary: list voip carriers
|
||||
operationId: listVoipCarriers
|
||||
responses:
|
||||
@@ -985,6 +1177,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- Voip Carriers
|
||||
summary: delete a voip carrier
|
||||
operationId: deleteVoipCarrier
|
||||
responses:
|
||||
@@ -1007,6 +1201,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Voip Carriers
|
||||
summary: retrieve voip carrier
|
||||
operationId: getVoipCarrier
|
||||
responses:
|
||||
@@ -1025,6 +1221,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
put:
|
||||
tags:
|
||||
- Voip Carriers
|
||||
summary: update voip carrier
|
||||
operationId: updateVoipCarrier
|
||||
parameters:
|
||||
@@ -1064,6 +1262,8 @@ paths:
|
||||
|
||||
/SipGateways:
|
||||
post:
|
||||
tags:
|
||||
- Sip Gateways
|
||||
summary: create sip gateway
|
||||
operationId: createSipGateway
|
||||
requestBody:
|
||||
@@ -1115,6 +1315,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Sip Gateways
|
||||
summary: list sip gateways
|
||||
operationId: listSipGateways
|
||||
parameters:
|
||||
@@ -1149,6 +1351,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- Sip Gateways
|
||||
summary: delete a sip gateway
|
||||
operationId: deleteSipGateway
|
||||
responses:
|
||||
@@ -1163,6 +1367,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Sip Gateways
|
||||
summary: retrieve sip gateway
|
||||
operationId: getSipGateway
|
||||
responses:
|
||||
@@ -1181,6 +1387,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
put:
|
||||
tags:
|
||||
- Sip Gateways
|
||||
summary: update sip gateway
|
||||
operationId: updateSipGateway
|
||||
requestBody:
|
||||
@@ -1207,6 +1415,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/SmppGateways:
|
||||
post:
|
||||
tags:
|
||||
- Smpp Gateways
|
||||
summary: create smpp gateway
|
||||
operationId: createSmppGateway
|
||||
requestBody:
|
||||
@@ -1262,6 +1472,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Smpp Gateways
|
||||
summary: list smpp gateways
|
||||
operationId: listSmppGateways
|
||||
responses:
|
||||
@@ -1289,6 +1501,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- Smpp Gateways
|
||||
summary: delete a smpp gateway
|
||||
operationId: deleteSmppGateway
|
||||
responses:
|
||||
@@ -1303,6 +1517,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Smpp Gateways
|
||||
summary: retrieve smpp gateway
|
||||
operationId: getSmppGateway
|
||||
responses:
|
||||
@@ -1321,7 +1537,9 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
put:
|
||||
summary: update sip gateway
|
||||
tags:
|
||||
- Smpp Gateways
|
||||
summary: update smpp gateway
|
||||
operationId: updateSmppGateway
|
||||
requestBody:
|
||||
content:
|
||||
@@ -1347,6 +1565,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/PhoneNumbers:
|
||||
post:
|
||||
tags:
|
||||
- Phone Numbers
|
||||
summary: provision a phone number into inventory from a Voip Carrier
|
||||
operationId: provisionPhoneNumber
|
||||
requestBody:
|
||||
@@ -1402,6 +1622,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Phone Numbers
|
||||
summary: list phone numbers
|
||||
operationId: listProvisionedPhoneNumbers
|
||||
responses:
|
||||
@@ -1429,6 +1651,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- Phone Numbers
|
||||
summary: delete a phone number
|
||||
operationId: deletePhoneNumber
|
||||
responses:
|
||||
@@ -1451,6 +1675,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Phone Numbers
|
||||
summary: retrieve phone number
|
||||
operationId: getPhoneNumber
|
||||
responses:
|
||||
@@ -1469,6 +1695,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
put:
|
||||
tags:
|
||||
- Phone Numbers
|
||||
summary: update phone number
|
||||
operationId: updatePhoneNumber
|
||||
requestBody:
|
||||
@@ -1500,6 +1728,8 @@ paths:
|
||||
|
||||
/ServiceProviders:
|
||||
post:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: create service provider
|
||||
operationId: createServiceProvider
|
||||
requestBody:
|
||||
@@ -1542,6 +1772,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: list service providers
|
||||
operationId: listServiceProviders
|
||||
responses:
|
||||
@@ -1570,6 +1802,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: delete a service provider
|
||||
operationId: deleteServiceProvider
|
||||
responses:
|
||||
@@ -1591,6 +1825,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: retrieve service provider
|
||||
operationId: getServiceProvider
|
||||
responses:
|
||||
@@ -1608,8 +1844,10 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
|
||||
put:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: update service provider
|
||||
operationId: updateServiceProvider
|
||||
requestBody:
|
||||
@@ -1644,6 +1882,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: get all accounts for a service provider
|
||||
operationId: getServiceProviderAccounts
|
||||
responses:
|
||||
@@ -1669,6 +1909,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: get all carriers for a service provider
|
||||
operationId: getServiceProviderCarriers
|
||||
responses:
|
||||
@@ -1685,6 +1927,8 @@ paths:
|
||||
404:
|
||||
description: service provider not found
|
||||
post:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: create a carrier
|
||||
operationId: createCarrierForServiceProvider
|
||||
requestBody:
|
||||
@@ -1716,6 +1960,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
post:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: add a VoiPCarrier to a service provider based on PredefinedCarrier template
|
||||
operationId: createVoipCarrierFromTemplate
|
||||
responses:
|
||||
@@ -1741,6 +1987,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
post:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: create a speech credential for a service provider
|
||||
operationId: addSpeechCredentialForSeerviceProvider
|
||||
requestBody:
|
||||
@@ -1772,6 +2020,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: get a specific speech credential
|
||||
operationId: getSpeechCredential
|
||||
responses:
|
||||
@@ -1784,6 +2034,8 @@ paths:
|
||||
404:
|
||||
description: credential not found
|
||||
put:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: update a speech credential
|
||||
operationId: updateSpeechCredential
|
||||
requestBody:
|
||||
@@ -1803,6 +2055,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
delete:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: delete a speech credential
|
||||
operationId: deleteSpeechCredential
|
||||
responses:
|
||||
@@ -1812,6 +2066,8 @@ paths:
|
||||
description: credential not found
|
||||
/ServiceProviders/{ServiceProviderSid}/SpeechCredentials/{SpeechCredentialSid}/test:
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: test a speech credential
|
||||
operationId: testSpeechCredential
|
||||
parameters:
|
||||
@@ -1866,6 +2122,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/ServiceProviders/{ServiceProviderSid}/Limits:
|
||||
post:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: create a limit for a service provider
|
||||
operationId: addLimitForServiceProvider
|
||||
parameters:
|
||||
@@ -1896,6 +2154,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: retrieve call capacity and other limits from the service provider
|
||||
operationId: getServiceProviderLimits
|
||||
parameters:
|
||||
@@ -1924,6 +2184,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Accounts/{AccountSid}/Limits:
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: create a limit for an account
|
||||
operationId: addLimitForAccount
|
||||
parameters:
|
||||
@@ -1954,6 +2216,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve call capacity and other limits from the account
|
||||
operationId: getAccountLimits
|
||||
parameters:
|
||||
@@ -1982,6 +2246,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/MicrosoftTeamsTenants:
|
||||
post:
|
||||
tags:
|
||||
- Microsoft Teams Tenants
|
||||
summary: provision a customer tenant for MS Teams
|
||||
operationId: createMsTeamsTenant
|
||||
requestBody:
|
||||
@@ -2029,6 +2295,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Microsoft Teams Tenants
|
||||
summary: list MS Teams tenants
|
||||
operationId: listMsTeamsTenants
|
||||
responses:
|
||||
@@ -2055,6 +2323,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
delete:
|
||||
tags:
|
||||
- Microsoft Teams Tenants
|
||||
summary: delete an MS Teams tenant
|
||||
operationId: deleteTenant
|
||||
responses:
|
||||
@@ -2069,6 +2339,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Microsoft Teams Tenants
|
||||
summary: retrieve an MS Teams tenant
|
||||
operationId: getTenant
|
||||
responses:
|
||||
@@ -2088,7 +2360,9 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
put:
|
||||
summary: update tenant
|
||||
tags:
|
||||
- Microsoft Teams Tenants
|
||||
summary: update an MS Teams tenant
|
||||
operationId: putTenant
|
||||
requestBody:
|
||||
content:
|
||||
@@ -2116,6 +2390,8 @@ paths:
|
||||
|
||||
/Accounts:
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: create an account
|
||||
operationId: createAccount
|
||||
requestBody:
|
||||
@@ -2283,6 +2559,8 @@ paths:
|
||||
address should be blacklisted by the platform (0 means forever).
|
||||
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: list accounts
|
||||
operationId: listAccounts
|
||||
responses:
|
||||
@@ -2310,6 +2588,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
delete:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: delete an account
|
||||
operationId: deleteAccount
|
||||
responses:
|
||||
@@ -2330,6 +2610,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve account
|
||||
operationId: getAccount
|
||||
responses:
|
||||
@@ -2349,6 +2631,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
put:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: update account
|
||||
operationId: updateAccount
|
||||
requestBody:
|
||||
@@ -2387,6 +2671,8 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: get webhook signing secret, regenerating if requested
|
||||
operationId: getWebhookSecret
|
||||
responses:
|
||||
@@ -2417,6 +2703,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: get all api keys for an account
|
||||
operationId: getAccountApiKeys
|
||||
responses:
|
||||
@@ -2451,6 +2739,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: add or change the sip realm
|
||||
operationId: createSipRealm
|
||||
responses:
|
||||
@@ -2479,6 +2769,8 @@ paths:
|
||||
format: uuid
|
||||
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: add a speech credential
|
||||
operationId: createSpeechCredential
|
||||
requestBody:
|
||||
@@ -2506,6 +2798,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve all speech credentials for an account
|
||||
operationId: listSpeechCredentials
|
||||
responses:
|
||||
@@ -2534,6 +2828,8 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: get a specific speech credential
|
||||
operationId: getSpeechCredential
|
||||
responses:
|
||||
@@ -2546,6 +2842,8 @@ paths:
|
||||
404:
|
||||
description: credential not found
|
||||
put:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: update a speech credential
|
||||
operationId: updateSpeechCredential
|
||||
requestBody:
|
||||
@@ -2565,6 +2863,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
delete:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: delete a speech credential
|
||||
operationId: deleteSpeechCredential
|
||||
responses:
|
||||
@@ -2574,6 +2874,8 @@ paths:
|
||||
description: credential not found
|
||||
/Accounts/{AccountSid}/SpeechCredentials/{SpeechCredentialSid}/test:
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: test a speech credential
|
||||
operationId: testSpeechCredential
|
||||
parameters:
|
||||
@@ -2687,6 +2989,8 @@ paths:
|
||||
- inbound
|
||||
- outbound
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve recent calls for an account
|
||||
operationId: listRecentCalls
|
||||
responses:
|
||||
@@ -2783,6 +3087,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve sip trace detail for a call
|
||||
operationId: getRecentCallTrace
|
||||
responses:
|
||||
@@ -2808,6 +3114,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: retrieve pcap for a call
|
||||
operationId: getRecentCallTrace
|
||||
responses:
|
||||
@@ -2880,6 +3188,8 @@ paths:
|
||||
- inbound
|
||||
- outbound
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: retrieve recent calls for an account
|
||||
operationId: listRecentCalls
|
||||
responses:
|
||||
@@ -2979,6 +3289,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: retrieve sip trace detail for a call
|
||||
operationId: getRecentCallTrace
|
||||
responses:
|
||||
@@ -3004,6 +3316,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve pcap for a call
|
||||
operationId: getRecentCallTrace
|
||||
responses:
|
||||
@@ -3076,6 +3390,8 @@ paths:
|
||||
- device-limit
|
||||
- api-limit
|
||||
get:
|
||||
tags:
|
||||
- Service Providers
|
||||
summary: retrieve alerts for a service provider
|
||||
operationId: listAlerts
|
||||
responses:
|
||||
@@ -3186,6 +3502,8 @@ paths:
|
||||
- device-limit
|
||||
- api-limit
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve alerts for an account
|
||||
operationId: listAlerts
|
||||
responses:
|
||||
@@ -3234,6 +3552,8 @@ paths:
|
||||
description: account not found
|
||||
/Applications:
|
||||
post:
|
||||
tags:
|
||||
- Applications
|
||||
summary: create application
|
||||
operationId: createApplication
|
||||
requestBody:
|
||||
@@ -3257,6 +3577,9 @@ paths:
|
||||
messaging_hook:
|
||||
$ref: '#/components/schemas/Webhook'
|
||||
description: application webhook to handle inbound SMS/MMS messages
|
||||
app_json:
|
||||
type: string
|
||||
description: Voice Application Json, call_hook will not be invoked if app_json is provided
|
||||
speech_synthesis_vendor:
|
||||
type: string
|
||||
speech_synthesis_voice:
|
||||
@@ -3296,6 +3619,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Applications
|
||||
summary: list applications
|
||||
operationId: listApplications
|
||||
responses:
|
||||
@@ -3324,6 +3649,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- Applications
|
||||
summary: delete an application
|
||||
operationId: deleteApplication
|
||||
responses:
|
||||
@@ -3344,6 +3671,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Applications
|
||||
summary: retrieve an application
|
||||
responses:
|
||||
200:
|
||||
@@ -3361,6 +3690,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
put:
|
||||
tags:
|
||||
- Applications
|
||||
summary: update application
|
||||
operationId: updateApplication
|
||||
requestBody:
|
||||
@@ -3389,6 +3720,8 @@ paths:
|
||||
|
||||
/Accounts/{AccountSid}/Calls:
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: create a call
|
||||
operationId: createCall
|
||||
parameters:
|
||||
@@ -3419,6 +3752,10 @@ paths:
|
||||
type: string
|
||||
description: The calling party number
|
||||
example: "16172375089"
|
||||
fromHost:
|
||||
type: string
|
||||
description: The hostname to put in the SIP From header of the INVITE
|
||||
example: "blf.finotel.com"
|
||||
timeout:
|
||||
type: integer
|
||||
description: the number of seconds to wait for call to be answered. Defaults to 60.
|
||||
@@ -3446,6 +3783,8 @@ paths:
|
||||
400:
|
||||
description: bad request
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: list calls
|
||||
operationId: listCalls
|
||||
parameters:
|
||||
@@ -3486,6 +3825,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: delete a call
|
||||
operationId: deleteCall
|
||||
responses:
|
||||
@@ -3506,6 +3847,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: retrieve a call
|
||||
operationId: getCall
|
||||
responses:
|
||||
@@ -3524,6 +3867,8 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: update a call
|
||||
operationId: updateCall
|
||||
requestBody:
|
||||
@@ -3609,6 +3954,8 @@ paths:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Accounts/{AccountSid}/Messages:
|
||||
post:
|
||||
tags:
|
||||
- Accounts
|
||||
summary: create an outgoing SMS message
|
||||
operationId: createMessage
|
||||
parameters:
|
||||
@@ -4130,6 +4477,8 @@ components:
|
||||
type: boolean
|
||||
stt_tested_ok:
|
||||
type: boolean
|
||||
riva_server_uri:
|
||||
type: string
|
||||
SpeechCredentialUpdate:
|
||||
properties:
|
||||
use_for_tts:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const formData = require('form-data');
|
||||
const Mailgun = require('mailgun.js');
|
||||
const mailgun = new Mailgun(formData);
|
||||
const bent = require('bent');
|
||||
const validateEmail = (email) => {
|
||||
// eslint-disable-next-line max-len
|
||||
const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
@@ -8,6 +9,44 @@ const validateEmail = (email) => {
|
||||
};
|
||||
|
||||
const emailSimpleText = async(logger, to, subject, text) => {
|
||||
const from = 'jambonz Support <support@jambonz.org>';
|
||||
if (process.env.CUSTOM_EMAIL_VENDOR_URL) {
|
||||
await sendEmailByCustomVendor(logger, from, to, subject, text);
|
||||
} else {
|
||||
await sendEmailByMailgun(logger, from, to, subject, text);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const sendEmailByCustomVendor = async(logger, from, to, subject, text) => {
|
||||
try {
|
||||
const post = bent('POST', {
|
||||
'Content-Type': 'application/json',
|
||||
...((process.env.CUSTOM_EMAIL_VENDOR_USERNAME && process.env.CUSTOM_EMAIL_VENDOR_PASSWORD) &&
|
||||
({
|
||||
'Authorization':`Basic ${Buffer.from(
|
||||
`${process.env.CUSTOM_EMAIL_VENDOR_USERNAME}:${process.env.CUSTOM_EMAIL_VENDOR_PASSWORD}`
|
||||
).toString('base64')}`
|
||||
}))
|
||||
});
|
||||
|
||||
const res = await post(process.env.CUSTOM_EMAIL_VENDOR_URL, {
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
text
|
||||
});
|
||||
logger.debug({
|
||||
res
|
||||
}, 'sent email to custom vendor.');
|
||||
} catch (err) {
|
||||
logger.info({
|
||||
err
|
||||
}, 'Error sending email From Custom email vendor');
|
||||
}
|
||||
};
|
||||
|
||||
const sendEmailByMailgun = async(logger, from, to, subject, text) => {
|
||||
const mg = mailgun.client({
|
||||
username: 'api',
|
||||
key: process.env.MAILGUN_API_KEY
|
||||
@@ -17,14 +56,18 @@ const emailSimpleText = async(logger, to, subject, text) => {
|
||||
|
||||
try {
|
||||
const res = await mg.messages.create(process.env.MAILGUN_DOMAIN, {
|
||||
from: 'jambonz Support <support@jambonz.org>',
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
text
|
||||
});
|
||||
logger.debug({res}, 'sent email');
|
||||
logger.debug({
|
||||
res
|
||||
}, 'sent email');
|
||||
} catch (err) {
|
||||
logger.info({err}, 'Error sending email');
|
||||
logger.info({
|
||||
err
|
||||
}, 'Error sending email From mailgun');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ const crypto = require('crypto');
|
||||
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))
|
||||
.update(process.env.ENCRYPTION_SECRET || process.env.JWT_SECRET)
|
||||
.digest('base64')
|
||||
.substr(0, 32);
|
||||
.substring(0, 32);
|
||||
|
||||
const encrypt = (text) => {
|
||||
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
class BadRequestError extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
class DbError extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
@@ -23,6 +29,7 @@ class DbErrorForbidden extends DbError {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BadRequestError,
|
||||
DbError,
|
||||
DbErrorBadRequest,
|
||||
DbErrorUnprocessableRequest,
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
const crypto = require('crypto');
|
||||
const { argon2i } = require('argon2-ffi');
|
||||
const argon2 = require('argon2');
|
||||
const util = require('util');
|
||||
|
||||
const { argon2i } = argon2;
|
||||
|
||||
const getRandomBytes = util.promisify(crypto.randomBytes);
|
||||
|
||||
const generateHashedPassword = async(password) => {
|
||||
const salt = await getRandomBytes(32);
|
||||
const passwordHash = await argon2i.hash(password, salt);
|
||||
const passwordHash = await argon2.hash(password, { type: argon2i, salt });
|
||||
return passwordHash;
|
||||
};
|
||||
|
||||
const verifyPassword = async(passwordHash, password) => {
|
||||
const isCorrect = await argon2i.verify(passwordHash, password);
|
||||
return isCorrect;
|
||||
const verifyPassword = (passwordHash, password) => {
|
||||
return argon2.verify(passwordHash, password);
|
||||
};
|
||||
|
||||
const hashString = (s) => crypto.createHash('md5').update(s).digest('hex');
|
||||
|
||||
@@ -1,13 +1,41 @@
|
||||
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 { TranscribeClient, ListVocabulariesCommand } = require('@aws-sdk/client-transcribe');
|
||||
const { Deepgram } = require('@deepgram/sdk');
|
||||
const sdk = require('microsoft-cognitiveservices-speech-sdk');
|
||||
const { SpeechClient } = require('@soniox/soniox-node');
|
||||
const bent = require('bent');
|
||||
const fs = require('fs');
|
||||
|
||||
const testGoogleTts = async(logger, credentials) => {
|
||||
const client = new ttsGoogle.TextToSpeechClient({credentials});
|
||||
await client.listVoices();
|
||||
|
||||
const testSonioxStt = async(logger, credentials) => {
|
||||
const api_key = credentials;
|
||||
const soniox = new SpeechClient(api_key);
|
||||
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
const result = await soniox.transcribeFileShort('data/test_audio.wav');
|
||||
if (result.words.length > 0) resolve(result);
|
||||
else reject(new Error('no transcript returned'));
|
||||
} catch (error) {
|
||||
logger.info({error}, 'failed to get soniox transcript');
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const testNuanceTts = async(logger, getTtsVoices, credentials) => {
|
||||
const voices = await getTtsVoices({vendor: 'nuance', credentials});
|
||||
return voices;
|
||||
};
|
||||
const testNuanceStt = async(logger, credentials) => {
|
||||
//TODO
|
||||
return true;
|
||||
};
|
||||
|
||||
const testGoogleTts = async(logger, getTtsVoices, credentials) => {
|
||||
const voices = await getTtsVoices({vendor: 'google', credentials});
|
||||
return voices;
|
||||
|
||||
};
|
||||
|
||||
const testGoogleStt = async(logger, credentials) => {
|
||||
@@ -32,25 +60,92 @@ const testGoogleStt = async(logger, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testAwsTts = (logger, credentials) => {
|
||||
const polly = new Polly(credentials);
|
||||
const testDeepgramStt = async(logger, credentials) => {
|
||||
const {api_key} = credentials;
|
||||
const deepgram = new Deepgram(api_key);
|
||||
|
||||
const mimetype = 'audio/wav';
|
||||
const source = {
|
||||
buffer: fs.readFileSync(`${__dirname}/../../data/test_audio.wav`),
|
||||
mimetype: mimetype
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
polly.describeVoices({LanguageCode: 'en-US'}, (err, data) => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
// Send the audio to Deepgram and get the response
|
||||
deepgram.transcription
|
||||
.preRecorded(source, {punctuate: true})
|
||||
.then((response) => {
|
||||
//logger.debug({response}, 'got transcript');
|
||||
if (response?.results?.channels[0]?.alternatives?.length > 0) resolve(response);
|
||||
else reject(new Error('no transcript returned'));
|
||||
return;
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.info({err}, 'failed to get deepgram transcript');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const testMicrosoftStt = async(logger, credentials) => {
|
||||
const {api_key, region} = credentials;
|
||||
|
||||
const speechConfig = sdk.SpeechConfig.fromSubscription(api_key, region);
|
||||
const audioConfig = sdk.AudioConfig.fromWavFileInput(fs.readFileSync(`${__dirname}/../../data/test_audio.wav`));
|
||||
speechConfig.speechRecognitionLanguage = 'en-US';
|
||||
const speechRecognizer = new sdk.SpeechRecognizer(speechConfig, audioConfig);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
speechRecognizer.recognizeOnceAsync((result) => {
|
||||
switch (result.reason) {
|
||||
case sdk.ResultReason.RecognizedSpeech:
|
||||
resolve();
|
||||
break;
|
||||
case sdk.ResultReason.NoMatch:
|
||||
reject('Speech could not be recognized.');
|
||||
break;
|
||||
case sdk.ResultReason.Canceled:
|
||||
const cancellation = sdk.CancellationDetails.fromResult(result);
|
||||
logger.info(`CANCELED: Reason=${cancellation.reason}`);
|
||||
if (cancellation.reason == sdk.CancellationReason.Error) {
|
||||
logger.info(`CANCELED: ErrorCode=${cancellation.ErrorCode}`);
|
||||
logger.info(`CANCELED: ErrorDetails=${cancellation.errorDetails}`);
|
||||
}
|
||||
reject(cancellation.reason);
|
||||
break;
|
||||
}
|
||||
speechRecognizer.close();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const testAwsStt = (logger, credentials) => {
|
||||
const transcribeservice = new AWS.TranscribeService(credentials);
|
||||
return new Promise((resolve, reject) => {
|
||||
transcribeservice.listVocabularies((err, data) => {
|
||||
if (err) return reject(err);
|
||||
logger.info({data}, 'retrieved language models');
|
||||
resolve();
|
||||
const testAwsTts = async(logger, getTtsVoices, credentials) => {
|
||||
try {
|
||||
const voices = await getTtsVoices({vendor: 'aws', credentials});
|
||||
return voices;
|
||||
} catch (err) {
|
||||
logger.info({err}, 'testMicrosoftTts - failed to list voices for region ${region}');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const testAwsStt = async(logger, credentials) => {
|
||||
try {
|
||||
const {region, accessKeyId, secretAccessKey} = credentials;
|
||||
const client = new TranscribeClient({
|
||||
region,
|
||||
credentials: {
|
||||
accessKeyId,
|
||||
secretAccessKey
|
||||
}
|
||||
});
|
||||
});
|
||||
const command = new ListVocabulariesCommand({});
|
||||
const response = await client.send(command);
|
||||
return response;
|
||||
} catch (err) {
|
||||
logger.info({err}, 'testMicrosoftTts - failed to list voices for region ${region}');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const testMicrosoftTts = async(logger, credentials) => {
|
||||
@@ -89,11 +184,6 @@ const testMicrosoftTts = async(logger, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testMicrosoftStt = async(logger, credentials) => {
|
||||
//TODO
|
||||
return true;
|
||||
};
|
||||
|
||||
const testWellSaidTts = async(logger, credentials) => {
|
||||
const {api_key} = credentials;
|
||||
try {
|
||||
@@ -113,6 +203,35 @@ const testWellSaidTts = async(logger, credentials) => {
|
||||
}
|
||||
};
|
||||
|
||||
const testIbmTts = async(logger, getTtsVoices, credentials) => {
|
||||
const {tts_api_key, tts_region} = credentials;
|
||||
const voices = await getTtsVoices({vendor: 'ibm', credentials: {tts_api_key, tts_region}});
|
||||
return voices;
|
||||
};
|
||||
|
||||
const testIbmStt = async(logger, credentials) => {
|
||||
const {stt_api_key, stt_region} = credentials;
|
||||
const SpeechToTextV1 = require('ibm-watson/speech-to-text/v1');
|
||||
const { IamAuthenticator } = require('ibm-watson/auth');
|
||||
const speechToText = new SpeechToTextV1({
|
||||
authenticator: new IamAuthenticator({
|
||||
apikey: stt_api_key
|
||||
}),
|
||||
serviceUrl: `https://api.${stt_region}.speech-to-text.watson.cloud.ibm.com`
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
speechToText.listModels()
|
||||
.then((speechModels) => {
|
||||
logger.debug({speechModels}, 'got IBM speech models');
|
||||
return resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.info({err}, 'failed to get speech models');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const testWellSaidStt = async(logger, credentials) => {
|
||||
//TODO
|
||||
return true;
|
||||
@@ -127,4 +246,10 @@ module.exports = {
|
||||
testMicrosoftTts,
|
||||
testMicrosoftStt,
|
||||
testWellSaidStt,
|
||||
testNuanceTts,
|
||||
testNuanceStt,
|
||||
testDeepgramStt,
|
||||
testIbmTts,
|
||||
testIbmStt,
|
||||
testSonioxStt
|
||||
};
|
||||
|
||||
6690
package-lock.json
generated
6690
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jambonz-api-server",
|
||||
"version": "v0.7.7",
|
||||
"version": "v0.8.2",
|
||||
"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 K8S=true K8S_FEATURE_SERVER_SERVICE_NAME=127.0.0.1 K8S_FEATURE_SERVER_SERVICE_PORT=3100 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",
|
||||
"integration-test": "NODE_ENV=test JAMBONES_AUTH_USE_JWT=1 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",
|
||||
@@ -18,23 +18,27 @@
|
||||
"url": "https://github.com/jambonz/jambonz-api-server.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/speech": "^4.10.2",
|
||||
"@google-cloud/text-to-speech": "^3.4.0",
|
||||
"@jambonz/db-helpers": "^0.6.18",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.29",
|
||||
"@aws-sdk/client-transcribe": "^3.290.0",
|
||||
"@deepgram/sdk": "^1.10.2",
|
||||
"@google-cloud/speech": "^5.1.0",
|
||||
"@jambonz/db-helpers": "^0.7.3",
|
||||
"@jambonz/realtimedb-helpers": "^0.7.0",
|
||||
"@jambonz/speech-utils": "^0.0.8",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"argon2-ffi": "^2.0.0",
|
||||
"aws-sdk": "^2.1152.0",
|
||||
"@jambonz/verb-specifications": "^0.0.3",
|
||||
"@soniox/soniox-node": "^1.1.0",
|
||||
"argon2": "^0.30.3",
|
||||
"bent": "^7.3.12",
|
||||
"cors": "^2.8.5",
|
||||
"debug": "^4.3.4",
|
||||
"express": "^4.18.1",
|
||||
"express-rate-limit": "^6.4.0",
|
||||
"form-data": "^2.5.1",
|
||||
"form-urlencoded": "^6.1.0",
|
||||
"helmet": "^5.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"ibm-watson": "^7.1.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"mailgun.js": "^3.7.3",
|
||||
"microsoft-cognitiveservices-speech-sdk": "^1.24.1",
|
||||
"mysql2": "^2.3.3",
|
||||
"passport": "^0.6.0",
|
||||
"passport-http-bearer": "^1.0.1",
|
||||
|
||||
@@ -219,6 +219,21 @@ test('account tests', async(t) => {
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully updated a call session limit to an account');
|
||||
|
||||
/* try to update an existing limit for an account giving a invalid sid */
|
||||
try {
|
||||
result = await request.post(`/Accounts/invalid-sid/Limits`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
category: 'voice_call_session',
|
||||
quantity: 205
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
t.ok(err.statusCode === 400, 'returns 400 bad request if sid param is not a valid uuid');
|
||||
}
|
||||
|
||||
/* query all limits for an account */
|
||||
result = await request.get(`/Accounts/${sid}/Limits`, {
|
||||
auth: authAdmin,
|
||||
@@ -232,7 +247,7 @@ test('account tests', async(t) => {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
//console.log(result);
|
||||
// console.log(result);
|
||||
t.ok(result.length === 1 && result[0].quantity === 205, 'successfully queried account limits by category');
|
||||
|
||||
/* delete call session limits for a service provider */
|
||||
|
||||
@@ -23,6 +23,37 @@ test('application tests', async(t) => {
|
||||
const service_provider_sid = await createServiceProvider(request);
|
||||
const phone_number_sid = await createPhoneNumber(request, voip_carrier_sid);
|
||||
const account_sid = await createAccount(request, service_provider_sid);
|
||||
|
||||
/* add an invalid application app_json */
|
||||
result = await request.post('/Applications', {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
name: 'daveh',
|
||||
account_sid,
|
||||
call_hook: {
|
||||
url: 'http://example.com'
|
||||
},
|
||||
call_status_hook: {
|
||||
url: 'http://example.com/status',
|
||||
method: 'POST'
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example.com/sms'
|
||||
},
|
||||
app_json : '[\
|
||||
{\
|
||||
"verb": "play",\
|
||||
"timeoutSecs": 10,\
|
||||
"seekOffset": 8000,\
|
||||
"actionHook": "/play/action"\
|
||||
}\
|
||||
]'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 400, 'Cant create application with invalid app_josn');
|
||||
|
||||
/* add an application */
|
||||
result = await request.post('/Applications', {
|
||||
@@ -41,7 +72,16 @@ test('application tests', async(t) => {
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example.com/sms'
|
||||
}
|
||||
},
|
||||
app_json : '[\
|
||||
{\
|
||||
"verb": "play",\
|
||||
"url": "https://example.com/example.mp3",\
|
||||
"timeoutSecs": 10,\
|
||||
"seekOffset": 8000,\
|
||||
"actionHook": "/play/action"\
|
||||
}\
|
||||
]'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created application');
|
||||
@@ -62,6 +102,9 @@ test('application tests', async(t) => {
|
||||
});
|
||||
t.ok(result.name === 'daveh' , 'successfully retrieved application by sid');
|
||||
t.ok(result.messaging_hook.url === 'http://example.com/sms' , 'successfully retrieved messaging_hook from application');
|
||||
let app_json = JSON.parse(result.app_json);
|
||||
t.ok(app_json[0].verb === 'play', 'successfully retrieved app_json from application')
|
||||
|
||||
|
||||
/* update applications */
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
@@ -74,7 +117,15 @@ test('application tests', async(t) => {
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example2.com/mms'
|
||||
}
|
||||
},
|
||||
app_json : '[\
|
||||
{\
|
||||
"verb": "hangup",\
|
||||
"headers": {\
|
||||
"X-Reason" : "maximum call duration exceeded"\
|
||||
}\
|
||||
}\
|
||||
]'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated application');
|
||||
@@ -85,6 +136,57 @@ test('application tests', async(t) => {
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.messaging_hook.url === 'http://example2.com/mms' , 'successfully updated messaging_hook');
|
||||
app_json = JSON.parse(result.app_json);
|
||||
t.ok(app_json[0].verb === 'hangup', 'successfully updated app_json from application')
|
||||
|
||||
/* remove applications app_json*/
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
call_hook: {
|
||||
url: 'http://example2.com'
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example2.com/mms'
|
||||
},
|
||||
app_json : null
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated application');
|
||||
|
||||
/* validate messaging hook was updated */
|
||||
result = await request.get(`/Applications/${sid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.app_json == undefined, 'successfully removed app_json from application')
|
||||
|
||||
/* Update invalid applications app_json*/
|
||||
result = await request.put(`/Applications/${sid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
body: {
|
||||
call_hook: {
|
||||
url: 'http://example2.com'
|
||||
},
|
||||
messaging_hook: {
|
||||
url: 'http://example2.com/mms'
|
||||
},
|
||||
app_json : '[\
|
||||
{\
|
||||
"verb": "play",\
|
||||
"timeoutSecs": 10,\
|
||||
"seekOffset": 8000,\
|
||||
"actionHook": "/play/action"\
|
||||
}\
|
||||
]'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 400, 'Cant update invalid application app_json');
|
||||
|
||||
/* assign phone number to application */
|
||||
result = await request.put(`/PhoneNumbers/${phone_number_sid}`, {
|
||||
|
||||
@@ -18,7 +18,9 @@ test('Create Call Success With Synthesizer in Payload', async (t) => {
|
||||
const service_provider_sid = await createServiceProvider(request, 'account_has_synthesizer');
|
||||
const account_sid = await createAccount(request, service_provider_sid, 'account_has_synthesizer');
|
||||
const token = jwt.sign({
|
||||
account_sid
|
||||
account_sid,
|
||||
scope: "account",
|
||||
permissions: ["PROVISION_USERS", "PROVISION_SERVICES", "VIEW_ONLY"]
|
||||
}, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
const authUser = { bearer: token };
|
||||
const speech_sid = await createGoogleSpeechCredentials(request, account_sid, null, authUser, true, true)
|
||||
@@ -58,7 +60,9 @@ test('Create Call Success Without Synthesizer in Payload', async (t) => {
|
||||
const service_provider_sid = await createServiceProvider(request, 'account2_has_synthesizer');
|
||||
const account_sid = await createAccount(request, service_provider_sid, 'account2_has_synthesizer');
|
||||
const token = jwt.sign({
|
||||
account_sid
|
||||
account_sid,
|
||||
scope: "account",
|
||||
permissions: ["PROVISION_USERS", "PROVISION_SERVICES", "VIEW_ONLY"]
|
||||
}, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
const authUser = { bearer: token };
|
||||
const speech_sid = await createGoogleSpeechCredentials(request, account_sid, null, authUser, true, true)
|
||||
|
||||
@@ -133,4 +133,14 @@ services:
|
||||
- "3100:3000/tcp"
|
||||
networks:
|
||||
jambonz-api:
|
||||
ipv4_address: 172.58.0.9
|
||||
ipv4_address: 172.58.0.9
|
||||
|
||||
webhook-tts-scaffold:
|
||||
image: jambonz/webhook-tts-test-scaffold:latest
|
||||
ports:
|
||||
- "3101:3000/tcp"
|
||||
volumes:
|
||||
- ./test-apps:/tmp
|
||||
networks:
|
||||
jambonz-api:
|
||||
ipv4_address: 172.58.0.10
|
||||
29
test/email_utils.js
Normal file
29
test/email_utils.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const test = require('tape');
|
||||
const {emailSimpleText} = require('../lib/utils/email-utils');
|
||||
const bent = require('bent');
|
||||
const getJSON = bent('json')
|
||||
const logger = {
|
||||
debug: () =>{},
|
||||
info: () => {}
|
||||
}
|
||||
|
||||
test('email-test', async(t) => {
|
||||
// Prepare env:
|
||||
process.env.CUSTOM_EMAIL_VENDOR_URL = 'http://127.0.0.1:3101/custom_email_vendor';
|
||||
process.env.CUSTOM_EMAIL_VENDOR_USERNAME = 'USERNAME';
|
||||
process.env.CUSTOM_EMAIL_VENDOR_PASSWORD = 'PASSWORD';
|
||||
|
||||
await emailSimpleText(logger, 'test@gmail.com', 'subject', 'body text');
|
||||
|
||||
const obj = await getJSON(`http://127.0.0.1:3101/lastRequest/custom_email_vendor`);
|
||||
t.ok(obj.headers['Content-Type'] == 'application/json');
|
||||
t.ok(obj.headers.Authorization == 'Basic VVNFUk5BTUU6UEFTU1dPUkQ=');
|
||||
t.ok(obj.body.from == 'jambonz Support <support@jambonz.org>');
|
||||
t.ok(obj.body.to == 'test@gmail.com');
|
||||
t.ok(obj.body.subject == 'subject');
|
||||
t.ok(obj.body.text == 'body text');
|
||||
|
||||
process.env.CUSTOM_EMAIL_VENDOR_URL = null;
|
||||
process.env.CUSTOM_EMAIL_VENDOR_USERNAME = null;
|
||||
process.env.CUSTOM_EMAIL_VENDOR_PASSWORD = null;
|
||||
});
|
||||
403
test/feature-server-test-scaffold/package-lock.json
generated
403
test/feature-server-test-scaffold/package-lock.json
generated
@@ -9,17 +9,17 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"express": "^4.17.3",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -31,39 +31,39 @@
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"version": "1.19.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
|
||||
"integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.0",
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"http-errors": "1.8.1",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
"qs": "6.9.7",
|
||||
"raw-body": "2.4.3",
|
||||
"type-is": "~1.6.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.1.2"
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -78,9 +78,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -101,7 +101,7 @@
|
||||
"node_modules/depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
|
||||
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -109,7 +109,7 @@
|
||||
"node_modules/destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
"integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg=="
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
@@ -132,22 +132,22 @@
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.17.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||
"version": "4.17.3",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz",
|
||||
"integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.7",
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"body-parser": "1.19.2",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie": "0.4.2",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
@@ -161,13 +161,13 @@
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.9.7",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.17.2",
|
||||
"serve-static": "1.14.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
@@ -195,9 +195,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -205,21 +205,21 @@
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
|
||||
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
|
||||
"dependencies": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -237,9 +237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
@@ -252,7 +252,7 @@
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -282,19 +282,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.45.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
|
||||
"integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==",
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.28",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz",
|
||||
"integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==",
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.45.0"
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -306,9 +306,9 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -338,11 +338,11 @@
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dependencies": {
|
||||
"forwarded": "~0.1.2",
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -350,11 +350,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
|
||||
"version": "6.9.7",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
|
||||
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
@@ -366,12 +369,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz",
|
||||
"integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "1.8.1",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
@@ -380,9 +383,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -390,9 +407,9 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"version": "0.17.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
|
||||
"integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
@@ -401,9 +418,9 @@
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"http-errors": "1.8.1",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
@@ -413,28 +430,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"version": "1.14.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
|
||||
"integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
|
||||
"dependencies": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
"send": "0.17.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "1.5.0",
|
||||
@@ -445,9 +462,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
@@ -499,12 +516,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
@@ -513,33 +530,33 @@
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"version": "1.19.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
|
||||
"integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"http-errors": "1.8.1",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
"qs": "6.9.7",
|
||||
"raw-body": "2.4.3",
|
||||
"type-is": "~1.6.18"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
"safe-buffer": "5.2.1"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
@@ -548,9 +565,9 @@
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
@@ -568,12 +585,12 @@
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
"integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg=="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
@@ -593,19 +610,19 @@
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.17.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||
"version": "4.17.3",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz",
|
||||
"integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.7",
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"body-parser": "1.19.2",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie": "0.4.2",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
@@ -619,13 +636,13 @@
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.9.7",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.17.2",
|
||||
"serve-static": "1.14.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
@@ -647,25 +664,25 @@
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
|
||||
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
"toidentifier": "1.0.1"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
@@ -677,9 +694,9 @@
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
@@ -689,7 +706,7 @@
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
@@ -707,16 +724,16 @@
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.45.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
|
||||
"integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w=="
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.28",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz",
|
||||
"integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==",
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"requires": {
|
||||
"mime-db": "1.45.0"
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
@@ -725,9 +742,9 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
@@ -748,18 +765,18 @@
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
"version": "6.9.7",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
|
||||
"integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
@@ -767,20 +784,20 @@
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz",
|
||||
"integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "1.8.1",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -788,9 +805,9 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"version": "0.17.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
|
||||
"integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
@@ -799,36 +816,36 @@
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"http-errors": "1.8.1",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"version": "1.14.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
|
||||
"integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
"send": "0.17.2"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
@@ -836,9 +853,9 @@
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"author": "Dave Horton",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"express": "^4.17.3",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ require('./sbcs');
|
||||
require('./ms-teams');
|
||||
require('./speech-credentials');
|
||||
require('./recent-calls');
|
||||
require('./users');
|
||||
require('./webapp_tests');
|
||||
// require('./homer');
|
||||
require('./call-test');
|
||||
require('./password-settings');
|
||||
require('./email_utils');
|
||||
require('./docker_stop');
|
||||
|
||||
71
test/password-settings.js
Normal file
71
test/password-settings.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const test = require('tape') ;
|
||||
const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de';
|
||||
const authAdmin = {bearer: ADMIN_TOKEN};
|
||||
const request = require('request-promise-native').defaults({
|
||||
baseUrl: 'http://127.0.0.1:3000/v1'
|
||||
});
|
||||
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
test('password settings tests', async(t) => {
|
||||
|
||||
/* Check Default Password Settings */
|
||||
result = await request.get('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.min_password_length == 8 &&
|
||||
!result.require_digit &&
|
||||
!result.require_special_character, "default password settings is correct!")
|
||||
|
||||
/* Post New Password settings*/
|
||||
|
||||
result = await request.post('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
min_password_length: 15,
|
||||
require_digit: 1,
|
||||
require_special_character: 1
|
||||
}
|
||||
});
|
||||
|
||||
t.ok(result.statusCode === 201, 'successfully added a password settings');
|
||||
|
||||
/* Check Password Settings*/
|
||||
result = await request.get('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
|
||||
t.ok(result.min_password_length === 15 &&
|
||||
result.require_digit === 1 &&
|
||||
result.require_special_character === 1, 'successfully queried password settings');
|
||||
|
||||
/* Update Password settings*/
|
||||
result = await request.post('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
min_password_length: 10,
|
||||
require_special_character: 0
|
||||
}
|
||||
});
|
||||
|
||||
t.ok(result.statusCode === 201, 'successfully updated a password settings');
|
||||
|
||||
/* Check Password Settings After update*/
|
||||
result = await request.get('/PasswordSettings', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
|
||||
t.ok(result.min_password_length === 10 &&
|
||||
result.require_digit === 1 &&
|
||||
result.require_special_character === 0, 'successfully queried password settings after updated');
|
||||
});
|
||||
@@ -24,12 +24,16 @@ test('recent calls tests', async(t) => {
|
||||
const account_sid = await createAccount(request, service_provider_sid);
|
||||
|
||||
const token = jwt.sign({
|
||||
account_sid
|
||||
account_sid,
|
||||
scope: "account",
|
||||
permissions: ["PROVISION_USERS", "PROVISION_SERVICES", "VIEW_ONLY"]
|
||||
}, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
const authUser = {bearer: token};
|
||||
|
||||
const tokenSP = jwt.sign({
|
||||
service_provider_sid
|
||||
service_provider_sid,
|
||||
scope: "account",
|
||||
permissions: ["PROVISION_USERS", "PROVISION_SERVICES", "VIEW_ONLY"]
|
||||
}, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
const authUserSP = {bearer: token};
|
||||
|
||||
|
||||
@@ -79,13 +79,14 @@ test('service provider tests', async(t) => {
|
||||
}
|
||||
});
|
||||
//console.log(`result: ${JSON.stringify(result)}`);
|
||||
t.ok(result.statusCode === 422, 'cannot add two service providers with the same name');
|
||||
t.ok(result.statusCode === 422, 'cannot add two service providers with the same root domain');
|
||||
|
||||
/* query all service providers */
|
||||
result = await request.get('/ServiceProviders', {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
//console.log(JSON.stringify(result));
|
||||
t.ok(result.length === 2 , 'successfully queried all service providers');
|
||||
|
||||
/* query one service providers */
|
||||
@@ -106,6 +107,20 @@ test('service provider tests', async(t) => {
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated service provider');
|
||||
|
||||
/* try to update service providers with invalid sid format*/
|
||||
try {
|
||||
result = await request.put(`/ServiceProviders/123`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
name: 'robb'
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
t.ok(err.statusCode === 400, 'returns 400 bad request if sid param is not a valid uuid');
|
||||
}
|
||||
|
||||
/* add an api key for a service provider */
|
||||
result = await request.post(`/ApiKeys`, {
|
||||
auth: authAdmin,
|
||||
|
||||
@@ -22,6 +22,24 @@ test('speech credentials tests', async(t) => {
|
||||
const service_provider_sid = await createServiceProvider(request);
|
||||
const account_sid = await createAccount(request, service_provider_sid);
|
||||
|
||||
/* return 400 if invalid sid param is used */
|
||||
try {
|
||||
result = await request.post(`/ServiceProviders/foobarbaz/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'google',
|
||||
service_key: jsonKey,
|
||||
use_for_tts: true,
|
||||
use_for_stt: true
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
t.ok(err.statusCode === 400, 'returns 400 bad request if sid param is not a valid uuid');
|
||||
}
|
||||
|
||||
/* add a speech credential to a service provider */
|
||||
result = await request.post(`/ServiceProviders/${service_provider_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
@@ -30,7 +48,9 @@ test('speech credentials tests', async(t) => {
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'google',
|
||||
service_key: jsonKey
|
||||
service_key: jsonKey,
|
||||
use_for_tts: true,
|
||||
use_for_stt: true
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added a speech credential to service provider');
|
||||
@@ -50,7 +70,9 @@ test('speech credentials tests', async(t) => {
|
||||
await deleteObjectBySid(request, `/ServiceProviders/${service_provider_sid}/SpeechCredentials`, speech_credential_sid);
|
||||
|
||||
const token = jwt.sign({
|
||||
account_sid
|
||||
account_sid,
|
||||
scope: 'account',
|
||||
permissions: ["PROVISION_USERS", "PROVISION_SERVICES", "VIEW_ONLY"]
|
||||
}, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
const authUser = {bearer: token};
|
||||
|
||||
@@ -61,14 +83,16 @@ test('speech credentials tests', async(t) => {
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'google',
|
||||
service_key: jsonKey
|
||||
service_key: jsonKey,
|
||||
use_for_tts: true,
|
||||
use_for_stt: true
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential');
|
||||
const sid1 = result.body.sid;
|
||||
|
||||
/* return 403 if invalid account is used */
|
||||
result = await request.post(`/Accounts/foobarbaz/SpeechCredentials`, {
|
||||
/* return 403 if invalid account is used - randomSid: bed7ae17-f8b4-4b74-9e5b-4f6318aae9c9 */
|
||||
result = await request.post(`/Accounts/bed7ae17-f8b4-4b74-9e5b-4f6318aae9c9/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
auth: authUser,
|
||||
@@ -110,20 +134,20 @@ 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) {
|
||||
/* add / test a credential for google */
|
||||
if (process.env.GCP_JSON_KEY) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'microsoft',
|
||||
vendor: 'google',
|
||||
use_for_tts: true,
|
||||
api_key: process.env.MICROSOFT_API_KEY,
|
||||
region: process.env.MICROSOFT_REGION
|
||||
use_for_stt: true,
|
||||
service_key: process.env.GCP_JSON_KEY
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential');
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for google');
|
||||
const ms_sid = result.body.sid;
|
||||
|
||||
/* test the speech credential */
|
||||
@@ -132,7 +156,66 @@ test('speech credentials tests', async(t) => {
|
||||
auth: authUser,
|
||||
json: true,
|
||||
});
|
||||
console.log(JSON.stringify(result));
|
||||
//console.log(JSON.stringify(result));
|
||||
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for google tts');
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for google stt');
|
||||
}
|
||||
|
||||
/* add / test 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,
|
||||
use_for_stt: true,
|
||||
api_key: process.env.MICROSOFT_API_KEY,
|
||||
region: process.env.MICROSOFT_REGION
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for microsoft');
|
||||
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));
|
||||
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for microsoft tts');
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for microsoft stt');
|
||||
}
|
||||
|
||||
/* add / test a credential for AWS */
|
||||
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY && process.env.AWS_REGION) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'aws',
|
||||
use_for_tts: true,
|
||||
use_for_stt: true,
|
||||
access_key_id: process.env.AWS_ACCESS_KEY_ID,
|
||||
secret_access_key: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
aws_region: process.env.AWS_REGION
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for AWS');
|
||||
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));
|
||||
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for AWS tts');
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for AWS stt');
|
||||
}
|
||||
|
||||
/* add a credential for wellsaid */
|
||||
@@ -156,7 +239,8 @@ test('speech credentials tests', async(t) => {
|
||||
auth: authUser,
|
||||
json: true,
|
||||
});
|
||||
console.log(JSON.stringify(result));
|
||||
//console.log(JSON.stringify(result));
|
||||
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for wellsaid');
|
||||
|
||||
/* delete the credential */
|
||||
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${ms_sid}`, {
|
||||
@@ -166,9 +250,194 @@ test('speech credentials tests', async(t) => {
|
||||
t.ok(result.statusCode === 204, 'successfully deleted speech credential');
|
||||
}
|
||||
|
||||
/* add a credential for deepgram */
|
||||
if (process.env.DEEPGRAM_API_KEY) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'deepgram',
|
||||
use_for_stt: true,
|
||||
api_key: process.env.DEEPGRAM_API_KEY
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for deepgram');
|
||||
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));
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for deepgram');
|
||||
|
||||
/* 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');
|
||||
}
|
||||
/* add a credential for ibm tts */
|
||||
if (process.env.IBM_TTS_API_KEY && process.env.IBM_TTS_REGION) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'ibm',
|
||||
use_for_tts: true,
|
||||
tts_api_key: process.env.IBM_TTS_API_KEY,
|
||||
tts_region: process.env.IBM_TTS_REGION
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for ibm');
|
||||
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));
|
||||
t.ok(result.statusCode === 200 && result.body.tts.status === 'ok', 'successfully tested speech credential for ibm tts');
|
||||
|
||||
/* 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');
|
||||
}
|
||||
|
||||
/* add a credential for ibm stt */
|
||||
if (process.env.IBM_STT_API_KEY && process.env.IBM_STT_REGION) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'ibm',
|
||||
use_for_stt: true,
|
||||
stt_api_key: process.env.IBM_STT_API_KEY,
|
||||
stt_region: process.env.IBM_STT_REGION
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for ibm');
|
||||
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));
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for ibm stt');
|
||||
|
||||
/* 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');
|
||||
}
|
||||
|
||||
/* add a credential for Siniox */
|
||||
if (process.env.SONIOX_API_KEY) {
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'soniox',
|
||||
use_for_stt: true,
|
||||
api_key: process.env.SONIOX_API_KEY
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for soniox');
|
||||
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));
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'ok', 'successfully tested speech credential for soniox');
|
||||
|
||||
/* 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');
|
||||
}
|
||||
|
||||
/* add a credential for nvidia */
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'nvidia',
|
||||
use_for_stt: true,
|
||||
use_for_tts: true,
|
||||
riva_server_uri: "192.168.1.2:5060"
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for nvidia');
|
||||
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,
|
||||
});
|
||||
// TODO Nvidia test.
|
||||
t.ok(result.statusCode === 200 && result.body.stt.status === 'not tested', 'successfully tested speech credential for nvida stt');
|
||||
|
||||
/* 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');
|
||||
|
||||
/* add a credential for nuance */
|
||||
result = await request.post(`/Accounts/${account_sid}/SpeechCredentials`, {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authUser,
|
||||
json: true,
|
||||
body: {
|
||||
vendor: 'nuance',
|
||||
use_for_stt: true,
|
||||
use_for_tts: true,
|
||||
client_id: 'client_id',
|
||||
secret: 'secret',
|
||||
nuance_tts_uri: "192.168.1.2:5060",
|
||||
nuance_stt_uri: "192.168.1.2:5061"
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully added speech credential for nuance');
|
||||
const nuance_sid = result.body.sid;
|
||||
|
||||
/* delete the credential */
|
||||
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${nuance_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();
|
||||
t.end();
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
|
||||
220
test/users.js
Normal file
220
test/users.js
Normal file
@@ -0,0 +1,220 @@
|
||||
const test = require('tape') ;
|
||||
const jwt = require('jsonwebtoken');
|
||||
const request = require('request-promise-native').defaults({
|
||||
baseUrl: 'http://127.0.0.1:3000/v1'
|
||||
});
|
||||
const exec = require('child_process').exec ;
|
||||
const {generateHashedPassword} = require('../lib/utils/password-utils');
|
||||
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
test('add an admin user', (t) => {
|
||||
exec(`${__dirname}/../db/reset_admin_password.js`, (err, stdout, stderr) => {
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
if (err) return t.end(err);
|
||||
t.pass('successfully added admin user');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('user tests', async(t) => {
|
||||
const app = require('../app');
|
||||
const password = 'abcde12345-';
|
||||
try {
|
||||
let result;
|
||||
|
||||
/* login as admin to get a jwt */
|
||||
result = await request.post('/login', {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
body: {
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 200 && result.body.token, 'successfully logged in as admin');
|
||||
const authAdmin = {bearer: result.body.token};
|
||||
const decodedJwt = jwt.verify(result.body.token, process.env.JWT_SECRET);
|
||||
|
||||
/* add admin user */
|
||||
result = await request.post(`/Users`, {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
auth: authAdmin,
|
||||
body: {
|
||||
name: 'admin2',
|
||||
email: 'admin2@jambonz.com',
|
||||
is_active: true,
|
||||
force_change: true,
|
||||
initial_password: password,
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201 && result.body.user_sid, 'admin user created');
|
||||
const admin_user_sid = result.body.user_sid;
|
||||
|
||||
/* add a service provider */
|
||||
result = await request.post('/ServiceProviders', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
name: 'sp',
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created service provider');
|
||||
const sp_sid = result.body.sid;
|
||||
|
||||
/* add service_provider user */
|
||||
result = await request.post(`/Users`, {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
auth: authAdmin,
|
||||
body: {
|
||||
name: 'service_provider',
|
||||
email: 'sp@jambonz.com',
|
||||
is_active: true,
|
||||
force_change: true,
|
||||
initial_password: password,
|
||||
service_provider_sid: sp_sid,
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201 && result.body.user_sid, 'service_provider scope user created');
|
||||
const sp_user_sid = result.body.sid;
|
||||
|
||||
/* add an account */
|
||||
result = await request.post('/Accounts', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
body: {
|
||||
name: 'sample_account',
|
||||
service_provider_sid: sp_sid,
|
||||
registration_hook: {
|
||||
url: 'http://example.com/reg',
|
||||
method: 'get'
|
||||
},
|
||||
webhook_secret: 'foobar'
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201, 'successfully created account');
|
||||
const account_sid = result.body.sid;
|
||||
|
||||
/* add account user */
|
||||
result = await request.post(`/Users`, {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
auth: authAdmin,
|
||||
body: {
|
||||
name: 'account',
|
||||
email: 'account@jambonz.com',
|
||||
is_active: true,
|
||||
force_change: true,
|
||||
initial_password: password,
|
||||
service_provider_sid: sp_sid,
|
||||
account_sid: account_sid
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201 && result.body.user_sid, 'account scope user created');
|
||||
const account_user_sid = result.body.sid;
|
||||
|
||||
/* retrieve list of users */
|
||||
result = await request.get(`/Users`, {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
auth: authAdmin,
|
||||
});
|
||||
t.ok(result.statusCode === 200 && result.body.length, 'successfully user list');
|
||||
|
||||
/* delete account user */
|
||||
result = await request.delete(`/Users/${account_user_sid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
auth: authAdmin,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'account scope user deleted');
|
||||
|
||||
/* delete sp user */
|
||||
result = await request.delete(`/Users/${sp_user_sid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
auth: authAdmin,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'account scope user deleted');
|
||||
|
||||
/* delete admin user */
|
||||
result = await request.delete(`/Users/${admin_user_sid}`, {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
auth: authAdmin,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'account scope user deleted');
|
||||
|
||||
// /* self delete as admin user */
|
||||
// result = await request.delete(`/Users/${decodedJwt.user_sid}`, {
|
||||
// resolveWithFullResponse: true,
|
||||
// json: true,
|
||||
// auth: authAdmin,
|
||||
// });
|
||||
// t.ok(result.statusCode === 500 && result.error.msg === 'cannot delete this admin user - there are no other active admin users');
|
||||
|
||||
/* add another service_provider user */
|
||||
result = await request.post(`/Users`, {
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
auth: authAdmin,
|
||||
body: {
|
||||
name: 'service_provider1',
|
||||
email: 'sp1@jambonz.com',
|
||||
is_active: true,
|
||||
force_change: false,
|
||||
initial_password: password,
|
||||
service_provider_sid: sp_sid,
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201 && result.body.user_sid, 'service_provider scope user created');
|
||||
|
||||
/* logout as sp to get a jwt */
|
||||
result = await request.post('/logout', {
|
||||
resolveWithFullResponse: true,
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully logged out');
|
||||
|
||||
// /* login as sp user to get a jwt */
|
||||
// result = await request.post('/login', {
|
||||
// resolveWithFullResponse: true,
|
||||
// json: true,
|
||||
// body: {
|
||||
// username: 'service_provider1',
|
||||
// password: 'abcd1234-',
|
||||
// }
|
||||
// });
|
||||
// t.ok(result.statusCode === 200 && result.body.token, 'successfully logged in as sp');
|
||||
// const authSPUser = {bearer: result.body.token};
|
||||
|
||||
// result = await request.post(`/Users`, {
|
||||
// resolveWithFullResponse: true,
|
||||
// json: true,
|
||||
// auth: authSPUser,
|
||||
// body: {
|
||||
// name: 'sp2',
|
||||
// email: 'sp2@jambonz.com',
|
||||
// is_active: true,
|
||||
// force_change: false,
|
||||
// initial_password: password,
|
||||
// }
|
||||
// });
|
||||
// t.ok(result.statusCode === 403, 'sp user cannot create admin users');
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
t.end(err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user