summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgumi <git@gumi.ca>2018-04-11 15:54:27 -0400
committergumi <git@gumi.ca>2018-04-11 15:54:27 -0400
commitb8cfb2a02fdfa74086b2e34a87af1f687a0b8fd0 (patch)
tree2305f447c037f002db0036d99d5ce810824507f2
parentac5a25d7aea0626aa0f42d6bc31399aee6e7e90f (diff)
downloadapi-b8cfb2a02fdfa74086b2e34a87af1f687a0b8fd0.tar.gz
api-b8cfb2a02fdfa74086b2e34a87af1f687a0b8fd0.tar.bz2
api-b8cfb2a02fdfa74086b2e34a87af1f687a0b8fd0.tar.xz
api-b8cfb2a02fdfa74086b2e34a87af1f687a0b8fd0.zip
EXPERIMENTAL: automated password resets for tmwa
-rw-r--r--package.json5
-rw-r--r--src/routers/tmwa/index.js2
-rw-r--r--src/routers/tmwa/middlewares/account.js127
3 files changed, 130 insertions, 4 deletions
diff --git a/package.json b/package.json
index b70c297..7201388 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "tmw-api",
- "version": "1.0.0",
+ "version": "1.1.0",
"description": "TMW RESTful API",
"author": "The Mana World",
"license": "CC0-1.0",
@@ -37,7 +37,8 @@
},
"dependencies": {
"express": "^4.16.3",
- "mysql": "^2.15.0"
+ "mysql": "^2.15.0",
+ "random-number-csprng": "^1.0.2"
},
"devDependencies": {
"nsp": "^3.2.1"
diff --git a/src/routers/tmwa/index.js b/src/routers/tmwa/index.js
index 495240b..f3eeb72 100644
--- a/src/routers/tmwa/index.js
+++ b/src/routers/tmwa/index.js
@@ -23,7 +23,7 @@ module.exports = exports = class TMWA {
this.router.get("/server", middlewares.server);
this.router.all("/account", rate_limit, challenge); // flood limit + captcha
- this.router.post("/account", express.json(), middlewares.account);
+ this.router.all("/account", express.json(), middlewares.account);
tmwa_poll(this); // first heartbeat
diff --git a/src/routers/tmwa/middlewares/account.js b/src/routers/tmwa/middlewares/account.js
index 1249618..9c3afce 100644
--- a/src/routers/tmwa/middlewares/account.js
+++ b/src/routers/tmwa/middlewares/account.js
@@ -1,4 +1,6 @@
-module.exports = exports = (req, res, next) => {
+const randomNumber = require("random-number-csprng");
+
+const create_account = (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}$/) ||
@@ -61,3 +63,126 @@ module.exports = exports = (req, res, next) => {
});
});
};
+
+
+
+const reset_password = async (req, res, next) => {
+ if (req.body && Reflect.has(req.body, "email") &&
+ Reflect.has(req.body, "username") &&
+ req.body.username.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 >= 3 && req.body.email.length < 40)
+ {
+ req.app.locals.tmwa.db_pool.getConnection(async (err, db) => {
+ if (err) {
+ res.status(500).json({
+ status: "error",
+ error: "couldn't reach the database"
+ });
+ console.warn("TMWA.account: a connection with the database couldn't be established");
+ return;
+ }
+
+ // XXX: we might want to use uuid instead, and put the value in the url directly
+ const rng = await randomNumber(1000000, 999999999999);
+ const code = String(rng).padStart(12, "0");
+
+ const query_params = { // SET
+ "PASSWORD": code,
+ "STATE": 3,
+ };
+
+ db.query(`UPDATE ${req.app.locals.tmwa.db_tables.register} SET ? WHERE USERNAME = ? AND EMAIL = ? AND STATE = 1`, [query_params, req.body.username, req.body.email], (err, rows, fields) => {
+ if (err) {
+ res.status(500).json({
+ status: "error",
+ error: "couldn't send a password reset"
+ });
+ console.error("TMWA.account: an unexpected sql error occured: %s", err.code);
+ } else {
+ res.status(200).json({
+ status: "success"
+ });
+ req.app.locals.rate_limiting.add(req.ip);
+ setTimeout(() => req.app.locals.rate_limiting.delete(req.ip), 5000);
+
+ // TODO: make the request expire and change the STATE back to 1 upon expiration
+ }
+
+ db.release(); // return this connection to the pool
+ });
+ });
+ return;
+ }
+
+ if (!req.body || !Reflect.has(req.body, "username") ||
+ !Reflect.has(req.body, "password") || !Reflect.has(req.body, "code") ||
+ !req.body.username.match(/^[a-zA-Z0-9]{4,23}$/) ||
+ !req.body.password.match(/^[a-zA-Z0-9]{4,23}$/) ||
+ !req.body.code.match(/^[a-zA-Z0-9-_]{6,128}$/))
+ {
+ res.status(400).json({
+ status: "error",
+ error: "malformed request"
+ });
+ 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("TMWA.account: a connection with the database couldn't be established");
+ return;
+ }
+
+ const query_params = { // SET
+ "PASSWORD": req.body.password,
+ "STATE": 5,
+ };
+
+ db.query(`UPDATE ${req.app.locals.tmwa.db_tables.register} SET ? WHERE USERNAME = ? AND PASSWORD = ? AND STATE = 4`, [query_params, req.body.username, req.body.code], (err, rows, fields) => {
+ if (err) {
+ res.status(500).json({
+ status: "error",
+ error: "couldn't change the password"
+ });
+ console.error("TMWA.account: an unexpected sql error occured: %s", err.code);
+ } else if (rows.affectedRows < 1) {
+ res.status(403).json({
+ status: "error",
+ error: "invalid code"
+ });
+ req.app.locals.rate_limiting.add(req.ip);
+ setTimeout(() => req.app.locals.rate_limiting.delete(req.ip), 2000);
+ return;
+ } else {
+ res.status(200).json({
+ status: "success"
+ });
+ console.info("TMWA.account: a password was reset: %s [%s]", query_params.USERNAME, req.ip);
+ 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
+ });
+ });
+};
+
+
+
+module.exports = exports = (req, res, next) => {
+ switch(req.method) {
+ case "POST":
+ return create_account(req, res, next);
+ case "PUT":
+ return reset_password(req, res, next);
+ default:
+ next(); // fallthrough to default endpoint (404)
+ }
+};