Skip to content

Commit

Permalink
Merge pull request #7 from akreisman-epam/layer-7-node-sdk
Browse files Browse the repository at this point in the history
Layer 7 encryption for node-sdk
  • Loading branch information
Peter Joseph Olamit committed Dec 20, 2018
2 parents 50814fd + addd477 commit ec57ff8
Show file tree
Hide file tree
Showing 14 changed files with 1,077 additions and 41 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ language: node_js
sudo: false

node_js:
- '0.12'
- '4'
- '5'
- '4.9.1' #argon
- '6.15.1' #boron
- '8.14.0' #carbon
- '10.14.2' #dubnium
- stable
- iojs

after_success:
- if [ $TRAVIS_NODE_VERSION = 'stable' ] && [ $TRAVIS_BRANCH = 'master' ] && [ $TRAVIS_PULL_REQUEST = 'false' ]; then sh ./scripts/travisPublishDocs.sh; fi
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
0.2.0 (in progress)
-------------------

- Added Layer 7 encryption
- Added payment status transition endpoint
- Added paper check endpoint
- Added bank card endpoint
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "lib/index.js",
"dependencies": {
"object-assign": "^4.1.0",
"superagent": "^2.0.0-alpha.3"
"superagent": "^2.0.0-alpha.3",
"node-jose": "^1.0.0"
},
"scripts": {
"lint": "eslint ./src && eslint ./test && eslint ./examples",
Expand Down
5 changes: 3 additions & 2 deletions src/Hyperwallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ export default class Hyperwallet {
* @param {string} config.username - The API username
* @param {string} config.password - The API password
* @param {string} [config.programToken] - The program token that is used for some API calls
* @param {Object} [config.encryptionData] - The JSON object of encryption data
* @param {string} [config.server=https://api.sandbox.hyperwallet.com] - The API server to connect to
*/
constructor({ username, password, programToken, server = "https://api.sandbox.hyperwallet.com" }) {
constructor({ username, password, programToken, encryptionData, server = "https://api.sandbox.hyperwallet.com" }) {
if (!username || !password) {
throw new Error("You need to specify your API username and password!");
}
Expand All @@ -26,7 +27,7 @@ export default class Hyperwallet {
* @type {ApiClient}
* @protected
*/
this.client = new ApiClient(username, password, server);
this.client = new ApiClient(username, password, server, encryptionData);

/**
* The program token that is used for some API calls
Expand Down
145 changes: 111 additions & 34 deletions src/utils/ApiClient.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import request from "superagent";
import packageJson from "../../package.json";
import Encryption from "./Encryption";

/**
* The callback interface for api calls
Expand All @@ -24,8 +25,9 @@ export default class ApiClient {
* @param {string} username - The API username
* @param {string} password - The API password
* @param {string} server - The API server to connect to
* @param {string} encryptionData - The API encryption data
*/
constructor(username, password, server) {
constructor(username, password, server, encryptionData) {
/**
* The API username
*
Expand Down Expand Up @@ -56,6 +58,21 @@ export default class ApiClient {
* @protected
*/
this.version = packageJson.version;

/**
* The flag shows if encryption is enabled
*
* @type {boolean}
* @protected
*/
this.isEncrypted = false;

if (encryptionData && encryptionData.clientPrivateKeySetPath && encryptionData.hyperwalletKeySetPath) {
this.isEncrypted = true;
this.clientPrivateKeySetPath = encryptionData.clientPrivateKeySetPath;
this.hyperwalletKeySetPath = encryptionData.hyperwalletKeySetPath;
this.encryption = new Encryption(this.clientPrivateKeySetPath, this.hyperwalletKeySetPath);
}
}

/**
Expand All @@ -67,15 +84,25 @@ export default class ApiClient {
* @param {api-callback} callback - The callback for this call
*/
doPost(partialUrl, data, params, callback) {
request
.post(`${this.server}/rest/v3/${partialUrl}`)
.auth(this.username, this.password)
.set("User-Agent", `Hyperwallet Node SDK v${this.version}`)
.type("json")
.accept("json")
.query(params)
.send(data)
.end(this.wrapCallback(callback));
let contentType = "json";
let processResponse = this.wrapCallback(callback);
let requestDataPromise = new Promise((resolve) => resolve(data));
if (this.isEncrypted) {
contentType = "application/jose+json";
processResponse = this.processEncryptedResponse("POST", callback);
requestDataPromise = this.encryption.encrypt(data);
}
requestDataPromise.then((requestData) => {
request
.post(`${this.server}/rest/v3/${partialUrl}`)
.auth(this.username, this.password)
.set("User-Agent", `Hyperwallet Node SDK v${this.version}`)
.type(contentType)
.accept("json")
.query(params)
.send(requestData)
.end(processResponse);
}).catch(() => callback("Failed to encrypt body for POST request", undefined, undefined));
}

/**
Expand All @@ -87,15 +114,25 @@ export default class ApiClient {
* @param {api-callback} callback - The callback for this call
*/
doPut(partialUrl, data, params, callback) {
request
.put(`${this.server}/rest/v3/${partialUrl}`)
.auth(this.username, this.password)
.set("User-Agent", `Hyperwallet Node SDK v${this.version}`)
.type("json")
.accept("json")
.query(params)
.send(data)
.end(this.wrapCallback(callback));
let contentType = "json";
let processResponse = this.wrapCallback(callback);
let requestDataPromise = new Promise((resolve) => resolve(data));
if (this.isEncrypted) {
contentType = "application/jose+json";
processResponse = this.processEncryptedResponse("PUT", callback);
requestDataPromise = this.encryption.encrypt(data);
}
requestDataPromise.then((requestData) => {
request
.put(`${this.server}/rest/v3/${partialUrl}`)
.auth(this.username, this.password)
.set("User-Agent", `Hyperwallet Node SDK v${this.version}`)
.type(contentType)
.accept("json")
.query(params)
.send(requestData)
.end(processResponse);
}).catch(() => callback("Failed to encrypt body for PUT request", undefined, undefined));
}

/**
Expand All @@ -106,13 +143,18 @@ export default class ApiClient {
* @param {api-callback} callback - The callback for this call
*/
doGet(partialUrl, params, callback) {
let contentType = "json";
if (this.isEncrypted) {
contentType = "application/jose+json";
}
request
.get(`${this.server}/rest/v3/${partialUrl}`)
.auth(this.username, this.password)
.set("User-Agent", `Hyperwallet Node SDK v${this.version}`)
.type(contentType)
.accept("json")
.query(params)
.end(this.wrapCallback(callback));
.end(this.isEncrypted ? this.processEncryptedResponse("GET", callback) : this.wrapCallback(callback));
}

/**
Expand All @@ -125,22 +167,57 @@ export default class ApiClient {
*/
wrapCallback(callback = () => null) {
return (err, res) => {
if (!err) {
callback(undefined, res.body, res);
return;
}
this.processResponse(err, res, callback);
};
}

/**
* Process response from server
*
* @param {Object} err - Error object
* @param {Object} res - Response object
* @param {api-callback} callback - The final callback
*
* @private
*/
processResponse(err, res, callback) {
if (!err) {
callback(undefined, res.body, res);
return;
}

let errors = [
{
message: `Could not communicate with ${this.server}`,
code: "COMMUNICATION_ERROR",
},
];
if (res && res.body && res.body.errors) {
errors = res.body.errors;
let errors = [
{
message: `Could not communicate with ${this.server}`,
code: "COMMUNICATION_ERROR",
},
];
if (res && res.body && res.body.errors) {
errors = res.body.errors;
}
callback(errors, res ? res.body : undefined, res);
}

/**
* Makes decryption for encrypted response bodies
*
* @param {string} httpMethod - The http method that is currently processing
* @param {api-callback} callback - The callback method to be invoked after decryption
*
* @private
*/
processEncryptedResponse(httpMethod, callback) {
return (error, response) => {
if (!error) {
const responseBody = response.rawResponse ? response.rawResponse : response.text;
this.encryption.decrypt(responseBody)
.then((decryptedData) => {
callback(undefined, JSON.parse(decryptedData.payload.toString()), decryptedData);
})
.catch(() => callback(`Failed to decrypt response for ${httpMethod} request`, responseBody, responseBody));
} else {
this.processResponse(error, response, callback);
}
callback(errors, res ? res.body : undefined, res);
};
}

}
Loading

0 comments on commit ec57ff8

Please sign in to comment.