From 3bc1fdbde76586a4260110a68bfbc86268f21710 Mon Sep 17 00:00:00 2001 From: hippoz Date: Thu, 26 Nov 2020 11:42:27 +0200 Subject: [PATCH] add a simple rate limiter --- api/v1/gateway/index.js | 11 +++++++++++ api/v1/gateway/ratelimiter.js | 33 +++++++++++++++++++++++++++++++++ config.js | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 api/v1/gateway/ratelimiter.js diff --git a/api/v1/gateway/index.js b/api/v1/gateway/index.js index 4c8cf9b..9f50acb 100644 --- a/api/v1/gateway/index.js +++ b/api/v1/gateway/index.js @@ -2,6 +2,7 @@ const User = require('../../../models/User'); const secret = require('../../../secret'); const config = require('../../../config'); const Category = require('../../../models/Category'); +const RateLimiter = require('./ratelimiter'); const jwt = require('jsonwebtoken'); const siolib = require('socket.io'); @@ -11,6 +12,11 @@ class GatewayServer { constructor(httpServer) { this._io = siolib(httpServer); this._gateway = this._io.of('/gateway'); + this.rateLimiter = new RateLimiter({ + points: 5, + time: 1000, + minPoints: 0 + }); this.eventSetup(); } } @@ -94,6 +100,11 @@ GatewayServer.prototype.eventSetup = function() { if (!category || !content || !socket.joinedCategories || !socket.isConnected || !socket.user || !(typeof content === 'string') || !(typeof category._id === 'string')) return; content = content.trim(); if (!content || content === '' || content === ' ' || content.length >= 2000) return; + if (!this.rateLimiter.consoom(socket.user.token)) { // TODO: maybe user ip instead of token? + console.log(`[E] [gateway] Rate limiting ${socket.user.username}`); + return; + } + // TODO: When/if category permissions are added, check if the user has permissions for that category const categoryTitle = socket.joinedCategories[category._id]; diff --git a/api/v1/gateway/ratelimiter.js b/api/v1/gateway/ratelimiter.js new file mode 100644 index 0000000..56d3bce --- /dev/null +++ b/api/v1/gateway/ratelimiter.js @@ -0,0 +1,33 @@ +// This is "inspired" by rate-limiter-flexible + +class RateLimiter { + constructor({ points=5, time=1000, minPoints=0 }) { + this.points = points; + this.minPoints = minPoints; + this.time = time; + + this._flooding = {}; + } +} + +RateLimiter.prototype.consoom = function(discriminator) { + if (!this._flooding[discriminator]) this._flooding[discriminator] = { points: this.points, lastReset: Date.now() }; + + if (Math.abs(new Date() - this._flooding[discriminator].lastReset) >= this.time) { + this._flooding[discriminator] = { points: this.points, lastReset: Date.now() }; + } + + this._flooding[discriminator].points--; + + if (this._flooding[discriminator].points <= this.minPoints) { + this._flooding[discriminator].flooding = true; + return false; + } + + if (this._flooding[discriminator].flooding === true) { + return false; + } + return true; +}; + +module.exports = RateLimiter; \ No newline at end of file diff --git a/config.js b/config.js index 38600f1..a225337 100755 --- a/config.js +++ b/config.js @@ -3,7 +3,7 @@ module.exports = { mainServerPort: 3000, }, address: 'localhost', - mongoUrl: 'mongodb://localhost:27017/app', + mongoUrl: 'mongodb://192.168.0.105:27017/app', bcryptRounds: 10, roleMap: { 'BANNED': 0,