From cd76cd0a1a15e62b88712090e418598b3fdc8122 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Tue, 1 Mar 2022 21:11:26 -0500 Subject: [PATCH 1/5] rate limit socket connections --- client/src/scripts/game.js | 6 ++++++ client/src/styles/home.css | 7 +++++++ client/src/views/home.html | 4 +++- package-lock.json | 5 +++++ package.json | 1 + server/main.js | 13 ++++++------ server/modules/GameManager.js | 8 +++---- server/modules/ServerBootstrapper.js | 31 ++++++++++++++++++++++++++-- 8 files changed, 61 insertions(+), 14 deletions(-) diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js index 5703b9b..bae1bc4 100644 --- a/client/src/scripts/game.js +++ b/client/src/scripts/game.js @@ -34,9 +34,15 @@ const game = () => { UserUtility.validateAnonUserSignature(res.content) ); }); + socket.on("connect_error", (err) => { + toast('Connection error: ' + err, 'error', true, false); + }); socket.on('disconnect', () => { toast('Disconnected. Attempting reconnect...', 'error', true, false); }); + socket.emit('hey'); + socket.emit('hey'); + socket.emit('hey'); setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager); }).catch((res) => { toast(res.content, 'error', true); diff --git a/client/src/styles/home.css b/client/src/styles/home.css index 32a3d4f..2a1c656 100644 --- a/client/src/styles/home.css +++ b/client/src/styles/home.css @@ -68,6 +68,13 @@ img[src='../images/logo_cropped.gif'] { margin: 3em 0 1em 0; } +#logo-container { + max-width: 400px; + width: 63vw; + min-width: 250px; + margin: 3em 0 1em 0; +} + form > div { margin: 15px 0; } diff --git a/client/src/views/home.html b/client/src/views/home.html index d3efe1c..7436050 100644 --- a/client/src/views/home.html +++ b/client/src/views/home.html @@ -24,7 +24,9 @@
- + + logo +

A tool to run werewolf when not in-person, or in any setting without a deck of cards.

diff --git a/package-lock.json b/package-lock.json index 04be469..5862ab2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4227,6 +4227,11 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, + "rate-limiter-flexible": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.3.6.tgz", + "integrity": "sha512-8DVFOe89rreyut/vzwBI7vgXJynyYoYnH5XogtAKs0F/neAbCTTglXxSJ7fZeZamcFXZDvMidCBvps4KM+1srw==" + }, "raw-body": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", diff --git a/package.json b/package.json index 1f36708..d94643f 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "jasmine": "^3.5.0", "karma-jasmine": "^4.0.1", "open": "^7.0.3", + "rate-limiter-flexible": "^2.3.6", "socket.io": "^4.4.0", "socket.io-client": "^4.4.0", "webpack": "^5.65.0", diff --git a/server/main.js b/server/main.js index ddb2faa..9aeb704 100644 --- a/server/main.js +++ b/server/main.js @@ -22,24 +22,23 @@ const main = ServerBootstrapper.createServerWithCorrectHTTPProtocol(app, args.us app.set('port', args.port); -const inGameSocketServer = ServerBootstrapper.createSocketServer(main, app, args.port); +const inGameSocketServer = ServerBootstrapper.createSocketServer(main, app, args.port, logger); +const gameNamespace = ServerBootstrapper.createGameSocketNamespace(inGameSocketServer, logger); let gameManager; /* Instantiate the singleton game manager */ if (process.env.NODE_ENV.trim() === 'development') { - gameManager = new GameManager(logger, globals.ENVIRONMENT.LOCAL).getInstance(); + gameManager = new GameManager(logger, globals.ENVIRONMENT.LOCAL, gameNamespace).getInstance(); } else { - gameManager = new GameManager(logger, globals.ENVIRONMENT.PRODUCTION).getInstance(); + gameManager = new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, gameNamespace).getInstance(); } -gameManager.namespace = inGameSocketServer; - -inGameSocketServer.on('connection', function (socket) { +gameNamespace.on('connection', function (socket) { socket.on('disconnecting', (reason) => { logger.trace('client socket disconnecting because: ' + reason); }); - gameManager.addGameSocketHandlers(inGameSocketServer, socket); + gameManager.addGameSocketHandlers(gameNamespace, socket); }); /* api endpoints */ diff --git a/server/modules/GameManager.js b/server/modules/GameManager.js index c665b07..1ba9d0d 100644 --- a/server/modules/GameManager.js +++ b/server/modules/GameManager.js @@ -6,11 +6,11 @@ const GameStateCurator = require('./GameStateCurator'); const UsernameGenerator = require('./UsernameGenerator'); class GameManager { - constructor (logger, environment) { + constructor (logger, environment, namespace) { this.logger = logger; this.environment = environment; this.activeGameRunner = new ActiveGameRunner(logger).getInstance(); - this.namespace = null; + this.namespace = namespace; } addGameSocketHandlers = (namespace, socket) => { @@ -477,10 +477,10 @@ function getGameSize (cards) { } class Singleton { - constructor (logger, environment) { + constructor (logger, environment, namespace) { if (!Singleton.instance) { logger.info('CREATING SINGLETON GAME MANAGER'); - Singleton.instance = new GameManager(logger, environment); + Singleton.instance = new GameManager(logger, environment, namespace); } } diff --git a/server/modules/ServerBootstrapper.js b/server/modules/ServerBootstrapper.js index 24cb62e..a3036a3 100644 --- a/server/modules/ServerBootstrapper.js +++ b/server/modules/ServerBootstrapper.js @@ -4,6 +4,7 @@ const https = require('https'); const path = require('path'); const fs = require('fs'); const crypto = require('crypto'); +const { RateLimiterMemory } = require('rate-limiter-flexible'); const ServerBootstrapper = { processCLIArgs: () => { @@ -79,7 +80,7 @@ const ServerBootstrapper = { return main; }, - createSocketServer: (main, app, port) => { + createSocketServer: (main, app, port, logger) => { let io; if (process.env.NODE_ENV.trim() === 'development') { io = require('socket.io')(main, { @@ -91,8 +92,34 @@ const ServerBootstrapper = { }); } - return io.of('/in-game'); + registerRateLimiter(io, logger); + + return io; + }, + + createGameSocketNamespace(server, logger) { + const namespace = server.of('/in-game'); + registerRateLimiter(namespace, logger); + return server.of('/in-game'); } }; +function registerRateLimiter(server, logger) { + const rateLimiter = new RateLimiterMemory( + { + points: 10, + duration: 1 + }); + + server.use(async (socket, next) => { + try { + await rateLimiter.consume(socket.handshake.address); + logger.trace('consumed point from ' + socket.handshake.address); + next(); + } catch(rejection) { + next(new Error("Your connection has been blocked.")); + } + }); +} + module.exports = ServerBootstrapper; From 83c82bae670a8d5b08649753383ea97fcf5bdc72 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Tue, 1 Mar 2022 21:12:30 -0500 Subject: [PATCH 2/5] remove test emits --- client/src/scripts/game.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js index bae1bc4..c8efdcf 100644 --- a/client/src/scripts/game.js +++ b/client/src/scripts/game.js @@ -40,9 +40,6 @@ const game = () => { socket.on('disconnect', () => { toast('Disconnected. Attempting reconnect...', 'error', true, false); }); - socket.emit('hey'); - socket.emit('hey'); - socket.emit('hey'); setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager); }).catch((res) => { toast(res.content, 'error', true); From c1cfb74fa7ed7e4b488bb0e502cf6a5f8804cd6d Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Tue, 1 Mar 2022 21:13:08 -0500 Subject: [PATCH 3/5] remove unnecessary log --- client/src/scripts/game.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js index c8efdcf..b3123a7 100644 --- a/client/src/scripts/game.js +++ b/client/src/scripts/game.js @@ -35,7 +35,7 @@ const game = () => { ); }); socket.on("connect_error", (err) => { - toast('Connection error: ' + err, 'error', true, false); + toast(err, 'error', true, false); }); socket.on('disconnect', () => { toast('Disconnected. Attempting reconnect...', 'error', true, false); From 6dd9095af33a00e4712ef357a2c1eaeb2e1291fa Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Tue, 1 Mar 2022 21:13:50 -0500 Subject: [PATCH 4/5] revert logo changes --- client/src/styles/home.css | 7 ------- client/src/views/home.html | 4 +--- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/client/src/styles/home.css b/client/src/styles/home.css index 2a1c656..32a3d4f 100644 --- a/client/src/styles/home.css +++ b/client/src/styles/home.css @@ -68,13 +68,6 @@ img[src='../images/logo_cropped.gif'] { margin: 3em 0 1em 0; } -#logo-container { - max-width: 400px; - width: 63vw; - min-width: 250px; - margin: 3em 0 1em 0; -} - form > div { margin: 15px 0; } diff --git a/client/src/views/home.html b/client/src/views/home.html index 7436050..d3efe1c 100644 --- a/client/src/views/home.html +++ b/client/src/views/home.html @@ -24,9 +24,7 @@
- - logo - +

A tool to run werewolf when not in-person, or in any setting without a deck of cards.

From a2888f9d1e2e6a48f00c51a63186a0bbe4c4aa0a Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Tue, 1 Mar 2022 21:21:24 -0500 Subject: [PATCH 5/5] fix constructor, lint --- client/src/scripts/game.js | 2 +- server/modules/ServerBootstrapper.js | 8 ++++---- spec/unit/server/modules/GameManager_Spec.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js index b3123a7..53066ea 100644 --- a/client/src/scripts/game.js +++ b/client/src/scripts/game.js @@ -34,7 +34,7 @@ const game = () => { UserUtility.validateAnonUserSignature(res.content) ); }); - socket.on("connect_error", (err) => { + socket.on('connect_error', (err) => { toast(err, 'error', true, false); }); socket.on('disconnect', () => { diff --git a/server/modules/ServerBootstrapper.js b/server/modules/ServerBootstrapper.js index a3036a3..c24914a 100644 --- a/server/modules/ServerBootstrapper.js +++ b/server/modules/ServerBootstrapper.js @@ -97,14 +97,14 @@ const ServerBootstrapper = { return io; }, - createGameSocketNamespace(server, logger) { + createGameSocketNamespace (server, logger) { const namespace = server.of('/in-game'); registerRateLimiter(namespace, logger); return server.of('/in-game'); } }; -function registerRateLimiter(server, logger) { +function registerRateLimiter (server, logger) { const rateLimiter = new RateLimiterMemory( { points: 10, @@ -116,8 +116,8 @@ function registerRateLimiter(server, logger) { await rateLimiter.consume(socket.handshake.address); logger.trace('consumed point from ' + socket.handshake.address); next(); - } catch(rejection) { - next(new Error("Your connection has been blocked.")); + } catch (rejection) { + next(new Error('Your connection has been blocked.')); } }); } diff --git a/spec/unit/server/modules/GameManager_Spec.js b/spec/unit/server/modules/GameManager_Spec.js index 3b958c3..9dd5be8 100644 --- a/spec/unit/server/modules/GameManager_Spec.js +++ b/spec/unit/server/modules/GameManager_Spec.js @@ -13,10 +13,10 @@ describe('GameManager', () => { beforeAll(() => { spyOn(logger, 'debug'); spyOn(logger, 'error'); - gameManager = new GameManager(logger, globals.ENVIRONMENT.PRODUCTION).getInstance(); + const inObj = { emit: () => {} }; namespace = { in: () => { return inObj; } }; - gameManager.namespace = namespace; + gameManager = new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, namespace).getInstance(); }); beforeEach(() => {