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')