blob: 3ce165fab04695f95270ef86ecce07c81da8c1fd [file] [log] [blame]
var tsscmp = require('tsscmp');
var crypto = require('crypto');
var Attachment = require('./attachment');
var Request = require('./request');
var builder = require('./build');
var resources = require('./schema').definitions;
var mailgunExpirey = 15 * 60 * 1000;
var mailgunHashType = 'sha256';
var mailgunSignatureEncoding = 'hex';
var Mailgun = function (options) {
if(!options.apiKey){
throw new Error('apiKey value must be defined!');
}
this.username = 'api';
this.apiKey = options.apiKey;
this.domain = options.domain;
this.auth = [this.username, this.apiKey].join(':');
this.mute = options.mute || false;
this.timeout = options.timeout;
this.host = options.host || 'api.mailgun.net';
this.endpoint = options.endpoint || '/v3';
this.protocol = options.protocol || 'https:';
this.port = options.port || 443;
this.retry = options.retry || 1;
if (options.proxy) {
this.proxy = options.proxy;
}
this.options = {
host: this.host,
endpoint: this.endpoint,
protocol: this.protocol,
port: this.port,
auth: this.auth,
proxy: this.proxy,
timeout: this.timeout,
retry: this.retry
};
this.mailgunTokens = {};
};
Mailgun.prototype.getDomain = function (method, resource) {
var d = this.domain;
//filter out API calls that do not require a domain specified
if ((resource.indexOf('/routes') >= 0)
|| (resource.indexOf('/lists') >= 0)
|| (resource.indexOf('/address') >= 0)
|| (resource.indexOf('/domains') >= 0 )) {
d = '';
}
else if ((resource.indexOf('/messages') >= 0)
&& (method === 'GET' || method === 'DELETE')) {
d = 'domains/' + this.domain;
}
return d;
};
Mailgun.prototype.request = function (method, resource, data, fn) {
var fullpath = resource;
var domain = this.getDomain(method, resource);
if (domain) {
fullpath = '/'.concat(domain, resource);
}
var req = new Request(this.options);
return req.request(method, fullpath, data, fn);
};
Mailgun.prototype.post = function (path, data, fn) {
var req = new Request(this.options);
return req.request('POST', path, data, fn);
};
Mailgun.prototype.get = function (path, data, fn) {
var req = new Request(this.options);
return req.request('GET', path, data, fn);
};
Mailgun.prototype.delete = function (path, data, fn) {
var req = new Request(this.options);
return req.request('DELETE', path, data, fn);
};
Mailgun.prototype.put = function (path, data, fn) {
var req = new Request(this.options);
return req.request('PUT', path, data, fn);
};
Mailgun.prototype.validateWebhook = function (timestamp, token, signature) {
var self = this;
var adjustedTimestamp = parseInt(timestamp, 10) * 1000;
var fresh = (Math.abs(Date.now() - adjustedTimestamp) < mailgunExpirey);
if (!fresh) {
if (!this.mute) {
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 (this.mailgunTokens[token]) {
if (!this.mute) {
console.error('[mailgun] Replay Attack');
}
return false;
}
this.mailgunTokens[token] = true;
setTimeout(function () {
delete self.mailgunTokens[token];
}, mailgunExpirey + (5 * 1000));
return tsscmp(
signature
, crypto.createHmac(mailgunHashType, self.apiKey)
.update(new Buffer(timestamp + token, 'utf-8'))
.digest(mailgunSignatureEncoding)
);
};
builder.build(Mailgun, resources);
Mailgun.prototype.Attachment = Attachment;
Mailgun.prototype.Mailgun = Mailgun;
module.exports = function (options) {
return new Mailgun(options);
};