mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-01-25 02:08:24 +00:00
revamped db schema and implemented some simple api
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
const Strategy = require('passport-http-bearer').Strategy;
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const sql = `
|
||||
SELECT api_keys.uuid, accounts.uuid
|
||||
SELECT *
|
||||
FROM api_keys
|
||||
LEFT JOIN accounts
|
||||
ON api_keys.account_id = accounts.id`;
|
||||
WHERE api_keys.token = ?`;
|
||||
|
||||
function makeStrategy(logger) {
|
||||
return new Strategy(
|
||||
@@ -15,7 +14,7 @@ function makeStrategy(logger) {
|
||||
logger.error(err, 'Error retrieving mysql connection');
|
||||
return done(err);
|
||||
}
|
||||
conn.query({sql, nestTables: '_'}, [token], (err, results, fields) => {
|
||||
conn.query(sql, [token], (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) {
|
||||
logger.error(err, 'Error querying for api key');
|
||||
@@ -28,9 +27,20 @@ function makeStrategy(logger) {
|
||||
}
|
||||
|
||||
// found api key
|
||||
return done(null,
|
||||
{accountSid: results[0].accounts_uuid},
|
||||
{scope: results[0].accounts_uuid ? ['user'] : ['admin']});
|
||||
logger.info(results, 'api key lookup');
|
||||
const user = {
|
||||
account_sid: results[0].account_sid,
|
||||
service_provider_sid: results[0].service_provider_sid,
|
||||
isAdmin: results[0].account_sid === null && results[0].service_provider_sid === null,
|
||||
isServiceProvider: results[0].service_provider_sid !== null,
|
||||
isUser: results[0].account_sid != null
|
||||
};
|
||||
const scope = [];
|
||||
if (user.isAdmin) scope.push('admin');
|
||||
else if (user.isServiceProvider) scope.push('service_provider');
|
||||
else scope.push('user');
|
||||
logger.info(user, `successfully validated with scope ${scope}`);
|
||||
return done(null, user, {scope});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
31
lib/models/api-key.js
Normal file
31
lib/models/api-key.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const Model = require('./model');
|
||||
|
||||
class ApiKey extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
ApiKey.table = 'api_keys';
|
||||
ApiKey.fields = [
|
||||
{
|
||||
name: 'api_key_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'token',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'account_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'service_provider_sid',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = ApiKey;
|
||||
141
lib/models/model.js
Normal file
141
lib/models/model.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const Emitter = require('events');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const assert = require('assert');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const {DbErrorBadRequest} = require('../utils/errors');
|
||||
|
||||
class Model extends Emitter {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static getPrimaryKey() {
|
||||
return this.fields.find((f) => f.primaryKey === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* check validity of object to be inserted into db
|
||||
*/
|
||||
static checkIsInsertable(obj) {
|
||||
// check all required fields are present
|
||||
const required = this.fields.filter((f) => f.required === true);
|
||||
const missing = required.find((f) => !(f.name in obj));
|
||||
if (missing) throw new DbErrorBadRequest(`missing field ${missing.name}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* insert object into the database
|
||||
*/
|
||||
static make(obj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const pk = this.getPrimaryKey();
|
||||
const uuid = uuidv4();
|
||||
obj[pk.name] = uuid;
|
||||
this.checkIsInsertable(obj);
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(`INSERT into ${this.table} SET ?`,
|
||||
obj,
|
||||
(err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(uuid);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* delete object from database
|
||||
*/
|
||||
static remove(uuid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const pk = this.getPrimaryKey();
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(`DELETE from ${this.table} WHERE ${pk.name} = ?`, uuid, (err, results) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results.affectedRows);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve all objects
|
||||
*/
|
||||
static retrieveAll() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(`SELECT * from ${this.table}`, (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve a specific object
|
||||
*/
|
||||
static retrieve(sid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const pk = this.getPrimaryKey();
|
||||
assert.ok(pk, 'field definitions must include the primary key');
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(`SELECT * from ${this.table} WHERE ${pk.name} = ?`, sid, (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* update an object
|
||||
*/
|
||||
static update(sid, obj) {
|
||||
const pk = this.getPrimaryKey();
|
||||
assert.ok(pk, 'field definitions must include the primary key');
|
||||
return new Promise((resolve, reject) => {
|
||||
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.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results.affectedRows);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static getForeignKeyReferences(fk, sid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const arr = /(.*)\.(.*)/.exec(fk);
|
||||
assert.ok(arr, `foreign key must be written as table.column: ${fk}`);
|
||||
const table = arr[1];
|
||||
const column = arr[2];
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query(`SELECT COUNT(*) as count from ${table} WHERE ${column} = ?`,
|
||||
sid, (err, results, fields) => {
|
||||
conn.release();
|
||||
if (err) return reject(err);
|
||||
resolve(results[0].count);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Model.table = 'subclassResponsibility';
|
||||
Model.fields = [];
|
||||
|
||||
module.exports = Model;
|
||||
@@ -1,23 +1,27 @@
|
||||
const Emitter = require('events');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
const scrubIds = require('../utils/scrub-ids');
|
||||
const Model = require('./model');
|
||||
|
||||
class ServiceProvider extends Emitter {
|
||||
class ServiceProvider extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static retrieveAll() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getMysqlConnection((err, conn) => {
|
||||
if (err) return reject(err);
|
||||
conn.query('SELECT * from service_providers', (err, results, fields) => {
|
||||
if (err) return reject(err);
|
||||
resolve(scrubIds(results));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ServiceProvider.table = 'service_providers';
|
||||
ServiceProvider.fields = [
|
||||
{
|
||||
name: 'service_provider_sid',
|
||||
type: 'string',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = ServiceProvider;
|
||||
|
||||
80
lib/routes/api/api-keys.js
Normal file
80
lib/routes/api/api-keys.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const router = require('express').Router();
|
||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||
const ApiKey = require('../../models/api-key');
|
||||
const {isAdmin, isServiceProvider, isUser} = require('../../utils/scopes');
|
||||
const decorate = require('./decorate');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const assert = require('assert');
|
||||
const sysError = require('./error');
|
||||
const preconditions = {
|
||||
'add': validateAddToken,
|
||||
'delete': validateDeleteToken
|
||||
};
|
||||
|
||||
/**
|
||||
* if user scope, add to the associated account
|
||||
* if admin scope, only admin-level tokens may be created
|
||||
*/
|
||||
function validateAddToken(req) {
|
||||
if (isAdmin(req) && ('account_sid' in req.body)) {
|
||||
throw new DbErrorBadRequest('admin users may not create account-level tokens');
|
||||
}
|
||||
else if (isServiceProvider(req) && (!('account_sid' in req.body) && !('service_provider_sid' in req.body))) {
|
||||
req.body['service_provider_sid'] = req.user.service_provider_sid
|
||||
}
|
||||
else if (isUser(req)) {
|
||||
delete req.body['service_provider_sid'];
|
||||
req.body['account_sid'] = req.user.account_sid;
|
||||
}
|
||||
req.body.token = uuidv4();
|
||||
}
|
||||
|
||||
/**
|
||||
* admin users can only delete admin tokens or service provider tokens
|
||||
* service_provider users can delete service provider or user tokens
|
||||
* user-scope may only delete their own tokens
|
||||
*/
|
||||
async function validateDeleteToken(req, sid) {
|
||||
const results = await ApiKey.retrieve(sid);
|
||||
if (0 == results.length) return;
|
||||
if (isAdmin(req)) {
|
||||
if (results[0].account_sid) {
|
||||
throw new DbErrorBadRequest('an admin user may not delete account level api keys');
|
||||
}
|
||||
}
|
||||
else if (isServiceProvider(req)) {
|
||||
if (results[0].service_provider_sid === null && results[0].account_sid === null) {
|
||||
throw new DbErrorBadRequest('a service provider user may not delete an admin token');
|
||||
}
|
||||
if (results[0].service_provider_sid && results[0].service_provider_sid != req.user.service_provider_sid) {
|
||||
throw new DbErrorBadRequest('a service provider user may not delete api key from another service provider');
|
||||
}
|
||||
}
|
||||
else if (isUser(req)) {
|
||||
if (results[0].account_sid !== req.user.account_sid) {
|
||||
throw new DbErrorBadRequest('a user may not delete a token associated with a different account');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* need to handle here because response is slightly different than standard for an insert
|
||||
* (returning the token generated along with the sid)
|
||||
*/
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
if ('add' in preconditions) {
|
||||
assert(typeof preconditions.add === 'function');
|
||||
await preconditions.add(req);
|
||||
}
|
||||
const uuid = await ApiKey.make(req.body);
|
||||
res.status(201).json({sid: uuid, token: req.body.token});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
decorate(router, ApiKey, ['delete'], preconditions);
|
||||
|
||||
module.exports = router;
|
||||
101
lib/routes/api/decorate.js
Normal file
101
lib/routes/api/decorate.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const assert = require('assert');
|
||||
const sysError = require('./error');
|
||||
|
||||
module.exports = decorate;
|
||||
|
||||
const decorators = {
|
||||
'list': list,
|
||||
'add': add,
|
||||
'retrieve': retrieve,
|
||||
'update': update,
|
||||
'delete': remove
|
||||
};
|
||||
|
||||
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);
|
||||
decorators[m](router, klass, preconditions);
|
||||
});
|
||||
}
|
||||
|
||||
function list(router, klass) {
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
//logger.info(`user: ${JSON.stringify(req.user)}`);
|
||||
//logger.info(`scope: ${JSON.stringify(req.authInfo.scope)}`);
|
||||
try {
|
||||
const results = await klass.retrieveAll();
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function add(router, klass, preconditions) {
|
||||
router.post('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
if ('add' in preconditions) {
|
||||
assert(typeof preconditions.add === 'function');
|
||||
await preconditions.add(req);
|
||||
}
|
||||
const uuid = await klass.make(req.body);
|
||||
res.status(201).json({sid: uuid});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function retrieve(router, klass) {
|
||||
router.get('/:sid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await klass.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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function update(router, klass) {
|
||||
router.put('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const rowsAffected = await klass.update(sid, req.body);
|
||||
if (rowsAffected === 0) {
|
||||
return res.status(404).end();
|
||||
}
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function remove(router, klass, preconditions) {
|
||||
router.delete('/:sid', async(req, res) => {
|
||||
const sid = req.params.sid;
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
if ('delete' in preconditions) {
|
||||
assert(typeof preconditions.delete === 'function');
|
||||
await preconditions.delete(req, sid);
|
||||
}
|
||||
const rowsAffected = await klass.remove(sid);
|
||||
if (rowsAffected === 0) {
|
||||
logger.info(`unable to delete ${klass.name} with sid ${sid}: not found`);
|
||||
return res.status(404).end();
|
||||
}
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
20
lib/routes/api/error.js
Normal file
20
lib/routes/api/error.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
|
||||
function sysError(logger, res, err) {
|
||||
if (err instanceof DbErrorBadRequest) {
|
||||
logger.error(err, 'invalid client request');
|
||||
return res.status(400).json({msg: err.message});
|
||||
}
|
||||
if (err instanceof DbErrorUnprocessableRequest) {
|
||||
logger.error(err, 'unprocessable request');
|
||||
return res.status(422).json({msg: err.message});
|
||||
}
|
||||
if (err.message.includes('ER_DUP_ENTRY')) {
|
||||
logger.error(err, 'duplicate entry on insert');
|
||||
return res.status(422).json({msg: err.message});
|
||||
}
|
||||
logger.error(err, 'Database error');
|
||||
res.status(500).json({msg: err.message});
|
||||
}
|
||||
|
||||
module.exports = sysError;
|
||||
@@ -1,21 +1,15 @@
|
||||
const api = require('express').Router();
|
||||
const {isAdmin} = require('../../utils/scopes');
|
||||
|
||||
function isAdmin(req, res, next) {
|
||||
if (req.authInfo.scope.includes('admin')) return next();
|
||||
function isAdminScope(req, res, next) {
|
||||
if (isAdmin(req)) return next();
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'insufficient privileges'
|
||||
});
|
||||
}
|
||||
|
||||
function isUser(req, res, next) {
|
||||
if (req.authInfo.scope.includes('user')) return next();
|
||||
res.status(403).json({
|
||||
status: 'fail',
|
||||
message: 'end-user data can not be modified with admin privileges'
|
||||
});
|
||||
}
|
||||
|
||||
api.use('/ServiceProviders', isAdmin, require('./service-providers'));
|
||||
api.use('/ServiceProviders', isAdminScope, require('./service-providers'));
|
||||
api.use('/ApiKeys', require('./api-keys'));
|
||||
|
||||
module.exports = api;
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
const router = require('express').Router();
|
||||
const {DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const decorate = require('./decorate');
|
||||
const preconditions = {
|
||||
'delete': noActiveAccounts
|
||||
};
|
||||
|
||||
function sysError(logger, res, err) {
|
||||
logger.error(err, 'Database error');
|
||||
res.status(500).end();
|
||||
/* can not delete a service provider if it has any active accounts */
|
||||
async function noActiveAccounts(req, sid) {
|
||||
const activeAccounts = await ServiceProvider.getForeignKeyReferences('accounts.service_provider_sid', sid);
|
||||
if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts');
|
||||
}
|
||||
|
||||
/* return list of all service providers */
|
||||
router.get('/', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
logger.info(`user: ${JSON.stringify(req.user)}`);
|
||||
logger.info(`scope: ${JSON.stringify(req.authInfo.scope)}`);
|
||||
try {
|
||||
const results = await ServiceProvider.retrieveAll();
|
||||
res.status(200).json(results);
|
||||
} catch (err) {
|
||||
logger.error(err, 'Error retrieving service providers');
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* add a service provider */
|
||||
router.post('/', (req, res) => {
|
||||
|
||||
});
|
||||
decorate(router, ServiceProvider, ['*'], preconditions);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -12,6 +12,58 @@ servers:
|
||||
- url: /v1
|
||||
description: development server
|
||||
paths:
|
||||
/Apikeys:
|
||||
post:
|
||||
summary: create an api key
|
||||
operationId: createApikey
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
service_provider_sid:
|
||||
type: string
|
||||
description: service provider scope for the generated api key
|
||||
account_sid:
|
||||
type: string
|
||||
description: account scope for the generated api key
|
||||
responses:
|
||||
201:
|
||||
description: api key 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'
|
||||
/Apikeys/{ApiKeySid}:
|
||||
parameters:
|
||||
- name: ApiKeySid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
delete:
|
||||
summary: delete api key
|
||||
operationId: deleteApiKey
|
||||
responses:
|
||||
200:
|
||||
description: api key deleted
|
||||
404:
|
||||
description: api key or account not found
|
||||
|
||||
/ServiceProviders:
|
||||
post:
|
||||
summary: create service provider
|
||||
@@ -35,17 +87,14 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- serviceProviderSid
|
||||
properties:
|
||||
serviceProviderSid:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 2531329f-fb09-4ef7-887e-84e648214436
|
||||
400:
|
||||
description: bad request
|
||||
409:
|
||||
description: an existing service provider already exists with this name
|
||||
$ref: '#/components/schemas/SuccessfulAdd'
|
||||
422:
|
||||
description: unprocessable entity
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
get:
|
||||
summary: list service providers
|
||||
operationId: listServiceProviders
|
||||
@@ -71,12 +120,16 @@ paths:
|
||||
summary: delete a service provider
|
||||
operationId: deleteServiceProvider
|
||||
responses:
|
||||
200:
|
||||
204:
|
||||
description: service provider successfully deleted
|
||||
404:
|
||||
description: service provider not found
|
||||
409:
|
||||
description: service provider with active accounts can not be deleted
|
||||
422:
|
||||
description: unprocessable entity
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
get:
|
||||
summary: retrieve service provider
|
||||
operationId: getServiceProvider
|
||||
@@ -98,14 +151,16 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServiceProvider'
|
||||
responses:
|
||||
200:
|
||||
204:
|
||||
description: service provider updated
|
||||
404:
|
||||
description: service provider not found
|
||||
422:
|
||||
description: unprocessable entity
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServiceProvider'
|
||||
404:
|
||||
description: service provider not found
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/ServiceProviders/{ServiceProviderSid}/Accounts:
|
||||
parameters:
|
||||
- name: ServiceProviderSid
|
||||
@@ -640,8 +695,8 @@ paths:
|
||||
|
||||
/Accounts/{AccountSid}/Apikeys:
|
||||
post:
|
||||
summary: create api key
|
||||
operationId: createApikey
|
||||
summary: create an account level api key
|
||||
operationId: createAccountApikey
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
in: path
|
||||
@@ -660,22 +715,22 @@ paths:
|
||||
- apiKey
|
||||
- apiKeySid
|
||||
properties:
|
||||
apiKeySid:
|
||||
api_key_sid:
|
||||
type: string
|
||||
description: system identifier for api key that was created
|
||||
format: uuid
|
||||
example: 7531328e-eb08-4eff-887e-84e648214872
|
||||
apiKey:
|
||||
token:
|
||||
type: string
|
||||
description: api key
|
||||
description: api key authorization token
|
||||
format: uuid
|
||||
example: 2531329f-fb09-4ef7-887e-84e648214436
|
||||
404:
|
||||
description: Account not found
|
||||
/Accounts/{AccountSid}/Apikeys/{ApiKeySid}:
|
||||
delete:
|
||||
summary: delete api key
|
||||
operationId: deleteApiKey
|
||||
summary: delete account-level api key
|
||||
operationId: deleteAccountApiKey
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
in: path
|
||||
@@ -775,121 +830,171 @@ components:
|
||||
scheme: bearer
|
||||
bearerFormat: token
|
||||
schemas:
|
||||
SuccessfulApiKeyAdd:
|
||||
type: object
|
||||
required:
|
||||
- sid
|
||||
- token
|
||||
properties:
|
||||
sid:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
example:
|
||||
sid: 9d26a637-1679-471f-8da8-7150266e1254
|
||||
token: 589cead6-de24-4689-8ac3-08ffaf102811
|
||||
SuccessfulAdd:
|
||||
type: object
|
||||
required:
|
||||
- sid
|
||||
properties:
|
||||
sid:
|
||||
type: string
|
||||
example:
|
||||
sid: 9d26a637-1679-471f-8da8-7150266e1254
|
||||
GeneralError:
|
||||
type: object
|
||||
required:
|
||||
- msg
|
||||
properties:
|
||||
msg:
|
||||
type: string
|
||||
example:
|
||||
msg: cannot delete service provider with active accounts
|
||||
ServiceProvider:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
uuid:
|
||||
service_provider_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
required:
|
||||
- service_provider_sid
|
||||
- name
|
||||
VoipCarrier:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
uuid:
|
||||
voip_carrier_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
required:
|
||||
- voip_carrier_sid
|
||||
- name
|
||||
Account:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
uuid:
|
||||
account_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
sipRealm:
|
||||
name:
|
||||
type: string
|
||||
registrationHook:
|
||||
sip_realm:
|
||||
type: string
|
||||
registration_hook:
|
||||
type: string
|
||||
format: url
|
||||
serviceProvider:
|
||||
service_provider:
|
||||
$ref: '#/components/schemas/ServiceProvider'
|
||||
required:
|
||||
- account_sid
|
||||
- name
|
||||
- service_provider
|
||||
Application:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
uuid:
|
||||
application_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
account:
|
||||
$ref: '#/components/schemas/Account'
|
||||
callHook:
|
||||
call_hook:
|
||||
type: string
|
||||
format: url
|
||||
callBackupHook:
|
||||
type: string
|
||||
format: url
|
||||
callStatusChangeHook:
|
||||
call_status_hook:
|
||||
type: string
|
||||
format: url
|
||||
required:
|
||||
- application_sid
|
||||
- name
|
||||
- account
|
||||
- call_hook
|
||||
- call_status_hook
|
||||
PhoneNumber:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
uuid:
|
||||
phone_number_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
number:
|
||||
type: string
|
||||
voipCarrier:
|
||||
voip_carrier:
|
||||
$ref: '#/components/schemas/VoipCarrier'
|
||||
account:
|
||||
$ref: '#/components/schemas/Account'
|
||||
application:
|
||||
$ref: '#/components/schemas/Application'
|
||||
RegisteredUser:
|
||||
required:
|
||||
- phone_number_sid
|
||||
- number
|
||||
- voip_carrier
|
||||
- account
|
||||
Registration:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
uuid:
|
||||
registration_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
username:
|
||||
type: string
|
||||
domain:
|
||||
type: string
|
||||
sip_contact:
|
||||
type: string
|
||||
sip_user_agent:
|
||||
type: string
|
||||
required:
|
||||
- registration_sid
|
||||
- username
|
||||
- domain
|
||||
- sip_contact
|
||||
Call:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
uuid:
|
||||
call_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
application:
|
||||
$ref: '#/components/schemas/Application'
|
||||
parentCall:
|
||||
parent_call:
|
||||
$ref: '#/components/schemas/Call'
|
||||
direction:
|
||||
type: string
|
||||
enum:
|
||||
- inbound
|
||||
- outbound
|
||||
phoneNumber:
|
||||
phone_number:
|
||||
$ref: '#/components/schemas/PhoneNumber'
|
||||
inboundUser:
|
||||
inbound_user:
|
||||
$ref: '#/components/schemas/RegisteredUser'
|
||||
outboundUser:
|
||||
outbound_user:
|
||||
$ref: '#/components/schemas/RegisteredUser'
|
||||
callingNumber:
|
||||
calling_number:
|
||||
type: string
|
||||
calledNumber:
|
||||
called_number:
|
||||
type: string
|
||||
required:
|
||||
- call_sid
|
||||
- application
|
||||
- direction
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
23
lib/utils/errors.js
Normal file
23
lib/utils/errors.js
Normal file
@@ -0,0 +1,23 @@
|
||||
class DbError extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
class DbErrorBadRequest extends DbError {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
class DbErrorUnprocessableRequest extends DbError {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DbError,
|
||||
DbErrorBadRequest,
|
||||
DbErrorUnprocessableRequest
|
||||
};
|
||||
17
lib/utils/scopes.js
Normal file
17
lib/utils/scopes.js
Normal file
@@ -0,0 +1,17 @@
|
||||
function isAdmin(req) {
|
||||
return req.authInfo.scope.includes('admin');
|
||||
}
|
||||
|
||||
function isServiceProvider(req) {
|
||||
return req.authInfo.scope.includes('service_provider');
|
||||
}
|
||||
|
||||
function isUser(req) {
|
||||
return req.authInfo.scope.includes('user');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAdmin,
|
||||
isServiceProvider,
|
||||
isUser
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = function(tuples) {
|
||||
return tuples.map((t) => {
|
||||
delete t.id;
|
||||
return t;
|
||||
});
|
||||
};
|
||||
23
lib/utils/transforms.js
Normal file
23
lib/utils/transforms.js
Normal file
@@ -0,0 +1,23 @@
|
||||
function scrubIds(tuples) {
|
||||
return tuples.map((t) => {
|
||||
delete t.id;
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
function rewriteKeys(tuples, obj) {
|
||||
return tuples.map((t) => {
|
||||
Object.keys(obj).forEach((k) => {
|
||||
if (k in t) {
|
||||
t[obj[k]] = t[k];
|
||||
delete t[k];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
scrubIds,
|
||||
rewriteKeys
|
||||
};
|
||||
Reference in New Issue
Block a user