diff options
Diffstat (limited to 'src/routers/tmwa')
-rw-r--r-- | src/routers/tmwa/index.js | 61 | ||||
-rw-r--r-- | src/routers/tmwa/middlewares/account.js | 65 | ||||
-rw-r--r-- | src/routers/tmwa/middlewares/server.js | 11 |
3 files changed, 137 insertions, 0 deletions
diff --git a/src/routers/tmwa/index.js b/src/routers/tmwa/index.js new file mode 100644 index 0000000..52d241f --- /dev/null +++ b/src/routers/tmwa/index.js @@ -0,0 +1,61 @@ +const express = require("express"); // from npm registry +const fs = require("fs"); // built-in +const poll_symbol = Symbol("TMWA.poll"); // private method + +const middlewares = { + account: require("./middlewares/account.js"), + server: require("./middlewares/server.js"), +}; + +module.exports = exports = class TMWA { + constructor(config, api, challenge, rate_limit) { + // XXX: having to pass a reference to `api` is weird, we should instead + // store config in this.config and make the middlewares (somehow) + // access this.config. the problem is that we can't pass arguments + // to middlewares, so we might have to curry them + + this.api = api; + this.api.locals.tmwa = config; + this.api.locals.tmwa.status = "OfflineTemporarily"; // XXX: storing these in the class feels wrong, I don't think it should be exported... maybe we could use some more curry? + this.api.locals.tmwa.num_online = 0; + this.timeout = null; + this.router = express.Router(["caseSensitive", "strict"]); + + this.router.get("/server", middlewares.server); + + this.router.all("/account", rate_limit); // filter out the flood + this.router.all("/account", challenge); // require a captcha + this.router.use("/account", express.json()); // parse the body as json + this.router.post("/account", middlewares.account); + + tmwa_poll(this); // first heartbeat + + console.info("Loaded TMWA router"); + return this.router; + } +}; + +const tmwa_poll = (_this) => { + fs.readFile("./online.txt", "utf8", (err, data) => { + const lines = data.split("\n"); + + if (err || lines.length < 2) { + console.error("encountered an error while retrieving online.txt", err); + _this.timeout = setTimeout(() => tmwa_poll(_this), 30000); // <= it failed, so check again later + return; + } + + const last_online = Date.parse(lines[0].match(/\((.+)\)/)[1] + ` ${_this.api.locals.tmwa.timezone}`); + + if (Date.now() - last_online < 30000) { + const num = lines[lines.length - 2].match(/([0-9]+) users are online./); + _this.api.locals.tmwa.status = "Online"; + _this.api.locals.tmwa.num_online = num ? num[1] : 0; + } else { + _this.api.locals.tmwa.status = "OfflineTemporarily"; + _this.api.locals.tmwa.num_online = 0; + } + + _this.timeout = setTimeout(() => tmwa_poll(_this), 2000); + }); +}; diff --git a/src/routers/tmwa/middlewares/account.js b/src/routers/tmwa/middlewares/account.js new file mode 100644 index 0000000..e29af24 --- /dev/null +++ b/src/routers/tmwa/middlewares/account.js @@ -0,0 +1,65 @@ +module.exports = exports = (req, res, next) => { + if (!req.body || !Reflect.has(req.body, "username") || + !Reflect.has(req.body, "password") || !Reflect.has(req.body, "email") || + !req.body.username.match(/^[a-zA-Z0-9]{4,23}$/) || + !req.body.password.match(/^[a-zA-Z0-9]{4,23}$/) || + !req.body.email.match(/^$|^(?:[a-zA-Z0-9.$&+=_~-]{1,34}@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,35}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,34}[a-zA-Z0-9])?){0,9})$/) || + req.body.email.length >= 40) + { + res.status(400).json({ + status: "error", + error: "malformed request" + }); + console.info("a malformed request was received", req.ip, req.body); + req.app.locals.rate_limiting.add(req.ip); + setTimeout(() => req.app.locals.rate_limiting.delete(req.ip), 300000); + return; + } + + req.app.locals.tmwa.db_pool.getConnection((err, db) => { + if (err) { + res.status(500).json({ + status: "error", + error: "couldn't reach the database" + }); + console.warn("a connection with the database couldn't be established"); + return; + } + + const query_params = { + "USERNAME": req.body.username, + "PASSWORD": req.body.password, + "EMAIL": req.body.email || "a@a.com", + "GENDER": "N", + }; + + db.query(`INSERT INTO ${req.app.locals.tmwa.db_tables.register} SET ?`, query_params, (err, rows, fields) => { + if (err) { + if (err.code === "ER_DUP_ENTRY") { + res.status(409).json({ + status: "error", + error: "already exists" + }); + console.info("a request to create an already-existent account was received", req.ip, query_params.USERNAME); + req.app.locals.rate_limiting.add(req.ip); + setTimeout(() => req.app.locals.rate_limiting.delete(req.ip), 2000); + } else { + res.status(500).json({ + status: "error", + error: "couldn't add the user" + }); + console.error("an unexpected sql error occured", err); + } + } else { + res.status(201).json({ + status: "success" + }); + console.info(`an account was created: ${query_params.USERNAME}`); + req.app.locals.rate_limiting.add(req.ip); + setTimeout(() => req.app.locals.rate_limiting.delete(req.ip), 300000); + } + + db.release(); // return this connection to the pool + }); + }); +}; diff --git a/src/routers/tmwa/middlewares/server.js b/src/routers/tmwa/middlewares/server.js new file mode 100644 index 0000000..261ecfd --- /dev/null +++ b/src/routers/tmwa/middlewares/server.js @@ -0,0 +1,11 @@ +module.exports = exports = (req, res, next) => { + res.append("Access-Control-Allow-Origin", "*"); // CORS ready + res.status(200).json({ + "@context": "http://schema.org", + "@type": "GameServer", + name: req.app.locals.tmwa.name, + url: req.app.locals.tmwa.url, + playersOnline: req.app.locals.tmwa.num_online, + serverStatus: req.app.locals.tmwa.status, + }); +}; |