mirror of
https://github.com/jambonz/jambonz-api-server.git
synced 2025-12-19 05:47:46 +00:00
Feature/add users api (#77)
* initial changes for jwt auth * return permissions as an array of string * Add JWT expiration environment variable (#74) * allow fromHost in createCall REST API * add JWT_EXPIRES_IN=<mins> env variable, 60 mins by default * add jwt expiration in register.js and signin.js * fix tests - add permissions and scope to encoded obj in jwt Co-authored-by: Dave Horton <daveh@beachdognet.com> Co-authored-by: eglehelms <e.helms@cognigy.com> * return only the jwt-token in the api response * update swagger.yaml * add /users api * apply review comments * add users test case * added User model * bugfix: admin user should be able to create a carrier for a service provider Co-authored-by: EgleH <egle.helms@gmail.com> Co-authored-by: eglehelms <e.helms@cognigy.com>
This commit is contained in:
85
lib/models/user.js
Normal file
85
lib/models/user.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
const Model = require('./model');
|
||||||
|
const {promisePool} = require('../db');
|
||||||
|
const sqlAccount = 'SELECT * FROM users WHERE account_sid = ?';
|
||||||
|
const sqlSP = 'SELECT * FROM users WHERE service_provider_sid = ?';
|
||||||
|
|
||||||
|
class User extends Model {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async retrieveAllForAccount(account_sid) {
|
||||||
|
const [rows] = await promisePool.query(sqlAccount, [account_sid]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async retrieveAllForServiceProvider(service_provider_sid) {
|
||||||
|
const [rows] = await promisePool.query(sqlSP, [service_provider_sid]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
User.table = 'users';
|
||||||
|
User.fields = [
|
||||||
|
{
|
||||||
|
name: 'user_sid',
|
||||||
|
type: 'string',
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pending_email',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phone',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hashed_password',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'account_sid',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'service_provider_sid',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'force_change',
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'provider',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'provider_userid',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email_activation_code',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email_validated',
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'is_active',
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = User;
|
||||||
@@ -26,10 +26,6 @@ router.post('/:sid', async(req, res) => {
|
|||||||
let service_provider_sid;
|
let service_provider_sid;
|
||||||
const {account_sid} = req.user;
|
const {account_sid} = req.user;
|
||||||
if (!account_sid) {
|
if (!account_sid) {
|
||||||
if (!req.user.hasScope('service_provider')) {
|
|
||||||
logger.error({user: req.user}, 'invalid creds');
|
|
||||||
return res.sendStatus(403);
|
|
||||||
}
|
|
||||||
service_provider_sid = parseServiceProviderSid(req);
|
service_provider_sid = parseServiceProviderSid(req);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//const assert = require('assert');
|
|
||||||
//const debug = require('debug')('jambonz:api-server');
|
|
||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
|
const User = require('../../models/user');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
const {DbErrorBadRequest} = require('../../utils/errors');
|
const {DbErrorBadRequest} = require('../../utils/errors');
|
||||||
const {generateHashedPassword, verifyPassword} = require('../../utils/password-utils');
|
const {generateHashedPassword, verifyPassword} = require('../../utils/password-utils');
|
||||||
const {promisePool} = require('../../db');
|
const {promisePool} = require('../../db');
|
||||||
@@ -45,6 +45,55 @@ const validateRequest = async(user_sid, payload) => {
|
|||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
router.get('/', async(req, res) => {
|
||||||
|
const logger = req.app.locals.logger;
|
||||||
|
const token = req.user.jwt;
|
||||||
|
const decodedJwt = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
|
||||||
|
let usersList;
|
||||||
|
try {
|
||||||
|
let results;
|
||||||
|
if (decodedJwt.scope === 'admin') {
|
||||||
|
results = await User.retrieveAll();
|
||||||
|
}
|
||||||
|
else if (decodedJwt.scope === 'account') {
|
||||||
|
results = await User.retrieveAllForAccount(decodedJwt.account_sid);
|
||||||
|
}
|
||||||
|
else if (decodedJwt.scope === 'service_provider') {
|
||||||
|
results = await User.retrieveAllForServiceProvider(decodedJwt.service_provider_sid);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new DbErrorBadRequest(`invalid scope: ${decodedJwt.scope}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.length === 0) throw new Error('failure retrieving users list');
|
||||||
|
|
||||||
|
usersList = results.map((user) => {
|
||||||
|
const {user_sid, name, email, force_change, is_active} = user;
|
||||||
|
let scope;
|
||||||
|
if (!user.account_sid && !user.service_provider_sid) {
|
||||||
|
scope = 'admin';
|
||||||
|
} else if (user.service_provider_sid) {
|
||||||
|
scope = 'service_provider';
|
||||||
|
} else {
|
||||||
|
scope = 'account';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user_sid,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
scope,
|
||||||
|
force_change,
|
||||||
|
is_active
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
sysError(logger, res, err);
|
||||||
|
}
|
||||||
|
res.status(200).json(usersList);
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/me', async(req, res) => {
|
router.get('/me', async(req, res) => {
|
||||||
const logger = req.app.locals.logger;
|
const logger = req.app.locals.logger;
|
||||||
const {user_sid} = req.user;
|
const {user_sid} = req.user;
|
||||||
|
|||||||
@@ -515,6 +515,28 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GeneralError'
|
$ref: '#/components/schemas/GeneralError'
|
||||||
|
/Users:
|
||||||
|
get:
|
||||||
|
summary: list all users
|
||||||
|
operationId: listUsers
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: list of users
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type:
|
||||||
|
array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Users'
|
||||||
|
403:
|
||||||
|
description: unauthorized
|
||||||
|
500:
|
||||||
|
description: system error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GeneralError'
|
||||||
/Users/{UserSid}:
|
/Users/{UserSid}:
|
||||||
parameters:
|
parameters:
|
||||||
- name: UserSid
|
- name: UserSid
|
||||||
|
|||||||
1598
package-lock.json
generated
1598
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -5,7 +5,7 @@
|
|||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
"test": "NODE_ENV=test APPLY_JAMBONZ_DB_LIMITS=1 JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall K8S=true K8S_FEATURE_SERVER_SERVICE_NAME=127.0.0.1 K8S_FEATURE_SERVER_SERVICE_PORT=3100 node test/ ",
|
"test": "NODE_ENV=test JAMBONES_AUTH_USE_JWT=1 APPLY_JAMBONZ_DB_LIMITS=1 JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall K8S=true K8S_FEATURE_SERVER_SERVICE_NAME=127.0.0.1 K8S_FEATURE_SERVER_SERVICE_PORT=3100 node test/ ",
|
||||||
"integration-test": "NODE_ENV=test JAMBONES_TIME_SERIES_HOST=127.0.0.1 AWS_REGION='us-east-1' JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/serve-integration.js",
|
"integration-test": "NODE_ENV=test JAMBONES_TIME_SERIES_HOST=127.0.0.1 AWS_REGION='us-east-1' JAMBONES_CURRENCY=USD JWT_SECRET=foobarbazzle JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug JAMBONES_CREATE_CALL_URL=http://localhost/v1/createCall node test/serve-integration.js",
|
||||||
"upgrade-db": "node ./db/upgrade-jambonz-db.js",
|
"upgrade-db": "node ./db/upgrade-jambonz-db.js",
|
||||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
"url": "https://github.com/jambonz/jambonz-api-server.git"
|
"url": "https://github.com/jambonz/jambonz-api-server.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/speech": "^4.10.2",
|
"@google-cloud/speech": "^5.1.0",
|
||||||
"@google-cloud/text-to-speech": "^3.4.0",
|
"@google-cloud/text-to-speech": "^4.0.3",
|
||||||
"@jambonz/db-helpers": "^0.6.19",
|
"@jambonz/db-helpers": "^0.7.3",
|
||||||
"@jambonz/realtimedb-helpers": "^0.5.1",
|
"@jambonz/realtimedb-helpers": "^0.5.7",
|
||||||
"@jambonz/time-series": "^0.2.5",
|
"@jambonz/time-series": "^0.2.5",
|
||||||
"argon2-ffi": "^2.0.0",
|
"argon2-ffi": "^2.0.0",
|
||||||
"aws-sdk": "^2.1152.0",
|
"aws-sdk": "^2.1152.0",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ require('./sbcs');
|
|||||||
require('./ms-teams');
|
require('./ms-teams');
|
||||||
require('./speech-credentials');
|
require('./speech-credentials');
|
||||||
require('./recent-calls');
|
require('./recent-calls');
|
||||||
|
require('./users');
|
||||||
require('./webapp_tests');
|
require('./webapp_tests');
|
||||||
// require('./homer');
|
// require('./homer');
|
||||||
require('./call-test');
|
require('./call-test');
|
||||||
|
|||||||
54
test/users.js
Normal file
54
test/users.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const test = require('tape') ;
|
||||||
|
const request = require('request-promise-native').defaults({
|
||||||
|
baseUrl: 'http://127.0.0.1:3000/v1'
|
||||||
|
});
|
||||||
|
const exec = require('child_process').exec ;
|
||||||
|
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add an admin user', (t) => {
|
||||||
|
exec(`${__dirname}/../db/reset_admin_password.js`, (err, stdout, stderr) => {
|
||||||
|
console.log(stderr);
|
||||||
|
console.log(stdout);
|
||||||
|
if (err) return t.end(err);
|
||||||
|
t.pass('successfully added admin user');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user tests', async(t) => {
|
||||||
|
const app = require('../app');
|
||||||
|
let sid;
|
||||||
|
try {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
/* login as admin to get a jwt */
|
||||||
|
result = await request.post('/login', {
|
||||||
|
resolveWithFullResponse: true,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.ok(result.statusCode === 200 && result.body.token, 'successfully logged in as admin');
|
||||||
|
|
||||||
|
/* retrieve list of users */
|
||||||
|
const authAdmin = {bearer: result.body.token};
|
||||||
|
result = await request.get(`/Users`, {
|
||||||
|
resolveWithFullResponse: true,
|
||||||
|
json: true,
|
||||||
|
auth: authAdmin,
|
||||||
|
});
|
||||||
|
//console.log(result.body);
|
||||||
|
t.ok(result.statusCode === 200 && result.body.length === 1, 'successfully user list');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
t.end(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user