rate-limiter-flexible 8.2.1 → 9.0.0

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/README.md CHANGED
@@ -96,7 +96,7 @@ const headers = {
96
96
  * no race conditions
97
97
  * no production dependencies
98
98
  * TypeScript declaration bundled
99
- * Block Strategy against really powerful DDoS attacks (like 100k requests per sec) [Read about it and benchmarking here](https://github.com/animir/node-rate-limiter-flexible/wiki/In-memory-Block-Strategy)
99
+ * Block Strategy against really powerful DoS attacks (like 100k requests per sec) [Read about it and benchmarking here](https://github.com/animir/node-rate-limiter-flexible/wiki/In-memory-Block-Strategy)
100
100
  * Insurance Strategy as emergency solution if database/store is down [Read about Insurance Strategy here](https://github.com/animir/node-rate-limiter-flexible/wiki/Insurance-Strategy)
101
101
  * works in Cluster or PM2 without additional software [See RateLimiterCluster benchmark and detailed description here](https://github.com/animir/node-rate-limiter-flexible/wiki/Cluster)
102
102
  * useful `get`, `set`, `block`, `delete`, `penalty` and `reward` methods
@@ -147,6 +147,7 @@ Some copy/paste examples on Wiki:
147
147
  * [BurstyRateLimiter](https://github.com/animir/node-rate-limiter-flexible/wiki/BurstyRateLimiter) Traffic burst support
148
148
  * [RateLimiterUnion](https://github.com/animir/node-rate-limiter-flexible/wiki/RateLimiterUnion) Combine 2 or more limiters to act as single
149
149
  * [RLWrapperBlackAndWhite](https://github.com/animir/node-rate-limiter-flexible/wiki/Black-and-White-lists) Black and White lists
150
+ * [RLWrapperTimeouts](https://github.com/animir/node-rate-limiter-flexible/wiki/RLWrapperTimeouts) Timeouts
150
151
  * [RateLimiterQueue](https://github.com/animir/node-rate-limiter-flexible/wiki/RateLimiterQueue) Rate limiter with FIFO queue
151
152
  * [AWS SDK v3 Client Rate Limiter](https://github.com/animir/node-rate-limiter-flexible/wiki/AWS-SDK-v3-Client-Rate-Limiter) Prevent punishing rate limit.
152
153
 
package/index.js CHANGED
@@ -6,6 +6,7 @@ const { RateLimiterClusterMaster, RateLimiterClusterMasterPM2, RateLimiterCluste
6
6
  const RateLimiterMemory = require('./lib/RateLimiterMemory');
7
7
  const RateLimiterMemcache = require('./lib/RateLimiterMemcache');
8
8
  const RLWrapperBlackAndWhite = require('./lib/RLWrapperBlackAndWhite');
9
+ const RLWrapperTimeouts = require('./lib/RLWrapperTimeouts');
9
10
  const RateLimiterUnion = require('./lib/RateLimiterUnion');
10
11
  const RateLimiterQueue = require('./lib/RateLimiterQueue');
11
12
  const BurstyRateLimiter = require('./lib/BurstyRateLimiter');
@@ -33,6 +34,7 @@ module.exports = {
33
34
  RateLimiterClusterMasterPM2,
34
35
  RateLimiterCluster,
35
36
  RLWrapperBlackAndWhite,
37
+ RLWrapperTimeouts,
36
38
  RateLimiterUnion,
37
39
  RateLimiterQueue,
38
40
  BurstyRateLimiter,
@@ -0,0 +1,82 @@
1
+ const RateLimiterAbstract = require('./RateLimiterAbstract');
2
+ const RateLimiterInsuredAbstract = require('./RateLimiterInsuredAbstract');
3
+
4
+ module.exports = class RLWrapperTimeouts extends RateLimiterInsuredAbstract {
5
+ constructor(opts= {}) {
6
+ super(opts);
7
+ this.limiter = opts.limiter;
8
+ this.timeoutMs = opts.timeoutMs || 0;
9
+ }
10
+
11
+ get limiter() {
12
+ return this._limiter;
13
+ }
14
+
15
+ set limiter(limiter) {
16
+ if (!(limiter instanceof RateLimiterAbstract)) {
17
+ throw new TypeError('limiter must be an instance of RateLimiterAbstract');
18
+ }
19
+ this._limiter = limiter;
20
+ if (!this.insuranceLimiter && limiter instanceof RateLimiterInsuredAbstract) {
21
+ this.insuranceLimiter = limiter.insuranceLimiter;
22
+ }
23
+ }
24
+
25
+ get timeoutMs() {
26
+ return this._timeoutMs;
27
+ }
28
+
29
+ set timeoutMs(value) {
30
+ if (typeof value !== 'number' || value < 0) {
31
+ throw new TypeError('timeoutMs must be a non-negative number');
32
+ }
33
+ this._timeoutMs = value;
34
+ }
35
+
36
+ _run(funcName, params) {
37
+ return new Promise(async (resolve, reject) => {
38
+ const timeout = setTimeout(() => {
39
+ return reject(new Error('Operation timed out'));
40
+ }, this.timeoutMs);
41
+
42
+ await this.limiter[funcName](...params)
43
+ .then((result) => {
44
+ clearTimeout(timeout);
45
+ resolve(result);
46
+ })
47
+ .catch((err) => {
48
+ clearTimeout(timeout);
49
+ reject(err);
50
+ });
51
+ });
52
+ }
53
+
54
+ _consume(key, pointsToConsume = 1, options = {}) {
55
+ return this._run('consume', [key, pointsToConsume, options]);
56
+ }
57
+
58
+ _penalty(key, points = 1, options = {}) {
59
+ return this._run('penalty', [key, points, options]);
60
+ }
61
+
62
+ _reward(key, points = 1, options = {}) {
63
+ return this._run('reward', [key, points, options]);
64
+ }
65
+
66
+ _get(key, options = {}) {
67
+ return this._run('get', [key, options]);
68
+ }
69
+
70
+ _set(key, points, secDuration, options = {}) {
71
+ return this._run('set', [key, points, secDuration, options]);
72
+ }
73
+
74
+ _block(key, secDuration, options = {}) {
75
+ return this._run('block', [key, secDuration, options]);
76
+ }
77
+
78
+ _delete(key, options = {}) {
79
+ return this._run('delete', [key, options]);
80
+ }
81
+
82
+ }
@@ -0,0 +1,112 @@
1
+ const RateLimiterAbstract = require('./RateLimiterAbstract');
2
+ const RateLimiterRes = require('./RateLimiterRes');
3
+
4
+ module.exports = class RateLimiterInsuredAbstract extends RateLimiterAbstract {
5
+ constructor(opts = {}) {
6
+ super(opts);
7
+ this.insuranceLimiter = opts.insuranceLimiter;
8
+ }
9
+
10
+ get insuranceLimiter() {
11
+ return this._insuranceLimiter;
12
+ }
13
+
14
+ set insuranceLimiter(value) {
15
+ if (typeof value !== 'undefined' && !(value instanceof RateLimiterAbstract)) {
16
+ throw new Error('insuranceLimiter must be instance of RateLimiterAbstract');
17
+ }
18
+ this._insuranceLimiter = value;
19
+ if (this._insuranceLimiter) {
20
+ this._insuranceLimiter.blockDuration = this.blockDuration;
21
+ this._insuranceLimiter.execEvenly = this.execEvenly;
22
+ }
23
+ }
24
+
25
+ _handleError(err, funcName, resolve, reject, params) {
26
+ if (err instanceof RateLimiterRes) {
27
+ reject(err);
28
+ } else if (!(this.insuranceLimiter instanceof RateLimiterAbstract)) {
29
+ reject(err);
30
+ } else {
31
+ this.insuranceLimiter[funcName](...params)
32
+ .then((res) => {
33
+ resolve(res);
34
+ })
35
+ .catch((res) => {
36
+ reject(res);
37
+ });
38
+ }
39
+ }
40
+
41
+ _operation(funcName, params) {
42
+ const promise = this[funcName](...params);
43
+ return new Promise((resolve, reject) => {
44
+ return promise.then((res) => {
45
+ resolve(res);
46
+ })
47
+ .catch((err) => {
48
+ if (funcName.startsWith('_')) {
49
+ funcName = funcName.slice(1);
50
+ }
51
+ this._handleError(err, funcName, resolve, reject, params);
52
+ });
53
+ });
54
+ }
55
+
56
+ consume(key, pointsToConsume = 1, options = {}) {
57
+ return this._operation('_consume', [key, pointsToConsume, options]);
58
+ }
59
+
60
+ penalty(key, points = 1, options = {}) {
61
+ return this._operation('_penalty', [key, points, options]);
62
+ }
63
+
64
+ reward(key, points = 1, options = {}) {
65
+ return this._operation('_reward', [key, points, options]);
66
+ }
67
+
68
+ get(key, options = {}) {
69
+ return this._operation('_get', [key, options]);
70
+ }
71
+
72
+ set(key, points, secDuration, options = {}) {
73
+ return this._operation('_set', [key, points, secDuration, options]);
74
+ }
75
+
76
+ block(key, secDuration, options = {}) {
77
+ return this._operation('_block', [key, secDuration, options]);
78
+ }
79
+
80
+ delete(key, options = {}) {
81
+ return this._operation('_delete', [key, options]);
82
+ }
83
+
84
+ _consume() {
85
+ throw new Error("You have to implement the method '_consume'!");
86
+ }
87
+
88
+ _penalty() {
89
+ throw new Error("You have to implement the method '_penalty'!");
90
+ }
91
+
92
+ _reward() {
93
+ throw new Error("You have to implement the method '_reward'!");
94
+ }
95
+
96
+ _get() {
97
+ throw new Error("You have to implement the method '_get'!");
98
+ }
99
+
100
+ _set() {
101
+ throw new Error("You have to implement the method '_set'!");
102
+ }
103
+
104
+ _block() {
105
+ throw new Error("You have to implement the method '_block'!");
106
+ }
107
+
108
+ _delete() {
109
+ throw new Error("You have to implement the method '_delete'!");
110
+ }
111
+
112
+ }
@@ -1,34 +1,6 @@
1
1
  const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');
2
2
  const RateLimiterRes = require('./RateLimiterRes');
3
3
 
4
- /**
5
- * Get MongoDB driver version as upsert options differ
6
- * @params {Object} Client instance
7
- * @returns {Object} Version Object containing major, feature & minor versions.
8
- */
9
- function getDriverVersion(client) {
10
- try {
11
- const _client = client.client ? client.client : client;
12
-
13
- let _v = [0, 0, 0];
14
- if (typeof _client.topology === 'undefined') {
15
- const { version } = _client.options.metadata.driver;
16
- _v = version.split('|', 1)[0].split('.').map(v => parseInt(v));
17
- } else {
18
- const { version } = _client.topology.s.options.metadata.driver;
19
- _v = version.split('.').map(v => parseInt(v));
20
- }
21
-
22
- return {
23
- major: _v[0],
24
- feature: _v[1],
25
- patch: _v[2],
26
- };
27
- } catch (err) {
28
- return { major: 0, feature: 0, patch: 0 };
29
- }
30
- }
31
-
32
4
  class RateLimiterMongo extends RateLimiterStoreAbstract {
33
5
  /**
34
6
  *
@@ -59,11 +31,9 @@ class RateLimiterMongo extends RateLimiterStoreAbstract {
59
31
  .then((conn) => {
60
32
  this.client = conn;
61
33
  this._initCollection();
62
- this._driverVersion = getDriverVersion(this.client);
63
34
  });
64
35
  } else {
65
36
  this._initCollection();
66
- this._driverVersion = getDriverVersion(this.client);
67
37
  }
68
38
  }
69
39
 
@@ -198,20 +168,11 @@ class RateLimiterMongo extends RateLimiterStoreAbstract {
198
168
  upsertData.$setOnInsert = Object.assign(upsertData.$setOnInsert, docAttrs);
199
169
  }
200
170
 
201
- // Options for collection updates differ between driver versions
171
+ // All supported MongoDB drivers (3.6.7+) use returnDocument: 'after'
202
172
  const upsertOptions = {
203
173
  upsert: true,
174
+ returnDocument: 'after',
204
175
  };
205
- if ((this._driverVersion.major >= 4) ||
206
- (this._driverVersion.major === 3 &&
207
- (this._driverVersion.feature >=7) ||
208
- (this._driverVersion.feature >= 6 &&
209
- this._driverVersion.patch >= 7 )))
210
- {
211
- upsertOptions.returnDocument = 'after';
212
- } else {
213
- upsertOptions.returnOriginal = false;
214
- }
215
176
 
216
177
  /*
217
178
  * 1. Find actual limit and increment points
@@ -1,8 +1,9 @@
1
1
  const RateLimiterAbstract = require('./RateLimiterAbstract');
2
2
  const BlockedKeys = require('./component/BlockedKeys');
3
3
  const RateLimiterRes = require('./RateLimiterRes');
4
+ const RateLimiterInsuredAbstract = require('./RateLimiterInsuredAbstract');
4
5
 
5
- module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
6
+ module.exports = class RateLimiterStoreAbstract extends RateLimiterInsuredAbstract {
6
7
  /**
7
8
  *
8
9
  * @param opts Object Defaults {
@@ -18,7 +19,6 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
18
19
 
19
20
  this.inMemoryBlockOnConsumed = opts.inMemoryBlockOnConsumed;
20
21
  this.inMemoryBlockDuration = opts.inMemoryBlockDuration;
21
- this.insuranceLimiter = opts.insuranceLimiter;
22
22
  this._inMemoryBlockedKeys = new BlockedKeys();
23
23
  }
24
24
 
@@ -92,20 +92,6 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
92
92
  }
93
93
  }
94
94
 
95
- _handleError(err, funcName, resolve, reject, key, data = false, options = {}) {
96
- if (!(this.insuranceLimiter instanceof RateLimiterAbstract)) {
97
- reject(err);
98
- } else {
99
- this.insuranceLimiter[funcName](key, data, options)
100
- .then((res) => {
101
- resolve(res);
102
- })
103
- .catch((res) => {
104
- reject(res);
105
- });
106
- }
107
- }
108
-
109
95
  getInMemoryBlockMsBeforeExpire(rlKey) {
110
96
  if (this.inMemoryBlockOnConsumed > 0) {
111
97
  return this._inMemoryBlockedKeys.msBeforeExpire(rlKey);
@@ -140,21 +126,6 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
140
126
  return this._inMemoryBlockDuration * 1000;
141
127
  }
142
128
 
143
- get insuranceLimiter() {
144
- return this._insuranceLimiter;
145
- }
146
-
147
- set insuranceLimiter(value) {
148
- if (typeof value !== 'undefined' && !(value instanceof RateLimiterAbstract)) {
149
- throw new Error('insuranceLimiter must be instance of RateLimiterAbstract');
150
- }
151
- this._insuranceLimiter = value;
152
- if (this._insuranceLimiter) {
153
- this._insuranceLimiter.blockDuration = this.blockDuration;
154
- this._insuranceLimiter.execEvenly = this.execEvenly;
155
- }
156
- }
157
-
158
129
  /**
159
130
  * Block any key for secDuration seconds
160
131
  *
@@ -191,7 +162,7 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
191
162
  * @param {Object} options
192
163
  * @returns Promise<RateLimiterRes>
193
164
  */
194
- consume(key, pointsToConsume = 1, options = {}) {
165
+ _consume(key, pointsToConsume = 1, options = {}) {
195
166
  return new Promise((resolve, reject) => {
196
167
  const rlKey = this.getKey(key);
197
168
 
@@ -204,9 +175,7 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
204
175
  .then((res) => {
205
176
  this._afterConsume(resolve, reject, rlKey, pointsToConsume, res);
206
177
  })
207
- .catch((err) => {
208
- this._handleError(err, 'consume', resolve, reject, key, pointsToConsume, options);
209
- });
178
+ .catch((err) => reject(err));
210
179
  });
211
180
  }
212
181
 
@@ -217,16 +186,14 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
217
186
  * @param {Object} options
218
187
  * @returns Promise<RateLimiterRes>
219
188
  */
220
- penalty(key, points = 1, options = {}) {
189
+ _penalty(key, points = 1, options = {}) {
221
190
  const rlKey = this.getKey(key);
222
191
  return new Promise((resolve, reject) => {
223
192
  this._upsert(rlKey, points, this._getKeySecDuration(options) * 1000, false, options)
224
193
  .then((res) => {
225
194
  resolve(this._getRateLimiterRes(rlKey, points, res));
226
195
  })
227
- .catch((err) => {
228
- this._handleError(err, 'penalty', resolve, reject, key, points, options);
229
- });
196
+ .catch((res) => reject(res));
230
197
  });
231
198
  }
232
199
 
@@ -237,16 +204,14 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
237
204
  * @param {Object} options
238
205
  * @returns Promise<RateLimiterRes>
239
206
  */
240
- reward(key, points = 1, options = {}) {
207
+ _reward(key, points = 1, options = {}) {
241
208
  const rlKey = this.getKey(key);
242
209
  return new Promise((resolve, reject) => {
243
210
  this._upsert(rlKey, -points, this._getKeySecDuration(options) * 1000, false, options)
244
211
  .then((res) => {
245
212
  resolve(this._getRateLimiterRes(rlKey, -points, res));
246
213
  })
247
- .catch((err) => {
248
- this._handleError(err, 'reward', resolve, reject, key, points, options);
249
- });
214
+ .catch((res) => reject(res));
250
215
  });
251
216
  }
252
217
 
@@ -268,7 +233,7 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
268
233
  }
269
234
  })
270
235
  .catch((err) => {
271
- this._handleError(err, 'get', resolve, reject, key, options);
236
+ this._handleError(err, 'get', resolve, reject, [key, options]);
272
237
  });
273
238
  });
274
239
  }
@@ -288,7 +253,7 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
288
253
  resolve(res);
289
254
  })
290
255
  .catch((err) => {
291
- this._handleError(err, 'delete', resolve, reject, key, options);
256
+ this._handleError(err, 'delete', resolve, reject, [key, options]);
292
257
  });
293
258
  });
294
259
  }
@@ -330,7 +295,7 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterAbstract {
330
295
  resolve(new RateLimiterRes(0, msDuration > 0 ? msDuration : -1, initPoints));
331
296
  })
332
297
  .catch((err) => {
333
- this._handleError(err, 'block', resolve, reject, this.parseKey(rlKey), msDuration / 1000, options);
298
+ this._handleError(err, 'block', resolve, reject, [this.parseKey(rlKey), msDuration / 1000, options]);
334
299
  });
335
300
  });
336
301
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rate-limiter-flexible",
3
- "version": "8.2.1",
4
- "description": "Node.js rate limiter by key and protection from DDoS and Brute-Force attacks in process Memory, Redis, MongoDb, Memcached, MySQL, PostgreSQL, Cluster or PM",
3
+ "version": "9.0.0",
4
+ "description": "Node.js atomic and non-atomic counters, rate limiting tools, protection from DoS and brute-force attacks at scale",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "dc:up": "docker-compose -f docker-compose.yml up -d",
package/types.d.ts CHANGED
@@ -204,7 +204,11 @@ export class RateLimiterAbstract {
204
204
  ): Promise<boolean>;
205
205
  }
206
206
 
207
- export class RateLimiterStoreAbstract extends RateLimiterAbstract {
207
+ export class RateLimiterInsuredAbstract extends RateLimiterAbstract {
208
+ constructor(opts: IRateLimiterOptions);
209
+ }
210
+
211
+ export class RateLimiterStoreAbstract extends RateLimiterInsuredAbstract {
208
212
  constructor(opts: IRateLimiterStoreOptions);
209
213
 
210
214
  /**
@@ -220,6 +224,7 @@ interface IRateLimiterOptions {
220
224
  execEvenly?: boolean;
221
225
  execEvenlyMinDelayMs?: number;
222
226
  blockDuration?: number;
227
+ insuranceLimiter?: RateLimiterAbstract;
223
228
  }
224
229
 
225
230
  interface IRateLimiterClusterOptions extends IRateLimiterOptions {
@@ -393,6 +398,15 @@ export class RLWrapperBlackAndWhite extends RateLimiterAbstract {
393
398
  constructor(opts: IRLWrapperBlackAndWhiteOptions);
394
399
  }
395
400
 
401
+ interface IRLWrapperTimeoutsOptions extends IRateLimiterOptions {
402
+ limiter: RateLimiterAbstract;
403
+ timeoutMs?: number;
404
+ }
405
+
406
+ export class RLWrapperTimeouts extends RateLimiterInsuredAbstract {
407
+ constructor(opts: IRLWrapperTimeoutsOptions);
408
+ }
409
+
396
410
  interface IRateLimiterQueueOpts {
397
411
  maxQueueSize?: number;
398
412
  }