From c6e4486c283f8ca7403b3e65b821a6b271158fe8 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sat, 10 Dec 2022 12:21:03 -0500 Subject: [PATCH 1/6] rate limit routes to html pages --- server/routes/router.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/server/routes/router.js b/server/routes/router.js index 7ee7263..494e9b9 100644 --- a/server/routes/router.js +++ b/server/routes/router.js @@ -1,32 +1,40 @@ const express = require('express'); const router = express.Router({ strict: true }); const path = require('path'); +const rateLimit = require('express-rate-limit').default; -router.get('/', function (request, response) { +const htmlPageLimiter = rateLimit({ + windowMs: 60000, + max: 100, + standardHeaders: true, + legacyHeaders: false +}); + +router.get('/', htmlPageLimiter, function (request, response) { response.sendFile(path.join(__dirname, '../../client/src/views/home.html')); }); -router.get('/create', function (request, response) { +router.get('/create', htmlPageLimiter, function (request, response) { response.sendFile(path.join(__dirname, '../../client/src/views/create.html')); }); -router.get('/join/:code', function (request, response) { +router.get('/join/:code', htmlPageLimiter, function (request, response) { response.sendFile(path.join(__dirname, '../../client/src/views/join.html')); }); -router.get('/how-to-use', function (request, response) { +router.get('/how-to-use', htmlPageLimiter, function (request, response) { response.sendFile(path.join(__dirname, '../../client/src/views/how-to-use.html')); }); -router.get('/game/:code', function (request, response) { +router.get('/game/:code', htmlPageLimiter, function (request, response) { response.sendFile(path.join(__dirname, '../../client/src/views/game.html')); }); -router.get('/liveness_check', (req, res) => { +router.get('/liveness_check', htmlPageLimiter, (req, res) => { res.sendStatus(200); }); -router.get('/readiness_check', (req, res) => { +router.get('/readiness_check', htmlPageLimiter, (req, res) => { res.sendStatus(200); }); From f6729bef20b5844ff55f24a06ff9b4df8350eb3a Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sat, 10 Dec 2022 23:14:49 -0500 Subject: [PATCH 2/6] refactor rate limiting --- server/api/AdminAPI.js | 12 ------------ server/api/GamesAPI.js | 20 ++++++-------------- server/modules/ServerBootstrapper.js | 19 ++++++++++++++++--- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/server/api/AdminAPI.js b/server/api/AdminAPI.js index 394c581..b1491fd 100644 --- a/server/api/AdminAPI.js +++ b/server/api/AdminAPI.js @@ -6,23 +6,11 @@ const socketManager = new (require('../modules/SocketManager.js'))().getInstance const gameManager = new (require('../modules/GameManager.js'))().getInstance(); const globals = require('../config/globals.js'); const cors = require('cors'); -const rateLimit = require('express-rate-limit').default; const KEY = process.env.NODE_ENV.trim() === 'development' ? globals.MOCK_AUTH : process.env.ADMIN_KEY; -const apiLimiter = rateLimit({ - windowMs: 60000, - max: 50, - standardHeaders: true, - legacyHeaders: false -}); - -if (process.env.NODE_ENV.trim() === 'production') { - router.use(apiLimiter); -} - router.use(cors(globals.CORS)); router.use((req, res, next) => { diff --git a/server/api/GamesAPI.js b/server/api/GamesAPI.js index 8c1344b..c6aa657 100644 --- a/server/api/GamesAPI.js +++ b/server/api/GamesAPI.js @@ -9,16 +9,13 @@ const cors = require('cors'); const gameManager = new GameManager().getInstance(); -const apiLimiter = rateLimit({ - windowMs: 60000, - max: 100, - standardHeaders: true, - legacyHeaders: false -}); +const gameCreationLimit = process.env.NODE_ENV.trim() === 'production' + ? 20 + : 1000 -const gameEndpointLimiter = rateLimit({ // further limit the rate of game creation to 30 games per 10 minutes. +const gameEndpointLimiter = rateLimit({ windowMs: 600000, - max: 30, + max: gameCreationLimit, standardHeaders: true, legacyHeaders: false }); @@ -38,12 +35,7 @@ router.patch('/restart', (req, res, next) => { globals.CONTENT_TYPE_VALIDATOR(req, res, next); }); -if (process.env.NODE_ENV.trim() === 'production') { - router.use(apiLimiter); - router.use('/create', gameEndpointLimiter); -} - -router.post('/create', function (req, res) { +router.post('/create', gameEndpointLimiter, function (req, res) { logger.debug('Received request to create new game: ' + JSON.stringify(req.body, null, 4)); const gameCreationPromise = gameManager.createGame(req.body, false); gameCreationPromise.then((result) => { diff --git a/server/modules/ServerBootstrapper.js b/server/modules/ServerBootstrapper.js index 17b65d2..0123ed0 100644 --- a/server/modules/ServerBootstrapper.js +++ b/server/modules/ServerBootstrapper.js @@ -7,6 +7,7 @@ const crypto = require('crypto'); const SocketManager = require('./SocketManager.js'); const GameManager = require('./GameManager.js'); const { ENVIRONMENT } = require('../config/globals.js'); +const rateLimit = require('express-rate-limit').default; const ServerBootstrapper = { @@ -93,22 +94,34 @@ const ServerBootstrapper = { }, establishRouting: (app, express) => { + + const standardRateLimit = rateLimit({ + windowMs: 60000, + max: 100, + standardHeaders: true, + legacyHeaders: false + }) + /* api endpoints */ const games = require('../api/GamesAPI'); const admin = require('../api/AdminAPI'); app.use('/api/games', games); app.use('/api/admin', admin); + if (process.env.NODE_ENV.trim() === 'production') { + app.use('/api/', standardRateLimit); + } + /* serve all the app's pages */ - app.use('/manifest.json', (req, res) => { + app.use('/manifest.json', standardRateLimit, (req, res) => { res.sendFile(path.join(__dirname, '../../manifest.json')); }); - app.use('/favicon.ico', (req, res) => { + app.use('/favicon.ico', standardRateLimit, (req, res) => { res.sendFile(path.join(__dirname, '../../client/favicon_package/favicon.ico')); }); - app.use('/apple-touch-icon.png', (req, res) => { + app.use('/apple-touch-icon.png', standardRateLimit, (req, res) => { res.sendFile(path.join(__dirname, '../../client/favicon_package/apple-touch-icon.png')); }); From 9ef91415139291a3c590ff8ed708d66f7b3af8a6 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sat, 10 Dec 2022 23:17:19 -0500 Subject: [PATCH 3/6] lint --- server/api/GamesAPI.js | 2 +- server/modules/ServerBootstrapper.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/server/api/GamesAPI.js b/server/api/GamesAPI.js index c6aa657..5a800fd 100644 --- a/server/api/GamesAPI.js +++ b/server/api/GamesAPI.js @@ -11,7 +11,7 @@ const gameManager = new GameManager().getInstance(); const gameCreationLimit = process.env.NODE_ENV.trim() === 'production' ? 20 - : 1000 + : 1000; const gameEndpointLimiter = rateLimit({ windowMs: 600000, diff --git a/server/modules/ServerBootstrapper.js b/server/modules/ServerBootstrapper.js index 0123ed0..1eb81f3 100644 --- a/server/modules/ServerBootstrapper.js +++ b/server/modules/ServerBootstrapper.js @@ -94,13 +94,12 @@ const ServerBootstrapper = { }, establishRouting: (app, express) => { - const standardRateLimit = rateLimit({ windowMs: 60000, max: 100, standardHeaders: true, legacyHeaders: false - }) + }); /* api endpoints */ const games = require('../api/GamesAPI'); From fe84db12fb6a96f76f51133660a8cd026a4ab208 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sat, 10 Dec 2022 23:27:23 -0500 Subject: [PATCH 4/6] refactor admin api auth --- server/api/AdminAPI.js | 24 ------------------------ server/modules/ServerBootstrapper.js | 28 +++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/server/api/AdminAPI.js b/server/api/AdminAPI.js index b1491fd..ee4e603 100644 --- a/server/api/AdminAPI.js +++ b/server/api/AdminAPI.js @@ -7,20 +7,8 @@ const gameManager = new (require('../modules/GameManager.js'))().getInstance(); const globals = require('../config/globals.js'); const cors = require('cors'); -const KEY = process.env.NODE_ENV.trim() === 'development' - ? globals.MOCK_AUTH - : process.env.ADMIN_KEY; - router.use(cors(globals.CORS)); -router.use((req, res, next) => { - if (isAuthorized(req)) { - next(); - } else { - res.status(401).send('You are not authorized to make this request.'); - } -}); - router.post('/sockets/broadcast', (req, res, next) => { globals.CONTENT_TYPE_VALIDATOR(req, res, next); }); @@ -40,16 +28,4 @@ router.get('/games/state', function (req, res) { res.status(200).send(gamesArray); }); -/* validates Bearer Auth */ -function isAuthorized (req) { - const header = req.headers.authorization; - if (header) { - const token = header.split(/\s+/).pop() || ''; - const decodedToken = Buffer.from(token, 'base64').toString(); - return decodedToken.trim() === KEY.trim(); - } - - return false; -} - module.exports = router; diff --git a/server/modules/ServerBootstrapper.js b/server/modules/ServerBootstrapper.js index 1eb81f3..c34d6f6 100644 --- a/server/modules/ServerBootstrapper.js +++ b/server/modules/ServerBootstrapper.js @@ -6,6 +6,7 @@ const fs = require('fs'); const crypto = require('crypto'); const SocketManager = require('./SocketManager.js'); const GameManager = require('./GameManager.js'); +const globals = require('../config/globals.js'); const { ENVIRONMENT } = require('../config/globals.js'); const rateLimit = require('express-rate-limit').default; @@ -107,9 +108,15 @@ const ServerBootstrapper = { app.use('/api/games', games); app.use('/api/admin', admin); - if (process.env.NODE_ENV.trim() === 'production') { - app.use('/api/', standardRateLimit); - } + app.use('/api/', standardRateLimit); + + app.use('/api/admin', (req, res, next) => { + if (isAuthorized(req)) { + next(); + } else { + res.status(401).send('You are not authorized to make this request.'); + } + }); /* serve all the app's pages */ app.use('/manifest.json', standardRateLimit, (req, res) => { @@ -143,4 +150,19 @@ const ServerBootstrapper = { } }; +/* validates Bearer Auth */ +function isAuthorized (req) { + const KEY = process.env.NODE_ENV.trim() === 'development' + ? globals.MOCK_AUTH + : process.env.ADMIN_KEY; + const header = req.headers.authorization; + if (header) { + const token = header.split(/\s+/).pop() || ''; + const decodedToken = Buffer.from(token, 'base64').toString(); + return decodedToken.trim() === KEY.trim(); + } + + return false; +} + module.exports = ServerBootstrapper; From 7de7b7453e30d5967ed4f1a28e9a33e4bbd90d1a Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sat, 10 Dec 2022 23:34:21 -0500 Subject: [PATCH 5/6] rate limit robots.txt --- server/modules/ServerBootstrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/ServerBootstrapper.js b/server/modules/ServerBootstrapper.js index c34d6f6..749ac06 100644 --- a/server/modules/ServerBootstrapper.js +++ b/server/modules/ServerBootstrapper.js @@ -140,7 +140,7 @@ const ServerBootstrapper = { app.use('/images', express.static(path.join(__dirname, '../../client/src/images'))); app.use('/styles', express.static(path.join(__dirname, '../../client/src/styles'))); app.use('/webfonts', express.static(path.join(__dirname, '../../client/src/webfonts'))); - app.use('/robots.txt', (req, res) => { + app.use('/robots.txt', standardRateLimit, (req, res) => { res.sendFile(path.join(__dirname, '../../client/robots.txt')); }); From 0935456ef5076427f63e1808e07c86ab0433b697 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Sat, 10 Dec 2022 23:50:17 -0500 Subject: [PATCH 6/6] refactor again --- server/modules/ServerBootstrapper.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/server/modules/ServerBootstrapper.js b/server/modules/ServerBootstrapper.js index 749ac06..d831068 100644 --- a/server/modules/ServerBootstrapper.js +++ b/server/modules/ServerBootstrapper.js @@ -102,23 +102,17 @@ const ServerBootstrapper = { legacyHeaders: false }); - /* api endpoints */ - const games = require('../api/GamesAPI'); - const admin = require('../api/AdminAPI'); - app.use('/api/games', games); - app.use('/api/admin', admin); - - app.use('/api/', standardRateLimit); - + // API endpoints + app.use('/api/games', standardRateLimit, require('../api/GamesAPI')); app.use('/api/admin', (req, res, next) => { if (isAuthorized(req)) { next(); } else { res.status(401).send('You are not authorized to make this request.'); } - }); + }, standardRateLimit, require('../api/AdminAPI')); - /* serve all the app's pages */ + // miscellaneous assets app.use('/manifest.json', standardRateLimit, (req, res) => { res.sendFile(path.join(__dirname, '../../manifest.json')); }); @@ -144,13 +138,12 @@ const ServerBootstrapper = { res.sendFile(path.join(__dirname, '../../client/robots.txt')); }); - app.use(function (req, res) { + app.use(standardRateLimit, function (req, res) { res.sendFile(path.join(__dirname, '../../client/src/views/404.html')); }); } }; -/* validates Bearer Auth */ function isAuthorized (req) { const KEY = process.env.NODE_ENV.trim() === 'development' ? globals.MOCK_AUTH @@ -159,7 +152,7 @@ function isAuthorized (req) { if (header) { const token = header.split(/\s+/).pop() || ''; const decodedToken = Buffer.from(token, 'base64').toString(); - return decodedToken.trim() === KEY.trim(); + return decodedToken.trim() === KEY?.trim(); } return false;