From 12737b74e3cd0e2c2d04cded0b7eca657d63a2fe Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 5 Jul 2017 23:11:32 +0000 Subject: [PATCH] add mailgun verification --- lib/apis.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/lib/apis.js b/lib/apis.js index db76fc6..9b0c915 100644 --- a/lib/apis.js +++ b/lib/apis.js @@ -430,7 +430,6 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { } }); - if (xconfx.debug) { console.log('[api.js] twilio added'); } var Twilio = require('twilio'); function twilioTel(/*opts*/) { if (_twilio) { @@ -444,12 +443,76 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { return apiDeps.Promise.resolve(_twilio); } + // TODO shared memory db + var mailgunTokens = {}; + function validateMailgun(apiKey, timestamp, token, signature) { + // https://gist.github.com/coolaj86/81a3b61353d2f0a2552c + // (realized later) + // HAHA HAHA HAHAHAHAHA this is my own gist... so much more polite attribution + var scmp = require('scmp') + , crypto = require('crypto') + , mailgunExpirey = 15 * 60 * 1000 + , mailgunHashType = 'sha256' + , mailgunSignatureEncoding = 'hex' + ; + var actual + , adjustedTimestamp = parseInt(timestamp, 10) * 1000 + , fresh = (Math.abs(Date.now() - adjustedTimestamp) < mailgunExpirey) + ; + + if (!fresh) { + console.error('[mailgun] Stale Timestamp: this may be an attack'); + console.error('[mailgun] However, this is most likely your fault\n'); + console.error('[mailgun] run `ntpdate ntp.ubuntu.com` and check your system clock\n'); + console.error('[mailgun] System Time: ' + new Date().toString()); + console.error('[mailgun] Mailgun Time: ' + new Date(adjustedTimestamp).toString(), timestamp); + console.error('[mailgun] Delta: ' + (Date.now() - adjustedTimestamp)); + return false; + } + + if (mailgunTokens[token]) { + console.error('[mailgun] Replay Attack'); + return false; + } + + mailgunTokens[token] = true; + + setTimeout(function () { + delete mailgunTokens[token]; + }, mailgunExpirey + (5 * 1000)); + + actual = crypto.createHmac(mailgunHashType, apiKey) + .update(new Buffer(timestamp + token, 'utf8')) + .digest(mailgunSignatureEncoding) + ; + return scmp(signature, actual); + } + function mailgunMail(/*opts*/) { return apiDeps.Promise.resolve(req.getSiteMailer()); } // Twilio Parameters are often 26 long var bodyParserTwilio = require('body-parser').urlencoded({ limit: '4kb', parameterLimit: 100, extended: false }); + //var bodyParserMailgun = require('body-parser').urlencoded({ limit: '4kb', parameterLimit: 100, extended: false }); + function bodyParserMailgun (req, res, next) { + var multiparty = require('multiparty'); + var form = new multiparty.Form(); + + form.parse(req, function (err, fields/*, files*/) { + var body; + req.body = req.body || {}; + Object.keys(fields).forEach(function (key) { + // TODO what if there were two of something? + // (even though there won't be) + req.body[key] = fields[key][0]; + }); + body = req.body; + + next(); + }); + } + var caps = { // // Capabilities for APIs @@ -463,6 +526,22 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) { // // Webhook Parsers // + , 'mailgun.urlencoded@daplie.com': function (req, res, next) { + return bodyParserMailgun(req, res, function () { + var body = req.body; + var mailconf = siteConfig['mailgun.org']; + + console.log('mailgun req.headers'); + console.log(req.headers); + if (!validateMailgun(mailconf.apiKey, body.timestamp, body.token, body.signature)) { + console.error('Request came, but not from Mailgun'); + res.send({ error: { message: 'Invalid signature. Are you even Mailgun?' } }); + return; + } + + next(); + }); + } , 'twilio.urlencoded@daplie.com': function (req, res, next) { // TODO null for res and Promise instead of next? return bodyParserTwilio(req, res, function () {