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() } }