diff --git a/README.md b/README.md index 1d6388cb1d..6cba0597e5 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,9 @@ const client = require('twilio')(accountSid, authToken); client.logLevel = 'debug'; ``` +## Using webhook validation +See [example](examples/express.js) for a code sample for incoming Twilio request validation + ## Handling Exceptions For an example on how to handle exceptions in this helper library, please see the [Twilio documentation](https://www.twilio.com/docs/libraries/node/usage-guide#exceptions). diff --git a/examples/express.js b/examples/express.js new file mode 100644 index 0000000000..9fda38ef05 --- /dev/null +++ b/examples/express.js @@ -0,0 +1,32 @@ +const twilio = require('twilio'); +const bodyParser = require('body-parser'); +const MessagingResponse = require('twilio').twiml.MessagingResponse; + +const express = require('express') +const app = express() +const port = 3000 + +app.use(bodyParser.json({ + verify: (req, res, buf) => { + req.rawBody = buf + } +})) + +app.get('/', (req, res) => { + res.send('Hello World!') +}) + +app.post('/message', twilio.webhook(), (req, res) => { + // Twilio Messaging URL - receives incoming messages from Twilio + const response = new MessagingResponse(); + + response.message(`Your text to me was ${req.body.Body}. + Webhooks are neat :)`); + + res.set('Content-Type', 'text/xml'); + res.send(response.toString()); +}); + +app.listen(port, () => { + console.log(`Example app listening at http://localhost:${port}`) +}) diff --git a/lib/webhooks/webhooks.js b/lib/webhooks/webhooks.js index 60f0d37361..8c0bb3794a 100644 --- a/lib/webhooks/webhooks.js +++ b/lib/webhooks/webhooks.js @@ -172,7 +172,7 @@ function validateExpressRequest(request, authToken, opts) { authToken, request.header('X-Twilio-Signature'), webhookUrl, - request.body || {} + request.rawBody || '{}' ); } else { return validateRequest( diff --git a/spec/validation.spec.js b/spec/validation.spec.js index 3c7d74ce91..5135c3fa9e 100644 --- a/spec/validation.spec.js +++ b/spec/validation.spec.js @@ -24,6 +24,10 @@ const bodySignature = '0a1ff7634d9ab3b95db5c9a2dfe9416e41502b283a80c7cf19632632f const requestUrlWithHash = requestUrl + '&bodySHA256=' + bodySignature; const requestUrlWithHashSignature = 'a9nBmqA0ju/hNViExpshrM61xv4='; +const requestUrlWithHashSignatureEmptyBody = 'Ldidvp12m26NI7eqEvxnEpkd9Hc='; +const bodySignatureEmpty = '44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a'; +const requestUrlWithHashEmpty = requestUrl + '&bodySHA256=' + bodySignatureEmpty; + describe('Request validation', () => { it('should succeed with the correct signature', () => { const isValid = validateRequest(token, defaultSignature, requestUrl, defaultParams); @@ -55,6 +59,12 @@ describe('Request validation', () => { expect(isValid).toBeTruthy(); }); + it('should validate request body when empty', () => { + const isValid = validateRequestWithBody(token, requestUrlWithHashSignatureEmptyBody, requestUrlWithHashEmpty, '{}'); + + expect(isValid).toBeTruthy(); + }); + it('should validate request body with only sha param', () => { const sig = bodySignature.replace('+', '%2B').replace('=', '%3D'); const shortUrl = 'https://mycompany.com/myapp.php?bodySHA256=' + sig; @@ -265,6 +275,7 @@ describe('Request validation middleware', () => { }), })); + request.rawBody = body; middleware(request, response, () => { done(); }); @@ -296,8 +307,8 @@ describe('Request validation middleware', () => { const newUrl = fullUrl.pathname + fullUrl.search + '&somethingUnexpected=true'; const request = httpMocks.createRequest(Object.assign({}, defaultRequestWithoutTwilioSignature, { - originalUrl: newUrl, - })); + originalUrl: newUrl, + })); middleware(request, response, () => { expect(true).toBeFalsy();