diff --git a/db/seed-integration-test.sql b/db/seed-integration-test.sql index 75b7010..19245bd 100644 --- a/db/seed-integration-test.sql +++ b/db/seed-integration-test.sql @@ -27,6 +27,14 @@ values ('2708b1b3-2736-40ea-b502-c53d8396247f', 'default service provider', 'sip insert into accounts (account_sid, service_provider_sid, name, webhook_secret) values ('9351f46a-678c-43f5-b8a6-d4eb58d131af','2708b1b3-2736-40ea-b502-c53d8396247f', 'default account', 'wh_secret_cJqgtMDPzDhhnjmaJH6Mtk'); +-- create account level api key +insert into api_keys (api_key_sid, token, service_provider_sid) +values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36fa', '38700987-c7a4-4685-a5bb-af378f9734da', '9351f46a-678c-43f5-b8a6-d4eb58d131af'); + +-- create SP level api key +insert into api_keys (api_key_sid, token, account_sid) +values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36fs', '38700987-c7a4-4685-a5bb-af378f9734ds', '2708b1b3-2736-40ea-b502-c53d8396247f'); + -- create two applications insert into webhooks(webhook_sid, url, method) values diff --git a/lib/routes/api/accounts.js b/lib/routes/api/accounts.js index 47a779c..7f0e0f1 100644 --- a/lib/routes/api/accounts.js +++ b/lib/routes/api/accounts.js @@ -52,7 +52,7 @@ const stripPort = (hostport) => { return hostport; }; -const validateUpdateForCarrier = async(req, account_sid) => { +const validateRequest = async(req, account_sid) => { try { if (req.user.hasScope('admin')) { return; @@ -63,7 +63,7 @@ const validateUpdateForCarrier = async(req, account_sid) => { return; } - throw new DbErrorForbidden('insufficient permissions to update account'); + throw new DbErrorForbidden('insufficient permissions'); } if (req.user.hasScope('service_provider')) { @@ -75,7 +75,7 @@ const validateUpdateForCarrier = async(req, account_sid) => { return; } - throw new DbErrorForbidden('insufficient permissions to update account'); + throw new DbErrorForbidden('insufficient permissions'); } } catch (error) { throw error; @@ -93,6 +93,7 @@ router.get('/:sid/Applications', async(req, res) => { const logger = req.app.locals.logger; try { const account_sid = parseAccountSid(req); + await validateRequest(req, account_sid); const results = await Application.retrieveAll(null, account_sid); res.status(200).json(results); } catch (err) { @@ -103,6 +104,7 @@ router.get('/:sid/VoipCarriers', async(req, res) => { const logger = req.app.locals.logger; try { const account_sid = parseAccountSid(req); + await validateRequest(req, account_sid); const results = await VoipCarrier.retrieveAll(account_sid); res.status(200).json(results); } catch (err) { @@ -115,7 +117,7 @@ router.put('/:sid/VoipCarriers/:voip_carrier_sid', async(req, res) => { try { const sid = parseVoipCarrierSid(req); const account_sid = parseAccountSid(req); - await validateUpdateForCarrier(req, account_sid); + await await validateRequest(req, account_sid); const rowsAffected = await VoipCarrier.update(sid, req.body); if (rowsAffected === 0) { @@ -133,6 +135,8 @@ router.post('/:sid/VoipCarriers', async(req, res) => { const payload = req.body; try { const account_sid = parseAccountSid(req); + await await validateRequest(req, account_sid); + logger.debug({payload}, 'POST /:sid/VoipCarriers'); const uuid = await VoipCarrier.make({ account_sid, @@ -343,7 +347,7 @@ async function validateCreateMessage(logger, sid, req) { async function validateAdd(req) { /* account-level token can not be used to add accounts */ if (req.user.hasAccountAuth) { - throw new DbErrorUnprocessableRequest('insufficient permissions to create accounts'); + throw new DbErrorForbidden('insufficient permissions'); } if (req.user.hasServiceProviderAuth && req.user.service_provider_sid) { /* service providers can only create accounts under themselves */ @@ -365,7 +369,7 @@ async function validateAdd(req) { async function validateUpdate(req, sid) { if (req.user.hasAccountAuth && req.user.account_sid !== sid) { - throw new DbErrorUnprocessableRequest('insufficient privileges to update this account'); + throw new DbErrorForbidden('insufficient privileges'); } if (req.user.hasAccountAuth && req.body.sip_realm) { throw new DbErrorBadRequest('use POST /Accounts/:sid/sip_realm/:realm to set or change the sip realm'); @@ -377,7 +381,7 @@ async function validateUpdate(req, sid) { throw new DbErrorBadRequest(`account not found for sid ${sid}`); } if (result[0].service_provider_sid !== req.user.service_provider_sid) { - throw new DbErrorUnprocessableRequest('cannot update account from different service provider'); + throw new DbErrorForbidden('insufficient privileges'); } } if (req.user.hasScope('admin')) { @@ -397,7 +401,7 @@ async function validateDelete(req, sid) { if (req.user.service_provider_sid && !req.user.hasScope('admin')) { const result = await Account.retrieve(sid); if (result[0].service_provider_sid !== req.user.service_provider_sid) { - throw new DbErrorUnprocessableRequest('cannot delete account from different service provider'); + throw new DbErrorForbidden('insufficient privileges'); } } } @@ -444,6 +448,8 @@ router.get('/:sid', async(req, res) => { const logger = req.app.locals.logger; try { const account_sid = parseAccountSid(req); + await validateRequest(req, account_sid); + const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null; const results = await Account.retrieve(account_sid, service_provider_sid); if (results.length === 0) return res.status(404).end(); @@ -458,6 +464,7 @@ router.get('/:sid/WebhookSecret', async(req, res) => { const logger = req.app.locals.logger; try { const account_sid = parseAccountSid(req); + await validateRequest(req, account_sid); const service_provider_sid = req.user.hasServiceProviderAuth ? req.user.service_provider_sid : null; const results = await Account.retrieve(account_sid, service_provider_sid); if (results.length === 0) return res.status(404).end(); @@ -535,6 +542,7 @@ router.put('/:sid', async(req, res) => { const logger = req.app.locals.logger; try { const sid = parseAccountSid(req); + await await validateRequest(req, sid); // create webhooks if provided const obj = Object.assign({}, req.body); @@ -601,6 +609,7 @@ router.delete('/:sid', async(req, res) => { try { const sid = parseAccountSid(req); + await await validateRequest(req, sid); await validateDelete(req, sid); const [account] = await promisePool.query('SELECT * FROM accounts WHERE account_sid = ?', sid); @@ -672,6 +681,8 @@ router.get('/:sid/ApiKeys', async(req, res) => { try { const sid = parseAccountSid(req); + await await validateRequest(req, sid); + const results = await ApiKey.retrieveAll(sid); res.status(200).json(results); updateLastUsed(logger, sid, req).catch((err) => {}); @@ -694,6 +705,8 @@ router.post('/:sid/Calls', async(req, res) => { try { const sid = parseAccountSid(req); + await await validateRequest(req, sid); + await validateCreateCall(logger, sid, req); updateLastUsed(logger, sid, req).catch((err) => {}); request({ @@ -727,6 +740,7 @@ router.get('/:sid/Calls', async(req, res) => { try { const accountSid = parseAccountSid(req); + await await validateRequest(req, accountSid); const calls = await listCalls(accountSid); logger.debug(`retrieved ${calls.length} calls for account sid ${accountSid}`); res.status(200).json(coerceNumbers(snakeCase(calls))); @@ -744,6 +758,7 @@ router.get('/:sid/Calls/:callSid', async(req, res) => { try { const accountSid = parseAccountSid(req); + await await validateRequest(req, accountSid); const callSid = parseCallSid(req); const callInfo = await retrieveCall(accountSid, callSid); if (callInfo) { @@ -768,6 +783,7 @@ router.delete('/:sid/Calls/:callSid', async(req, res) => { try { const accountSid = parseAccountSid(req); + await await validateRequest(req, accountSid); const callSid = parseCallSid(req); const result = await deleteCall(accountSid, callSid); if (result) { @@ -792,6 +808,7 @@ const updateCall = async(req, res) => { try { const accountSid = parseAccountSid(req); + await await validateRequest(req, accountSid); const callSid = parseCallSid(req); validateUpdateCall(req.body); const call = await retrieveCall(accountSid, callSid); @@ -832,6 +849,8 @@ router.post('/:sid/Messages', async(req, res) => { try { const account_sid = parseAccountSid(req); + await await validateRequest(req, account_sid); + const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`; const serviceUrl = await getFsUrl(logger, retrieveSet, setName); if (!serviceUrl) res.json({msg: 'no available feature servers at this time'}).status(480); diff --git a/lib/routes/api/applications.js b/lib/routes/api/applications.js index 563310b..1125a47 100644 --- a/lib/routes/api/applications.js +++ b/lib/routes/api/applications.js @@ -1,5 +1,5 @@ const router = require('express').Router(); -const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors'); +const {DbErrorBadRequest, DbErrorUnprocessableRequest, DbErrorForbidden} = require('../../utils/errors'); const Application = require('../../models/application'); const Account = require('../../models/account'); const Webhook = require('../../models/webhook'); @@ -13,6 +13,36 @@ const preconditions = { 'update': validateUpdate }; +const validateRequest = async(req, account_sid) => { + try { + if (req.user.hasScope('admin')) { + return; + } + + if (req.user.hasScope('account')) { + if (account_sid === req.user.account_sid) { + return; + } + + throw new DbErrorForbidden('insufficient permissions'); + } + + if (req.user.hasScope('service_provider')) { + const [r] = await promisePool.execute( + 'SELECT service_provider_sid from accounts WHERE account_sid = ?', [account_sid] + ); + + if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) { + return; + } + + throw new DbErrorForbidden('insufficient permissions'); + } + } catch (error) { + throw error; + } +}; + /* only user-level tokens can add applications */ async function validateAdd(req) { if (req.user.account_sid) { @@ -23,7 +53,7 @@ async function validateAdd(req) { if (!req.body.account_sid) throw new DbErrorBadRequest('missing required field: \'account_sid\''); const result = await Account.retrieve(req.body.account_sid, req.user.service_provider_sid); if (result.length === 0) { - throw new DbErrorBadRequest('insufficient privileges to create an application under the specified account'); + throw new DbErrorForbidden('insufficient privileges'); } } if (req.body.call_hook && typeof req.body.call_hook !== 'object') { @@ -35,12 +65,26 @@ async function validateAdd(req) { } async function validateUpdate(req, sid) { - if (req.user.account_sid) { - const app = await Application.retrieve(sid); - if (!app || !app.length || app[0].account_sid !== req.user.account_sid) { - throw new DbErrorBadRequest('you may not update or delete an application associated with a different account'); + const app = await Application.retrieve(sid); + if (req.user.hasAccountAuth) { + if (!app || 0 === app.length || app[0].account_sid !== req.user.account_sid) { + throw new DbErrorForbidden('insufficient privileges'); } } + + if (req.user.hasServiceProviderAuth) { + const [r] = await promisePool.execute( + 'SELECT service_provider_sid from accounts WHERE account_sid = ?', [app[0].account_sid] + ); + + if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) { + return; + } + + throw new DbErrorForbidden('insufficient permissions'); + } + + if (req.body.call_hook && typeof req.body.call_hook !== 'object') { throw new DbErrorBadRequest('\'call_hook\' must be an object when updating an application'); } @@ -50,13 +94,24 @@ async function validateUpdate(req, sid) { } async function validateDelete(req, sid) { + const result = await Application.retrieve(sid); if (req.user.hasAccountAuth) { - const result = await Application.retrieve(sid); if (!result || 0 === result.length) throw new DbErrorBadRequest('application does not exist'); if (result[0].account_sid !== req.user.account_sid) { - throw new DbErrorUnprocessableRequest('cannot delete application owned by a different account'); + throw new DbErrorUnprocessableRequest('insufficient permissions'); } } + if (req.user.hasServiceProviderAuth) { + const [r] = await promisePool.execute( + 'SELECT service_provider_sid from accounts WHERE account_sid = ?', [result[0].account_sid] + ); + + if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) { + return; + } + + throw new DbErrorForbidden('insufficient permissions'); + } const assignedPhoneNumbers = await Application.getForeignKeyReferences('phone_numbers.application_sid', sid); if (assignedPhoneNumbers > 0) throw new DbErrorUnprocessableRequest('cannot delete application with phone numbers'); } @@ -117,6 +172,7 @@ router.get('/:sid', async(req, res) => { const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null; const results = await Application.retrieve(application_sid, service_provider_sid, account_sid); if (results.length === 0) return res.status(404).end(); + await validateRequest(req, results[0].account_sid); return res.status(200).json(results[0]); } catch (err) { diff --git a/lib/routes/api/phone-numbers.js b/lib/routes/api/phone-numbers.js index 0a869d8..31972f3 100644 --- a/lib/routes/api/phone-numbers.js +++ b/lib/routes/api/phone-numbers.js @@ -1,8 +1,9 @@ const router = require('express').Router(); -const {DbErrorUnprocessableRequest, DbErrorBadRequest} = require('../../utils/errors'); +const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors'); const PhoneNumber = require('../../models/phone-number'); const VoipCarrier = require('../../models/voip-carrier'); const decorate = require('./decorate'); +const {promisePool} = require('../../db'); const {e164} = require('../../utils/phone-number-utils'); const preconditions = { 'add': validateAdd, @@ -42,11 +43,11 @@ async function checkInUse(req, sid) { const phoneNumber = await PhoneNumber.retrieve(sid); if (req.user.hasAccountAuth) { if (phoneNumber && phoneNumber.length && phoneNumber[0].account_sid !== req.user.account_sid) { - throw new DbErrorUnprocessableRequest('cannot delete a phone number that belongs to another account'); + throw new DbErrorForbidden('insufficient privileges'); } } if (!req.user.hasAccountAuth && phoneNumber.account_sid) { - throw new DbErrorUnprocessableRequest('cannot delete phone number that is assigned to an account'); + throw new DbErrorForbidden('insufficient privileges'); } } @@ -58,7 +59,7 @@ async function validateUpdate(req, sid) { const phoneNumber = await PhoneNumber.retrieve(sid); if (req.user.hasAccountAuth) { if (phoneNumber && phoneNumber.length && phoneNumber[0].account_sid !== req.user.account_sid) { - throw new DbErrorUnprocessableRequest('cannot operate on a phone number that belongs to another account'); + throw new DbErrorForbidden('insufficient privileges'); } } @@ -75,7 +76,9 @@ decorate(router, PhoneNumber, ['add', 'update', 'delete'], preconditions); router.get('/', async(req, res) => { const logger = req.app.locals.logger; try { - const results = await PhoneNumber.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null); + const results = req.user.hasAdminAuth ? + await PhoneNumber.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null) : + await PhoneNumber.retrieveAllForSP(req.user.service_provider_sid); res.status(200).json(results); } catch (err) { sysError(logger, res, err); @@ -90,6 +93,15 @@ router.get('/:sid', async(req, res) => { const account_sid = req.user.hasAccountAuth ? req.user.account_sid : null; const results = await PhoneNumber.retrieve(sid, account_sid); if (results.length === 0) return res.status(404).end(); + + if (req.user.hasServiceProviderAuth && results.length === 1) { + const account_sid = results[0].account_sid; + const [r] = await promisePool.execute( + 'SELECT service_provider_sid from accounts WHERE account_sid = ?', [account_sid]); + if (r.length === 1 && r[0].service_provider_sid === !req.user.service_provider_sid) { + throw new DbErrorBadRequest('insufficient privileges'); + } + } return res.status(200).json(results[0]); } catch (err) { diff --git a/lib/routes/api/sbcs.js b/lib/routes/api/sbcs.js index 0f6f910..b6f6d56 100644 --- a/lib/routes/api/sbcs.js +++ b/lib/routes/api/sbcs.js @@ -2,25 +2,53 @@ const router = require('express').Router(); const Sbc = require('../../models/sbc'); const decorate = require('./decorate'); const sysError = require('../error'); -//const {DbErrorBadRequest} = require('../../utils/errors'); -//const {promisePool} = require('../../db'); +const {DbErrorBadRequest} = require('../../utils/errors'); +const {promisePool} = require('../../db'); -decorate(router, Sbc, ['add', 'delete']); +const validate = (req, res) => { + if (req.user.hasScope('admin')) return; + res.status(403).json({ + status: 'fail', + message: 'insufficient privileges' + }); +}; + +const preconditions = { + 'add': validate, + 'delete': validate +}; + +decorate(router, Sbc, ['add', 'delete'], preconditions); /* list */ router.get('/', async(req, res) => { const logger = req.app.locals.logger; try { - const service_provider_sid = req.query.service_provider_sid; - /* + let service_provider_sid = req.query.service_provider_sid; + if (req.user.hasAccountAuth) { const [r] = await promisePool.query('SELECT * from accounts WHERE account_sid = ?', req.user.account_sid); if (0 === r.length) throw new Error('invalid account_sid'); + service_provider_sid = r[0].service_provider_sid; } - if (!service_provider_sid) throw new DbErrorBadRequest('missing service_provider_sid in query'); - */ - const results = await Sbc.retrieveAll(service_provider_sid); + + if (req.user.hasServiceProviderAuth) { + const [r] = await promisePool.query( + 'SELECT * from service_providers where service_provider_sid = ?', + service_provider_sid); + if (0 === r.length) throw new Error('invalid account_sid'); + + service_provider_sid = r[0].service_provider_sid; + + if (!service_provider_sid) throw new DbErrorBadRequest('missing service_provider_sid in query'); + } + + /** generally, we have a global set of SBCs that all accounts use. + * However, we can have a set of SBCs that are specific for use by a service provider. + */ + let results = await Sbc.retrieveAll(service_provider_sid); + if (results.length === 0) results = await Sbc.retrieveAll(); res.status(200).json(results); } catch (err) { sysError(logger, res, err); diff --git a/lib/routes/api/service-providers.js b/lib/routes/api/service-providers.js index e6c1660..266bee7 100644 --- a/lib/routes/api/service-providers.js +++ b/lib/routes/api/service-providers.js @@ -1,6 +1,6 @@ const router = require('express').Router(); const {promisePool} = require('../../db'); -const {DbErrorUnprocessableRequest, DbErrorForbidden} = require('../../utils/errors'); +const {DbErrorForbidden} = require('../../utils/errors'); const Webhook = require('../../models/webhook'); const ServiceProvider = require('../../models/service-provider'); const Account = require('../../models/account'); @@ -8,7 +8,11 @@ const VoipCarrier = require('../../models/voip-carrier'); const Application = require('../../models/application'); const PhoneNumber = require('../../models/phone-number'); const ApiKey = require('../../models/api-key'); -const {hasServiceProviderPermissions, parseServiceProviderSid, parseVoipCarrierSid} = require('./utils'); +const { + hasServiceProviderPermissions, + parseServiceProviderSid, + parseVoipCarrierSid, +} = require('./utils'); const sysError = require('../error'); const decorate = require('./decorate'); const preconditions = { @@ -42,18 +46,11 @@ async function validateRetrieve(req) { return; } - if (req.user.hasScope('service_provider')) { - if (service_provider_sid === req.user.service_provider_sid) return ; + if (req.user.hasScope('service_provider') || req.user.hasScope('account')) { + if (service_provider_sid === req.user.service_provider_sid) return; } - if (req.user.hasScope('account')) { - /* allow account users to retrieve service provider data from parent SP */ - const sid = req.user.account_sid; - const [r] = await promisePool.execute('SELECT service_provider_sid from accounts WHERE account_sid = ?', [sid]); - if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) return; - } - - throw new DbErrorForbidden('insufficient permissions to update service provider'); + throw new DbErrorForbidden('insufficient permissions'); } catch (error) { throw error; } @@ -84,14 +81,10 @@ async function noActiveAccountsOrUsers(req, sid) { } const activeAccounts = await ServiceProvider.getForeignKeyReferences('accounts.service_provider_sid', sid); const activeUsers = await ServiceProvider.getForeignKeyReferences('users.service_provider_sid', sid); - if (activeAccounts > 0 && activeUsers > 0) throw new DbErrorUnprocessableRequest( - 'cannot delete service provider with active accounts or users' - ); + if (activeAccounts > 0 && activeUsers > 0) throw new DbErrorForbidden('insufficient privileges'); - if (activeAccounts > 0) throw new DbErrorUnprocessableRequest('cannot delete service provider with active accounts'); - if (activeUsers > 0) throw new DbErrorUnprocessableRequest( - 'cannot delete service provider with active service provider level users' - ); + if (activeAccounts > 0) throw new DbErrorForbidden('insufficient privileges'); + if (activeUsers > 0) throw new DbErrorForbidden('insufficient privileges'); /* ok we can delete -- no active accounts. remove carriers and speech credentials */ await promisePool.execute('DELETE from speech_credentials WHERE service_provider_sid = ?', [sid]); @@ -122,6 +115,7 @@ router.get('/:sid/Accounts', async(req, res) => { sysError(logger, res, err); } }); + router.get('/:sid/Applications', async(req, res) => { const logger = req.app.locals.logger; try { @@ -250,6 +244,7 @@ router.get('/', async(req, res) => { router.get('/:sid', async(req, res) => { const logger = req.app.locals.logger; try { + await validateRetrieve(req); const sid = parseServiceProviderSid(req); const results = await ServiceProvider.retrieve(sid); if (results.length === 0) return res.status(404).end(); diff --git a/lib/routes/api/signin.js b/lib/routes/api/signin.js index 127d60f..89cc10d 100644 --- a/lib/routes/api/signin.js +++ b/lib/routes/api/signin.js @@ -6,6 +6,12 @@ const {verifyPassword} = require('../../utils/password-utils'); const {cacheClient} = require('../../helpers'); const jwt = require('jsonwebtoken'); const sysError = require('../error'); +const retrievePermissionsSql = ` +SELECT p.name +FROM permissions p, user_permissions up +WHERE up.permission_sid = p.permission_sid +AND up.user_sid = ? +`; const validateRequest = async(req) => { const {email, password} = req.body || {}; @@ -53,6 +59,7 @@ router.post('/', async(req, res) => { email: user.email, phone: user.phone, account_sid: user.account_sid, + service_provider_sid: a[0].service_provider_sid, force_change: !!user.force_change, provider: user.provider, provider_userid: user.provider_userid, @@ -65,12 +72,22 @@ router.post('/', async(req, res) => { pristine: false }); + const [p] = await promisePool.query(retrievePermissionsSql, user.user_sid); + const permissions = p.map((x) => x.name); + const expiresIn = parseInt(process.env.JWT_EXPIRES_IN || 60) * 60; // generate a json web token for this session - const token = jwt.sign({ + const payload = { + scope: 'account', + permissions, user_sid: userProfile.user_sid, - account_sid: userProfile.account_sid - }, process.env.JWT_SECRET, { expiresIn }); + account_sid: userProfile.account_sid, + service_provider_sid: userProfile.service_provider_sid + }; + const token = jwt.sign(payload, + process.env.JWT_SECRET, + { expiresIn } + ); logger.debug({ user_sid: userProfile.user_sid, diff --git a/lib/routes/api/sip-gateways.js b/lib/routes/api/sip-gateways.js index b75571f..b827df2 100644 --- a/lib/routes/api/sip-gateways.js +++ b/lib/routes/api/sip-gateways.js @@ -1,11 +1,42 @@ const router = require('express').Router(); const SipGateway = require('../../models/sip-gateway'); -const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors'); +const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors'); +//const {parseSipGatewaySid} = require('./utils'); const decorate = require('./decorate'); const sysError = require('../error'); +const checkUserScope = async(req, voip_carrier_sid) => { + const {lookupCarrierBySid} = req.app.locals; + if (req.user.hasAdminAuth) return; + if (req.user.hasAccountAuth) { + const carrier = await lookupCarrierBySid(voip_carrier_sid); + if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid'); + + if ((!carrier.service_provider_sid || carrier.service_provider_sid === req.user.service_provider_sid) && + (!carrier.account_sid || carrier.account_sid === req.user.account_sid)) { + + if (req.method !== 'GET' && !carrier.account_sid) { + throw new DbErrorForbidden('insufficient privileges'); + } + + return; + } + } + if (req.user.hasServiceProviderAuth) { + const carrier = await lookupCarrierBySid(voip_carrier_sid); + if (!carrier) { + throw new DbErrorBadRequest('invalid voip_carrier_sid'); + } + + if (carrier.service_provider_sid === req.user.service_provider_sid) { + return; + } + } + throw new DbErrorForbidden('insufficient privileges'); +}; + const validate = async(req, sid) => { - const {lookupCarrierBySid, lookupSipGatewayBySid} = req.app.locals; + const {lookupSipGatewayBySid} = req.app.locals; let voip_carrier_sid; if (sid) { @@ -17,13 +48,7 @@ const validate = async(req, sid) => { voip_carrier_sid = req.body.voip_carrier_sid; if (!voip_carrier_sid) throw new DbErrorBadRequest('missing voip_carrier_sid'); } - if (req.hasAccountAuth) { - const carrier = await lookupCarrierBySid(voip_carrier_sid); - if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid'); - if (carrier.account_sid !== req.user.account_sid) { - throw new DbErrorUnprocessableRequest('user can not add gateway for voip_carrier belonging to other account'); - } - } + await checkUserScope(req, voip_carrier_sid); }; const preconditions = { @@ -39,6 +64,7 @@ router.get('/', async(req, res) => { const logger = req.app.locals.logger; const voip_carrier_sid = req.query.voip_carrier_sid; try { + await checkUserScope(req, voip_carrier_sid); if (!voip_carrier_sid) { logger.info('GET /SipGateways missing voip_carrier_sid param'); return res.status(400).json({message: 'missing voip_carrier_sid query param'}); diff --git a/lib/routes/api/smpp-gateways.js b/lib/routes/api/smpp-gateways.js index 8953702..dcd26f5 100644 --- a/lib/routes/api/smpp-gateways.js +++ b/lib/routes/api/smpp-gateways.js @@ -1,11 +1,34 @@ const router = require('express').Router(); const SmppGateway = require('../../models/smpp-gateway'); -const {DbErrorBadRequest, DbErrorUnprocessableRequest} = require('../../utils/errors'); +const {DbErrorBadRequest, DbErrorForbidden} = require('../../utils/errors'); const decorate = require('./decorate'); const sysError = require('../error'); +const checkUserScope = async(req, voip_carrier_sid) => { + const {lookupCarrierBySid} = req.app.locals; + if (req.user.hasAdminAuth) return; + + if (req.user.hasAccountAuth) { + const carrier = await lookupCarrierBySid(voip_carrier_sid); + if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid'); + + if ((!carrier.service_provider_sid || carrier.service_provider_sid === req.user.service_provider_sid) && + (!carrier.account_sid || carrier.account_sid === req.user.account_sid)) { + return; + } + } + if (req.user.hasServiceProviderAuth) { + const carrier = await lookupCarrierBySid(voip_carrier_sid); + if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid'); + if (carrier.service_provider_sid === req.user.service_provider_sid) { + return; + } + } + throw new DbErrorForbidden('insufficient privileges'); +}; + const validate = async(req, sid) => { - const {lookupCarrierBySid, lookupSmppGatewayBySid} = req.app.locals; + const {lookupSmppGatewayBySid} = req.app.locals; let voip_carrier_sid; if (sid) { @@ -17,13 +40,8 @@ const validate = async(req, sid) => { voip_carrier_sid = req.body.voip_carrier_sid; if (!voip_carrier_sid) throw new DbErrorBadRequest('missing voip_carrier_sid'); } - if (req.hasAccountAuth) { - const carrier = await lookupCarrierBySid(voip_carrier_sid); - if (!carrier) throw new DbErrorBadRequest('invalid voip_carrier_sid'); - if (carrier.account_sid !== req.user.account_sid) { - throw new DbErrorUnprocessableRequest('user can not add gateway for voip_carrier belonging to other account'); - } - } + + await checkUserScope(req, voip_carrier_sid); }; const preconditions = { @@ -39,6 +57,7 @@ router.get('/', async(req, res) => { const logger = req.app.locals.logger; const voip_carrier_sid = req.query.voip_carrier_sid; try { + await checkUserScope(req, voip_carrier_sid); if (!voip_carrier_sid) { logger.info('GET /SmppGateways missing voip_carrier_sid param'); return res.status(400).json({message: 'missing voip_carrier_sid query param'}); diff --git a/lib/routes/api/speech-credentials.js b/lib/routes/api/speech-credentials.js index 6934689..04823c6 100644 --- a/lib/routes/api/speech-credentials.js +++ b/lib/routes/api/speech-credentials.js @@ -5,7 +5,7 @@ const SpeechCredential = require('../../models/speech-credential'); const sysError = require('../error'); const {decrypt, encrypt} = require('../../utils/encrypt-decrypt'); const {parseAccountSid, parseServiceProviderSid, parseSpeechCredentialSid} = require('./utils'); -const {DbErrorUnprocessableRequest} = require('../../utils/errors'); +const {DbErrorUnprocessableRequest, DbErrorForbidden} = require('../../utils/errors'); const { testGoogleTts, testGoogleStt, @@ -21,6 +21,83 @@ const { testIbmTts, testIbmStt } = require('../../utils/speech-utils'); +const {promisePool} = require('../../db'); + +const validateAdd = async(req) => { + const account_sid = parseAccountSid(req); + const service_provider_sid = parseServiceProviderSid(req); + + if (service_provider_sid) { + if (req.user.hasServiceProviderAuth && service_provider_sid !== req.user.service_provider_sid) { + throw new DbErrorForbidden('Insufficient privileges'); + } + if (req.user.hasAccountAuth && service_provider_sid !== req.user.service_provider_sid && + req.body.account_sid !== req.user.account_sid) { + throw new DbErrorForbidden('Insufficient privileges'); + } + } + + if (account_sid) { + if (req.user.hasAccountAuth && account_sid !== req.user.account_sid) { + throw new DbErrorForbidden('Insufficient privileges'); + } + + const [r] = await promisePool.execute( + 'SELECT service_provider_sid from accounts WHERE account_sid = ?', [account_sid] + ); + + if (req.user.hasServiceProviderAuth && r[0].service_provider_sid !== req.user.service_provider_sid) { + throw new DbErrorForbidden('Insufficient privileges'); + } + } + return; +}; + +const validateRetrieveUpdateDelete = async(req, speech_credentials) => { + if (req.user.hasServiceProviderAuth && speech_credentials[0].service_provider_sid !== req.user.service_provider_sid) { + throw new DbErrorForbidden('Insufficient privileges'); + } + + if (req.user.hasAccountAuth && speech_credentials[0].account_sid !== req.user.account_sid) { + throw new DbErrorForbidden('Insufficient privileges'); + } + return; +}; + +const validateRetrieveList = async(req) => { + const service_provider_sid = parseServiceProviderSid(req); + + if (service_provider_sid) { + if ((req.user.hasServiceProviderAuth || req.user.hasAccountAuth) && + service_provider_sid !== req.user.service_provider_sid) { + throw new DbErrorForbidden('Insufficient privileges'); + } + } + return; +}; + +const validateTest = async(req, speech_credentials) => { + if (req.user.hasAdminAuth) { + return; + } + + if (!req.user.hasAdminAuth && speech_credentials.service_provider_sid !== req.user.service_provider_sid) { + throw new DbErrorForbidden('Insufficient privileges'); + } + + if (speech_credentials.service_provider_sid === req.user.service_provider_sid) { + if (req.user.hasServiceProviderAuth) { + return; + } + + if (req.user.hasAccountAuth && (!speech_credentials.account_sid || + speech_credentials.account_sid === req.user.account_sid)) { + return; + } + + throw new DbErrorForbidden('Insufficient privileges'); + } +}; const obscureKey = (key) => { const key_spoiler_length = 6; @@ -146,6 +223,8 @@ router.post('/', async(req, res) => { const service_provider_sid = req.user.service_provider_sid || req.body.service_provider_sid || parseServiceProviderSid(req); + await validateAdd(req); + if (!account_sid) { if (!req.user.hasServiceProviderAuth && !req.user.hasAdminAuth) { logger.error('POST /SpeechCredentials invalid credentials'); @@ -175,8 +254,11 @@ router.get('/', async(req, res) => { const logger = req.app.locals.logger; try { - const account_sid = parseAccountSid(req) || req.user.account_sid; - const service_provider_sid = parseServiceProviderSid(req) || req.user.service_provider_sid; + const account_sid = parseAccountSid(req) ? parseAccountSid(req) : req.user.account_sid; + const service_provider_sid = parseServiceProviderSid(req); + + await validateRetrieveList(req); + const credsAccount = account_sid ? await SpeechCredential.retrieveAll(account_sid) : []; const credsSP = service_provider_sid ? await SpeechCredential.retrieveAllForSP(service_provider_sid) : @@ -190,6 +272,7 @@ router.get('/', async(req, res) => { res.status(200).json(creds.map((c) => { const {credential, ...obj} = c; + if ('google' === obj.vendor) { const o = JSON.parse(decrypt(credential)); const key_header = '-----BEGIN PRIVATE KEY-----\n'; @@ -250,6 +333,15 @@ router.get('/', async(req, res) => { obj.custom_stt_url = o.custom_stt_url; obj.custom_tts_url = o.custom_tts_url; } + + if (req.user.hasAccountAuth && obj.account_sid === null) { + delete obj.api_key; + delete obj.secret_access_key; + delete obj.secret; + delete obj.auth_token; + delete obj.stt_api_key; + delete obj.tts_api_key; + } return obj; })); } catch (err) { @@ -266,6 +358,9 @@ router.get('/:sid', async(req, res) => { const sid = parseSpeechCredentialSid(req); const cred = await SpeechCredential.retrieve(sid); if (0 === cred.length) return res.sendStatus(404); + + await validateRetrieveUpdateDelete(req, cred); + const {credential, ...obj} = cred[0]; if ('google' === obj.vendor) { const o = JSON.parse(decrypt(credential)); @@ -327,6 +422,16 @@ router.get('/:sid', async(req, res) => { obj.custom_stt_url = o.custom_stt_url; obj.custom_tts_url = o.custom_tts_url; } + + if (req.user.hasAccountAuth && obj.account_sid === null) { + delete obj.api_key; + delete obj.secret_access_key; + delete obj.secret; + delete obj.auth_token; + delete obj.stt_api_key; + delete obj.tts_api_key; + } + res.status(200).json(obj); } catch (err) { sysError(logger, res, err); @@ -340,6 +445,8 @@ router.delete('/:sid', async(req, res) => { const logger = req.app.locals.logger; try { const sid = parseSpeechCredentialSid(req); + const cred = await SpeechCredential.retrieve(sid); + await validateRetrieveUpdateDelete(req, cred); const count = await SpeechCredential.remove(sid); if (0 === count) return res.sendStatus(404); res.sendStatus(204); @@ -372,6 +479,9 @@ router.put('/:sid', async(req, res) => { /* update the credential if provided */ try { const cred = await SpeechCredential.retrieve(sid); + + await validateRetrieveUpdateDelete(req, cred); + if (1 === cred.length) { const {credential, vendor} = cred[0]; const o = JSON.parse(decrypt(credential)); @@ -428,8 +538,11 @@ router.get('/:sid/test', async(req, res) => { try { const sid = parseSpeechCredentialSid(req); const creds = await SpeechCredential.retrieve(sid); + if (!creds || 0 === creds.length) return res.sendStatus(404); + await validateTest(req, creds[0]); + const cred = creds[0]; const credential = JSON.parse(decrypt(cred.credential)); const results = { diff --git a/lib/routes/api/users.js b/lib/routes/api/users.js index f50c504..9326283 100644 --- a/lib/routes/api/users.js +++ b/lib/routes/api/users.js @@ -1,9 +1,9 @@ const router = require('express').Router(); const User = require('../../models/user'); -const {DbErrorBadRequest} = require('../../utils/errors'); +const {DbErrorBadRequest, BadRequestError, DbErrorForbidden} = require('../../utils/errors'); const {generateHashedPassword, verifyPassword} = require('../../utils/password-utils'); const {promisePool} = require('../../db'); -const {validatePasswordSettings} = require('./utils'); +const {validatePasswordSettings, parseUserSid} = require('./utils'); const {decrypt} = require('../../utils/encrypt-decrypt'); const {cacheClient} = require('../../helpers'); const sysError = require('../error'); @@ -99,6 +99,48 @@ const validateRequest = async(user_sid, req) => { return user; }; +const getActiveAdminUsers = (users) => { + return users.filter((e) => !e.account_sid && !e.service_provider_sid && e.is_active); +}; + +const ensureUserActionIsAllowed = (req, user) => { + if (req.user.hasAdminAuth) { + return; + } + + if (req.user.hasServiceProviderAuth && req.user.service_provider_sid === user.service_provider_sid) { + return; + } + + if (req.user.hasAccountAuth && req.user.account_sid === user.account_sid) { + return; + } + + throw new DbErrorForbidden('insufficient permissions'); +}; + +const ensureUserDeletionIsAllowed = (req, activeAdminUsers, user) => { + try { + if (req.user.hasAdminAuth && activeAdminUsers.length === 1 && activeAdminUsers[0].user_sid === user[0].user_sid) { + throw new BadRequestError('cannot delete this admin user - there are no other active admin users'); + } + + ensureUserActionIsAllowed(req, user[0]); + return; + } catch (error) { + throw error; + } +}; + +const ensureUserRetrievalIsAllowed = (req, user) => { + try { + ensureUserActionIsAllowed(req, user); + return; + } catch (error) { + throw error; + } +}; + router.get('/', async(req, res) => { const logger = req.app.locals.logger; @@ -256,22 +298,20 @@ router.get('/me', async(req, res) => { router.get('/:user_sid', async(req, res) => { const logger = req.app.locals.logger; - const {user_sid} = req.params; try { + const user_sid = parseUserSid(req); const [user] = await User.retrieve(user_sid); - // eslint-disable-next-line no-unused-vars - const {hashed_password, ...rest} = user; - if (!user) throw new Error('failure retrieving user'); - if (req.user.hasAdminAuth || - req.user.hasAccountAuth && req.user.account_sid === user.account_sid || - req.user.hasServiceProviderAuth && req.user.service_provider_sid === user.service_provider_sid) { - res.status(200).json(rest); - } else { - res.sendStatus(403); + if (!user) { + throw new Error('failure retrieving user'); } + ensureUserRetrievalIsAllowed(req, user); + + // eslint-disable-next-line no-unused-vars + const { hashed_password, ...rest } = user; + return res.status(200).json(rest); } catch (err) { sysError(logger, res, err); } @@ -449,28 +489,21 @@ router.post('/', async(req, res) => { router.delete('/:user_sid', async(req, res) => { const logger = req.app.locals.logger; - const {user_sid} = req.params; - const allUsers = await User.retrieveAll(); - const activeAdminUsers = allUsers.filter((e) => !e.account_sid && !e.service_provider_sid && e.is_active); - const user = await User.retrieve(user_sid); try { - if (req.user.hasAdminAuth && activeAdminUsers.length === 1) { - throw new Error('cannot delete this admin user - there are no other active admin users'); - } + const user_sid = parseUserSid(req); + const allUsers = await User.retrieveAll(); + const activeAdminUsers = getActiveAdminUsers(allUsers); + const user = allUsers.filter((user) => user.user_sid === user_sid); - if (req.user.hasAdminAuth || - (req.user.hasAccountAuth && req.user.account_sid === user[0].account_sid) || - (req.user.hasServiceProviderAuth && req.user.service_provider_sid === user[0].service_provider_sid)) { - await User.remove(user_sid); - /* invalidate the jwt of the deleted user */ - const redisKey = cacheClient.generateRedisKey('jwt', user_sid, 'v2'); - await cacheClient.delete(redisKey); + ensureUserDeletionIsAllowed(req, activeAdminUsers, user); + await User.remove(user_sid); - return res.sendStatus(204); - } else { - throw new DbErrorBadRequest('invalid request'); - } + /* invalidate the jwt of the deleted user */ + const redisKey = cacheClient.generateRedisKey('jwt', user_sid, 'v2'); + await cacheClient.delete(redisKey); + + return res.sendStatus(204); } catch (err) { sysError(logger, res, err); } diff --git a/lib/routes/api/utils.js b/lib/routes/api/utils.js index 81a329d..b48e79e 100644 --- a/lib/routes/api/utils.js +++ b/lib/routes/api/utils.js @@ -218,20 +218,48 @@ const parseWebhookSid = (req) => { } }; -const hasAccountPermissions = (req, res, next) => { +const parseSipGatewaySid = (req) => { + try { + return validateSid('SipGateways', req); + } catch (error) { + throw error; + } +}; + +const parseUserSid = (req) => { + try { + return validateSid('Users', req); + } catch (error) { + throw error; + } +}; + +const hasAccountPermissions = async(req, res, next) => { try { if (req.user.hasScope('admin')) { return next(); } if (req.user.hasScope('service_provider')) { - return next(); + const service_provider_sid = parseServiceProviderSid(req); + if (service_provider_sid === req.user.service_provider_sid) return next(); } if (req.user.hasScope('account')) { const account_sid = parseAccountSid(req); - if (account_sid === req.user.account_sid) { - return next(); + const service_provider_sid = parseServiceProviderSid(req); + const [r] = await Account.retrieve(account_sid); + + if (account_sid) { + if (r && r.account_sid === req.user.account_sid) { + return next(); + } + } + + if (service_provider_sid) { + if (r && r.service_provider_sid === req.user.service_provider_sid) { + return next(); + } } } @@ -405,6 +433,8 @@ module.exports = { parseSpeechCredentialSid, parseVoipCarrierSid, parseWebhookSid, + parseSipGatewaySid, + parseUserSid, hasAccountPermissions, hasServiceProviderPermissions, checkLimits, diff --git a/lib/routes/api/voip-carriers.js b/lib/routes/api/voip-carriers.js index d80e630..5a898a5 100644 --- a/lib/routes/api/voip-carriers.js +++ b/lib/routes/api/voip-carriers.js @@ -74,7 +74,14 @@ decorate(router, VoipCarrier, ['add', 'update', 'delete'], preconditions); router.get('/', async(req, res) => { const logger = req.app.locals.logger; try { - const results = await VoipCarrier.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null); + const results = req.user.hasAdminAuth ? + await VoipCarrier.retrieveAll(req.user.hasAccountAuth ? req.user.account_sid : null) : + await VoipCarrier.retrieveAllForSP(req.user.service_provider_sid); + + if (req.user.hasScope('account')) { + return res.status(200).json(results.filter((c) => c.account_sid === req.user.account_sid || !c.account_sid)); + } + res.status(200).json(results); } catch (err) { sysError(logger, res, err); @@ -91,6 +98,18 @@ router.get('/:sid', async(req, res) => { if (results.length === 0) return res.status(404).end(); const ret = results[0]; ret.register_status = JSON.parse(ret.register_status || '{}'); + + if (req.user.hasServiceProviderAuth && results.length === 1) { + if (results.length === 1 && results[0].service_provider_sid !== req.user.service_provider_sid) { + throw new DbErrorBadRequest('insufficient privileges'); + } + } + if (req.user.hasAccountAuth && results.length === 1) { + if (results.length === 1 && results[0].account_sid !== req.user.account_sid) { + throw new DbErrorBadRequest('insufficient privileges'); + } + } + return res.status(200).json(results[0]); } catch (err) { diff --git a/lib/routes/api/webhooks.js b/lib/routes/api/webhooks.js index e397097..31aabf2 100644 --- a/lib/routes/api/webhooks.js +++ b/lib/routes/api/webhooks.js @@ -2,17 +2,37 @@ const router = require('express').Router(); const Webhook = require('../../models/webhook'); const decorate = require('./decorate'); const sysError = require('../error'); +const {DbErrorForbidden} = require('../../utils/errors'); const { parseWebhookSid } = require('./utils'); +const {promisePool} = require('../../db'); decorate(router, Webhook, ['add']); /* retrieve */ router.get('/:sid', async(req, res) => { const logger = req.app.locals.logger; + try { const sid = parseWebhookSid(req); const results = await Webhook.retrieve(sid); + if (results.length === 0) return res.status(404).end(); + + if (req.user.hasAccountAuth) { + /* can only update carriers for the user's account */ + if (results[0].account_sid != req.user.account_sid) { + throw new DbErrorForbidden('insufficient privileges'); + } + } + if (req.user.hasServiceProviderAuth) { + const [r] = await promisePool.execute( + 'SELECT service_provider_sid from accounts WHERE account_sid = ?', [results[0].account_sid] + ); + if (r.length === 1 && r[0].service_provider_sid === req.user.service_provider_sid) { + return; + } + throw new DbErrorForbidden('insufficient permissions'); + } return res.status(200).json(results[0]); } catch (err) { diff --git a/test/accounts.js b/test/accounts.js index 9416242..640848e 100644 --- a/test/accounts.js +++ b/test/accounts.js @@ -1,6 +1,10 @@ const test = require('tape') ; const ADMIN_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734de'; const authAdmin = {bearer: ADMIN_TOKEN}; +const SP_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734ds'; +const authSP = {bearer: ADMIN_TOKEN}; +const ACC_TOKEN = '38700987-c7a4-4685-a5bb-af378f9734da'; +const authAcc = {bearer: ADMIN_TOKEN}; const request = require('request-promise-native').defaults({ baseUrl: 'http://127.0.0.1:3000/v1' }); diff --git a/test/auth.js b/test/auth.js index 41e8397..1f90af3 100644 --- a/test/auth.js +++ b/test/auth.js @@ -127,7 +127,7 @@ test('authentication tests', async(t) => { sip_realm: 'sip.foo.bar' } }); - t.ok(result.statusCode === 422 && result.body.msg === 'cannot update account from different service provider', + t.ok(result.statusCode === 403 && result.body.msg === 'insufficient permissions', 'service provider token B cannot be used to update account from service provider A'); /* cannot delete account from different service provider */ @@ -137,7 +137,7 @@ test('authentication tests', async(t) => { simple: false, json: true, }); - t.ok(result.statusCode === 422 && result.body.msg === 'cannot delete account from different service provider', + t.ok(result.statusCode === 403 && result.body.msg === 'insufficient permissions', 'service provider token B cannot be used to delete account from service provider A'); /* service provider token A can update account A1 */ @@ -179,7 +179,7 @@ test('authentication tests', async(t) => { } }); //console.log(`result: ${JSON.stringify(result)}`); - t.ok(result.statusCode === 422 && result.body.msg === 'insufficient permissions to create accounts', + t.ok(result.statusCode === 403 && result.body.msg === 'insufficient permissions', 'cannot create an account using an account-level token'); /* using account token we see one account */ @@ -200,8 +200,7 @@ test('authentication tests', async(t) => { sip_realm: 'sip.foo.bar' } }); - //console.log(`result: ${JSON.stringify(result)}`); - t.ok(result.statusCode === 422 && result.body.msg === 'insufficient privileges to update this account', + t.ok(result.statusCode === 403 && result.body.msg === 'insufficient permissions', 'cannot update account A2 using auth token for account A1'); /* can update an account using an appropriate account-level token */ @@ -251,7 +250,8 @@ test('authentication tests', async(t) => { } } }); - t.ok(result.statusCode === 400 && result.body.msg === 'insufficient privileges to create an application under the specified account', + //console.log(`result: ${JSON.stringify(result)}`); + t.ok(result.statusCode === 403 && result.body.msg === 'insufficient privileges', 'cannot create application for account A2 using service provider token B'); result = await request.post('/Applications', { diff --git a/test/speech-credentials.js b/test/speech-credentials.js index 78de99e..5c4a3d2 100644 --- a/test/speech-credentials.js +++ b/test/speech-credentials.js @@ -71,6 +71,7 @@ test('speech credentials tests', async(t) => { const token = jwt.sign({ account_sid, + service_provider_sid, scope: 'account', permissions: ["PROVISION_USERS", "PROVISION_SERVICES", "VIEW_ONLY"] }, process.env.JWT_SECRET, { expiresIn: '1h' }); @@ -393,6 +394,7 @@ test('speech credentials tests', async(t) => { auth: authUser, json: true, body: { + service_provider_sid: service_provider_sid, vendor: 'nvidia', use_for_stt: true, use_for_tts: true, diff --git a/test/users.js b/test/users.js index 1be3e16..1d81d9a 100644 --- a/test/users.js +++ b/test/users.js @@ -83,7 +83,7 @@ test('user tests', async(t) => { } }); t.ok(result.statusCode === 201 && result.body.user_sid, 'service_provider scope user created'); - const sp_user_sid = result.body.sid; + const sp_user_sid = result.body.user_sid; /* add an account */ result = await request.post('/Accounts', { @@ -119,7 +119,7 @@ test('user tests', async(t) => { } }); t.ok(result.statusCode === 201 && result.body.user_sid, 'account scope user created'); - const account_user_sid = result.body.sid; + const account_user_sid = result.body.user_sid; /* retrieve list of users */ result = await request.get(`/Users`, { diff --git a/test/webapp_tests.js b/test/webapp_tests.js index 2b44436..0a6d6e2 100644 --- a/test/webapp_tests.js +++ b/test/webapp_tests.js @@ -243,17 +243,20 @@ test('webapp tests', async(t) => { t.ok(result.statusCode === 200 && result.body.phonenumbers.length === 1 && result.body.applications.length === 1, 'retrieves test number and application'); - /* update user name */ - result = await request.put(`/Users/foobar`, { - resolveWithFullResponse: true, - json: true, - simple: false, - auth: authUser, - body: { - name: 'Jane Doe' - } - }); - t.ok(result.statusCode === 403, 'rejects attempt to update different user'); + /* try to update user name passing an invalid uuid */ + try { + await request.put(`/Users/foobar`, { + resolveWithFullResponse: true, + json: true, + simple: false, + auth: authUser, + body: { + name: 'Jane Doe' + } + }); + } catch (error) { + t.ok(error.statusCode === 400, 'returns 400 bad request if user sid param is not a valid uuid'); + } /* update user name */ result = await request.put(`/Users/${user_sid}`, {