diff options
author | gumi <git@gumi.ca> | 2020-03-03 23:02:36 -0500 |
---|---|---|
committer | gumi <git@gumi.ca> | 2020-03-03 23:02:36 -0500 |
commit | 349053954d45e4625ab35e6b2383608e5132eba3 (patch) | |
tree | 1939eb58d8296bd43ce21e80708381c56e0aa120 /src | |
parent | 2df2f8a3f9eafdf1a28ce458a874135d666d0cf9 (diff) | |
download | apiv1-349053954d45e4625ab35e6b2383608e5132eba3.tar.gz apiv1-349053954d45e4625ab35e6b2383608e5132eba3.tar.bz2 apiv1-349053954d45e4625ab35e6b2383608e5132eba3.tar.xz apiv1-349053954d45e4625ab35e6b2383608e5132eba3.zip |
add rudimentary anti-bruteforcing
Diffstat (limited to 'src')
-rw-r--r-- | src/api.js | 2 | ||||
-rw-r--r-- | src/brute.js | 18 | ||||
-rw-r--r-- | src/routers/vault/middlewares/evol/account.js | 12 | ||||
-rw-r--r-- | src/routers/vault/middlewares/identity.js | 1 | ||||
-rw-r--r-- | src/routers/vault/middlewares/legacy/account.js | 39 | ||||
-rw-r--r-- | src/routers/vault/middlewares/session.js | 18 |
6 files changed, 79 insertions, 11 deletions
@@ -2,6 +2,7 @@ const express = require("express"); // from npm registry const https = require("https"); // built-in const Limiter = require("./limiter.js"); const Logger = require("./logger.js"); +const Brute = require("./brute.js"); const api = express(); if (!process.env.NODE_ENV) { @@ -28,6 +29,7 @@ api.locals = Object.assign({ from: process.env.MAILER__FROM, }, logger: Logger, + brute: Brute, }, api.locals); diff --git a/src/brute.js b/src/brute.js new file mode 100644 index 0000000..2ea767e --- /dev/null +++ b/src/brute.js @@ -0,0 +1,18 @@ +const limiters = new Map(); // Map<route, Map<ip, counter>> + +const consume = (req, max = 5, expire = 3.6e6) => { + const route = req.method + req.baseUrl + req.path; + const route_map = limiters.get(route) || limiters.set(route, new Map()).get(route); + const attempts = route_map.get(req.ip) || route_map.set(req.ip, []).get(req.ip); + + if (attempts.length >= max) { + return 0; + } else { + attempts.push(setTimeout(() => attempts.pop(), expire)); + return max - attempts.length; + } +}; + +module.exports = { + consume, +}; diff --git a/src/routers/vault/middlewares/evol/account.js b/src/routers/vault/middlewares/evol/account.js index 5067334..80f741d 100644 --- a/src/routers/vault/middlewares/evol/account.js +++ b/src/routers/vault/middlewares/evol/account.js @@ -13,8 +13,16 @@ const get_account_list = async (req, vault_id) => { where: {vaultId: vault_id}, }); - for (let acc of claimed) { - acc = await req.app.locals.evol.login.findByPk(acc.accountId); + for (const acc_ of claimed) { + const acc = await req.app.locals.evol.login.findByPk(acc_.accountId); + + if (acc === null || acc === undefined) { + // unexpected: account was deleted + console.info(`Vault.evol.account: unlinking deleted account ${acc_.accountId} {${vault_id}} [${req.ip}]`); + await acc_.destroy(); // un-claim the account + continue; + } + const chars = []; const chars_ = await req.app.locals.evol.char.findAll({ where: {accountId: acc.accountId}, diff --git a/src/routers/vault/middlewares/identity.js b/src/routers/vault/middlewares/identity.js index 51a986f..638d5bc 100644 --- a/src/routers/vault/middlewares/identity.js +++ b/src/routers/vault/middlewares/identity.js @@ -229,6 +229,7 @@ const add_identity = async (req, res, next) => { if (process.env.NODE_ENV === "development") { console.log(`uuid: ${uuid}`); } else { + // TODO: limit total number of emails that can be dispatched by a single ip in an hour transporter.sendMail({ from: process.env.VAULT__MAILER__FROM, to: req.body.email, diff --git a/src/routers/vault/middlewares/legacy/account.js b/src/routers/vault/middlewares/legacy/account.js index dddabdb..fa42ca2 100644 --- a/src/routers/vault/middlewares/legacy/account.js +++ b/src/routers/vault/middlewares/legacy/account.js @@ -1,5 +1,4 @@ "use strict"; -const uuidv4 = require("uuid/v4"); const md5saltcrypt = require("../../utils/md5saltcrypt.js"); const flatfile = require("../../utils/flatfile.js"); @@ -17,8 +16,16 @@ const get_account_list = async (req, vault_id) => { where: {vaultId: vault_id}, }); - for (let acc of claimed) { - acc = await req.app.locals.legacy.login.findByPk(acc.accountId); + for (const acc_ of claimed) { + const acc = await req.app.locals.legacy.login.findByPk(acc_.accountId); + + if (acc === null || acc === undefined) { + // unexpected: account was deleted + console.info(`Vault.legacy.account: unlinking deleted account ${acc_.accountId} {${vault_id}} [${req.ip}]`); + await acc_.destroy(); // un-claim the account + continue; + } + const chars = []; const chars_ = await req.app.locals.legacy.char.findAll({ where: {accountId: acc.accountId}, @@ -150,7 +157,17 @@ const claim_by_password = async (req, res, next) => { status: "error", error: "not found", }); - req.app.locals.cooldown(req, 1e3); + + // max 5 attempts per 15 minutes + if (req.app.locals.brute.consume(req, 5, 9e5)) { + // some attempts left + console.warn(`Vault.legacy.account: failed to log in to Legacy account {${session.vault}} [${req.ip}]`); + req.app.locals.cooldown(req, 3e3); + } else { + // no attempts left: big cooldown + req.app.locals.logger.warn(`Vault.legacy.account: login request flood {${session.vault}} [${req.ip}]`); + req.app.locals.cooldown(req, 3.6e6); + } return; } @@ -169,9 +186,17 @@ const claim_by_password = async (req, res, next) => { status: "error", error: "not found", }); - console.warn(`Vault.legacy.account: failed to log in to Legacy account {${session.vault}} [${req.ip}]`); - req.app.locals.cooldown(req, 3e5); - // TODO: huge cooldown after 8 attempts + + // max 5 attempts per 15 minutes + if (req.app.locals.brute.consume(req, 5, 9e5)) { + // some attempts left + console.warn(`Vault.legacy.account: failed to log in to Legacy account {${session.vault}} [${req.ip}]`); + req.app.locals.cooldown(req, 3e3); + } else { + // no attempts left: big cooldown + req.app.locals.logger.warn(`Vault.legacy.account: login request flood {${session.vault}} [${req.ip}]`); + req.app.locals.cooldown(req, 3.6e6); + } return; } } diff --git a/src/routers/vault/middlewares/session.js b/src/routers/vault/middlewares/session.js index b12a535..0073e90 100644 --- a/src/routers/vault/middlewares/session.js +++ b/src/routers/vault/middlewares/session.js @@ -229,13 +229,27 @@ const new_session = async (req, res, next) => { res.status(200).json({ status: "success" }); - req.app.locals.cooldown(req, 6e4); + + // max 5 attempts per 15 minutes + if (req.app.locals.brute.consume(req, 5, 9e5)) { + req.app.locals.cooldown(req, 6e4); + } else { + req.app.locals.logger.warn(`Vault.session: account creation request flood [${req.ip}]`); + req.app.locals.cooldown(req, 3.6e6); + } return; } else { res.status(202).json({ status: "pending", }); - req.app.locals.cooldown(req, 1e3); + + // max 5 attempts per 15 minutes + if (req.app.locals.brute.consume(req, 5, 9e5)) { + req.app.locals.cooldown(req, 1e3); + } else { + req.app.locals.logger.warn(`Vault.session: email check flood [${req.ip}]`); + req.app.locals.cooldown(req, 3.6e6); + } return; } } else { |