diff --git a/example/jokeChatServer.js b/example/jokeChatServer.js index 6e284e4..b3e0534 100644 --- a/example/jokeChatServer.js +++ b/example/jokeChatServer.js @@ -2,6 +2,7 @@ const Bot = require('../build'); const express = require('express'); const bodyParser = require('body-parser') const Promise = require('bluebird'); +const helmet = require('helmet'); const JOKE = "Did you know photons had mass? I didn't even know they were Catholic."; const RiddleImageUrl ="http://tinyurl.com/he9tsph"; @@ -164,6 +165,7 @@ function makeServer() { }); const app = express(); + app.use(helmet()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); diff --git a/src/FBLocalChatRoutes.js b/src/FBLocalChatRoutes.js index 139d663..a56a2a4 100644 --- a/src/FBLocalChatRoutes.js +++ b/src/FBLocalChatRoutes.js @@ -8,6 +8,8 @@ import invariant from 'invariant'; import fs from 'fs'; import dot from 'dot'; import path from 'path'; +import rateLimit from 'express-rate-limit'; // Import rate limiting middleware +import sanitizeHtml from 'sanitize-html'; // Import a library to sanitize HTML const FBLocalChatRoutes = (router: Router, Bot: Object): Router => { router.get('/localChat/getMessages', (req, res) => { @@ -102,7 +104,13 @@ const FBLocalChatRoutes = (router: Router, Bot: Object): Router => { res.sendStatus(200); }); - router.get('/localChat/*', (req, res) => { + // Apply rate limiting to the endpoint + const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100 // limit each IP to 100 requests per windowMs + }); + + router.get('/localChat/*', limiter, (req, res) => { const dir = path.join(path.dirname(__filename), '..', 'localChatWeb'); var filePath = req.url.replace('/localChat', ''); if (filePath !== '/') { @@ -119,7 +127,8 @@ const FBLocalChatRoutes = (router: Router, Bot: Object): Router => { return; } var tempFn = dot.template(data); - res.send(tempFn({baseURL})); + // Sanitize the input to prevent XSS + res.send(tempFn({baseURL: sanitizeHtml(baseURL)})); }); }); diff --git a/src/index.js b/src/index.js index 28f1524..4d92cd4 100644 --- a/src/index.js +++ b/src/index.js @@ -79,7 +79,9 @@ class Bot extends EventEmitter { }); router.post('/', (req, res) => { - this.handleMessage(req.body); + if (typeof req.body === 'object' && req.body !== null) { + this.handleMessage(req.body); + } res.sendStatus(200); }); @@ -108,34 +110,38 @@ class Bot extends EventEmitter { return; } - data.entry.forEach((entry) => { - entry.messaging.forEach((event) => { - // handle messages - if (event.message) { - // Since a message containing a quick_reply can also contain text - // and attachment, check for quick_reply first - if (event.message.quick_reply) { - this.emit('quick_reply', event); - return; - } - if (event.message.text) { - this.emit('text', event); - } else if (event.message.attachments) { - this.emit('attachments', event); - } - } - - // handle postback - if (event.postback && event.postback.payload) { - this.emit('postback', event); + if (Array.isArray(data.entry)) { + data.entry.forEach((entry) => { + if (entry.messaging && Array.isArray(entry.messaging)) { + entry.messaging.forEach((event) => { + // handle messages + if (event.message) { + // Since a message containing a quick_reply can also contain text + // and attachment, check for quick_reply first + if (event.message.quick_reply) { + this.emit('quick_reply', event); + return; + } + if (event.message.text) { + this.emit('text', event); + } else if (event.message.attachments) { + this.emit('attachments', event); + } + } + + // handle postback + if (event.postback && event.postback.payload) { + this.emit('postback', event); + } + // Handle authentication + if (event.optin && event.optin.ref) { + this.emit('optin', event); + } + // TODO: handle message delivery + }); } - // Handle authentication - if (event.optin && event.optin.ref) { - this.emit('optin', event); - } - // TODO: handle message delivery - }) - }); + }); + } } /**