1
0
mirror of https://github.com/sasjs/server.git synced 2026-01-06 14:10:06 +00:00

chore: move brute force protection logic to middleware and a singleton class

This commit is contained in:
2023-03-29 15:33:32 +05:00
parent a82cabb001
commit 89048ce943
12 changed files with 190 additions and 121 deletions

View File

@@ -8,7 +8,7 @@ import Client from '../model/Client'
import {
getWebBuildFolder,
generateAuthCode,
getRateLimiters,
RateLimiter,
AuthProviderType,
LDAPClient,
secondsToHms
@@ -83,47 +83,6 @@ const login = async (
req: express.Request,
{ username, password }: LoginPayload
) => {
// code for preventing brute force attack
const {
MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY,
MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP
} = process.env
const { limiterSlowBruteByIP, limiterConsecutiveFailsByUsernameAndIP } =
getRateLimiters()
const ipAddr = req.ip
const usernameIPkey = getUsernameIPkey(username, ipAddr)
const [resSlowByIP, resUsernameAndIP] = await Promise.all([
limiterSlowBruteByIP.get(ipAddr),
limiterConsecutiveFailsByUsernameAndIP.get(usernameIPkey)
])
let retrySecs = 0
// Check if IP or Username + IP is already blocked
if (
resSlowByIP !== null &&
resSlowByIP.consumedPoints >= Number(MAX_WRONG_ATTEMPTS_BY_IP_PER_DAY)
) {
retrySecs = Math.round(resSlowByIP.msBeforeNext / 1000) || 1
} else if (
resUsernameAndIP !== null &&
resUsernameAndIP.consumedPoints >=
Number(MAX_CONSECUTIVE_FAILS_BY_USERNAME_AND_IP)
) {
retrySecs = Math.round(resUsernameAndIP.msBeforeNext / 1000) || 1
}
if (retrySecs > 0) {
throw {
code: 429,
message: `Too Many Requests! Retry after ${secondsToHms(retrySecs)}`
}
}
// Authenticate User
const user = await User.findOne({ username })
@@ -143,28 +102,16 @@ const login = async (
}
}
// Consume 1 point from limiters on wrong attempt and block if limits reached
// code to prevent brute force attack
const rateLimiter = RateLimiter.getInstance()
if (!validPass) {
try {
const promises = [limiterSlowBruteByIP.consume(ipAddr)]
if (user) {
// Count failed attempts by Username + IP only for registered users
promises.push(
limiterConsecutiveFailsByUsernameAndIP.consume(usernameIPkey)
)
}
await Promise.all(promises)
} catch (rlRejected: any) {
if (rlRejected instanceof Error) {
throw rlRejected
} else {
retrySecs = Math.round(rlRejected.msBeforeNext / 1000) || 1
throw {
code: 429,
message: `Too Many Requests! Retry after ${secondsToHms(retrySecs)}`
}
const retrySecs = await rateLimiter.consume(req.ip, user?.username)
if (retrySecs > 0) {
throw {
code: 429,
message: `Too Many Requests! Retry after ${secondsToHms(retrySecs)}`
}
}
}
@@ -172,10 +119,8 @@ const login = async (
if (!user) throw { code: 401, message: 'Username is not found.' }
if (!validPass) throw { code: 401, message: 'Invalid Password.' }
if (resUsernameAndIP !== null && resUsernameAndIP.consumedPoints > 0) {
// Reset on successful authorization
await limiterConsecutiveFailsByUsernameAndIP.delete(usernameIPkey)
}
// Reset on successful authorization
rateLimiter.resetOnSuccess(req.ip, user.username)
req.session.loggedIn = true
req.session.user = {