mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2026-07-04 19:21:53 +00:00
add list api keys for account, track last_used for api_keys
This commit is contained in:
@@ -56,6 +56,8 @@ token CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36),
|
||||
service_provider_sid CHAR(36),
|
||||
expires_at TIMESTAMP,
|
||||
last_used TIMESTAMP ON UPDATE NOW(),
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
PRIMARY KEY (api_key_sid)
|
||||
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
|
||||
|
||||
|
||||
+20
-9
@@ -137,12 +137,12 @@
|
||||
<comment><![CDATA[An authorization token that is used to access the REST api]]></comment>
|
||||
<tableType><![CDATA[InnoDB]]></tableType>
|
||||
<location>
|
||||
<x>1302.00</x>
|
||||
<y>61.00</y>
|
||||
<x>1319.00</x>
|
||||
<y>38.00</y>
|
||||
</location>
|
||||
<size>
|
||||
<width>252.00</width>
|
||||
<height>120.00</height>
|
||||
<height>160.00</height>
|
||||
</size>
|
||||
<zorder>1</zorder>
|
||||
<SQLField>
|
||||
@@ -196,6 +196,17 @@
|
||||
<type><![CDATA[TIMESTAMP]]></type>
|
||||
<uid><![CDATA[DE86BC18-858E-4D7E-9B83-891DB2861434]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[last_used]]></name>
|
||||
<type><![CDATA[TIMESTAMP]]></type>
|
||||
<uid><![CDATA[11A93288-B892-436B-9BB4-D5C3B70DB061]]></uid>
|
||||
</SQLField>
|
||||
<SQLField>
|
||||
<name><![CDATA[created_at]]></name>
|
||||
<type><![CDATA[TIMESTAMP]]></type>
|
||||
<defaultValue><![CDATA[NOW()]]></defaultValue>
|
||||
<uid><![CDATA[C84C9B6A-80B5-4B0B-8C14-EB02F7421BBE]]></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>
|
||||
@@ -1072,17 +1083,17 @@
|
||||
<overviewPanelHidden><![CDATA[0]]></overviewPanelHidden>
|
||||
<pageBoundariesVisible><![CDATA[0]]></pageBoundariesVisible>
|
||||
<PageGridVisible><![CDATA[0]]></PageGridVisible>
|
||||
<RightSidebarWidth><![CDATA[1788.000000]]></RightSidebarWidth>
|
||||
<RightSidebarWidth><![CDATA[1944.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[1250.000000]]></windowHeight>
|
||||
<windowLocationX><![CDATA[408.000000]]></windowLocationX>
|
||||
<windowLocationY><![CDATA[94.000000]]></windowLocationY>
|
||||
<windowScrollOrigin><![CDATA[{0, 0.5}]]></windowScrollOrigin>
|
||||
<windowWidth><![CDATA[2065.000000]]></windowWidth>
|
||||
<windowHeight><![CDATA[1194.000000]]></windowHeight>
|
||||
<windowLocationX><![CDATA[26.000000]]></windowLocationX>
|
||||
<windowLocationY><![CDATA[100.000000]]></windowLocationY>
|
||||
<windowScrollOrigin><![CDATA[{0, 5}]]></windowScrollOrigin>
|
||||
<windowWidth><![CDATA[2221.000000]]></windowWidth>
|
||||
</SQLDocumentInfo>
|
||||
<AllowsIndexRenamingOnInsert><![CDATA[1]]></AllowsIndexRenamingOnInsert>
|
||||
<defaultLabelExpanded><![CDATA[1]]></defaultLabelExpanded>
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
const Model = require('./model');
|
||||
const {getMysqlConnection} = require('../db');
|
||||
|
||||
class ApiKey extends Model {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* list all api keys for an account
|
||||
*/
|
||||
static retrieveAll(account_sid) {
|
||||
const sql = account_sid ?
|
||||
'SELECT * from api_keys WHERE account_sid = ?' :
|
||||
'SELECT * from api_keys WHERE account_sid IS NULL';
|
||||
const args = account_sid ? [account_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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* update last_used api key for an account
|
||||
*/
|
||||
static updateLastUsed(account_sid) {
|
||||
const sql = 'UPDATE api_keys SET last_used = NOW() WHERE account_sid = ?';
|
||||
const args = [account_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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ApiKey.table = 'api_keys';
|
||||
ApiKey.fields = [
|
||||
{
|
||||
@@ -25,6 +67,18 @@ ApiKey.fields = [
|
||||
{
|
||||
name: 'service_provider_sid',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
name: 'expires_at',
|
||||
type: 'date'
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
type: 'date'
|
||||
},
|
||||
{
|
||||
name: 'last_used',
|
||||
type: 'date'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ const request = require('request');
|
||||
const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors');
|
||||
const Account = require('../../models/account');
|
||||
const Webhook = require('../../models/webhook');
|
||||
const ApiKey = require('../../models/api-key');
|
||||
const ServiceProvider = require('../../models/service-provider');
|
||||
const decorate = require('./decorate');
|
||||
const snakeCase = require('../../utils/snake-case');
|
||||
@@ -27,6 +28,15 @@ function coerceNumbers(callInfo) {
|
||||
return callInfo;
|
||||
}
|
||||
|
||||
async function updateLastUsed(logger, sid, req) {
|
||||
if (req.user.hasAdminAuth || req.user.hasServiceProviderAuth) return;
|
||||
try {
|
||||
await ApiKey.updateLastUsed(sid);
|
||||
} catch (err) {
|
||||
logger.error({err}, `Error updating last used for accountSid ${sid}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateUpdateCall(opts) {
|
||||
// only one type of update can be supplied per request
|
||||
const hasWhisper = opts.whisper;
|
||||
@@ -251,6 +261,19 @@ router.put('/:sid', async(req, res) => {
|
||||
return res.status(404).end();
|
||||
}
|
||||
res.status(204).end();
|
||||
updateLastUsed(logger, sid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
});
|
||||
|
||||
/* retrieve account level api keys */
|
||||
router.get('/:sid/ApiKeys', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
try {
|
||||
const results = await ApiKey.retrieveAll(req.params.sid);
|
||||
res.status(200).json(results);
|
||||
updateLastUsed(logger, req.params.sid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -276,6 +299,7 @@ router.post('/:sid/Calls', async(req, res) => {
|
||||
await validateCreateCall(logger, sid, req);
|
||||
|
||||
logger.debug({payload: req.body}, `sending createCall API request to to ${ip}`);
|
||||
updateLastUsed(logger, sid, req).catch((err) => {});
|
||||
request({
|
||||
url: serviceUrl,
|
||||
method: 'POST',
|
||||
@@ -308,6 +332,7 @@ router.get('/:sid/Calls', async(req, res) => {
|
||||
const calls = await listCalls(accountSid);
|
||||
logger.debug(`retrieved ${calls.length} calls for account sid ${accountSid}`);
|
||||
res.status(200).json(coerceNumbers(snakeCase(calls)));
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -331,6 +356,7 @@ router.get('/:sid/Calls/:callSid', async(req, res) => {
|
||||
logger.debug(`call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -354,6 +380,7 @@ router.delete('/:sid/Calls/:callSid', async(req, res) => {
|
||||
logger.debug(`call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
@@ -384,6 +411,7 @@ router.post('/:sid/Calls/:callSid', async(req, res) => {
|
||||
logger.debug(`updateCall: call not found for call sid ${callSid}`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
updateLastUsed(logger, accountSid, req).catch((err) => {});
|
||||
} catch (err) {
|
||||
sysError(logger, res, err);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessfulApiKeyAdd'
|
||||
$ref: '#/components/schemas/SuccessfulAdd'
|
||||
400:
|
||||
description: bad request
|
||||
content:
|
||||
@@ -222,7 +222,8 @@ paths:
|
||||
description: password successfully changed
|
||||
content:
|
||||
application/json:
|
||||
schema: '#/components/schemas/Login'
|
||||
schema:
|
||||
$ref: '#/components/schemas/Login'
|
||||
403:
|
||||
description: password change failed
|
||||
content:
|
||||
@@ -1201,6 +1202,34 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
/Accounts/{AccountSid}/ApiKeys:
|
||||
parameters:
|
||||
- name: AccountSid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
summary: get all api keys for an account
|
||||
operationId: getAccountApiKeys
|
||||
responses:
|
||||
200:
|
||||
description: list of api keys
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ApiKey'
|
||||
404:
|
||||
description: account not found
|
||||
500:
|
||||
description: system error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralError'
|
||||
|
||||
/Applications:
|
||||
post:
|
||||
@@ -1701,6 +1730,30 @@ components:
|
||||
- account_sid
|
||||
- inbound_hook
|
||||
- inbound_status_hook
|
||||
ApiKey:
|
||||
type: object
|
||||
properties:
|
||||
api_key_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
token:
|
||||
type: string
|
||||
format: uuid
|
||||
account_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
service_provider_sid:
|
||||
type: string
|
||||
format: uuid
|
||||
expires_at:
|
||||
type: dateTime
|
||||
created_at:
|
||||
type: dateTime
|
||||
last_used:
|
||||
type: dateTime
|
||||
required:
|
||||
- api_key_sid
|
||||
- token
|
||||
PhoneNumber:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jambonz-api-server",
|
||||
"version": "1.1.6",
|
||||
"version": "1.1.7",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
@@ -15,27 +15,27 @@
|
||||
"url": "https://github.com/jambonz/jambonz-api-server.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"@jambonz/db-helpers": "^0.3.8",
|
||||
"@jambonz/realtimedb-helpers": "0.2.15",
|
||||
"mysql2": "^2.0.2",
|
||||
"passport": "^0.4.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"mysql2": "^2.1.0",
|
||||
"passport": "^0.4.1",
|
||||
"passport-http-bearer": "^1.0.1",
|
||||
"pino": "^5.14.0",
|
||||
"request": "^2.88.0",
|
||||
"pino": "^5.17.0",
|
||||
"request": "^2.88.2",
|
||||
"request-debug": "^0.2.0",
|
||||
"swagger-ui-express": "^4.1.2",
|
||||
"uuid": "^3.3.3",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"uuid": "^3.4.0",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"nyc": "^15.0.1",
|
||||
"request-promise-native": "^1.0.8",
|
||||
"nyc": "^15.1.0",
|
||||
"request-promise-native": "^1.0.9",
|
||||
"tap-dot": "^2.0.0",
|
||||
"tap-spec": "^5.0.0",
|
||||
"tape": "^4.13.2"
|
||||
"tape": "^4.13.3"
|
||||
}
|
||||
}
|
||||
|
||||
+37
-5
@@ -4,7 +4,11 @@ const authAdmin = {bearer: ADMIN_TOKEN};
|
||||
const request = require('request-promise-native').defaults({
|
||||
baseUrl: 'http://127.0.0.1:3000/v1'
|
||||
});
|
||||
const {createVoipCarrier, createServiceProvider, createPhoneNumber, deleteObjectBySid} = require('./utils');
|
||||
const {
|
||||
createVoipCarrier,
|
||||
createServiceProvider,
|
||||
createPhoneNumber,
|
||||
deleteObjectBySid} = require('./utils');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
@@ -38,6 +42,26 @@ test('account tests', async(t) => {
|
||||
t.ok(result.statusCode === 201, 'successfully created account');
|
||||
const sid = result.body.sid;
|
||||
|
||||
/* add an account level api key */
|
||||
result = await request.post(`/ApiKeys`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
account_sid: sid
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 201 && result.body.token, 'successfully created account level token');
|
||||
const apiKeySid = result.body.sid;
|
||||
const accountLevelToken = result.body.token;
|
||||
|
||||
/* query all account level api keys */
|
||||
result = await request.get(`/Accounts/${sid}/ApiKeys`, {
|
||||
auth: {bearer: accountLevelToken},
|
||||
json: true,
|
||||
});
|
||||
t.ok(Array.isArray(result) && result.length === 1, 'successfully queried account level keys');
|
||||
|
||||
/* query all accounts */
|
||||
result = await request.get('/Accounts', {
|
||||
auth: authAdmin,
|
||||
@@ -54,9 +78,9 @@ test('account tests', async(t) => {
|
||||
});
|
||||
t.ok(result.name === 'daveh' , 'successfully retrieved account by sid');
|
||||
|
||||
/* update accounts */
|
||||
/* update account with account level token */
|
||||
result = await request.put(`/Accounts/${sid}`, {
|
||||
auth: authAdmin,
|
||||
auth: {bearer: accountLevelToken},
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
body: {
|
||||
@@ -67,8 +91,15 @@ test('account tests', async(t) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
t.ok(result.statusCode === 204, 'successfully updated account');
|
||||
t.ok(result.statusCode === 204, 'successfully updated account using account level token');
|
||||
|
||||
/* verify that account level api key last_used was updated*/
|
||||
result = await request.get(`/Accounts/${sid}/ApiKeys`, {
|
||||
auth: {bearer: accountLevelToken},
|
||||
json: true,
|
||||
});
|
||||
t.ok(typeof result[0].last_used === 'string', 'api_key last_used timestamp was updated');
|
||||
|
||||
result = await request.get(`/Accounts/${sid}`, {
|
||||
auth: authAdmin,
|
||||
json: true,
|
||||
@@ -97,6 +128,7 @@ test('account tests', async(t) => {
|
||||
t.ok(result.statusCode === 422 && result.body.msg === 'cannot delete account with phone numbers', 'cannot delete account with phone numbers');
|
||||
|
||||
/* delete account */
|
||||
await request.delete(`ApiKeys/${apiKeySid}`, {auth: {bearer: accountLevelToken}});
|
||||
await request.delete(`/PhoneNumbers/${phone_number_sid}`, {auth: authAdmin});
|
||||
result = await request.delete(`/Accounts/${sid}`, {
|
||||
auth: authAdmin,
|
||||
@@ -109,7 +141,7 @@ test('account tests', async(t) => {
|
||||
t.end();
|
||||
}
|
||||
catch (err) {
|
||||
//console.error(err);
|
||||
console.error(err);
|
||||
t.end(err);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user