summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgumi <git@gumi.ca>2018-04-09 14:27:24 -0400
committergumi <git@gumi.ca>2018-04-09 14:27:24 -0400
commit662044418fcda97669c9401c6e6a7275e7e71b93 (patch)
tree85ddeb563f891a32920ff02036d1db103fafe18b
parent6fbea1b71025c16f537546459f739d73b1c3f3f9 (diff)
downloadapi-662044418fcda97669c9401c6e6a7275e7e71b93.tar.gz
api-662044418fcda97669c9401c6e6a7275e7e71b93.tar.bz2
api-662044418fcda97669c9401c6e6a7275e7e71b93.tar.xz
api-662044418fcda97669c9401c6e6a7275e7e71b93.zip
add rate limiting
-rw-r--r--package.json13
-rw-r--r--server.js26
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();