@hackthedev/dsync-ratelimit 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/README.md +103 -0
- package/bun.lock +18 -0
- package/index.mjs +85 -0
- package/package.json +23 -0
package/.gitattributes
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# dSyncRateLimit
|
|
2
|
+
|
|
3
|
+
This small library is part of the dSync concept and will handle rate limits by either defining and using custom, generic rate limits or by using an express middleware to enforce rate limits.
|
|
4
|
+
|
|
5
|
+
------
|
|
6
|
+
|
|
7
|
+
## Basics
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import dSyncRateLimit from "@hackthedev/dsync-ratelimit";
|
|
11
|
+
|
|
12
|
+
const rateLimiter = new dSyncRateLimit({
|
|
13
|
+
getIpLimit: async (req) => {
|
|
14
|
+
if (!req.user) return 20;
|
|
15
|
+
if (req.user.plan === "pro") return 200;
|
|
16
|
+
return 50;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
getTotalLimit: async () => 100,
|
|
20
|
+
|
|
21
|
+
getBlockUntil: async (req) => {
|
|
22
|
+
if (req.path === "/login") {
|
|
23
|
+
return new Date(Date.now() + 60_000);
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
------
|
|
32
|
+
|
|
33
|
+
## Example express endpoint
|
|
34
|
+
|
|
35
|
+
The following example code is just an example and meant to showcase how the rate limit middleware can be used.
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
app.post(
|
|
39
|
+
"/login",
|
|
40
|
+
rateLimiter.middleware({
|
|
41
|
+
getIpLimit: async () => 5,
|
|
42
|
+
getTotalLimit: async () => 20,
|
|
43
|
+
getBlockUntil: async () => new Date(Date.now() + 5 * 60_000)
|
|
44
|
+
}),
|
|
45
|
+
async (req, res) => {
|
|
46
|
+
// whatever you wanna do in this endpoint
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
------
|
|
52
|
+
|
|
53
|
+
## Manual rate limiting
|
|
54
|
+
|
|
55
|
+
By using `rateLimiter.check()`
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
io.on("connection", (socket) => {
|
|
59
|
+
socket.on("message", (msg) => {
|
|
60
|
+
const r = rateLimiter.check(`message/${socket.handshake.address}`,20);
|
|
61
|
+
|
|
62
|
+
if (!r.ok) {
|
|
63
|
+
socket.emit("error", "rate_limited");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
handleMessage(msg);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### With total check
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
io.on("connection", (socket) => {
|
|
76
|
+
socket.on("message", (msg) => {
|
|
77
|
+
const rUser = rateLimiter.check(`message/${socket.handshake.address}`,20);
|
|
78
|
+
const rTotal = rateLimiter.check(`message/total`,200);
|
|
79
|
+
|
|
80
|
+
if (!rUser.ok || !rTotal.ok) {
|
|
81
|
+
socket.emit("error", "rate_limited");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
handleMessage(msg);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
package/bun.lock
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 0,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "@hackthedev/dsync-ratelimit",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@hackthedev/arraytools": "^1.0.1",
|
|
9
|
+
"@hackthedev/dsync-ipsec": "^1.0.1",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
"packages": {
|
|
14
|
+
"@hackthedev/arraytools": ["@hackthedev/arraytools@1.0.1", "", {}, "sha512-1XHKuqr0sNFUxYddX5G9HQdqtTmAAj7dAtEpqp9cFe+mGGkVjD8ce6/wIFLp0X+UlAMC8plIwePPN5klis64rA=="],
|
|
15
|
+
|
|
16
|
+
"@hackthedev/dsync-ipsec": ["@hackthedev/dsync-ipsec@1.0.1", "", {}, "sha512-KkmckbsyXY/CXnB0Ygh+GsXEKC//3/5ihJANPd9Nd9xmd6kr/lShS3K41ExpdgbTSIroK6RpbPqPGGCqjPWBBg=="],
|
|
17
|
+
}
|
|
18
|
+
}
|
package/index.mjs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import dSyncIPSec from '@hackthedev/dsync-ipsec';
|
|
2
|
+
|
|
3
|
+
export default class dSyncRateLimit {
|
|
4
|
+
constructor({
|
|
5
|
+
windowMs = 10_000,
|
|
6
|
+
trustProxy = true,
|
|
7
|
+
getIpLimit = async () => Infinity,
|
|
8
|
+
getTotalLimit = async () => Infinity,
|
|
9
|
+
getBlockUntil = async () => null
|
|
10
|
+
} = {}) {
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
this.ipsec = new dSyncIPSec();
|
|
14
|
+
|
|
15
|
+
this.windowMs = windowMs;
|
|
16
|
+
this.trustProxy = trustProxy;
|
|
17
|
+
this.getIpLimit = getIpLimit;
|
|
18
|
+
this.getTotalLimit = getTotalLimit;
|
|
19
|
+
this.getBlockUntil = getBlockUntil;
|
|
20
|
+
this.store = new Map();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
now() {
|
|
24
|
+
return Date.now();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
check(key, limit, blockUntilDate = null) {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
let rec = this.store.get(key);
|
|
30
|
+
|
|
31
|
+
if (!rec || now >= rec.resetAt) {
|
|
32
|
+
rec = {
|
|
33
|
+
count: 0,
|
|
34
|
+
resetAt: now + this.windowMs,
|
|
35
|
+
blockedUntil: 0
|
|
36
|
+
};
|
|
37
|
+
this.store.set(key, rec);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (rec.blockedUntil && now < rec.blockedUntil) {
|
|
41
|
+
return { ok: false, blocked: true, blockedUntil: rec.blockedUntil };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
rec.count++;
|
|
45
|
+
|
|
46
|
+
if (rec.count > limit && blockUntilDate instanceof Date) {
|
|
47
|
+
rec.blockedUntil = blockUntilDate.getTime();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
ok: rec.count <= limit,
|
|
52
|
+
remaining: Math.max(0, limit - rec.count),
|
|
53
|
+
resetAt: rec.resetAt
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
middleware(overrides = {}) {
|
|
59
|
+
return async (req, res, next) => {
|
|
60
|
+
const getIpLimit = overrides.getIpLimit || this.getIpLimit;
|
|
61
|
+
const getTotalLimit = overrides.getTotalLimit || this.getTotalLimit;
|
|
62
|
+
const getBlockUntil = overrides.getBlockUntil || this.getBlockUntil;
|
|
63
|
+
|
|
64
|
+
const ip = this.ipsec.getClientIp(req);
|
|
65
|
+
const sig = `${req.method} ${req.path}`;
|
|
66
|
+
|
|
67
|
+
const blockUntil = getBlockUntil ? await getBlockUntil(req) : null;
|
|
68
|
+
|
|
69
|
+
if (getIpLimit) {
|
|
70
|
+
if (!this.check(`ip:${ip}`, await getIpLimit(req), blockUntil).ok) {
|
|
71
|
+
return res.sendStatus(429);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (getTotalLimit) {
|
|
76
|
+
if (!this.check(`sig:${sig}`, await getTotalLimit(req), blockUntil).ok) {
|
|
77
|
+
return res.sendStatus(429);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
next();
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hackthedev/dsync-ratelimit",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "This small library is part of the dSync concept and will handle rate limits by either defining and using custom, generic rate limits or by using an express middleware to enforce rate limits.",
|
|
5
|
+
"homepage": "https://github.com/NETWORK-Z-Dev/dsync-ratelimit#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/NETWORK-Z-Dev/dsync-ratelimit/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/NETWORK-Z-Dev/dsync-ratelimit.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"author": "",
|
|
15
|
+
"main": "index.mjs",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@hackthedev/arraytools": "^1.0.1",
|
|
21
|
+
"@hackthedev/dsync-ipsec": "^1.0.1"
|
|
22
|
+
}
|
|
23
|
+
}
|