UNPKG

node-spark

Version:
158 lines (134 loc) 3.91 kB
'use strict'; 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;