Add schema validation to create-call req.body, validate app_json via verb-specifications (#488)

* Add schema for create-call, validate app_json via verb-specifications

* trigger new build

---------

Co-authored-by: Markus Frindt <m.frindt@cognigy.com>
This commit is contained in:
Markus Frindt
2023-10-19 20:37:30 +02:00
committed by GitHub
parent 320baf4ac8
commit 0a2808e64e
5 changed files with 433 additions and 263 deletions

View File

@@ -1,4 +1,4 @@
# jambones-feature-server ![Build Status](https://github.com/jambonz/jambonz-feature-server/workflows/CI/badge.svg) # jambonz-feature-server ![Build Status](https://github.com/jambonz/jambonz-feature-server/workflows/CI/badge.svg)
This application implements the core feature server of the jambones platform. This application implements the core feature server of the jambones platform.

View File

@@ -5,27 +5,47 @@ const CallInfo = require('../../session/call-info');
const {CallDirection, CallStatus} = require('../../utils/constants'); const {CallDirection, CallStatus} = require('../../utils/constants');
const uuidv4 = require('uuid-random'); const uuidv4 = require('uuid-random');
const SipError = require('drachtio-srf').SipError; const SipError = require('drachtio-srf').SipError;
const { validationResult } = require('express-validator');
const { validate } = require('@jambonz/verb-specifications');
const sysError = require('./error'); const sysError = require('./error');
const HttpRequestor = require('../../utils/http-requestor'); const HttpRequestor = require('../../utils/http-requestor');
const WsRequestor = require('../../utils/ws-requestor'); const WsRequestor = require('../../utils/ws-requestor');
const RootSpan = require('../../utils/call-tracer'); const RootSpan = require('../../utils/call-tracer');
const dbUtils = require('../../utils/db-utils'); const dbUtils = require('../../utils/db-utils');
const { mergeSdpMedia, extractSdpMedia } = require('../../utils/sdp-utils'); const { mergeSdpMedia, extractSdpMedia } = require('../../utils/sdp-utils');
const { createCallSchema } = require('../schemas/create-call');
router.post('/', async(req, res) => { router.post('/',
createCallSchema,
async(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {logger} = req.app.locals; const {logger} = req.app.locals;
const accountSid = req.body.account_sid; const accountSid = req.body.account_sid;
const {srf} = require('../../..'); const {srf} = require('../../..');
const app_json = req.body['app_json'];
try {
// app_json is created only by api-server.
if (app_json) {
// if available, delete from req before creating task
delete req.body.app_json;
// validate possible app_json via verb-specifications
validate(logger, JSON.parse(app_json));
}
} catch (err) {
logger.debug({ err }, `invalid app_json: ${err.message}`);
}
logger.debug({body: req.body}, 'got createCall request'); logger.debug({body: req.body}, 'got createCall request');
try { try {
let uri, cs, to; let uri, cs, to;
// app_json is creaeted by only api-server.
// if it available, take it and delete before creating task
const app_json = req.body.app_json;
delete req.body.app_json;
const restDial = makeTask(logger, { 'rest:dial': req.body }); const restDial = makeTask(logger, { 'rest:dial': req.body });
restDial.appJson = app_json; restDial.appJson = app_json;
const {lookupAccountDetails, lookupCarrierByPhoneNumber, lookupCarrier} = dbUtils(logger, srf); const {lookupAccountDetails, lookupCarrierByPhoneNumber, lookupCarrier} = dbUtils(logger, srf);
const { const {
lookupAppBySid lookupAppBySid

View File

@@ -0,0 +1,114 @@
const { checkSchema } = require('express-validator');
/**
* @path api-server {{base_url}}/v1/Accounts/:account_sid/Calls
* @see https://api.jambonz.org/#243a2edd-7999-41db-bd0d-08082bbab401
*/
const createCallSchema = checkSchema({
application_sid: {
isString: true,
optional: true,
isLength: { options: { min: 36, max: 36 } },
errorMessage: 'Invalid application_sid',
},
answerOnBridge: {
isBoolean: true,
optional: true,
errorMessage: 'Invalid answerOnBridge',
},
from: {
errorMessage: 'Invalid from',
isString: true,
isLength: {
options: { min: 1, max: 256 },
},
},
fromHost: {
isString: true,
optional: true,
errorMessage: 'Invalid fromHost',
},
to: {
errorMessage: 'Invalid to',
isObject: true,
},
callerName: {
isString: true,
optional: true,
errorMessage: 'Invalid callerName',
},
amd: {
isObject: true,
optional: true,
},
tag: {
isObject: true,
optional: true,
errorMessage: 'Invalid tag',
},
'tag.*': {
trim: true,
escape: true,
stripLow: true,
},
app_json: {
isString: true,
optional: true,
errorMessage: 'Invalid app_json',
},
account_sid: {
isString: true,
optional: true,
errorMessage: 'Invalid account_sid',
isLength: { options: { min: 36, max: 36 } },
},
timeout: {
isInt: true,
optional: true,
errorMessage: 'Invalid timeout',
},
timeLimit: {
isInt: true,
optional: true,
errorMessage: 'Invalid timeLimit',
},
call_hook: {
isObject: true,
optional: true,
errorMessage: 'Invalid call_hook',
},
call_status_hook: {
isObject: true,
optional: true,
errorMessage: 'Invalid call_status_hook',
},
speech_synthesis_vendor: {
isString: true,
optional: true,
errorMessage: 'Invalid speech_synthesis_vendor',
},
speech_synthesis_language: {
isString: true,
optional: true,
errorMessage: 'Invalid speech_synthesis_language',
},
speech_synthesis_voice: {
isString: true,
optional: true,
errorMessage: 'Invalid speech_synthesis_voice',
},
speech_recognizer_vendor: {
isString: true,
optional: true,
errorMessage: 'Invalid speech_recognizer_vendor',
},
speech_recognizer_language: {
isString: true,
optional: true,
errorMessage: 'Invalid speech_recognizer_language',
}
}, ['body']);
module.exports = {
createCallSchema
};

35
package-lock.json generated
View File

@@ -33,6 +33,7 @@
"drachtio-fsmrf": "^3.0.27", "drachtio-fsmrf": "^3.0.27",
"drachtio-srf": "^4.5.29", "drachtio-srf": "^4.5.29",
"express": "^4.18.2", "express": "^4.18.2",
"express-validator": "^7.0.1",
"ip": "^1.1.8", "ip": "^1.1.8",
"moment": "^2.29.4", "moment": "^2.29.4",
"parse-url": "^8.1.0", "parse-url": "^8.1.0",
@@ -5836,6 +5837,18 @@
"node": ">= 0.10.0" "node": ">= 0.10.0"
} }
}, },
"node_modules/express-validator": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz",
"integrity": "sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==",
"dependencies": {
"lodash": "^4.17.21",
"validator": "^13.9.0"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/express/node_modules/debug": { "node_modules/express/node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -10238,6 +10251,14 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true "dev": true
}, },
"node_modules/validator": {
"version": "13.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
"integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -15184,6 +15205,15 @@
} }
} }
}, },
"express-validator": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz",
"integrity": "sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==",
"requires": {
"lodash": "^4.17.21",
"validator": "^13.9.0"
}
},
"ext": { "ext": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
@@ -18478,6 +18508,11 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true "dev": true
}, },
"validator": {
"version": "13.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
"integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -49,6 +49,7 @@
"drachtio-fsmrf": "^3.0.27", "drachtio-fsmrf": "^3.0.27",
"drachtio-srf": "^4.5.29", "drachtio-srf": "^4.5.29",
"express": "^4.18.2", "express": "^4.18.2",
"express-validator": "^7.0.1",
"ip": "^1.1.8", "ip": "^1.1.8",
"moment": "^2.29.4", "moment": "^2.29.4",
"parse-url": "^8.1.0", "parse-url": "^8.1.0",