You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
5.9 KiB
155 lines
5.9 KiB
var crypto = require('crypto'),
|
|
_ = require('underscore'),
|
|
scmp = require('scmp'),
|
|
url = require('url');
|
|
|
|
/**
|
|
Utility function to validate an incoming request is indeed from Twilio
|
|
|
|
@param {string} authToken - The auth token, as seen in the Twilio portal
|
|
@param {string} twilioHeader - The value of the X-Twilio-Signature header from the request
|
|
@param {string} url - The full URL (with query string) you configured to handle this request
|
|
@param {object} params - the parameters sent with this request
|
|
*/
|
|
exports.validateRequest = function(authToken, twilioHeader, url, params) {
|
|
Object.keys(params).sort().forEach(function(key, i) {
|
|
url = url + key + params[key];
|
|
});
|
|
|
|
return scmp(twilioHeader, crypto.createHmac('sha1', authToken).update(new Buffer(url, 'utf-8')).digest('Base64'));
|
|
};
|
|
|
|
/**
|
|
Utility function to validate an incoming request is indeed from Twilio (for use with express).
|
|
adapted from https://github.com/crabasa/twiliosig
|
|
|
|
@param {object} request - An expressjs request object (http://expressjs.com/api.html#req.params)
|
|
@param {string} authToken - The auth token, as seen in the Twilio portal
|
|
@param {object} options - options for request validation:
|
|
- webhookUrl: The full URL (with query string) you used to configure the webhook with Twilio - overrides host/protocol options
|
|
- host: manually specify the host name used by Twilio in a number's webhook config
|
|
- protocol: manually specify the protocol used by Twilio in a number's webhook config
|
|
*/
|
|
exports.validateExpressRequest = function(request, authToken, opts) {
|
|
var options = opts||{}, webhookUrl;
|
|
if (options.url) {
|
|
// Let the user specify the full URL
|
|
webhookUrl = options.url;
|
|
} else {
|
|
// Use configured host/protocol, or infer based on request
|
|
var protocol = options.protocol||request.protocol;
|
|
var host = options.host||request.headers.host;
|
|
webhookUrl = url.format({
|
|
protocol: protocol,
|
|
host: host,
|
|
pathname: request.originalUrl
|
|
});
|
|
}
|
|
|
|
return exports.validateRequest(
|
|
authToken,
|
|
request.header('X-Twilio-Signature'),
|
|
webhookUrl,
|
|
request.body||{}
|
|
);
|
|
};
|
|
|
|
/**
|
|
Express middleware to accompany a Twilio webhook. Provides Twilio
|
|
request validation, and makes the response a little more friendly for our
|
|
TwiML generator. Request validation requires the express.urlencoded middleware
|
|
to have been applied (e.g. app.use(express.urlencoded()); in your app config).
|
|
|
|
Options:
|
|
- validate: {Boolean} whether or not the middleware should validate the request
|
|
came from Twilio. Default true. If the request does not originate from
|
|
Twilio, we will return a text body and a 403. If there is no configured
|
|
auth token and validate=true, this is an error condition, so we will return
|
|
a 500.
|
|
- includeHelpers: {Boolean} add helpers to the response object to improve support
|
|
for XML (TwiML) rendering. Default true.
|
|
- host: manually specify the host name used by Twilio in a number's webhook config
|
|
- protocol: manually specify the protocol used by Twilio in a number's webhook config
|
|
|
|
Returns a middleware function.
|
|
|
|
Examples:
|
|
var webhookMiddleware = twilio.webhook();
|
|
var webhookMiddleware = twilio.webhook('asdha9dhjasd'); //init with auth token
|
|
var webhookMiddleware = twilio.webhook({
|
|
validate:false // don't attempt request validation
|
|
});
|
|
var webhookMiddleware = twilio.webhook({
|
|
host: 'hook.twilio.com',
|
|
protocol: 'https'
|
|
});
|
|
*/
|
|
exports.webhook = function() {
|
|
var opts = {
|
|
validate:true,
|
|
includeHelpers:true
|
|
};
|
|
|
|
// Process arguments
|
|
var tokenString;
|
|
for (var i = 0, l = arguments.length; i<l; i++) {
|
|
var arg = arguments[i];
|
|
if (typeof arg === 'string') {
|
|
tokenString = arg;
|
|
} else {
|
|
opts = _.extend(opts, arg);
|
|
}
|
|
}
|
|
|
|
// set auth token from input or environment variable
|
|
opts.authToken = tokenString ? tokenString : process.env.TWILIO_AUTH_TOKEN;
|
|
|
|
// Create middleware function
|
|
return function hook(request, response, next) {
|
|
// Add helpers, unless disabled
|
|
if (opts.includeHelpers) {
|
|
var oldSend = response.send;
|
|
response.send = function() {
|
|
// This is a special TwiML-aware version of send. If we detect
|
|
// A twiml response object, we'll set the content-type and
|
|
// automatically call .toString()
|
|
if (arguments.length == 1 && arguments[0].legalNodes) {
|
|
response.type('text/xml');
|
|
oldSend.call(response,arguments[0].toString());
|
|
} else {
|
|
// Continue with old version of send
|
|
oldSend.apply(response,arguments);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Do validation if requested
|
|
if (opts.validate) {
|
|
// Check for a valid auth token
|
|
if (!opts.authToken) {
|
|
console.error('[Twilio]: Error - Twilio auth token is required for webhook request validation.');
|
|
response
|
|
.type('text/plain')
|
|
.status(500)
|
|
.send('Webhook Error - we attempted to validate this request without first configuring our auth token.');
|
|
} else {
|
|
// Check that the request originated from Twilio
|
|
valid = exports.validateExpressRequest(request, opts.authToken, {
|
|
url: opts.url,
|
|
host: opts.host,
|
|
protocol: opts.protocol
|
|
});
|
|
if (valid) {
|
|
next();
|
|
} else {
|
|
return response
|
|
.type('text/plain')
|
|
.status(403)
|
|
.send('Twilio Request Validation Failed.');
|
|
}
|
|
}
|
|
} else {
|
|
next();
|
|
}
|
|
};
|
|
}; |