Here's how you validate a mailgun webhook in Node.js (as per the mailgun docs for securing webhooks)
'use strict';
var scmp = require('scmp')
, crypto = require('crypto')
. mailgunPrivateKey = 'XXXXXXXXXXXXX'
, mailgunTokens = {}
, mailgunExpirey = 15 * 60 * 1000
, mailgunHashType = 'sha256'
, mailgunSignatureEncoding = 'hex'
;
function validateMailgun(apiKey, timestamp, token, signature) {
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));
return scmp(
signature
, crypto.createHmac(mailgunHashType, apiKey)
.update(new Buffer(timestamp + token, 'utf-8'))
.digest(mailgunSignatureEncoding)
);
}
function router(app) {
app.post('/webhooks/mailgun/*', function (req, res, next) {
var body = req.body
;
if (!validateMailgun(mailgunPrivateKey, 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();
});
app.post('/webhooks/mailgun/catchall', function (req, res) {
// actually handle request here
});
}
Note: needs
multipart/form-data
parser (not urlencoded) for mailgun relpy