diff --git a/CHANGELOG.md b/CHANGELOG.md index 9593578..1aa8556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ # Change Log -## [4.6.0](https://github.com/plivo/plivo-node/releases/tag/v4.6.0)(2020-05-04) +## [4.7.0](https://github.com/plivo/plivo-node/releases/tag/v4.7.0)(2020-05-28) - Add JWT helper functions. +## [4.6.0](https://github.com/plivo/plivo-node/releases/tag/v4.6.0)(2020-04-29) +- Add V3 signature helper functions. + +## [4.5.2](https://github.com/plivo/plivo-node/releases/tag/v4.5.2)(2020-04-28) +- Fix List Conferences API response. + ## [4.5.1](https://github.com/plivo/plivo-node/releases/tag/v4.5.1)(2020-04-13) - Fix Cannot read property 'hasOwnProperty' of undefined error. diff --git a/lib/resources/conferences.js b/lib/resources/conferences.js index 9730943..1e506f3 100644 --- a/lib/resources/conferences.js +++ b/lib/resources/conferences.js @@ -326,7 +326,7 @@ export class ConferenceInterface extends PlivoResourceInterface { name: conference })); }); - resolve(conferences); + resolve(response.body); }) .catch(error => { reject(error); diff --git a/lib/rest/client.js b/lib/rest/client.js index 78234ca..6cf527d 100644 --- a/lib/rest/client.js +++ b/lib/rest/client.js @@ -15,6 +15,7 @@ import { RecordingInterface } from "../resources/recordings"; import { Response } from "../utils/plivoxml"; import { AccessToken } from "../utils/jwt"; import { validateSignature } from "../utils/security"; +import { validateV3Signature } from "../utils/v3Security"; import { stringify } from "./../utils/jsonStrinfigier"; import { CallFeedbackInterface } from "../resources/callFeedback"; import { MediaInterface } from "../resources/media.js"; @@ -23,8 +24,8 @@ exports.Response = function() { return new Response(); }; -exports.AccessToken = function(authId, authToken, username, validityOptions, uid) { - return new AccessToken(authId, authToken, username, validityOptions, uid); +exports.validateV3Signature = function(method, uri, nonce, auth_token, v3_signature, params={}) { + return validateV3Signature(method, uri, nonce, auth_token, v3_signature, params); }; exports.validateSignature = function(uri, nonce, signature, auth_token) { diff --git a/lib/utils/v3Security.js b/lib/utils/v3Security.js new file mode 100644 index 0000000..af9c97b --- /dev/null +++ b/lib/utils/v3Security.js @@ -0,0 +1,111 @@ +//@flow +const utf8 = require('utf8'); +const buildUrl = require('build-url'); +const base64 = require('base-64'); +const qs = require('querystring'); +import * as parser from 'uri-parser'; +import crypto from 'crypto'; +import _ from 'lodash'; + +function get_map_from_query(params1, params2) { + let params = {}; + Object.keys(params1).forEach(function (key) { + let val = params1[key]; + if (val instanceof Array) { + params[key] = val + } else { + params[key] = [val] + } + }); + Object.keys(params2).forEach(function (key) { + let val = params2[key]; + if (!(val instanceof Array)) { + val = [val]; + } + if (key in params) { + params[key] = params[key].concat(val) + } else { + params[key] = val + } + }); + return params; +} + +function get_sorted_query_string(params) { + let query_string = []; + Object.keys(params).sort().forEach(function (key) { + let val = params[key]; + val.sort().forEach(function (value) { + query_string.push(key.toString() + '=' + value.toString()); + }); + }); + return query_string.join('&'); +} + +function get_sorted_params_string(params) { + let paramsString = []; + Object.keys(params).sort().forEach(function (key) { + let val = params[key]; + if (val instanceof Array) { + val.sort().forEach(function (value) { + paramsString.push(key.toString() + value.toString()); + }); + } else { + paramsString.push(key.toString() + val.toString()); + } + }); + return paramsString.join('') +} + +function construct_get_url(uri, params, empty_post_params=true) { + let parsed_uri = parser.parse(uri); + let url_protocol = parsed_uri.protocol === '' ? 'http://' : parsed_uri.protocol+'://'; + let proxy = parsed_uri.port === '' ? parsed_uri.host : parsed_uri.host + ':' + parsed_uri.port; + let base_url = buildUrl(url_protocol + proxy , { path: parsed_uri.path }); + params = get_map_from_query(qs.parse(parsed_uri.query), params); + let query_params = get_sorted_query_string(params); + if (query_params.length > 0 || !empty_post_params) { + base_url = base_url + '?' + query_params; + } + if (query_params.length > 0 && !empty_post_params) { + base_url = base_url + '.'; + } + return base_url; +} + +function construct_post_url(uri, params) { + let base_url = construct_get_url(uri, {}, _.isEmpty(params)); + return base_url + get_sorted_params_string(params); +} + +function get_signature_v3(auth_token, base_url, nonce) { + base_url = base_url + '.' + nonce; + let hmac = crypto.createHmac('sha256', auth_token); + let hmacBytes = base64.decode(hmac.update(base_url).digest('base64')); + return base64.encode(hmacBytes); +} + +export function validateV3Signature(method: string, uri: string, + nonce: string, auth_token: string, + v3_signature: string, params={}) { + auth_token = utf8.encode(auth_token); + nonce = utf8.encode(nonce); + v3_signature = utf8.encode(v3_signature); + uri = utf8.encode(uri); + let base_url = uri; + if (method === 'GET') { + base_url = construct_get_url(uri, params); + } else if (method === 'POST') { + base_url = construct_post_url(uri, params); + } else { + throw new Error("Please provide authToken"); + } + let signature = get_signature_v3(auth_token, base_url, nonce); + let matched = false; + _.split(v3_signature, ',').forEach(function (plivo_sign) { + if (plivo_sign === signature) { + matched = true; + } + }); + return matched; +} diff --git a/package.json b/package.json index ecfa7e9..bda1ac2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plivo", - "version": "4.6.0", + "version": "4.7.0", "description": "A Node.js SDK to make voice calls and send SMS using Plivo and to generate Plivo XML", "homepage": "https://github.com/plivo/plivo-node", "files": [ diff --git a/test/conferences.js b/test/conferences.js index 6541f41..5282930 100644 --- a/test/conferences.js +++ b/test/conferences.js @@ -16,8 +16,8 @@ describe('Conference', function () { it('should get all conferences', function () { return client.conferences.list() - .then(function(conferences) { - assert.equal(conferences[0].name, 'My Conf Room' ) + .then(function(response) { + assert.equal(response.conferences[0], 'My Conf Room' ) }) });