fix: plugin/antibrute: was not considering parallel requests

Massimo Melina committed May 1, 2022 at 20:18 UTC d7ae2fcf21899338197252f29d69e2b2d388ea9b
1 file changed +21 -19
plugins/antibrute/plugin.js
+21 -19
@@ -1,33 +1,35 @@
1 -exports.version = 1
1 +exports.version = 2
2 exports.description = "Introduce increasing delays between login attempts."
3 -exports.apiRequired = 1
3 +exports.apiRequired = 3 // log
4
5 -// these settings will grant 4 attempts in first minute, 2 in second minute, and 1 from the third one on
6 -const INCREMENT = 5_000
7 -const CAP = 60_000
5 +exports.config = {
6 + increment: { type: 'number', min: 1, defaultValue: 5, md: 6, helperText: "Seconds to add to the delay for each login attempt" },
7 + max: { type: 'number', min: 1, defaultValue: 60, md: 6, helperText: "Max seconds to delay before next login is allowed" },
8 +}
9
10 const byIp = {}
11
12 exports.init = api => {
13 const LOGIN_URI = api.const.API_URI + 'loginSrp1'
13 - return ({
14 + const { getOrSet } = api.require('./misc')
15 + return {
16 async middleware(ctx) {
17 if (ctx.path !== LOGIN_URI) return
16 - const k = ctx.ip
18 + const { ip } = ctx
19 const now = Date.now()
18 - const rec = byIp[k]
19 - if (rec) {
20 - const wait = rec.when - now
21 - if (wait > 0) {
22 - console.log('plugin antibrute is delaying', k, 'for', Math.round(wait / 1000))
23 - await new Promise(resolve => setTimeout(resolve, wait))
24 - }
20 + const rec = getOrSet(byIp, ip, () => ({ delay: 0, next: now }))
21 + const wait = rec.next - now
22 + const max = api.getConfig('max') * 1000
23 + const inc = api.getConfig('increment') * 1000
24 + rec.delay = Math.min(max, rec.delay + inc)
25 + rec.next += rec.delay
26 + clearTimeout(rec.timer)
27 + if (wait > 0) {
28 + api.log('delaying', ip, 'for', Math.round(wait / 1000))
29 ctx.set('x-anti-brute-force', wait)
30 + await new Promise(resolve => setTimeout(resolve, wait))
31 }
27 - const delay = Math.min(CAP, (rec?.delay || 0) + INCREMENT)
28 - byIp[k] = { delay, when: now + delay }
29 - setTimeout(() => delete byIp[k], delay * 10) // no memory leak
32 + rec.timer = setTimeout(() => delete byIp[ip], rec.delay * 10) // no memory leak
33 }
31 - })
34 + }
35 }
33 -