UNPKG

twilio

Version:
167 lines (147 loc) 5.63 kB
'use strict'; var crypto = require('crypto'); var _ = require('lodash'); var scmp = require('scmp'); var 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 */ function validateRequest(authToken, twilioHeader, url, params) { Object.keys(params).sort().forEach(function(key) { 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} opts - options for request validation: - url: 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 */ function validateExpressRequest(request, authToken, opts) { var options = opts || {}; var 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 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' }); */ function webhook() { 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 var valid = 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(); } }; } module.exports = { validateRequest: validateRequest, validateExpressRequest: validateExpressRequest, webhook: webhook };