node-spark
Version:
Cisco Spark API for Node JS
158 lines (134 loc) • 3.91 kB
JavaScript
;
const EventEmitter = require('events').EventEmitter;
const crypto = require('crypto');
const util = require('util');
const when = require('when');
// promisfy JSON.stringify()
var jsonStringify = when.lift(JSON.stringify);
// xor function
function xor(a,b) {
return (a || b) && !(a && b);
}
function Webhook(options) {
EventEmitter.call(this);
// if secret is defined
if(options && options.hasOwnProperty('secret') && typeof options.secret === 'string') {
this.secret = options.secret;
} else {
this.secret = null;
}
// if reqObjProp is defined
if(options && options.hasOwnProperty('reqObjProp') && typeof options.reqObjProp === 'string') {
this.reqObjProp = options.reqObjProp;
} else {
this.reqObjProp = 'body';
}
}
util.inherits(Webhook, EventEmitter);
// process request from web app
Webhook.prototype.listen = function() {
var webhook = this;
return function(req, res) {
// variable to hold request properties
var headers;
var body;
// if "res" is passed to function...
if(typeof res !== 'undefined') {
res.status(200);
res.send('OK');
}
// validate "req"
if(req && req.hasOwnProperty('headers') && req.hasOwnProperty(webhook.reqObjProp)) {
// headers
headers = req.headers;
// body
if(typeof req[webhook.reqObjProp] === 'object') {
body = req[webhook.reqObjProp];
} else if(typeof req[webhook.reqObjProp] === 'string') {
try {
body = JSON.parse(req[webhook.reqObjProp]);
}
catch(err) {
return;
}
}
} else {
return;
}
// get signature from headers
var sig = headers['x-spark-signature'] || null;
// process webhook body
function processBody(body) {
var _resource = body.hasOwnProperty('resource') ? body.resource.toLowerCase() : null;
var _event = body.hasOwnProperty('event') ? body.event.toLowerCase() : null;
var _data = body.hasOwnProperty('data') ? body.data : null;
if(_resource && _event && _data) {
webhook.emit(_resource, _event, _data);
webhook.emit('request', body);
}
}
// if sig and secret defined
if(sig && webhook.secret) {
webhook.auth(body, sig, webhook.secret, function(err) {
if(!err) {
processBody(body);
}
});
}
// if either sig or secret is defined, but the other is not
else if(xor(sig, webhook.secret)) {
return;
}
// sig nor secret defined, assume no hash authentication
else {
processBody(body);
}
}
}
// authenticate webhook payload
Webhook.prototype.auth = function(payload, sig, secret, callback) {
var strPayload;
// if json object...
if(typeof payload === 'object') {
strPayload = jsonStringify(payload);
} else if(typeof payload === 'string') {
strPayload = when(payload);
}
//validate
if(typeof sig === 'string' && typeof secret === 'string') {
var invalidErr = new Error('invalid payload');
var paramsErr = new Error('missing or invalid function params');
var hmac = crypto.createHmac('sha1', secret);
if(callback) {
when(strPayload)
.then(pl => {
hmac.update(pl);
if(sig === hmac.digest('hex')) {
callback(null, payload);
} else {
callback(invalidErr, null);
}
})
.catch(err => {
callback(invalidErr, null);
})
} else {
return when(strPayload)
.then(pl => {
hmac.update(pl);
if(sig === hmac.digest('hex')) {
return when(payload);
} else {
return when.reject(invalidErr);
}
});
}
} else {
if(callback) {
callback(invalidParams, null);
} else {
return when.reject(invalidParams);
}
}
};
module.exports = Webhook;