init commit
This commit is contained in:
91
server/middlewares/preventBruteForce.ts
Executable file
91
server/middlewares/preventBruteForce.ts
Executable file
@@ -0,0 +1,91 @@
|
||||
import myError from '../api/myError'
|
||||
import * as rateLimiteFlexible from 'rate-limiter-flexible'
|
||||
import * as redis from 'redis'
|
||||
|
||||
export const globalRedisClient = redis.createClient({
|
||||
port: process.env.REDIS_PORT,
|
||||
host: process.env.REDIS_HOST,
|
||||
enable_offline_queue: false
|
||||
})
|
||||
globalRedisClient.on('connect', function (err) {
|
||||
console.log('Redis-server is connected')
|
||||
})
|
||||
globalRedisClient.on('error', function (err) {
|
||||
console.log('Error ' + err)
|
||||
})
|
||||
|
||||
const RateLimiterRedis = rateLimiteFlexible.RateLimiterRedis
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const maxWrongAttemptsByIPperDay = 100
|
||||
const maxConsecutiveFailsByUsernameAndIP = 5
|
||||
|
||||
const rateLimiterRedis = new RateLimiterRedis({
|
||||
storeClient: globalRedisClient,
|
||||
points: maxWrongAttemptsByIPperDay, // Number of points
|
||||
duration: 1000, // Per second,
|
||||
blockDuration: 60 * 60 // Per second
|
||||
})
|
||||
|
||||
export const rateLimiterMiddleware = (req, res, next) => {
|
||||
rateLimiterRedis.consume(req.ip)
|
||||
.then(() => {
|
||||
next()
|
||||
})
|
||||
.catch(err => {
|
||||
err.statusCode = 429
|
||||
err.clientCode = 11
|
||||
err.clientMessage = 'درخواست بیش از حد انجام داده اید! بعد از یک ساعت دیگر دوباره اقدام بفرمایید!'
|
||||
err.messageEnglish = 'Too Many Requests'
|
||||
next(err)
|
||||
})
|
||||
}
|
||||
|
||||
const limiterSlowBruteByIP = new RateLimiterRedis({
|
||||
storeClient: globalRedisClient,
|
||||
keyPrefix: 'login_fail_ip_per_day',
|
||||
points: maxWrongAttemptsByIPperDay,
|
||||
duration: 60 * 60 * 24,
|
||||
blockDuration: 60 * 60 * 24 // Block for 1 day, if 100 wrong attempts per day
|
||||
})
|
||||
|
||||
const limiterConsecutiveFailsByUsernameAndIP = new RateLimiterRedis({
|
||||
storeClient: globalRedisClient,
|
||||
keyPrefix: 'login_fail_consecutive_username_and_ip',
|
||||
points: maxConsecutiveFailsByUsernameAndIP,
|
||||
duration: 60 * 60, // Store number for 90 days since first fail
|
||||
blockDuration: 60 * 60 // Block for 1 hour
|
||||
})
|
||||
|
||||
const getUsernameIPkey = (username, ip) => `${username}_${ip}`
|
||||
|
||||
export async function preventBruteForce (req, res, next) {
|
||||
const ipAddr = req.ip
|
||||
const usernameIPkey = getUsernameIPkey(req.body.email, ipAddr)
|
||||
|
||||
const [resUsernameAndIP, resSlowByIP] = await Promise.all([
|
||||
limiterConsecutiveFailsByUsernameAndIP.get(usernameIPkey),
|
||||
limiterSlowBruteByIP.get(ipAddr)
|
||||
])
|
||||
let retrySecs = 0
|
||||
|
||||
// Check if IP or Username + IP is already blocked
|
||||
if (resSlowByIP !== null && resSlowByIP.consumedPoints > maxWrongAttemptsByIPperDay) {
|
||||
retrySecs = Math.round(resSlowByIP.msBeforeNext / 1000) || 1
|
||||
} else if (resUsernameAndIP !== null && resUsernameAndIP.consumedPoints > maxConsecutiveFailsByUsernameAndIP) {
|
||||
retrySecs = Math.round(resUsernameAndIP.msBeforeNext / 1000) || 1
|
||||
}
|
||||
|
||||
if (retrySecs > 0) {
|
||||
const error = new myError(`Too many requests for user ${req.body.email} with ip ${ipAddr}`, 1, 429, 'درخواست بیش از حد انجام داده اید! بعد از یک ساعت دیگر دوباره اقدام بفرمایید!', 'خطا رخ داد')
|
||||
next(error)
|
||||
} else {
|
||||
limiterConsecutiveFailsByUsernameAndIP.consume(usernameIPkey)
|
||||
limiterSlowBruteByIP.consume(ipAddr)
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user