Compare commits

..

13 Commits

Author SHA1 Message Date
Dave Horton
a746bbc4c9 fix for service provider api 2020-05-29 09:54:26 -04:00
Dave Horton
0e248cb393 add support for ms teams 2020-05-26 08:57:19 -04:00
Dave Horton
5d5bd223cd add script to create default service provider and account to database 2020-04-21 09:27:54 -04:00
Dave Horton
ed74ec45c2 minor swagger fix 2020-04-21 08:12:46 -04:00
Dave Horton
2fb2fc5c10 fix bug in choosing feature server for createCall 2020-04-21 08:08:55 -04:00
Dave Horton
e38137ae7b remove assert about env var for feature server location as we now get from db 2020-04-20 20:05:39 -04:00
Dave Horton
8e6b0ec111 bump deps 2020-04-20 12:14:13 -04:00
Dave Horton
be011db109 add support for sbc_addresses and ms_teams_tenants tables 2020-04-20 11:44:49 -04:00
Dave Horton
993833212f route createCall api requests among active freeswitch servers 2020-04-20 10:12:00 -04:00
Dave Horton
61a5ce2672 added basic login support for admin user 2020-04-01 16:27:18 -04:00
Dave Horton
c43d24b477 add script to initialize/reset admin password to default 2020-04-01 14:26:28 -04:00
Dave Horton
2accbcef74 remove application_sid from createCall payload if provided as null value 2020-03-24 08:07:39 -04:00
Dave Horton
7a144ffe74 support for login 2020-03-21 13:39:03 -04:00
26 changed files with 1498 additions and 211 deletions

View File

@@ -16,7 +16,6 @@ JAMBONES_REDIS_HOST
JAMBONES_REDIS_PORT
JAMBONES_LOGLEVEL # defaults to info
JAMBONES_API_VERSION # defaults to v1
JAMBONES_CREATE_CALL_URL
HTTP_PORT # defaults to 3000
```

18
app.js
View File

@@ -15,21 +15,21 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
process.env.JAMBONES_MYSQL_PASSWORD &&
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_CREATE_CALL_URL, 'missing JAMBONES_CREATE_CALL_URL env var');
const {
retrieveCall,
deleteCall,
listCalls,
purgeCalls
} = require('jambonz-realtimedb-helpers')({
purgeCalls,
retrieveSet
} = require('@jambonz/realtimedb-helpers')({
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
const {
lookupAppBySid,
lookupAccountBySid
} = require('jambonz-db-helpers')({
} = require('@jambonz/db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
password: process.env.JAMBONES_MYSQL_PASSWORD,
@@ -47,14 +47,22 @@ Object.assign(app.locals, {
deleteCall,
listCalls,
purgeCalls,
retrieveSet,
lookupAppBySid,
lookupAccountBySid
});
const unless = (paths, middleware) => {
return (req, res, next) => {
if (paths.find((path) => req.path.startsWith(path))) return next();
return middleware(req, res, next);
};
};
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use('/v1', passport.authenticate('bearer', { session: false }));
app.use('/v1', unless(['/login', '/Users'], passport.authenticate('bearer', { session: false })));
app.use('/', routes);
app.use((err, req, res, next) => {
logger.error(err, 'burped error');

View File

@@ -0,0 +1,4 @@
insert into service_providers (service_provider_sid, name)
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'default service provider');
insert into accounts (account_sid, service_provider_sid, name)
values ('9351f46a-678c-43f5-b8a6-d4eb58d131af','2708b1b3-2736-40ea-b502-c53d8396247f', 'default account');

View File

@@ -0,0 +1,4 @@
insert into service_providers (service_provider_sid, name)
values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'default service provider');
insert into accounts (account_sid, service_provider_sid, name)
values ('9351f46a-678c-43f5-b8a6-d4eb58d131af','2708b1b3-2736-40ea-b502-c53d8396247f', 'default account');

View File

@@ -1,204 +1,256 @@
/* SQLEditor (MySQL (2))*/
SET FOREIGN_KEY_CHECKS = 0;
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `call_routes`;
DROP TABLE IF EXISTS call_routes;
DROP TABLE IF EXISTS `lcr_carrier_set_entry`;
DROP TABLE IF EXISTS lcr_carrier_set_entry;
DROP TABLE IF EXISTS `lcr_routes`;
DROP TABLE IF EXISTS lcr_routes;
DROP TABLE IF EXISTS `api_keys`;
DROP TABLE IF EXISTS api_keys;
DROP TABLE IF EXISTS `phone_numbers`;
DROP TABLE IF EXISTS ms_teams_tenants;
DROP TABLE IF EXISTS `sip_gateways`;
DROP TABLE IF EXISTS sbc_addresses;
DROP TABLE IF EXISTS `voip_carriers`;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS `accounts`;
DROP TABLE IF EXISTS phone_numbers;
DROP TABLE IF EXISTS `applications`;
DROP TABLE IF EXISTS sip_gateways;
DROP TABLE IF EXISTS `service_providers`;
DROP TABLE IF EXISTS voip_carriers;
DROP TABLE IF EXISTS `webhooks`;
DROP TABLE IF EXISTS accounts;
SET FOREIGN_KEY_CHECKS = 1;
DROP TABLE IF EXISTS applications;
CREATE TABLE IF NOT EXISTS `call_routes`
DROP TABLE IF EXISTS service_providers;
DROP TABLE IF EXISTS webhooks;
CREATE TABLE call_routes
(
`call_route_sid` CHAR(36) NOT NULL UNIQUE ,
`priority` INTEGER NOT NULL,
`account_sid` CHAR(36) NOT NULL,
`regex` VARCHAR(255) NOT NULL,
`application_sid` CHAR(36) NOT NULL,
PRIMARY KEY (`call_route_sid`)
call_route_sid CHAR(36) NOT NULL UNIQUE ,
priority INTEGER NOT NULL,
account_sid CHAR(36) NOT NULL,
regex VARCHAR(255) NOT NULL,
application_sid CHAR(36) NOT NULL,
PRIMARY KEY (call_route_sid)
) ENGINE=InnoDB COMMENT='a regex-based pattern match for call routing';
CREATE TABLE IF NOT EXISTS `lcr_routes`
CREATE TABLE lcr_routes
(
`lcr_route_sid` CHAR(36),
`regex` VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
`description` VARCHAR(1024),
`priority` INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
PRIMARY KEY (`lcr_route_sid`)
lcr_route_sid CHAR(36),
regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
description VARCHAR(1024),
priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
PRIMARY KEY (lcr_route_sid)
) COMMENT='Least cost routing table';
CREATE TABLE IF NOT EXISTS `api_keys`
CREATE TABLE api_keys
(
`api_key_sid` CHAR(36) NOT NULL UNIQUE ,
`token` CHAR(36) NOT NULL UNIQUE ,
`account_sid` CHAR(36),
`service_provider_sid` CHAR(36),
PRIMARY KEY (`api_key_sid`)
api_key_sid CHAR(36) NOT NULL UNIQUE ,
token CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36),
service_provider_sid CHAR(36),
expires_at TIMESTAMP,
PRIMARY KEY (api_key_sid)
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
CREATE TABLE IF NOT EXISTS `voip_carriers`
CREATE TABLE ms_teams_tenants
(
`voip_carrier_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(64) NOT NULL UNIQUE ,
`description` VARCHAR(255),
`account_sid` CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
`application_sid` CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
PRIMARY KEY (`voip_carrier_sid`)
ms_teams_tenant_sid CHAR(36) NOT NULL UNIQUE ,
service_provider_sid CHAR(36) NOT NULL,
account_sid CHAR(36) NOT NULL,
application_sid CHAR(36),
tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
PRIMARY KEY (ms_teams_tenant_sid)
) COMMENT='A Microsoft Teams customer tenant';
CREATE TABLE sbc_addresses
(
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(255) NOT NULL,
port INTEGER NOT NULL DEFAULT 5060,
service_provider_sid CHAR(36),
PRIMARY KEY (sbc_address_sid)
);
CREATE TABLE users
(
user_sid CHAR(36) NOT NULL UNIQUE ,
name CHAR(36) NOT NULL UNIQUE ,
hashed_password VARCHAR(1024) NOT NULL,
salt CHAR(16) NOT NULL,
force_change BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (user_sid)
);
CREATE TABLE voip_carriers
(
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL UNIQUE ,
description VARCHAR(255),
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
application_sid CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
PRIMARY KEY (voip_carrier_sid)
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls';
CREATE TABLE IF NOT EXISTS `phone_numbers`
CREATE TABLE phone_numbers
(
`phone_number_sid` CHAR(36) UNIQUE ,
`number` VARCHAR(32) NOT NULL UNIQUE ,
`voip_carrier_sid` CHAR(36) NOT NULL,
`account_sid` CHAR(36),
`application_sid` CHAR(36),
PRIMARY KEY (`phone_number_sid`)
phone_number_sid CHAR(36) UNIQUE ,
number VARCHAR(32) NOT NULL UNIQUE ,
voip_carrier_sid CHAR(36) NOT NULL,
account_sid CHAR(36),
application_sid CHAR(36),
PRIMARY KEY (phone_number_sid)
) ENGINE=InnoDB COMMENT='A phone number that has been assigned to an account';
CREATE TABLE IF NOT EXISTS `webhooks`
CREATE TABLE webhooks
(
`webhook_sid` CHAR(36) NOT NULL UNIQUE ,
`url` VARCHAR(1024) NOT NULL,
`method` ENUM("GET","POST") NOT NULL DEFAULT 'POST',
`username` VARCHAR(255),
`password` VARCHAR(255),
PRIMARY KEY (`webhook_sid`)
webhook_sid CHAR(36) NOT NULL UNIQUE ,
url VARCHAR(1024) NOT NULL,
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
username VARCHAR(255),
password VARCHAR(255),
PRIMARY KEY (webhook_sid)
) COMMENT='An HTTP callback';
CREATE TABLE IF NOT EXISTS `lcr_carrier_set_entry`
CREATE TABLE sip_gateways
(
`lcr_carrier_set_entry_sid` CHAR(36),
`workload` INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
`lcr_route_sid` CHAR(36) NOT NULL,
`voip_carrier_sid` CHAR(36) NOT NULL,
`priority` INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
PRIMARY KEY (`lcr_carrier_set_entry_sid`)
) COMMENT='An entry in the LCR routing list';
CREATE TABLE IF NOT EXISTS `sip_gateways`
(
`sip_gateway_sid` CHAR(36),
`ipv4` VARCHAR(32) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
`port` INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
`inbound` BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
`outbound` BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
`voip_carrier_sid` CHAR(36) NOT NULL,
`is_active` BOOLEAN NOT NULL DEFAULT 1,
PRIMARY KEY (`sip_gateway_sid`)
sip_gateway_sid CHAR(36),
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
voip_carrier_sid CHAR(36) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT 1,
PRIMARY KEY (sip_gateway_sid)
) COMMENT='A whitelisted sip gateway used for origination/termination';
CREATE TABLE IF NOT EXISTS `applications`
CREATE TABLE lcr_carrier_set_entry
(
`application_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(64) NOT NULL,
`account_sid` CHAR(36) NOT NULL COMMENT 'account that this application belongs to',
`call_hook_sid` CHAR(36) COMMENT 'webhook to call for inbound calls to phone numbers owned by this account',
`call_status_hook_sid` CHAR(36) COMMENT 'webhook to call for call status events',
`speech_synthesis_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
`speech_synthesis_voice` VARCHAR(64) NOT NULL DEFAULT 'en-US-Wavenet-C',
`speech_recognizer_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
`speech_recognizer_language` VARCHAR(64) NOT NULL DEFAULT 'en-US',
PRIMARY KEY (`application_sid`)
lcr_carrier_set_entry_sid CHAR(36),
workload INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
lcr_route_sid CHAR(36) NOT NULL,
voip_carrier_sid CHAR(36) NOT NULL,
priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
PRIMARY KEY (lcr_carrier_set_entry_sid)
) COMMENT='An entry in the LCR routing list';
CREATE TABLE applications
(
application_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL,
account_sid CHAR(36) NOT NULL COMMENT 'account that this application belongs to',
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls to phone numbers owned by this account',
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
speech_synthesis_voice VARCHAR(64),
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
PRIMARY KEY (application_sid)
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls ';
CREATE TABLE IF NOT EXISTS `service_providers`
CREATE TABLE service_providers
(
`service_provider_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(64) NOT NULL UNIQUE ,
`description` VARCHAR(255),
`root_domain` VARCHAR(128) UNIQUE ,
`registration_hook_sid` CHAR(36),
PRIMARY KEY (`service_provider_sid`)
service_provider_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL UNIQUE ,
description VARCHAR(255),
root_domain VARCHAR(128) UNIQUE ,
registration_hook_sid CHAR(36),
ms_teams_fqdn VARCHAR(255),
PRIMARY KEY (service_provider_sid)
) ENGINE=InnoDB COMMENT='A partition of the platform used by one service provider';
CREATE TABLE IF NOT EXISTS `accounts`
CREATE TABLE accounts
(
`account_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(64) NOT NULL,
`sip_realm` VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
`service_provider_sid` CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
`registration_hook_sid` CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
`device_calling_application_sid` CHAR(36) COMMENT 'application to use for outbound calling from an account',
`is_active` BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (`account_sid`)
account_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL,
sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
is_active BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (account_sid)
) ENGINE=InnoDB COMMENT='An enterprise that uses the platform for comm services';
CREATE INDEX `call_routes_call_route_sid_idx` ON `call_routes` (`call_route_sid`);
ALTER TABLE `call_routes` ADD FOREIGN KEY account_sid_idxfk (`account_sid`) REFERENCES `accounts` (`account_sid`);
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE `call_routes` ADD FOREIGN KEY application_sid_idxfk (`application_sid`) REFERENCES `applications` (`application_sid`);
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
CREATE INDEX `api_keys_api_key_sid_idx` ON `api_keys` (`api_key_sid`);
CREATE INDEX `api_keys_account_sid_idx` ON `api_keys` (`account_sid`);
ALTER TABLE `api_keys` ADD FOREIGN KEY account_sid_idxfk_1 (`account_sid`) REFERENCES `accounts` (`account_sid`);
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
CREATE INDEX account_sid_idx ON api_keys (account_sid);
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX `api_keys_service_provider_sid_idx` ON `api_keys` (`service_provider_sid`);
ALTER TABLE `api_keys` ADD FOREIGN KEY service_provider_sid_idxfk (`service_provider_sid`) REFERENCES `service_providers` (`service_provider_sid`);
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX `voip_carriers_voip_carrier_sid_idx` ON `voip_carriers` (`voip_carrier_sid`);
CREATE INDEX `voip_carriers_name_idx` ON `voip_carriers` (`name`);
ALTER TABLE `voip_carriers` ADD FOREIGN KEY account_sid_idxfk_2 (`account_sid`) REFERENCES `accounts` (`account_sid`);
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE `voip_carriers` ADD FOREIGN KEY application_sid_idxfk_1 (`application_sid`) REFERENCES `applications` (`application_sid`);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX `phone_numbers_phone_number_sid_idx` ON `phone_numbers` (`phone_number_sid`);
CREATE INDEX `phone_numbers_voip_carrier_sid_idx` ON `phone_numbers` (`voip_carrier_sid`);
ALTER TABLE `phone_numbers` ADD FOREIGN KEY voip_carrier_sid_idxfk (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
ALTER TABLE `phone_numbers` ADD FOREIGN KEY account_sid_idxfk_3 (`account_sid`) REFERENCES `accounts` (`account_sid`);
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port);
ALTER TABLE `phone_numbers` ADD FOREIGN KEY application_sid_idxfk_2 (`application_sid`) REFERENCES `applications` (`application_sid`);
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid);
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX `webhooks_webhook_sid_idx` ON `webhooks` (`webhook_sid`);
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY lcr_route_sid_idxfk (`lcr_route_sid`) REFERENCES `lcr_routes` (`lcr_route_sid`);
CREATE INDEX user_sid_idx ON users (user_sid);
CREATE INDEX name_idx ON users (name);
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
CREATE INDEX name_idx ON voip_carriers (name);
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
CREATE UNIQUE INDEX `sip_gateways_sip_gateway_idx_hostport` ON `sip_gateways` (`ipv4`,`port`);
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE `sip_gateways` ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
CREATE UNIQUE INDEX `applications_idx_name` ON `applications` (`account_sid`,`name`);
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX `applications_application_sid_idx` ON `applications` (`application_sid`);
CREATE INDEX `applications_name_idx` ON `applications` (`name`);
CREATE INDEX `applications_account_sid_idx` ON `applications` (`account_sid`);
ALTER TABLE `applications` ADD FOREIGN KEY account_sid_idxfk_4 (`account_sid`) REFERENCES `accounts` (`account_sid`);
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
CREATE UNIQUE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
ALTER TABLE `applications` ADD FOREIGN KEY call_hook_sid_idxfk (`call_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE `applications` ADD FOREIGN KEY call_status_hook_sid_idxfk (`call_status_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid);
CREATE INDEX `service_providers_service_provider_sid_idx` ON `service_providers` (`service_provider_sid`);
CREATE INDEX `service_providers_name_idx` ON `service_providers` (`name`);
CREATE INDEX `service_providers_root_domain_idx` ON `service_providers` (`root_domain`);
ALTER TABLE `service_providers` ADD FOREIGN KEY registration_hook_sid_idxfk (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
CREATE INDEX `accounts_account_sid_idx` ON `accounts` (`account_sid`);
CREATE INDEX `accounts_name_idx` ON `accounts` (`name`);
CREATE INDEX `accounts_sip_realm_idx` ON `accounts` (`sip_realm`);
CREATE INDEX `accounts_service_provider_sid_idx` ON `accounts` (`service_provider_sid`);
ALTER TABLE `accounts` ADD FOREIGN KEY service_provider_sid_idxfk_1 (`service_provider_sid`) REFERENCES `service_providers` (`service_provider_sid`);
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
ALTER TABLE `accounts` ADD FOREIGN KEY registration_hook_sid_idxfk_1 (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
CREATE INDEX application_sid_idx ON applications (application_sid);
CREATE INDEX account_sid_idx ON applications (account_sid);
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE `accounts` ADD FOREIGN KEY device_calling_application_sid_idxfk (`device_calling_application_sid`) REFERENCES `applications` (`application_sid`);
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE applications ADD FOREIGN KEY call_status_hook_sid_idxfk (call_status_hook_sid) REFERENCES webhooks (webhook_sid);
CREATE INDEX service_provider_sid_idx ON service_providers (service_provider_sid);
CREATE INDEX name_idx ON service_providers (name);
CREATE INDEX root_domain_idx ON service_providers (root_domain);
ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (registration_hook_sid) REFERENCES webhooks (webhook_sid);
CREATE INDEX account_sid_idx ON accounts (account_sid);
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=1;

View File

@@ -1,5 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<SQLContainer>
<SQLTable>
<name><![CDATA[users]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1599.00</x>
<y>37.00</y>
</location>
<size>
<width>250.00</width>
<height>120.00</height>
</size>
<zorder>11</zorder>
<SQLField>
<name><![CDATA[user_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<primaryKey>1</primaryKey>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[F33F9604-ADC9-46E3-AE51-A2A839A81758]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<SQLField>
<name><![CDATA[name]]></name>
<type><![CDATA[CHAR(36)]]></type>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[6E8DE796-46DA-4644-AA7F-1A75D9B834FB]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<SQLField>
<name><![CDATA[hashed_password]]></name>
<type><![CDATA[VARCHAR(1024)]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[3E7E38A2-96FC-4E28-BF10-332823FB17F1]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[salt]]></name>
<type><![CDATA[CHAR(16)]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[F21A170F-5922-4749-A062-6C7516051560]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[force_change]]></name>
<type><![CDATA[BOOLEAN]]></type>
<defaultValue><![CDATA[TRUE]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[57655C0C-9427-4CC7-9502-24ACF56AAECF]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[3]]></labelWindowIndex>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[2A735FAB-592C-42E5-9C8B-06B109314799]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[voip_carriers]]></name>
<schema><![CDATA[]]></schema>
@@ -67,7 +119,7 @@
<objectComment><![CDATA[If provided, all incoming calls from this source will be routed to the associated application]]></objectComment>
<uid><![CDATA[B6545E2E-7F55-4082-AEFA-29F50C137D64]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[5]]></labelWindowIndex>
<labelWindowIndex><![CDATA[8]]></labelWindowIndex>
<objectComment><![CDATA[A Carrier or customer PBX that can send or receive calls]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[3D3136A7-AFC0-4A70-AEC3-68577955CA2E]]></uid>
@@ -78,12 +130,12 @@
<comment><![CDATA[An authorization token that is used to access the REST api]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>1279.00</x>
<x>1302.00</x>
<y>61.00</y>
</location>
<size>
<width>252.00</width>
<height>100.00</height>
<height>120.00</height>
</size>
<zorder>1</zorder>
<SQLField>
@@ -132,7 +184,12 @@
<indexed><![CDATA[1]]></indexed>
<uid><![CDATA[3F553F20-AA47-471E-A650-0B4CEC9DCB0A]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[10]]></labelWindowIndex>
<SQLField>
<name><![CDATA[expires_at]]></name>
<type><![CDATA[TIMESTAMP]]></type>
<uid><![CDATA[DE86BC18-858E-4D7E-9B83-891DB2861434]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[13]]></labelWindowIndex>
<objectComment><![CDATA[An authorization token that is used to access the REST api]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[3EDF89A0-FD38-4DF9-BB65-E0FCD0A678BE]]></uid>
@@ -142,8 +199,8 @@
<schema><![CDATA[]]></schema>
<comment><![CDATA[An HTTP callback]]></comment>
<location>
<x>1383.00</x>
<y>365.00</y>
<x>1315.00</x>
<y>376.00</y>
</location>
<size>
<width>254.00</width>
@@ -183,7 +240,7 @@
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[04BB457A-D532-4780-8A58-5900094171EC]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[1]]></labelWindowIndex>
<labelWindowIndex><![CDATA[4]]></labelWindowIndex>
<objectComment><![CDATA[An HTTP callback]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></uid>
@@ -254,11 +311,89 @@
<uid><![CDATA[9B4208B5-9E3B-4B76-B7F7-4E5D36B99BF2]]></uid>
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<labelWindowIndex><![CDATA[9]]></labelWindowIndex>
<labelWindowIndex><![CDATA[12]]></labelWindowIndex>
<objectComment><![CDATA[a regex-based pattern match for call routing]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[78584D93-2CD7-4495-9C5E-893C7B869133]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[ms_teams_tenants]]></name>
<schema><![CDATA[]]></schema>
<comment><![CDATA[A Microsoft Teams customer tenant]]></comment>
<location>
<x>1309.00</x>
<y>219.00</y>
</location>
<size>
<width>298.00</width>
<height>120.00</height>
</size>
<zorder>12</zorder>
<SQLField>
<name><![CDATA[ms_teams_tenant_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<primaryKey>1</primaryKey>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[A0E48CB5-C2B2-47FC-8486-EF5F16DAF797]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<SQLField>
<name><![CDATA[service_provider_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>service_provider_sid</referencesField>
<referencesTable>service_providers</referencesTable>
<referencesField><![CDATA[service_provider_sid]]></referencesField>
<referencesTable><![CDATA[service_providers]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>1</destinationCardinality>
<referencesFieldUID><![CDATA[58E1702C-6A95-4B17-8C08-8A3810EA16A1]]></referencesFieldUID>
<referencesTableUID><![CDATA[F294B51E-F867-47CA-BC1F-F70BDF8170FF]]></referencesTableUID>
<indexed><![CDATA[0]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[519A33A0-B678-4F60-892F-ADA90E656500]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[account_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>account_sid</referencesField>
<referencesTable>accounts</referencesTable>
<referencesField><![CDATA[account_sid]]></referencesField>
<referencesTable><![CDATA[accounts]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[1342FAFA-C15C-429B-809B-C6C55F9FA5B6]]></referencesFieldUID>
<referencesTableUID><![CDATA[985D6997-B1A7-4AB3-80F4-4D59B45480C8]]></referencesTableUID>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[EB48F39F-9D5F-43E0-BE8A-34A5C1304A76]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[application_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>application_sid</referencesField>
<referencesTable>applications</referencesTable>
<referencesField><![CDATA[application_sid]]></referencesField>
<referencesTable><![CDATA[applications]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>2</destinationCardinality>
<referencesFieldUID><![CDATA[EF943D13-DCB0-43C1-B03F-550612E20F9D]]></referencesFieldUID>
<referencesTableUID><![CDATA[E97EE4F0-7ED7-4E8C-862E-D98192D6EAE0]]></referencesTableUID>
<forcedUnique><![CDATA[0]]></forcedUnique>
<uid><![CDATA[7959F455-D49D-4185-97B5-37C7FAAB339B]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[tenant_fqdn]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[1DDAD1A1-942D-4487-89C8-D496B7F82274]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<labelWindowIndex><![CDATA[2]]></labelWindowIndex>
<objectComment><![CDATA[A Microsoft Teams customer tenant]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[92FD042A-5AEC-4D8F-AB94-C73C0F566F75]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[lcr_carrier_set_entry]]></name>
<schema><![CDATA[]]></schema>
@@ -323,7 +458,7 @@
<objectComment><![CDATA[lower priority carriers are attempted first]]></objectComment>
<uid><![CDATA[01F61C68-799B-49B0-9E6A-0E2162EE5A54]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[4]]></labelWindowIndex>
<labelWindowIndex><![CDATA[7]]></labelWindowIndex>
<objectComment><![CDATA[An entry in the LCR routing list]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[956025F5-0798-47F7-B76C-457814C7B52E]]></uid>
@@ -356,7 +491,7 @@
<SQLField>
<name><![CDATA[name]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<indexed><![CDATA[1]]></indexed>
<indexed><![CDATA[0]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[87414407-17CB-4582-9C0B-73CA548E1016]]></uid>
</SQLField>
@@ -421,7 +556,7 @@
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[C7130A90-DBB4-424D-A9A9-CB203C32350C]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[8]]></labelWindowIndex>
<labelWindowIndex><![CDATA[11]]></labelWindowIndex>
<objectComment><![CDATA[An enterprise that uses the platform for comm services]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[985D6997-B1A7-4AB3-80F4-4D59B45480C8]]></uid>
@@ -503,7 +638,7 @@
<uid><![CDATA[962CB80A-54CB-4C6A-9591-9BFC644CF80F]]></uid>
<unsigned><![CDATA[0]]></unsigned>
</SQLField>
<labelWindowIndex><![CDATA[7]]></labelWindowIndex>
<labelWindowIndex><![CDATA[10]]></labelWindowIndex>
<objectComment><![CDATA[A phone number that has been assigned to an account]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[BA650DDC-AC7B-4DFE-A5E5-828C75607807]]></uid>
@@ -529,7 +664,7 @@
</SQLField>
<SQLField>
<name><![CDATA[ipv4]]></name>
<type><![CDATA[VARCHAR(32)]]></type>
<type><![CDATA[VARCHAR(128)]]></type>
<notNull><![CDATA[1]]></notNull>
<objectComment><![CDATA[ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.]]></objectComment>
<uid><![CDATA[F18DB7D4-F902-4863-870C-CB07032AE17C]]></uid>
@@ -595,7 +730,7 @@
<indexType><![CDATA[UNIQUE]]></indexType>
<uid><![CDATA[1C744DE3-39BD-4EC6-B427-7EB2DD258771]]></uid>
</SQLIndex>
<labelWindowIndex><![CDATA[3]]></labelWindowIndex>
<labelWindowIndex><![CDATA[6]]></labelWindowIndex>
<objectComment><![CDATA[A whitelisted sip gateway used for origination/termination]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[D8A564E2-DA41-4217-8ACE-06CF77E9BEC1]]></uid>
@@ -611,7 +746,7 @@
</location>
<size>
<width>345.00</width>
<height>220.00</height>
<height>240.00</height>
</size>
<zorder>0</zorder>
<SQLField>
@@ -627,7 +762,7 @@
<SQLField>
<name><![CDATA[name]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<indexed><![CDATA[1]]></indexed>
<indexed><![CDATA[0]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[E0EDB7B1-B7F7-4F56-B94F-6B81BB87C514]]></uid>
</SQLField>
@@ -683,11 +818,17 @@
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[05D3C937-255D-44A5-A102-40303FA37CF1]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[speech_synthesis_language]]></name>
<type><![CDATA[VARCHAR(12)]]></type>
<defaultValue><![CDATA[en-US]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[2498FC63-58A1-40AF-8502-ABC4F1F5F541]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[speech_synthesis_voice]]></name>
<type><![CDATA[VARCHAR(64)]]></type>
<defaultValue><![CDATA[en-US-Wavenet-C]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<notNull><![CDATA[0]]></notNull>
<uid><![CDATA[929D66F0-64B9-4D7C-AB4B-24F131E1178F]]></uid>
</SQLField>
<SQLField>
@@ -722,11 +863,80 @@
<indexType><![CDATA[UNIQUE]]></indexType>
<uid><![CDATA[3FDDDF3B-375D-4DE4-B759-514438845F7D]]></uid>
</SQLIndex>
<labelWindowIndex><![CDATA[6]]></labelWindowIndex>
<labelWindowIndex><![CDATA[9]]></labelWindowIndex>
<objectComment><![CDATA[A defined set of behaviors to be applied to phone calls ]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[E97EE4F0-7ED7-4E8C-862E-D98192D6EAE0]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[sbc_addresses]]></name>
<schema><![CDATA[]]></schema>
<location>
<x>1305.00</x>
<y>564.00</y>
</location>
<size>
<width>281.00</width>
<height>120.00</height>
</size>
<zorder>13</zorder>
<SQLField>
<name><![CDATA[sbc_address_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<primaryKey>1</primaryKey>
<indexed><![CDATA[1]]></indexed>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[468F8C06-5A38-494A-8E77-3F53F111237B]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<SQLField>
<name><![CDATA[ipv4]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[4FE97DA7-8D8E-4BE3-982A-01B0444FB070]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[port]]></name>
<type><![CDATA[INTEGER]]></type>
<defaultValue><![CDATA[5060]]></defaultValue>
<notNull><![CDATA[1]]></notNull>
<uid><![CDATA[707EF28F-F4AF-4B7E-AB04-C128C894ECE9]]></uid>
</SQLField>
<SQLIndex>
<name><![CDATA[sbc_addresses_idx_host_port]]></name>
<fieldName><![CDATA[ipv4]]></fieldName>
<fieldName><![CDATA[port]]></fieldName>
<SQLIndexEntry>
<name><![CDATA[ipv4]]></name>
<prefixSize><![CDATA[]]></prefixSize>
<fieldUid><![CDATA[4FE97DA7-8D8E-4BE3-982A-01B0444FB070]]></fieldUid>
</SQLIndexEntry>
<SQLIndexEntry>
<name><![CDATA[port]]></name>
<prefixSize><![CDATA[]]></prefixSize>
<fieldUid><![CDATA[707EF28F-F4AF-4B7E-AB04-C128C894ECE9]]></fieldUid>
</SQLIndexEntry>
<indexNamePrefix><![CDATA[sbc_addresses]]></indexNamePrefix>
<uid><![CDATA[174BD35A-B09C-4F4A-B3D2-D5464D927D9B]]></uid>
</SQLIndex>
<SQLField>
<name><![CDATA[service_provider_sid]]></name>
<type><![CDATA[CHAR(36)]]></type>
<referencesField>service_provider_sid</referencesField>
<referencesTable>service_providers</referencesTable>
<referencesField><![CDATA[service_provider_sid]]></referencesField>
<referencesTable><![CDATA[service_providers]]></referencesTable>
<sourceCardinality>4</sourceCardinality>
<destinationCardinality>1</destinationCardinality>
<referencesFieldUID><![CDATA[58E1702C-6A95-4B17-8C08-8A3810EA16A1]]></referencesFieldUID>
<referencesTableUID><![CDATA[F294B51E-F867-47CA-BC1F-F70BDF8170FF]]></referencesTableUID>
<indexed><![CDATA[1]]></indexed>
<uid><![CDATA[6F249D1F-111F-45B4-B76C-8B5E6B9CB43F]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[1]]></labelWindowIndex>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[F0EE651E-DBF6-4CAC-A517-AC85BCC2D3AF]]></uid>
</SQLTable>
<SQLTable>
<name><![CDATA[lcr_routes]]></name>
<schema><![CDATA[]]></schema>
@@ -768,7 +978,7 @@
<uid><![CDATA[B73773BA-AB1B-47AA-B995-2D2FE006198F]]></uid>
<unique><![CDATA[1]]></unique>
</SQLField>
<labelWindowIndex><![CDATA[2]]></labelWindowIndex>
<labelWindowIndex><![CDATA[5]]></labelWindowIndex>
<objectComment><![CDATA[Least cost routing table]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
<uid><![CDATA[F283D572-F670-4571-91FD-A665A9D3E15D]]></uid>
@@ -779,12 +989,12 @@
<comment><![CDATA[A partition of the platform used by one service provider]]></comment>
<tableType><![CDATA[InnoDB]]></tableType>
<location>
<x>813.00</x>
<y>99.00</y>
<x>838.00</x>
<y>96.00</y>
</location>
<size>
<width>293.00</width>
<height>120.00</height>
<height>140.00</height>
</size>
<zorder>3</zorder>
<SQLField>
@@ -831,6 +1041,11 @@
<referencesTableUID><![CDATA[64D64CB9-0990-4C68-BE71-F9FD43C2BE19]]></referencesTableUID>
<uid><![CDATA[506BBE72-1A97-4776-B2C3-D94169652FFE]]></uid>
</SQLField>
<SQLField>
<name><![CDATA[ms_teams_fqdn]]></name>
<type><![CDATA[VARCHAR(255)]]></type>
<uid><![CDATA[FA39B463-61C7-4654-BE9C-D1AC39AB1B97]]></uid>
</SQLField>
<labelWindowIndex><![CDATA[0]]></labelWindowIndex>
<objectComment><![CDATA[A partition of the platform used by one service provider]]></objectComment>
<ui.treeExpanded><![CDATA[1]]></ui.treeExpanded>
@@ -850,17 +1065,17 @@
<overviewPanelHidden><![CDATA[0]]></overviewPanelHidden>
<pageBoundariesVisible><![CDATA[0]]></pageBoundariesVisible>
<PageGridVisible><![CDATA[0]]></PageGridVisible>
<RightSidebarWidth><![CDATA[2146.000000]]></RightSidebarWidth>
<RightSidebarWidth><![CDATA[1756.000000]]></RightSidebarWidth>
<sidebarIndex><![CDATA[2]]></sidebarIndex>
<snapToGrid><![CDATA[0]]></snapToGrid>
<SourceSidebarWidth><![CDATA[0.000000]]></SourceSidebarWidth>
<SQLEditorFileFormatVersion><![CDATA[4]]></SQLEditorFileFormatVersion>
<uid><![CDATA[58C99A00-06C9-478C-A667-C63842E088F3]]></uid>
<windowHeight><![CDATA[1322.000000]]></windowHeight>
<windowLocationX><![CDATA[2553.000000]]></windowLocationX>
<windowLocationY><![CDATA[95.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{0, 0}]]></windowScrollOrigin>
<windowWidth><![CDATA[2423.000000]]></windowWidth>
<windowHeight><![CDATA[1337.000000]]></windowHeight>
<windowLocationX><![CDATA[2625.000000]]></windowLocationX>
<windowLocationY><![CDATA[80.000000]]></windowLocationY>
<windowScrollOrigin><![CDATA[{0, 1}]]></windowScrollOrigin>
<windowWidth><![CDATA[2033.000000]]></windowWidth>
</SQLDocumentInfo>
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>
<defaultLabelExpanded><![CDATA[1]]></defaultLabelExpanded>

62
db/reset_admin_password.js Executable file
View File

@@ -0,0 +1,62 @@
#!/usr/bin/env node
const {getMysqlConnection} = require('../lib/db');
const crypto = require('crypto');
const uuidv4 = require('uuid/v4');
const sqlInsert = `INSERT into users
(user_sid, name, hashed_password, salt)
values (?, ?, ?, ?)
`;
/**
* generates random string of characters i.e salt
* @function
* @param {number} length - Length of the random string.
*/
const genRandomString = (len) => {
return crypto.randomBytes(Math.ceil(len / 2))
.toString('hex') /** convert to hexadecimal format */
.slice(0, len); /** return required number of characters */
};
/**
* hash password with sha512.
* @function
* @param {string} password - List of required fields.
* @param {string} salt - Data to be validated.
*/
const sha512 = function(password, salt) {
const hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
hash.update(password);
var value = hash.digest('hex');
return {
salt:salt,
passwordHash:value
};
};
const saltHashPassword = (userpassword) => {
var salt = genRandomString(16); /** Gives us salt of length 16 */
return sha512(userpassword, salt);
};
/* reset admin password */
getMysqlConnection((err, conn) => {
if (err) return console.log(err, 'Error connecting to database');
/* delete admin user if it exists */
conn.query('DELETE from users where name = "admin"', (err) => {
if (err) return console.log(err, 'Error removing admin user');
const {salt, passwordHash} = saltHashPassword('admin');
const sid = uuidv4();
conn.query(sqlInsert, [
sid,
'admin',
passwordHash,
salt
], (err) => {
if (err) return console.log(err, 'Error inserting admin user');
console.log('successfully reset admin password');
conn.release();
process.exit(0);
});
});
});

View File

@@ -10,6 +10,7 @@ function transmogrifyResults(results) {
const obj = row.acc;
if (row.rh && Object.keys(row.rh).length && row.rh.url !== null) {
Object.assign(obj, {registration_hook: row.rh});
delete obj.registration_hook.webhook_sid;
}
else obj.registration_hook = null;
delete obj.registration_hook_sid;

55
lib/models/sbc.js Normal file
View File

@@ -0,0 +1,55 @@
const Model = require('./model');
const {getMysqlConnection} = require('../db');
class Sbc extends Model {
constructor() {
super();
}
/**
* list all SBCs either for a given service provider, or those not associated with a
* service provider (i.e. community SBCs)
*/
static retrieveAll(service_provider_sid) {
const sql = service_provider_sid ?
'SELECT * from sbc_addresses WHERE service_provider_sid = ?' :
'SELECT * from sbc_addresses WHERE service_provider_sid IS NULL';
const args = service_provider_sid ? [service_provider_sid] : [];
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
conn.query(sql, args, (err, results) => {
conn.release();
if (err) return reject(err);
resolve(results);
});
});
});
}
}
Sbc.table = 'sbc_addresses';
Sbc.fields = [
{
name: 'sbc_address_sid',
type: 'string',
primaryKey: true
},
{
name: 'ipv4',
type: 'string',
required: true
},
{
name: 'port',
type: 'number'
},
{
name: 'service_provider_sid',
type: 'string'
}
];
module.exports = Sbc;

View File

@@ -1,9 +1,65 @@
const Model = require('./model');
const {getMysqlConnection} = require('../db');
const retrieveSql = `SELECT * from service_providers sp
LEFT JOIN webhooks AS rh
ON sp.registration_hook_sid = rh.webhook_sid`;
function transmogrifyResults(results) {
return results.map((row) => {
const obj = row.sp;
if (row.rh && Object.keys(row.rh).length && row.rh.url !== null) {
Object.assign(obj, {registration_hook: row.rh});
delete obj.registration_hook.webhook_sid;
}
else obj.registration_hook = null;
delete obj.registration_hook_sid;
return obj;
});
}
class ServiceProvider extends Model {
constructor() {
super();
}
/**
* list all service providers
*/
static retrieveAll() {
const sql = retrieveSql;
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
conn.query({sql, nestTables: true}, [], (err, results, fields) => {
conn.release();
if (err) return reject(err);
const r = transmogrifyResults(results);
resolve(r);
});
});
});
}
/**
* retrieve a service provider
*/
static retrieve(sid) {
const args = [sid];
const sql = `${retrieveSql} WHERE sp.service_provider_sid = ?`;
return new Promise((resolve, reject) => {
getMysqlConnection((err, conn) => {
if (err) return reject(err);
conn.query({sql, nestTables: true}, args, (err, results, fields) => {
conn.release();
if (err) return reject(err);
const r = transmogrifyResults(results);
resolve(r);
});
});
});
}
}
ServiceProvider.table = 'service_providers';
@@ -27,7 +83,11 @@ ServiceProvider.fields = [
type: 'string',
},
{
name: 'registration_hook',
name: 'registration_hook_sid',
type: 'string',
},
{
name: 'ms_teams_fqdn',
type: 'string',
}

37
lib/models/tenant.js Normal file
View File

@@ -0,0 +1,37 @@
const Model = require('./model');
class MsTeamsTenant extends Model {
constructor() {
super();
}
}
MsTeamsTenant.table = 'ms_teams_tenants';
MsTeamsTenant.fields = [
{
name: 'ms_teams_tenant_sid',
type: 'string',
primaryKey: true
},
{
name: 'service_provider_sid',
type: 'string',
required: true
},
{
name: 'account_sid',
type: 'string',
required: true
},
{
name: 'application_sid',
type: 'string'
},
{
name: 'tenant_fqdn',
type: 'string',
required: true
}
];
module.exports = MsTeamsTenant;

View File

@@ -12,6 +12,7 @@ const preconditions = {
'update': validateUpdate,
'delete': validateDelete
};
let idx = 0;
function coerceNumbers(callInfo) {
if (Array.isArray(callInfo)) {
@@ -90,7 +91,6 @@ async function validateCreateCall(logger, sid, req) {
try {
logger.debug(`Accounts:validateCreateCall retrieving application ${obj.application_sid}`);
const application = await lookupAppBySid(obj.application_sid);
logger.debug(`Accounts:validateCreateCall retrieved application ${JSON.stringify(application)}`);
Object.assign(obj, {
call_hook: application.call_hook,
call_status_hook: application.call_status_hook,
@@ -106,6 +106,9 @@ async function validateCreateCall(logger, sid, req) {
}
}
else {
delete obj.application_sid;
// TODO: these should be retrieved from account, using account_sid if provided
Object.assign(obj, {
speech_synthesis_vendor: 'google',
speech_synthesis_voice: 'en-US-Wavenet-C',
@@ -258,13 +261,21 @@ router.put('/:sid', async(req, res) => {
*/
router.post('/:sid/Calls', async(req, res) => {
const sid = req.params.sid;
const logger = req.app.locals.logger;
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
const {retrieveSet, logger} = req.app.locals;
try {
const serviceUrl = process.env.JAMBONES_CREATE_CALL_URL;
const fs = await retrieveSet(setName);
if (0 === fs.length) {
logger.info('No available feature servers to handle createCall API request');
return res.json({msg: 'no available feature servers at this time'}).status(500);
}
const ip = fs[idx++ % fs.length];
logger.info({fs}, `feature servers available for createCall API request, selecting ${ip}`);
const serviceUrl = `http://${ip}:3000/v1/createCall`;
await validateCreateCall(logger, sid, req);
logger.debug({payload: req.body}, `sending POST to ${serviceUrl}`);
logger.debug({payload: req.body}, `sending createCall API request to to ${ip}`);
request({
url: serviceUrl,
method: 'POST',
@@ -272,11 +283,11 @@ router.post('/:sid/Calls', async(req, res) => {
body: req.body
}, (err, response, body) => {
if (err) {
logger.error(err, `Error sending createCall POST to ${serviceUrl}`);
logger.error(err, `Error sending createCall POST to ${ip}`);
return res.sendStatus(500);
}
if (response.statusCode !== 201) {
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${serviceUrl}`);
logger.error({statusCode: response.statusCode}, `Non-success response returned by createCall ${ip}`);
return res.sendStatus(500);
}
res.status(201).json(body);

View File

@@ -11,7 +11,7 @@ const decorators = {
'delete': remove
};
function decorate(router, klass, methods, preconditions) {
function decorate(router, klass, methods, preconditions = {}) {
const decs = methods && Array.isArray(methods) && methods[0] !== '*' ? methods : Object.keys(decorators);
decs.forEach((m) => {
assert(m in decorators);

View File

@@ -15,5 +15,9 @@ api.use('/PhoneNumbers', isAdminScope, require('./phone-numbers'));
api.use('/ApiKeys', require('./api-keys'));
api.use('/Accounts', require('./accounts'));
api.use('/Applications', require('./applications'));
api.use('/MicrosoftTeamsTenants', require('./tenants'));
api.use('/Sbcs', isAdminScope, require('./sbcs'));
api.use('/Users', require('./users'));
api.use('/login', require('./login'));
module.exports = api;

75
lib/routes/api/login.js Normal file
View File

@@ -0,0 +1,75 @@
const router = require('express').Router();
const crypto = require('crypto');
const {getMysqlConnection} = require('../../db');
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';
const sha512 = function(password, salt) {
const hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
hash.update(password);
var value = hash.digest('hex');
return {
salt:salt,
passwordHash:value
};
};
router.post('/', (req, res) => {
const logger = req.app.locals.logger;
const {username, password} = req.body;
if (!username || !password) {
logger.info('Bad POST to /login is missing username or password');
return res.sendStatus(400);
}
getMysqlConnection((err, conn) => {
if (err) {
logger.error({err}, 'Error getting db connection');
return res.sendStatus(500);
}
conn.query(retrieveSql, [username], (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 salt = results[0].salt;
const trueHash = results[0].hashed_password;
const forceChange = results[0].force_change;
const {passwordHash} = sha512(password, salt);
if (trueHash !== passwordHash) return res.sendStatus(403);
if (forceChange) return res.json({user_sid: results[0].user_sid, force_change: true});
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, token: tokenResults[0].token});
});
});
});
});
});
module.exports = router;

19
lib/routes/api/sbcs.js Normal file
View File

@@ -0,0 +1,19 @@
const router = require('express').Router();
const Sbc = require('../../models/sbc');
const decorate = require('./decorate');
const sysError = require('./error');
decorate(router, Sbc, ['add', 'delete']);
/* list */
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
try {
const results = await Sbc.retrieveAll(req.query.service_provider_sid);
res.status(200).json(results);
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -1,6 +1,8 @@
const router = require('express').Router();
const {DbErrorUnprocessableRequest} = require('../../utils/errors');
const Webhook = require('../../models/webhook');
const ServiceProvider = require('../../models/service-provider');
const sysError = require('./error');
const decorate = require('./decorate');
const preconditions = {
'delete': noActiveAccounts
@@ -12,6 +14,89 @@ async function noActiveAccounts(req, sid) {
if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts');
}
decorate(router, ServiceProvider, ['*'], preconditions);
decorate(router, ServiceProvider, ['delete'], preconditions);
/* add */
router.post('/', async(req, res) => {
const logger = req.app.locals.logger;
try {
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['registration_hook']) {
if (obj[prop]) {
obj[`${prop}_sid`] = await Webhook.make(obj[prop]);
delete obj[prop];
}
}
//logger.debug(`Attempting to add account ${JSON.stringify(obj)}`);
const uuid = await ServiceProvider.make(obj);
res.status(201).json({sid: uuid});
} catch (err) {
sysError(logger, res, err);
}
});
/* list */
router.get('/', async(req, res) => {
const logger = req.app.locals.logger;
try {
const results = await ServiceProvider.retrieveAll();
res.status(200).json(results);
} catch (err) {
sysError(logger, res, err);
}
});
/* retrieve */
router.get('/:sid', async(req, res) => {
const logger = req.app.locals.logger;
try {
const results = await ServiceProvider.retrieve(req.params.sid);
if (results.length === 0) return res.status(404).end();
return res.status(200).json(results[0]);
}
catch (err) {
sysError(logger, res, err);
}
});
/* update */
router.put('/:sid', async(req, res) => {
const sid = req.params.sid;
const logger = req.app.locals.logger;
try {
// create webhooks if provided
const obj = Object.assign({}, req.body);
for (const prop of ['registration_hook']) {
if (prop in obj && Object.keys(obj[prop]).length) {
if ('webhook_sid' in obj[prop]) {
const sid = obj[prop]['webhook_sid'];
delete obj[prop]['webhook_sid'];
await Webhook.update(sid, obj[prop]);
}
else {
const sid = await Webhook.make(obj[prop]);
obj[`${prop}_sid`] = sid;
}
}
else {
obj[`${prop}_sid`] = null;
}
delete obj[prop];
}
const rowsAffected = await ServiceProvider.update(sid, obj);
if (rowsAffected === 0) {
return res.status(404).end();
}
res.status(204).end();
} catch (err) {
sysError(logger, res, err);
}
});
module.exports = router;

View File

@@ -0,0 +1,8 @@
const router = require('express').Router();
const Tenant = require('../../models/tenant');
const decorate = require('./decorate');
const preconditions = {};
decorate(router, Tenant, ['*'], preconditions);
module.exports = router;

96
lib/routes/api/users.js Normal file
View File

@@ -0,0 +1,96 @@
const router = require('express').Router();
const crypto = require('crypto');
const {getMysqlConnection} = require('../../db');
const retrieveSql = 'SELECT * from users where user_sid = ?';
const updateSql = 'UPDATE users set hashed_password = ?, salt = ?, force_change = false WHERE user_sid = ?';
const tokenSql = 'SELECT token from api_keys where account_sid IS NULL AND service_provider_sid IS NULL';
const genRandomString = (len) => {
return crypto.randomBytes(Math.ceil(len / 2))
.toString('hex') /** convert to hexadecimal format */
.slice(0, len); /** return required number of characters */
};
const sha512 = function(password, salt) {
const hash = crypto.createHmac('sha512', salt); /** Hashing algorithm sha512 */
hash.update(password);
var value = hash.digest('hex');
return {
salt:salt,
passwordHash:value
};
};
const saltHashPassword = (userpassword) => {
var salt = genRandomString(16); /** Gives us salt of length 16 */
return sha512(userpassword, salt);
};
router.put('/:user_sid', (req, res) => {
const logger = req.app.locals.logger;
const {old_password, new_password} = req.body;
if (!old_password || !new_password) {
logger.info('Bad PUT to /Users is missing old_password or new password');
return res.sendStatus(400);
}
getMysqlConnection((err, conn) => {
if (err) {
logger.error({err}, 'Error getting db connection');
return res.sendStatus(500);
}
conn.query(retrieveSql, [req.params.user_sid], (err, results) => {
conn.release();
if (err) {
logger.error({err}, 'Error getting db connection');
return res.sendStatus(500);
}
if (0 === results.length) {
logger.info(`Failed to find user with sid ${req.params.user_sid}`);
return res.sendStatus(404);
}
logger.info({results}, 'successfully retrieved user');
const old_salt = results[0].salt;
const old_hashed_password = results[0].hashed_password;
const {passwordHash} = sha512(old_password, old_salt);
if (old_hashed_password !== passwordHash) return res.sendStatus(403);
getMysqlConnection((err, conn) => {
if (err) {
logger.error({err}, 'Error getting db connection');
return res.sendStatus(500);
}
const {salt, passwordHash} = saltHashPassword(new_password);
conn.query(updateSql, [passwordHash, salt, req.params.user_sid], (err, r) => {
conn.release();
if (err) {
logger.error({err}, 'Error getting db connection');
return res.sendStatus(500);
}
if (0 === r.changedRows) {
logger.error('Failed updating database with new password');
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, token: tokenResults[0].token});
});
});
});
});
});
});
module.exports = router;

View File

@@ -12,6 +12,92 @@ servers:
- url: /v1
description: development server
paths:
/Sbcs:
post:
summary: add an SBC address
operationId: createSbc
requestBody:
content:
application/json:
schema:
type: object
properties:
ipv4:
type: string
port:
type: number
service_provider_sid:
type: string
description: service provider scope for the generated api key
required:
- ipv4
responses:
201:
description: sbc address successfully created
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessfulApiKeyAdd'
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
500:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
get:
summary: retrieve public IP addresses of the jambonz Sbcs
operationId: listSbcs
parameters:
- in: query
name: service_provider_sid
required: false
schema:
type: string
description: return only the SBCs operated for the sole use of this service provider
responses:
200:
description: list of SBC addresses
content:
application/json:
schema:
type: array
items:
properties:
ipv4:
type: string
description: ip address of one of our Sbcs
required:
- ipv4
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Sbcs/{SbcSid}:
parameters:
- name: SbcSid
in: path
required: true
schema:
type: string
delete:
summary: delete sbc address
operationId: deleteSbcAddress
responses:
200:
description: sbc address deleted
404:
description: sbc address not found
/ApiKeys:
post:
summary: create an api key
@@ -28,6 +114,9 @@ paths:
account_sid:
type: string
description: account scope for the generated api key
expiry_secs:
type: number
description: duration of key validity, in seconds
responses:
201:
description: api key successfully created
@@ -64,6 +153,88 @@ paths:
404:
description: api key or account not found
/login:
post:
summary: login a user and receive an api token
operationId: loginUser
requestBody:
content:
application/json:
schema:
type: object
properties:
username:
type: string
description: login username
password:
type: string
description: login password
required:
- username
- password
responses:
200:
description: login succeeded
content:
application/json:
schema: '#/components/schemas/Login'
403:
description: login failed
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Users/{UserSid}:
parameters:
- name: UserSid
in: path
required: true
style: simple
explode: false
schema:
type: string
put:
summary: update a user password
operationId: updateUser
requestBody:
content:
application/json:
schema:
type: object
properties:
old_password:
type: string
description: existing password, which is to be replaced
new_password:
type: string
description: new password
required:
- old_password
- new_password
responses:
200:
description: password successfully changed
content:
application/json:
schema: '#/components/schemas/Login'
403:
description: password change failed
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/VoipCarriers:
post:
summary: create voip carrier
@@ -516,16 +687,12 @@ paths:
description: root domain for group of accounts that share a registration hook
example: example.com
registration_hook:
type: string
format: url
$ref: '#/components/schemas/Webhook'
description: authentication webhook for registration
example: https://mycompany.com
hook_basic_auth_user:
ms_teams_fqdn:
type: string
description: username to use for http basic auth when calling hook
hook_basic_auth_password:
type: string
description: password to use for http basic auth when calling hook
description: SBC domain name for Microsoft Teams
example: contoso.com
required:
- name
responses:
@@ -636,6 +803,140 @@ paths:
schema:
$ref: '#/components/schemas/GeneralError'
/MicrosoftTeamsTenants:
post:
summary: provision a customer tenant for MS Teams
operationId: createMsTeamsTenant
requestBody:
content:
application/json:
schema:
type: object
properties:
service_provider_sid:
type: string
format: uuid
example: 85f9c036-ba61-4f28-b2f5-617c23fa68ff
account_sid:
type: string
format: uuid
example: 85f9c036-ba61-4f28-b2f5-617c23fa68ff
application_sid:
type: string
format: uuid
example: 85f9c036-ba61-4f28-b2f5-617c23fa68ff
tenant_fqdn:
type: string
example: customer.contoso.com
required:
- service_provider_sid
- account
- tenant_fqdn
responses:
201:
description: tenant successfully created
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessfulAdd'
400:
description: bad request
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
get:
summary: list MS Teams tenants
operationId: listMsTeamsTenants
responses:
200:
description: list of tenants
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/MsTeamsTenant'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/MicrosoftTeamsTenants/{TenantSid}:
parameters:
- name: TenantSid
in: path
required: true
schema:
type: string
format: uuid
delete:
summary: delete an MS Teams tenant
operationId: deleteTenant
responses:
204:
description: tenant successfully deleted
404:
description: tenant not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
get:
summary: retrieve an MS Teams tenant
operationId: getTenant
responses:
200:
description: tenant found
content:
application/json:
schema:
$ref: '#/components/schemas/MsTeamsTenant'
404:
description: account not found
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
put:
summary: update tenant
operationId: updateAccount
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Tenant'
responses:
204:
description: tenant updated
404:
description: tenant not found
422:
description: unprocessable entity
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
500:
description: system error
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
/Accounts:
post:
summary: create an account
@@ -1082,7 +1383,7 @@ paths:
from:
type: string
description: The calling party number
example: 16172375089
example: "16172375089"
timeout:
type: integer
description: the number of seconds to wait for call to be answered. Defaults to 60.
@@ -1240,6 +1541,17 @@ components:
scheme: bearer
bearerFormat: token
schemas:
Login:
type: object
properties:
user_sid:
type: string
api_token:
type: string
change_password:
type: boolean
required:
- user_sid
SuccessfulApiKeyAdd:
type: object
required:
@@ -1283,12 +1595,11 @@ components:
type: string
root_domain:
type: string
hook_basic_auth_user:
registration_hook:
$ref: '#/components/schemas/Webhook'
description: authentication webhook for registration
ms_teams_fqdn:
type: string
format: url
hook_basic_auth_password:
type: string
format: url
required:
- service_provider_sid
- name
@@ -1444,6 +1755,23 @@ components:
required:
- url
example: {"url": "https://acme.com", "method": "POST"}
MsTeamsTenant:
type: object
properties:
service_provider_sid:
type: string
format: uuid
account_sid:
type: string
format: uuid
application_sid:
type: string
format: uuid
tenant_fqdn:
type: string
required:
- service_provider_sid
- tenant_fqdn
Call:
type: object
properties:

View File

@@ -1,6 +1,6 @@
{
"name": "jambonz-api-server",
"version": "1.1.4",
"version": "1.1.6",
"description": "",
"main": "app.js",
"scripts": {
@@ -17,8 +17,8 @@
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"jambonz-db-helpers": "^0.3.2",
"jambonz-realtimedb-helpers": "0.1.7",
"@jambonz/db-helpers": "^0.3.8",
"@jambonz/realtimedb-helpers": "0.2.15",
"mysql2": "^2.0.2",
"passport": "^0.4.0",
"passport-http-bearer": "^1.0.1",
@@ -26,17 +26,16 @@
"request": "^2.88.0",
"request-debug": "^0.2.0",
"swagger-ui-express": "^4.1.2",
"tape": "^4.11.0",
"uuid": "^3.3.3",
"yamljs": "^0.3.0"
},
"devDependencies": {
"eslint": "^6.7.2",
"eslint": "^6.8.0",
"eslint-plugin-promise": "^4.2.1",
"nyc": "^14.1.1",
"nyc": "^15.0.1",
"request-promise-native": "^1.0.8",
"tap": "^14.10.2",
"tap-dot": "^2.0.0",
"tap-spec": "^5.0.0"
"tap-spec": "^5.0.0",
"tape": "^4.13.2"
}
}

View File

@@ -45,7 +45,7 @@ test('account tests', async(t) => {
});
let regHook = result[0].registration_hook;
t.ok(result.length === 1 &&
Object.keys(regHook).length == 5, 'successfully queried all accounts');
Object.keys(regHook).length == 4, 'successfully queried all accounts');
/* query one accounts */
result = await request.get(`/Accounts/${sid}`, {
@@ -74,7 +74,7 @@ test('account tests', async(t) => {
json: true,
});
//console.log(`retrieved account after update: ${JSON.stringify(result)}`);
t.ok(Object.keys(result.registration_hook).length === 5, 'successfully removed a hook from account');
t.ok(Object.keys(result.registration_hook).length === 4, 'successfully removed a hook from account');
/* assign phone number to account */
result = await request.put(`/PhoneNumbers/${phone_number_sid}`, {

View File

@@ -6,4 +6,6 @@ require('./accounts');
require('./phone-numbers');
require('./applications');
require('./auth');
require('./sbcs');
require('./ms-teams');
require('./remove-test-db');

89
test/ms-teams.js Normal file
View File

@@ -0,0 +1,89 @@
const test = require('tape').test ;
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'
});
const {createServiceProvider, createAccount, deleteObjectBySid} = require('./utils');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
test('sbc_addresses tests', async(t) => {
const app = require('../app');
let sid;
try {
let result;
const service_provider_sid = await createServiceProvider(request);
const account_sid = await createAccount(request, service_provider_sid);
const account_sid2 = await createAccount(request, service_provider_sid, 'account2');
/* add a tenant */
result = await request.post('/MicrosoftTeamsTenants', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
body: {
service_provider_sid,
account_sid,
tenant_fqdn: 'foo.bar.baz'
}
});
t.ok(result.statusCode === 201, 'successfully added ms teams tenant');
const sid1 = result.body.sid;
/* add a second tenant */
result = await request.post('/MicrosoftTeamsTenants', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
body: {
service_provider_sid,
account_sid: account_sid2,
tenant_fqdn: 'junk.bar.baz'
}
});
t.ok(result.statusCode === 201, 'successfully added ms teams tenant');
const sid2 = result.body.sid;
result = await request.get('/MicrosoftTeamsTenants', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true
});
t.ok(result.body.length === 2, 'successfully retrieved tenants');
/* update tenant */
result = await request.put(`/MicrosoftTeamsTenants/${sid1}`, {
auth: authAdmin,
json: true,
resolveWithFullResponse: true,
body: {
tenant_fqdn: 'foo.bar.bazzle'
}
});
t.ok(result.statusCode === 204, 'successfully updated ms teams tenant');
/* get tenant */
result = await request.get(`/MicrosoftTeamsTenants/${sid1}`, {
auth: authAdmin,
json: true
});
t.ok(result.tenant_fqdn === 'foo.bar.bazzle', 'successfully retrieved ms teams tenant');
await deleteObjectBySid(request, '/MicrosoftTeamsTenants', sid1);
await deleteObjectBySid(request, '/MicrosoftTeamsTenants', sid2);
await deleteObjectBySid(request, '/Accounts', account_sid);
await deleteObjectBySid(request, '/Accounts', account_sid2);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
t.end();
}
catch (err) {
console.error(err);
t.end(err);
}
});

70
test/sbcs.js Normal file
View File

@@ -0,0 +1,70 @@
const test = require('tape').test ;
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'
});
const {createServiceProvider, deleteObjectBySid} = require('./utils');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
test('sbc_addresses tests', async(t) => {
const app = require('../app');
let sid;
try {
let result;
const service_provider_sid = await createServiceProvider(request);
/* add a community sbc */
result = await request.post('/Sbcs', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
body: {
ipv4: '192.168.1.1'
}
});
t.ok(result.statusCode === 201, 'successfully created community sbc ');
const sid1 = result.body.sid;
/* add a service provider sbc */
result = await request.post('/Sbcs', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true,
body: {
ipv4: '192.168.1.4',
service_provider_sid
}
});
t.ok(result.statusCode === 201, 'successfully created service provider sbc ');
const sid2 = result.body.sid;
result = await request.get('/Sbcs', {
resolveWithFullResponse: true,
auth: authAdmin,
json: true
});
t.ok(result.body.length === 1 && result.body[0].ipv4 === '192.168.1.1', 'successfully retrieved community sbc');
result = await request.get(`/Sbcs?service_provider_sid=${service_provider_sid}`, {
resolveWithFullResponse: true,
auth: authAdmin,
json: true
});
t.ok(result.body.length === 1 && result.body[0].ipv4 === '192.168.1.4', 'successfully retrieved service provider sbc');
await deleteObjectBySid(request, '/Sbcs', sid1);
await deleteObjectBySid(request, '/Sbcs', sid2);
await deleteObjectBySid(request, '/ServiceProviders', service_provider_sid);
t.end();
}
catch (err) {
console.error(err);
t.end(err);
}
});

View File

@@ -30,7 +30,8 @@ test('service provider tests', async(t) => {
auth: authAdmin,
json: true,
body: {
name: 'daveh'
name: 'daveh',
ms_teams_fqdn: 'contoso.com'
}
});
t.ok(result.statusCode === 201, 'successfully created service provider');
@@ -43,7 +44,10 @@ test('service provider tests', async(t) => {
json: true,
body: {
name: 'johndoe',
root_domain: 'example.com'
root_domain: 'example.com',
registration_hook: {
url: 'http://a.com'
}
}
});
t.ok(result.statusCode === 201, 'successfully created service provider with a root domain');
@@ -84,11 +88,11 @@ test('service provider tests', async(t) => {
t.ok(result.length === 2 , 'successfully queried all service providers');
/* query one service providers */
result = await request.get(`/ServiceProviders/${sid}`, {
result = await request.get(`/ServiceProviders/${sid2}`, {
auth: authAdmin,
json: true,
});
t.ok(result.name === 'daveh' , 'successfully retrieved service provider by sid');
t.ok(result.name === 'johndoe' && result.root_domain === 'example.com', 'successfully retrieved service provider by sid');
/* update service providers */