plugin/antibrute: new "block IP after" option #578

Massimo Melina committed May 5, 2024 at 22:50 UTC c171649ad31d582027a803615f925a47c86c4c4e
1 file changed +12 -4
plugins/antibrute/plugin.js
+12 -4
@@ -3,8 +3,10 @@ exports.description = "Introduce increasing delays between login attempts."
3 exports.apiRequired = 8.8 // attemptingLogin
4
5 exports.config = {
6 - increment: { type: 'number', min: 1, defaultValue: 5, helperText: "Seconds to add to the delay for each login attempt" },
7 - max: { type: 'number', min: 1, defaultValue: 60, helperText: "Max seconds to delay before next login is allowed" },
6 + increment: { type: 'number', min: 1, defaultValue: 5, unit: "seconds", helperText: "How longer user must wait for each login attempt" },
7 + max: { type: 'number', min: 1, defaultValue: 60, label: "Max delay", unit: "seconds", helperText: "Max seconds to delay before next login is allowed" },
8 + blockAfter: { type: 'number', xs: 6, min: 1, max: 9999, defaultValue: 100, label: "Block IP after", unit: "attempts", helperText: "localhost excluded" },
9 + blockForHours: { type: 'number', xs: 6, min: 0, defaultValue: 24, label: "Block for", unit: "hours" },
10 }
11 exports.configDialog = {
12 maxWidth: 'xs',
@@ -13,7 +15,8 @@ exports.configDialog = {
15 const byIp = {}
16
17 exports.init = api => {
16 - const { getOrSet } = api.require('./misc')
18 + const { getOrSet, isLocalHost, HOUR } = api.require('./misc')
19 + const { block } = api.require('./block')
20 return {
21 unload: api.events.multi({
22 attemptingLogin: async ctx => {
@@ -24,13 +27,18 @@ exports.init = api => {
27 const delay = Math.min(max, 1000 * api.getConfig('increment') * ++rec.attempts)
28 const wait = rec.next - now
29 rec.next = new Date(+rec.next + delay)
30 + if (rec.attempts > api.getConfig('blockAfter') && !isLocalHost(ctx)) {
31 + const hours = api.getConfig('blockForHours')
32 + const newRule = { ip, comment: "From antibrute plugin", expire: hours ? new Date(now.getTime() + hours * HOUR) : undefined }
33 + block.set(x => [...x, newRule])
34 + }
35 clearTimeout(rec.timer)
36 if (wait > 0) {
37 api.log('delaying', ip, 'for', Math.round(wait / 1000))
38 ctx.set('x-anti-brute-force', wait)
39 await new Promise(resolve => setTimeout(resolve, wait))
40 }
33 - rec.timer = setTimeout(() => delete byIp[ip], max * 10) // no memory leak
41 + rec.timer = setTimeout(() => delete byIp[ip], 24 * HOUR) // no memory leak
42 },
43 login: ctx => {
44 if (ctx.state.account)