add login rate limiter

This commit is contained in:
Louis Lam 2021-10-23 16:35:13 +08:00
parent 8a481a1be0
commit b77b33e790
3 changed files with 66 additions and 11 deletions

View File

@ -1,8 +1,9 @@
const basicAuth = require("express-basic-auth") const basicAuth = require("express-basic-auth");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { setting } = require("./util-server"); const { setting } = require("./util-server");
const { debug } = require("../src/util"); const { debug } = require("../src/util");
const { loginRateLimiter } = require("./rate-limiter");
/** /**
* *
@ -13,7 +14,7 @@ const { debug } = require("../src/util");
exports.login = async function (username, password) { exports.login = async function (username, password) {
let user = await R.findOne("user", " username = ? AND active = 1 ", [ let user = await R.findOne("user", " username = ? AND active = 1 ", [
username, username,
]) ]);
if (user && passwordHash.verify(password, user.password)) { if (user && passwordHash.verify(password, user.password)) {
// Upgrade the hash to bcrypt // Upgrade the hash to bcrypt
@ -27,21 +28,30 @@ exports.login = async function (username, password) {
} }
return null; return null;
} };
function myAuthorizer(username, password, callback) { function myAuthorizer(username, password, callback) {
setting("disableAuth").then((result) => { setting("disableAuth").then((result) => {
if (result) { if (result) {
callback(null, true) callback(null, true);
} else { } else {
exports.login(username, password).then((user) => { // Login Rate Limit
callback(null, user != null) loginRateLimiter.pass(null, 0).then((pass) => {
}) if (pass) {
} exports.login(username, password).then((user) => {
}) callback(null, user != null);
if (user == null) {
loginRateLimiter.removeTokens(1);
}
});
} else {
callback(null, false);
}
});
}
});
} }
exports.basicAuth = basicAuth({ exports.basicAuth = basicAuth({

39
server/rate-limiter.js Normal file
View File

@ -0,0 +1,39 @@
const { RateLimiter } = require("limiter");
const { debug } = require("../src/util");
class KumaRateLimiter {
constructor(config) {
this.errorMessage = config.errorMessage;
this.rateLimiter = new RateLimiter(config);
}
async pass(callback, num = 1) {
const remainingRequests = await this.removeTokens(num);
debug("Rate Limit (remainingRequests):" + remainingRequests);
if (remainingRequests < 0) {
if (callback) {
callback({
ok: false,
msg: this.errorMessage,
});
}
return false;
}
return true;
}
async removeTokens(num = 1) {
return await this.rateLimiter.removeTokens(num);
}
}
const loginRateLimiter = new KumaRateLimiter({
tokensPerInterval: 20,
interval: "minute",
fireImmediately: true,
errorMessage: "Too frequently, try again later."
});
module.exports = {
loginRateLimiter
};

View File

@ -52,6 +52,7 @@ const Database = require("./database");
debug("Importing Background Jobs"); debug("Importing Background Jobs");
const { initBackgroundJobs } = require("./jobs"); const { initBackgroundJobs } = require("./jobs");
const { loginRateLimiter } = require("./rate-limiter");
const { basicAuth } = require("./auth"); const { basicAuth } = require("./auth");
const { login } = require("./auth"); const { login } = require("./auth");
@ -281,6 +282,11 @@ exports.entryPage = "dashboard";
socket.on("login", async (data, callback) => { socket.on("login", async (data, callback) => {
console.log("Login"); console.log("Login");
// Login Rate Limit
if (! await loginRateLimiter.pass(callback)) {
return;
}
let user = await login(data.username, data.password); let user = await login(data.username, data.password);
if (user) { if (user) {