diff --git a/facebook_bot.js b/facebook_bot.js index fc5f8534b..8d0dab858 100755 --- a/facebook_bot.js +++ b/facebook_bot.js @@ -76,6 +76,11 @@ if (!process.env.verify_token) { process.exit(1); } +if (!process.env.app_secret) { + console.log('Error: Specify app_secret in environment'); + process.exit(1); +} + var Botkit = require('./lib/Botkit.js'); var os = require('os'); var commandLineArgs = require('command-line-args'); @@ -99,6 +104,8 @@ var controller = Botkit.facebookbot({ log: true, access_token: process.env.page_token, verify_token: process.env.verify_token, + app_secret: process.env.app_secret + validate_requests: true, // Refuse any requests that don't come from FB on your receive webhook, must provide FB_APP_SECRET in environment variables }); var bot = controller.spawn({ diff --git a/lib/Facebook.js b/lib/Facebook.js index 3b534060f..5a258afa9 100644 --- a/lib/Facebook.js +++ b/lib/Facebook.js @@ -2,6 +2,7 @@ var Botkit = require(__dirname + '/CoreBot.js'); var request = require('request'); var express = require('express'); var bodyParser = require('body-parser'); +var crypto = require('crypto') function Facebookbot(configuration) { @@ -355,6 +356,14 @@ function Facebookbot(configuration) { facebook_botkit.config.port = port; facebook_botkit.webserver = express(); + + // Validate that requests come from facebook, and abort on validation errors + if (facebook_botkit.config.validate_requests === true) { + // Load verify middleware just for post route on our receive webhook, and catch any errors it might throw to prevent the request from being parsed further. + facebook_botkit.webserver.post('/facebook/receive', bodyParser.json({verify: verifyRequest})) + facebook_botkit.webserver.use(abortOnValidationError) + } + facebook_botkit.webserver.use(bodyParser.json()); facebook_botkit.webserver.use(bodyParser.urlencoded({ extended: true })); facebook_botkit.webserver.use(express.static(static_dir)); @@ -475,6 +484,34 @@ function Facebookbot(configuration) { } }; + // Verifies the SHA1 signature of the raw request payload before bodyParser parses it + // Will abort parsing if signature is invalid, and pass a generic error to response + function verifyRequest(req, res, buf, encoding) { + var expected = req.headers['x-hub-signature']; + var calculated = getSignature(buf); + if (expected !== calculated) { + throw new Error("Invalid signature on incoming request"); + } else { + facebook_botkit.debug('** X-Hub Verification successful!') + } + } + + function getSignature(buf) { + var hmac = crypto.createHmac("sha1", facebook_botkit.config.app_secret); + hmac.update(buf, "utf-8"); + return "sha1=" + hmac.digest("hex"); + } + + function abortOnValidationError(err, req, res, next) { + if (err) { + facebook_botkit.log('** Invalid X-HUB signature on incoming request!') + facebook_botkit.debug('** X-HUB Validation Error:', err) + res.status(400).send({ error: "Invalid signature." }); + } else { + next(); + } + } + return facebook_botkit; }; diff --git a/readme-facebook.md b/readme-facebook.md index 20f832842..1d1d52953 100644 --- a/readme-facebook.md +++ b/readme-facebook.md @@ -54,6 +54,15 @@ Since Facebook delivers messages via web hook, your application must be availabl When you are ready to go live, consider [LetsEncrypt.org](http://letsencrypt.org), a _free_ SSL Certificate Signing Authority which can be used to secure your website very quickly. It is fabulous and we love it. +## Validate Requests - Secure your webhook! +Facebook sends an X-HUB signature header with requests to your webhook. You can verify the requests are coming from Facebook by enabling `validate_requests: true` when creating your bot controller. This checks the sha1 signature of the incoming payload against your Facebook App Secret (which is seperate from your webhook's verify_token), preventing unauthorized access to your webhook. You must also pass your `app_secret` into your environment variables when running your bot. + +The Facebook App secret is available on the Overview page of your Facebook App's admin page. Click show to reveal it. + +``` +app_secret=abcdefg12345 page_token=123455abcd verify_token=VerIfY-tOkEn node facebook_bot.js +``` + ## Facebook-specific Events Once connected to Facebook, bots receive a constant stream of events.