From 662044418fcda97669c9401c6e6a7275e7e71b93 Mon Sep 17 00:00:00 2001 From: gumi Date: Mon, 9 Apr 2018 14:27:24 -0400 Subject: add rate limiting --- package.json | 13 +++++++------ server.js | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b8d511f..c43e10c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "config": { "port": 8080, "timezone": "UTC", - "sql": { "host": "localhost", "user": "db user", @@ -15,12 +14,10 @@ "database": "db", "table": "table" }, - "recaptcha": { "secret": "recaptcha secret key" }, - - "tmwa" : { + "tmwa": { "name": "The Mana World Legacy Server", "url": "tmwa://server.themanaworld.org:6901" } @@ -35,12 +32,16 @@ "main": "server.js", "private": true, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "node_modules/nsp/bin/nsp check", "start": "node server.js" }, "dependencies": { "body-parser": "^1.18.2", "express": "^4.16.3", - "mysql": "^2.15.0" + "mysql": "^2.15.0", + "safe-regex": "^1.1.0" + }, + "devDependencies": { + "nsp": "^3.2.1" } } diff --git a/server.js b/server.js index 2e9bb2b..6fe33ef 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,8 @@ const https = require("https"); const fs = require("fs"); const api = express(); +const rate_limiting = new Set(); + const tmwa = { status: "OfflineTemporarily", num_online: 0, @@ -35,6 +37,18 @@ const tmwa = { } }; +const checkRateLimiting = (req, res, next) => { + if (rate_limiting.has(req.ip)) { + res.status(429).json({ + status: "error", + error: "too many requests" + }); + } else { + next(); + } + return; +}; + const checkCaptcha = (req, res, next) => { const token = String(req.get("X-CAPTCHA-TOKEN")); @@ -44,6 +58,8 @@ const checkCaptcha = (req, res, next) => { error: "no token sent" }); console.info("a request with an empty token was received", req.ip); + rate_limiting.add(req.ip); + setTimeout(() => rate_limiting.delete(req.ip), 300000); return; } @@ -58,6 +74,8 @@ const checkCaptcha = (req, res, next) => { error: "captcha validation failed" }); console.info("a request failed to validate", req.ip); + rate_limiting.add(req.ip); + setTimeout(() => rate_limiting.delete(req.ip), 300000); return; } @@ -86,6 +104,7 @@ api.get("/api/tmwa", (req, res) => { }); }); +api.use(checkRateLimiting); api.use(checkCaptcha); api.use(bodyParser.json()); api.post("/api/account", (req, res) => { @@ -101,6 +120,8 @@ api.post("/api/account", (req, res) => { error: "malformed request" }); console.info("a malformed request was received", req.ip, req.body); + rate_limiting.add(req.ip); + setTimeout(() => rate_limiting.delete(req.ip), 300000); return; } @@ -135,6 +156,8 @@ api.post("/api/account", (req, res) => { error: "already exists" }); console.info("a request to create an already-existent account was received", req.ip, account.username); + rate_limiting.add(req.ip); + setTimeout(() => rate_limiting.delete(req.ip), 2000); } else { res.status(500).json({ status: "error", @@ -147,6 +170,8 @@ api.post("/api/account", (req, res) => { status: "success" }); console.info(`an account was created: ${account.username}`); + rate_limiting.add(req.ip); + setTimeout(() => rate_limiting.delete(req.ip), 300000); } db.end(); @@ -170,5 +195,6 @@ if (process.env.npm_package_config_port === undefined) { } api.set("trust proxy", "loopback"); // only allow localhost to communicate with the API +api.disable("x-powered-by"); // we don't need this header api.listen(process.env.npm_package_config_port, () => console.info(`Listening on port ${process.env.npm_package_config_port}`)); tmwa.poll(); -- cgit v1.2.3-70-g09d2