From cd76cd0a1a15e62b88712090e418598b3fdc8122 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Tue, 1 Mar 2022 21:11:26 -0500 Subject: [PATCH] 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;