max-downloads-ip plugin

Massimo Melina committed Apr 25, 2022 at 22:13 UTC 8be5c309ebda2acc761b75851c71a1a3df6440e0
2 files changed +55 -1
plugins/max-downloads-ip/plugin.js new
+53
@@ -0,0 +1,53 @@
1 +exports.description = "Limit number of simultaneous downloads per IP address"
2 +exports.version = 1
3 +exports.apiRequired = 1
4 +
5 +exports.config = {
6 + limit: { type: 'number', min: 1, placeholder: "no limit" }
7 +}
8 +
9 +exports.init = api => {
10 + const _ = api.require('lodash')
11 + // see who's already downloading
12 + const countByIp = _.countBy(api.getConnections(), conn => {
13 + const { ctx } = conn
14 + if (!isToBeCounted(ctx))
15 + return 'ignore'
16 + watchForEnd(ctx)
17 + return ctx.ip
18 + })
19 + delete countByIp.ignore
20 +
21 + const timer = setInterval(() => { // hourly garbage collector
22 + for (const k in countByIp)
23 + if (countByIp[k] <= 0)
24 + delete countByIp[k]
25 + }, 3600_000)
26 +
27 + function watchForEnd(ctx) {
28 + ctx.body.on('close', () => --countByIp[ctx.ip])
29 + }
30 +
31 + return {
32 + unload() {
33 + clearInterval(timer)
34 + },
35 + middleware(ctx) {
36 + return () => { // information we need is set by another middleware, so we wait for it
37 + if (!isToBeCounted(ctx)) return
38 + const { ip } = ctx
39 + const n = 1 + (countByIp[ip] || 0) // keep track
40 + if (n > api.getConfig('limit')) {
41 + ctx.status = 429
42 + return true
43 + }
44 + countByIp[ip] = n
45 + watchForEnd(ctx)
46 + }
47 + }
48 + }
49 +}
50 +
51 +function isToBeCounted(ctx) {
52 + return ctx?.vfsNode // when this property is set, the request is serving a file from the vfs
53 +}
server/src/plugins.ts
+2 -1
@@ -138,7 +138,8 @@ if (!existsSync(PATH))
138 catch {}
139 watchDir(PATH, rescanAsap)
140
141 -subscribeConfig({ k:'disable_plugins', defaultValue:['download-counter', 'redirect-root'] }, rescanAsap)
141 +const defaultValue = ['download-counter', 'redirect-root','max-downloads-ip']
142 +subscribeConfig({ k:'disable_plugins', defaultValue }, rescanAsap)
143
144 async function rescan() {
145 console.debug('scanning plugins')