@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 ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
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
+ }