|
|
@ -1,4 +1,5 @@
|
|
|
|
const {RateLimiterPostgres, RateLimiterUnion} = require('rate-limiter-flexible');
|
|
|
|
const {RateLimiterPostgres, RateLimiterUnion} = require('rate-limiter-flexible');
|
|
|
|
|
|
|
|
const {Duration} = require('luxon')
|
|
|
|
const pg = require('../../db/pg')
|
|
|
|
const pg = require('../../db/pg')
|
|
|
|
|
|
|
|
|
|
|
|
const globalOptions = {
|
|
|
|
const globalOptions = {
|
|
|
@ -32,19 +33,31 @@ const loginRateLimitPool = new RateLimiterUnion(...loginRateLimits)
|
|
|
|
|
|
|
|
|
|
|
|
module.exports.loginRateLimit = async (req, res) => {
|
|
|
|
module.exports.loginRateLimit = async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
|
|
|
|
let currentLimits = await Promise.all(
|
|
|
|
|
|
|
|
loginRateLimits.map(limiter => limiter.get(req.body.email))
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const failed = currentLimits.filter(limit => limit?.remainingPoints === 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(failed.length > 0)
|
|
|
|
|
|
|
|
throw failed;
|
|
|
|
|
|
|
|
|
|
|
|
await loginRateLimitPool.consume(req.body.email)
|
|
|
|
await loginRateLimitPool.consume(req.body.email)
|
|
|
|
return 'next';
|
|
|
|
return 'next';
|
|
|
|
} catch (limits) {
|
|
|
|
} catch (limits) {
|
|
|
|
const wait = Math.max(...Object.values(limits).map(limit => limit?.msBeforeNext || 1));
|
|
|
|
const waitMillis = Math.max(...Object.values(limits).map(limit => limit?.msBeforeNext || 1));
|
|
|
|
|
|
|
|
const wait = Math.ceil(waitMillis / 1000)
|
|
|
|
|
|
|
|
|
|
|
|
res.status(429)
|
|
|
|
res.status(429)
|
|
|
|
res.set({
|
|
|
|
res.set({
|
|
|
|
'Retry-After': Math.ceil(wait / 1000)
|
|
|
|
'Retry-After': wait
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const duration = secondsToTimeString(wait)
|
|
|
|
|
|
|
|
|
|
|
|
res.json({errors: [{
|
|
|
|
res.json({errors: [{
|
|
|
|
param: 'email',
|
|
|
|
param: 'email',
|
|
|
|
msg: `Too many password attempts, please wait ${Math.ceil(wait / 1000)} seconds`
|
|
|
|
msg: `Too many login attempts, please wait ${duration}`
|
|
|
|
},{
|
|
|
|
},{
|
|
|
|
param: 'password',
|
|
|
|
param: 'password',
|
|
|
|
msg: ' '
|
|
|
|
msg: ' '
|
|
|
@ -57,3 +70,31 @@ module.exports.loginRateLimit.reset = email => Promise.all(
|
|
|
|
rateLimit.delete(email)
|
|
|
|
rateLimit.delete(email)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function secondsToTimeString(seconds) {
|
|
|
|
|
|
|
|
const duration = Duration.fromObject({seconds}).shiftTo('days', 'hours', 'minutes', 'seconds')
|
|
|
|
|
|
|
|
let string = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(duration.days)
|
|
|
|
|
|
|
|
string += `${duration.days} day${duration.days > 1 ? 's' : ''}`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(duration.hours) {
|
|
|
|
|
|
|
|
if (string) string += ', '
|
|
|
|
|
|
|
|
string += `${duration.hours} hour${duration.hours > 1 ? 's' : ''}`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(duration.minutes) {
|
|
|
|
|
|
|
|
if (string) string += ', '
|
|
|
|
|
|
|
|
string += `${duration.minutes} minute${duration.minutes > 1 ? 's' : ''}`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(duration.seconds) {
|
|
|
|
|
|
|
|
if (string) string += ', '
|
|
|
|
|
|
|
|
string += `${duration.seconds} second${duration.seconds > 1 ? 's' : ''}`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(!string)
|
|
|
|
|
|
|
|
return '1 second'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return string
|
|
|
|
|
|
|
|
}
|