smartacme/dist/smartacme.classes.jwebclient.js

222 lines
15 KiB
JavaScript
Raw Normal View History

2016-11-01 17:27:57 +00:00
"use strict";
2016-11-01 19:16:43 +00:00
const plugins = require("./smartacme.plugins");
2016-11-01 17:27:57 +00:00
const https = require("https");
let jwa = require('jwa');
const url = require("url");
2016-11-03 17:57:42 +00:00
const q = require("q");
2016-11-01 17:27:57 +00:00
/**
* json_to_utf8base64url
* @private
* @description convert JSON to base64-url encoded string using UTF-8 encoding
* @param {Object} obj
* @return {string}
* @throws Exception if object cannot be stringified or contains cycle
*/
2016-11-03 17:57:42 +00:00
let json_to_utf8base64url = (obj) => {
2016-11-01 19:16:43 +00:00
return plugins.smartstring.base64.encodeUri(JSON.stringify(obj));
2016-11-01 17:27:57 +00:00
};
/**
* @class JWebClient
* @constructor
* @description Implementation of HTTPS-based JSON-Web-Client
*/
class JWebClient {
constructor() {
/**
2016-11-03 17:57:42 +00:00
* User account key pair
2016-11-01 17:27:57 +00:00
*/
2016-11-03 17:57:42 +00:00
this.keyPair = {};
2016-11-01 17:27:57 +00:00
/**
2016-11-03 17:57:42 +00:00
* Cached nonce returned with last request
2016-11-01 17:27:57 +00:00
*/
2016-11-03 17:57:42 +00:00
this.lastNonce = null;
2016-11-01 19:16:43 +00:00
this.verbose = false;
2016-11-01 17:27:57 +00:00
}
/**
* createJWT
* @description create JSON-Web-Token signed object
* @param {string|undefined} nonce
* @param {Object|string|number|boolean} payload
* @param {string} alg
* @param {Object|string} key
* @param {Object} jwk
* @return {string}
*/
createJWT(nonce, payload, alg, key, jwk) {
/*jshint -W069 */
// prepare key
if (key instanceof Object) {
2016-11-01 19:16:43 +00:00
key = new Buffer(plugins.smartstring.base64.decode(key['k']));
2016-11-01 17:27:57 +00:00
}
// prepare header
let header = {
typ: 'JWT',
alg: alg,
jwk: jwk,
nonce: null
};
if (nonce !== void 0) {
header.nonce = nonce;
}
// concatenate header and payload
let input = [
json_to_utf8base64url(header),
json_to_utf8base64url(payload)
].join('.');
// sign input
let hmac = jwa(alg);
let sig = hmac.sign(input, key);
// concatenate input and signature
let output = [
input,
sig
].join('.');
// output
return output;
}
/**
* request
* @description make GET or POST request over HTTPS and use JOSE as payload type
* @param {string} query
* @param {string} payload
* @param {function} callback
* @param {function} errorCallback
*/
2016-11-03 17:57:42 +00:00
request(query, payload = null) {
let done = q.defer();
2016-11-01 17:27:57 +00:00
// prepare options
let uri = url.parse(query);
let options = {
hostname: uri.hostname,
port: parseInt(uri.port, 10),
path: uri.path,
method: null,
headers: {}
};
2016-11-03 17:57:42 +00:00
if (!payload === null) {
2016-11-01 17:27:57 +00:00
options.method = 'POST';
options.headers = {
'Content-Type': 'application/jose',
'Content-Length': payload.length
};
}
else {
options.method = 'GET';
}
// prepare request
2016-11-03 17:57:42 +00:00
let req = https.request(options, (res) => {
2016-11-01 17:27:57 +00:00
// receive data
let data = [];
2016-11-03 17:57:42 +00:00
res.on('data', (block) => {
2016-11-01 17:27:57 +00:00
if (block instanceof Buffer) {
data.push(block);
}
});
2016-11-03 17:57:42 +00:00
res.on('end', () => {
2016-11-01 17:27:57 +00:00
let buf = Buffer.concat(data);
let isJSON = ((res instanceof Object)
&& (res['headers'] instanceof Object)
&& (typeof res.headers['content-type'] === 'string')
&& (res.headers['content-type'].indexOf('json') > -1));
if (isJSON && buf.length > 0) {
try {
// convert to JSON
let json = JSON.parse(buf.toString('utf8'));
2016-11-03 17:57:42 +00:00
done.resolve({ json: json, res: res });
2016-11-01 17:27:57 +00:00
}
catch (e) {
// error (if empty or invalid JSON)
2016-11-03 17:57:42 +00:00
done.reject(e);
2016-11-01 17:27:57 +00:00
}
}
});
2016-11-03 17:57:42 +00:00
}).on('error', (e) => {
2016-11-01 17:27:57 +00:00
console.error('Error occured', e);
// error
2016-11-03 17:57:42 +00:00
done.reject(e);
2016-11-01 17:27:57 +00:00
});
// write POST body if payload was specified
2016-11-03 17:57:42 +00:00
if (!payload === null) {
2016-11-01 17:27:57 +00:00
req.write(payload);
}
// make request
req.end();
2016-11-03 17:57:42 +00:00
return done.promise;
2016-11-01 17:27:57 +00:00
}
/**
* get
* @description make GET request
* @param {string} uri
* @param {function} callback
* @param {function} errorCallback
*/
2016-11-03 17:57:42 +00:00
get(uri) {
let done = q.defer();
this.request(uri)
.then((reqResArg) => {
this.evaluateStatus(uri, null, reqResArg.ans, reqResArg.res);
2016-11-01 17:27:57 +00:00
// save replay nonce for later requests
2016-11-03 17:57:42 +00:00
if ((reqResArg.res instanceof Object) && (reqResArg.res['headers'] instanceof Object)) {
this.lastNonce = reqResArg.res.headers['replay-nonce'];
2016-11-01 17:27:57 +00:00
}
2016-11-03 17:57:42 +00:00
done.resolve(reqResArg);
});
return done.promise;
2016-11-01 17:27:57 +00:00
}
/**
2016-11-03 17:57:42 +00:00
* make POST request
2016-11-01 17:27:57 +00:00
* @param {string} uri
* @param {Object|string|number|boolean} payload
* @param {function} callback
* @param {function} errorCallback
*/
2016-11-03 17:57:42 +00:00
post(uri, payload) {
let done = q.defer();
let jwt = this.createJWT(this.lastNonce, payload, 'RS256', this.keyPair['private_pem'], this.keyPair['public_jwk']);
this.request(uri, jwt)
.then((reqResArg) => {
this.evaluateStatus(uri, payload, reqResArg.ans, reqResArg.res);
2016-11-01 17:27:57 +00:00
// save replay nonce for later requests
2016-11-03 17:57:42 +00:00
if ((reqResArg.res instanceof Object) && (reqResArg.res['headers'] instanceof Object)) {
this.lastNonce = reqResArg.res.headers['replay-nonce'];
2016-11-01 17:27:57 +00:00
}
2016-11-03 17:57:42 +00:00
done.resolve(reqResArg);
});
return done.promise;
2016-11-01 17:27:57 +00:00
}
/**
2016-11-03 17:57:42 +00:00
* checks if status is expected and log errors
2016-11-01 17:27:57 +00:00
* @param {string} uri
* @param {Object|string|number|boolean} payload
* @param {Object|string} ans
* @param {Object} res
*/
evaluateStatus(uri, payload, ans, res) {
if (this.verbose) {
if ((payload instanceof Object)
|| (typeof payload === 'string')
|| (typeof payload === 'number')
|| (typeof payload === 'boolean')) {
console.error('Send :', payload); // what has been sent
}
}
let uri_parsed = url.parse(uri);
if (res['statusCode'] >= 100 && res['statusCode'] < 400) {
console.error('HTTP :', res['statusCode'], uri_parsed.path); // response code if successful
}
if (res['statusCode'] >= 400 && res['statusCode'] < 500) {
console.error('HTTP :', res['statusCode'], uri_parsed.path); // response code if error
if (ans instanceof Object) {
if (typeof ans['detail'] === 'string') {
console.error('Message:', ans.detail.split(' :: ').pop()); // error message if any
}
}
}
if (this.verbose) {
console.error('Receive:', res['headers']); // received headers
console.error('Receive:', ans); // received data
}
}
}
exports.JWebClient = JWebClient;
2016-11-03 17:57:42 +00:00
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuandlYmNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0YWNtZS5jbGFzc2VzLmp3ZWJjbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUE4QztBQUM5QywrQkFBOEI7QUFDOUIsSUFBSSxHQUFHLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFBO0FBQ3hCLDJCQUEwQjtBQUMxQix1QkFBc0I7QUFRdEI7Ozs7Ozs7R0FPRztBQUNILElBQUkscUJBQXFCLEdBQUcsQ0FBQyxHQUFHO0lBQzVCLE1BQU0sQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFBO0FBQ3BFLENBQUMsQ0FBQTtBQUVEOzs7O0dBSUc7QUFDSDtJQWdCSTtRQWZBOztXQUVHO1FBQ0gsWUFBTyxHQUFRLEVBQUUsQ0FBQTtRQUVqQjs7V0FFRztRQUNILGNBQVMsR0FBVyxJQUFJLENBQUE7UUFRcEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUE7SUFDeEIsQ0FBQztJQUVEOzs7Ozs7Ozs7T0FTRztJQUNILFNBQVMsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRztRQUNuQyxpQkFBaUI7UUFDakIsY0FBYztRQUNkLEVBQUUsQ0FBQyxDQUFDLEdBQUcsWUFBWSxNQUFNLENBQUMsQ0FBQyxDQUFDO1lBQ3hCLEdBQUcsR0FBRyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQTtRQUNqRSxDQUFDO1FBQ0QsaUJBQWlCO1FBQ2pCLElBQUksTUFBTSxHQUFHO1lBQ1QsR0FBRyxFQUFFLEtBQUs7WUFDVixHQUFHLEVBQUUsR0FBRztZQUNSLEdBQUcs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