summaryrefslogtreecommitdiff
path: root/src/routers
diff options
context:
space:
mode:
Diffstat (limited to 'src/routers')
-rw-r--r--src/routers/tmwa/index.js61
-rw-r--r--src/routers/tmwa/middlewares/account.js65
-rw-r--r--src/routers/tmwa/middlewares/server.js11
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,
+ });
+};