@depup/rate-limiter-flexible 9.1.1-depup.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/LICENSE.md +7 -0
- package/README.md +25 -0
- package/changes.json +5 -0
- package/index.js +55 -0
- package/lib/BurstyRateLimiter.js +78 -0
- package/lib/ExpressBruteFlexible.js +359 -0
- package/lib/RLWrapperBlackAndWhite.js +195 -0
- package/lib/RLWrapperTimeouts.js +82 -0
- package/lib/RateLimiterAbstract.js +125 -0
- package/lib/RateLimiterCluster.js +367 -0
- package/lib/RateLimiterDrizzle.js +174 -0
- package/lib/RateLimiterDrizzleNonAtomic.js +175 -0
- package/lib/RateLimiterDynamo.js +401 -0
- package/lib/RateLimiterEtcd.js +63 -0
- package/lib/RateLimiterEtcdNonAtomic.js +80 -0
- package/lib/RateLimiterInsuredAbstract.js +112 -0
- package/lib/RateLimiterMemcache.js +150 -0
- package/lib/RateLimiterMemory.js +106 -0
- package/lib/RateLimiterMongo.js +261 -0
- package/lib/RateLimiterMySQL.js +400 -0
- package/lib/RateLimiterPostgres.js +351 -0
- package/lib/RateLimiterPrisma.js +127 -0
- package/lib/RateLimiterQueue.js +131 -0
- package/lib/RateLimiterRedis.js +209 -0
- package/lib/RateLimiterRedisNonAtomic.js +195 -0
- package/lib/RateLimiterRes.js +64 -0
- package/lib/RateLimiterSQLite.js +338 -0
- package/lib/RateLimiterStoreAbstract.js +349 -0
- package/lib/RateLimiterUnion.js +51 -0
- package/lib/RateLimiterValkey.js +117 -0
- package/lib/RateLimiterValkeyGlide.js +273 -0
- package/lib/component/BlockedKeys/BlockedKeys.js +75 -0
- package/lib/component/BlockedKeys/index.js +3 -0
- package/lib/component/MemoryStorage/MemoryStorage.js +83 -0
- package/lib/component/MemoryStorage/Record.js +40 -0
- package/lib/component/MemoryStorage/index.js +3 -0
- package/lib/component/RateLimiterEtcdTransactionFailedError.js +10 -0
- package/lib/component/RateLimiterQueueError.js +13 -0
- package/lib/component/RateLimiterSetupError.js +10 -0
- package/lib/constants.js +21 -0
- package/package.json +100 -0
- package/types.d.ts +581 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## ISC License (ISC)
|
|
2
|
+
|
|
3
|
+
Copyright 2019 Roman Voloboev
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @depup/rate-limiter-flexible
|
|
2
|
+
|
|
3
|
+
> Dependency-bumped version of [rate-limiter-flexible](https://www.npmjs.com/package/rate-limiter-flexible)
|
|
4
|
+
|
|
5
|
+
Generated by [DepUp](https://github.com/depup/npm) -- all production
|
|
6
|
+
dependencies bumped to latest versions.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @depup/rate-limiter-flexible
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
| Field | Value |
|
|
15
|
+
|-------|-------|
|
|
16
|
+
| Original | [rate-limiter-flexible](https://www.npmjs.com/package/rate-limiter-flexible) @ 9.1.1 |
|
|
17
|
+
| Processed | 2026-03-09 |
|
|
18
|
+
| Smoke test | passed |
|
|
19
|
+
| Deps updated | 0 |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
Source: https://github.com/depup/npm | Original: https://www.npmjs.com/package/rate-limiter-flexible
|
|
24
|
+
|
|
25
|
+
License inherited from the original package.
|
package/changes.json
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const RateLimiterRedis = require('./lib/RateLimiterRedis');
|
|
2
|
+
const RateLimiterRedisNonAtomic = require('./lib/RateLimiterRedisNonAtomic');
|
|
3
|
+
const RateLimiterMongo = require('./lib/RateLimiterMongo');
|
|
4
|
+
const RateLimiterMySQL = require('./lib/RateLimiterMySQL');
|
|
5
|
+
const RateLimiterPostgres = require('./lib/RateLimiterPostgres');
|
|
6
|
+
const { RateLimiterClusterMaster, RateLimiterClusterMasterPM2, RateLimiterCluster } = require('./lib/RateLimiterCluster');
|
|
7
|
+
const RateLimiterMemory = require('./lib/RateLimiterMemory');
|
|
8
|
+
const RateLimiterMemcache = require('./lib/RateLimiterMemcache');
|
|
9
|
+
const RLWrapperBlackAndWhite = require('./lib/RLWrapperBlackAndWhite');
|
|
10
|
+
const RLWrapperTimeouts = require('./lib/RLWrapperTimeouts');
|
|
11
|
+
const RateLimiterUnion = require('./lib/RateLimiterUnion');
|
|
12
|
+
const RateLimiterQueue = require('./lib/RateLimiterQueue');
|
|
13
|
+
const BurstyRateLimiter = require('./lib/BurstyRateLimiter');
|
|
14
|
+
const RateLimiterRes = require('./lib/RateLimiterRes');
|
|
15
|
+
const RateLimiterDynamo = require('./lib/RateLimiterDynamo');
|
|
16
|
+
const RateLimiterPrisma = require('./lib/RateLimiterPrisma');
|
|
17
|
+
const RateLimiterDrizzle = require('./lib/RateLimiterDrizzle');
|
|
18
|
+
const RateLimiterDrizzleNonAtomic = require('./lib/RateLimiterDrizzleNonAtomic');
|
|
19
|
+
const RateLimiterValkey = require('./lib/RateLimiterValkey');
|
|
20
|
+
const RateLimiterValkeyGlide = require('./lib/RateLimiterValkeyGlide');
|
|
21
|
+
const RateLimiterSQLite = require('./lib/RateLimiterSQLite');
|
|
22
|
+
const RateLimiterEtcd = require('./lib/RateLimiterEtcd');
|
|
23
|
+
const RateLimiterEtcdNonAtomic = require('./lib/RateLimiterEtcdNonAtomic');
|
|
24
|
+
const RateLimiterQueueError = require('./lib/component/RateLimiterQueueError');
|
|
25
|
+
const RateLimiterEtcdTransactionFailedError = require('./lib/component/RateLimiterEtcdTransactionFailedError');
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
RateLimiterRedis,
|
|
29
|
+
RateLimiterRedisNonAtomic,
|
|
30
|
+
RateLimiterMongo,
|
|
31
|
+
RateLimiterMySQL,
|
|
32
|
+
RateLimiterPostgres,
|
|
33
|
+
RateLimiterMemory,
|
|
34
|
+
RateLimiterMemcache,
|
|
35
|
+
RateLimiterClusterMaster,
|
|
36
|
+
RateLimiterClusterMasterPM2,
|
|
37
|
+
RateLimiterCluster,
|
|
38
|
+
RLWrapperBlackAndWhite,
|
|
39
|
+
RLWrapperTimeouts,
|
|
40
|
+
RateLimiterUnion,
|
|
41
|
+
RateLimiterQueue,
|
|
42
|
+
BurstyRateLimiter,
|
|
43
|
+
RateLimiterRes,
|
|
44
|
+
RateLimiterDynamo,
|
|
45
|
+
RateLimiterPrisma,
|
|
46
|
+
RateLimiterValkey,
|
|
47
|
+
RateLimiterValkeyGlide,
|
|
48
|
+
RateLimiterSQLite,
|
|
49
|
+
RateLimiterEtcd,
|
|
50
|
+
RateLimiterDrizzle,
|
|
51
|
+
RateLimiterDrizzleNonAtomic,
|
|
52
|
+
RateLimiterEtcdNonAtomic,
|
|
53
|
+
RateLimiterQueueError,
|
|
54
|
+
RateLimiterEtcdTransactionFailedError,
|
|
55
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const RateLimiterRes = require("./RateLimiterRes");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bursty rate limiter exposes only msBeforeNext time and doesn't expose points from bursty limiter by default
|
|
5
|
+
* @type {BurstyRateLimiter}
|
|
6
|
+
*/
|
|
7
|
+
module.exports = class BurstyRateLimiter {
|
|
8
|
+
constructor(rateLimiter, burstLimiter) {
|
|
9
|
+
this._rateLimiter = rateLimiter;
|
|
10
|
+
this._burstLimiter = burstLimiter
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Merge rate limiter response objects. Responses can be null
|
|
15
|
+
*
|
|
16
|
+
* @param {RateLimiterRes} [rlRes] Rate limiter response
|
|
17
|
+
* @param {RateLimiterRes} [blRes] Bursty limiter response
|
|
18
|
+
*/
|
|
19
|
+
_combineRes(rlRes, blRes) {
|
|
20
|
+
if (!rlRes) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return new RateLimiterRes(
|
|
25
|
+
rlRes.remainingPoints,
|
|
26
|
+
Math.min(rlRes.msBeforeNext, blRes ? blRes.msBeforeNext : 0),
|
|
27
|
+
rlRes.consumedPoints,
|
|
28
|
+
rlRes.isFirstInDuration
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param key
|
|
34
|
+
* @param pointsToConsume
|
|
35
|
+
* @param options
|
|
36
|
+
* @returns {Promise<any>}
|
|
37
|
+
*/
|
|
38
|
+
consume(key, pointsToConsume = 1, options = {}) {
|
|
39
|
+
return this._rateLimiter.consume(key, pointsToConsume, options)
|
|
40
|
+
.catch((rlRej) => {
|
|
41
|
+
if (rlRej instanceof RateLimiterRes) {
|
|
42
|
+
return this._burstLimiter.consume(key, pointsToConsume, options)
|
|
43
|
+
.then((blRes) => {
|
|
44
|
+
return Promise.resolve(this._combineRes(rlRej, blRes))
|
|
45
|
+
})
|
|
46
|
+
.catch((blRej) => {
|
|
47
|
+
if (blRej instanceof RateLimiterRes) {
|
|
48
|
+
return Promise.reject(this._combineRes(rlRej, blRej))
|
|
49
|
+
} else {
|
|
50
|
+
return Promise.reject(blRej)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
} else {
|
|
55
|
+
return Promise.reject(rlRej)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* It doesn't expose available points from burstLimiter
|
|
62
|
+
*
|
|
63
|
+
* @param key
|
|
64
|
+
* @returns {Promise<RateLimiterRes>}
|
|
65
|
+
*/
|
|
66
|
+
get(key) {
|
|
67
|
+
return Promise.all([
|
|
68
|
+
this._rateLimiter.get(key),
|
|
69
|
+
this._burstLimiter.get(key),
|
|
70
|
+
]).then(([rlRes, blRes]) => {
|
|
71
|
+
return this._combineRes(rlRes, blRes);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get points() {
|
|
76
|
+
return this._rateLimiter.points;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
const {
|
|
2
|
+
LIMITER_TYPES,
|
|
3
|
+
ERR_UNKNOWN_LIMITER_TYPE_MESSAGE,
|
|
4
|
+
} = require('./constants');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const {
|
|
7
|
+
RateLimiterMemory,
|
|
8
|
+
RateLimiterCluster,
|
|
9
|
+
RateLimiterMemcache,
|
|
10
|
+
RateLimiterMongo,
|
|
11
|
+
RateLimiterMySQL,
|
|
12
|
+
RateLimiterPostgres,
|
|
13
|
+
RateLimiterRedis,
|
|
14
|
+
RateLimiterValkey,
|
|
15
|
+
RateLimiterValkeyGlide,
|
|
16
|
+
} = require('../index');
|
|
17
|
+
|
|
18
|
+
function getDelayMs(count, delays, maxWait) {
|
|
19
|
+
let msDelay = maxWait;
|
|
20
|
+
const delayIndex = count - 1;
|
|
21
|
+
if (delayIndex >= 0 && delayIndex < delays.length) {
|
|
22
|
+
msDelay = delays[delayIndex];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return msDelay;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ExpressBruteFlexible = function (limiterType, options) {
|
|
29
|
+
ExpressBruteFlexible.instanceCount++;
|
|
30
|
+
this.name = `brute${ExpressBruteFlexible.instanceCount}`;
|
|
31
|
+
|
|
32
|
+
this.options = Object.assign({}, ExpressBruteFlexible.defaults, options);
|
|
33
|
+
if (this.options.minWait < 1) {
|
|
34
|
+
this.options.minWait = 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const validLimiterTypes = Object.keys(ExpressBruteFlexible.LIMITER_TYPES).map(k => ExpressBruteFlexible.LIMITER_TYPES[k]);
|
|
38
|
+
if (!validLimiterTypes.includes(limiterType)) {
|
|
39
|
+
throw new Error(ERR_UNKNOWN_LIMITER_TYPE_MESSAGE);
|
|
40
|
+
}
|
|
41
|
+
this.limiterType = limiterType;
|
|
42
|
+
|
|
43
|
+
this.delays = [this.options.minWait];
|
|
44
|
+
while (this.delays[this.delays.length - 1] < this.options.maxWait) {
|
|
45
|
+
const nextNum = this.delays[this.delays.length - 1] + (this.delays.length > 1 ? this.delays[this.delays.length - 2] : 0);
|
|
46
|
+
this.delays.push(nextNum);
|
|
47
|
+
}
|
|
48
|
+
this.delays[this.delays.length - 1] = this.options.maxWait;
|
|
49
|
+
|
|
50
|
+
// set default lifetime
|
|
51
|
+
if (typeof this.options.lifetime === 'undefined') {
|
|
52
|
+
this.options.lifetime = Math.ceil((this.options.maxWait / 1000) * (this.delays.length + this.options.freeRetries));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.prevent = this.getMiddleware({
|
|
56
|
+
prefix: this.options.prefix,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
ExpressBruteFlexible.prototype.getMiddleware = function (options) {
|
|
61
|
+
const opts = Object.assign({}, options);
|
|
62
|
+
const commonKeyPrefix = opts.prefix || '';
|
|
63
|
+
const freeLimiterOptions = {
|
|
64
|
+
storeClient: this.options.storeClient,
|
|
65
|
+
storeType: this.options.storeType,
|
|
66
|
+
keyPrefix: `${commonKeyPrefix}free`,
|
|
67
|
+
dbName: this.options.dbName,
|
|
68
|
+
tableName: this.options.tableName,
|
|
69
|
+
points: this.options.freeRetries > 0 ? this.options.freeRetries - 1 : 0,
|
|
70
|
+
duration: this.options.lifetime,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const blockLimiterOptions = {
|
|
74
|
+
storeClient: this.options.storeClient,
|
|
75
|
+
storeType: this.options.storeType,
|
|
76
|
+
keyPrefix: `${commonKeyPrefix}block`,
|
|
77
|
+
dbName: this.options.dbName,
|
|
78
|
+
tableName: this.options.tableName,
|
|
79
|
+
points: 1,
|
|
80
|
+
duration: Math.min(this.options.lifetime, Math.ceil((this.options.maxWait / 1000))),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const counterLimiterOptions = {
|
|
84
|
+
storeClient: this.options.storeClient,
|
|
85
|
+
storeType: this.options.storeType,
|
|
86
|
+
keyPrefix: `${commonKeyPrefix}counter`,
|
|
87
|
+
dbName: this.options.dbName,
|
|
88
|
+
tableName: this.options.tableName,
|
|
89
|
+
points: 1,
|
|
90
|
+
duration: this.options.lifetime,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
switch (this.limiterType) {
|
|
94
|
+
case 'memory':
|
|
95
|
+
this.freeLimiter = new RateLimiterMemory(freeLimiterOptions);
|
|
96
|
+
this.blockLimiter = new RateLimiterMemory(blockLimiterOptions);
|
|
97
|
+
this.counterLimiter = new RateLimiterMemory(counterLimiterOptions);
|
|
98
|
+
break;
|
|
99
|
+
case 'cluster':
|
|
100
|
+
this.freeLimiter = new RateLimiterCluster(freeLimiterOptions);
|
|
101
|
+
this.blockLimiter = new RateLimiterCluster(blockLimiterOptions);
|
|
102
|
+
this.counterLimiter = new RateLimiterCluster(counterLimiterOptions);
|
|
103
|
+
break;
|
|
104
|
+
case 'memcache':
|
|
105
|
+
this.freeLimiter = new RateLimiterMemcache(freeLimiterOptions);
|
|
106
|
+
this.blockLimiter = new RateLimiterMemcache(blockLimiterOptions);
|
|
107
|
+
this.counterLimiter = new RateLimiterMemcache(counterLimiterOptions);
|
|
108
|
+
break;
|
|
109
|
+
case 'mongo':
|
|
110
|
+
this.freeLimiter = new RateLimiterMongo(freeLimiterOptions);
|
|
111
|
+
this.blockLimiter = new RateLimiterMongo(blockLimiterOptions);
|
|
112
|
+
this.counterLimiter = new RateLimiterMongo(counterLimiterOptions);
|
|
113
|
+
break;
|
|
114
|
+
case 'mysql':
|
|
115
|
+
this.freeLimiter = new RateLimiterMySQL(freeLimiterOptions);
|
|
116
|
+
this.blockLimiter = new RateLimiterMySQL(blockLimiterOptions);
|
|
117
|
+
this.counterLimiter = new RateLimiterMySQL(counterLimiterOptions);
|
|
118
|
+
break;
|
|
119
|
+
case 'postgres':
|
|
120
|
+
this.freeLimiter = new RateLimiterPostgres(freeLimiterOptions);
|
|
121
|
+
this.blockLimiter = new RateLimiterPostgres(blockLimiterOptions);
|
|
122
|
+
this.counterLimiter = new RateLimiterPostgres(counterLimiterOptions);
|
|
123
|
+
break;
|
|
124
|
+
case 'valkey-glide':
|
|
125
|
+
this.freeLimiter = new RateLimiterValkeyGlide(freeLimiterOptions);
|
|
126
|
+
this.blockLimiter = new RateLimiterValkeyGlide(blockLimiterOptions);
|
|
127
|
+
this.counterLimiter = new RateLimiterValkeyGlide(counterLimiterOptions);
|
|
128
|
+
break;
|
|
129
|
+
case 'valkey':
|
|
130
|
+
this.freeLimiter = new RateLimiterValkey(freeLimiterOptions);
|
|
131
|
+
this.blockLimiter = new RateLimiterValkey(blockLimiterOptions);
|
|
132
|
+
this.counterLimiter = new RateLimiterValkey(counterLimiterOptions);
|
|
133
|
+
break;
|
|
134
|
+
case 'redis':
|
|
135
|
+
this.freeLimiter = new RateLimiterRedis(freeLimiterOptions);
|
|
136
|
+
this.blockLimiter = new RateLimiterRedis(blockLimiterOptions);
|
|
137
|
+
this.counterLimiter = new RateLimiterRedis(counterLimiterOptions);
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
throw new Error(ERR_UNKNOWN_LIMITER_TYPE_MESSAGE);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let keyFunc = opts.key;
|
|
144
|
+
if (typeof keyFunc !== 'function') {
|
|
145
|
+
keyFunc = function (req, res, next) {
|
|
146
|
+
next(opts.key);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const getFailCallback = (() => (typeof opts.failCallback === 'undefined' ? this.options.failCallback : opts.failCallback));
|
|
151
|
+
|
|
152
|
+
return (req, res, next) => {
|
|
153
|
+
const cannotIncrementErrorObjectBase = {
|
|
154
|
+
req,
|
|
155
|
+
res,
|
|
156
|
+
next,
|
|
157
|
+
message: 'Cannot increment request count',
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
keyFunc(req, res, (key) => {
|
|
161
|
+
if (!opts.ignoreIP) {
|
|
162
|
+
key = ExpressBruteFlexible._getKey([req.ip, this.name, key]);
|
|
163
|
+
} else {
|
|
164
|
+
key = ExpressBruteFlexible._getKey([this.name, key]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// attach a simpler "reset" function to req.brute.reset
|
|
168
|
+
if (this.options.attachResetToRequest) {
|
|
169
|
+
let reset = ((callback) => {
|
|
170
|
+
Promise.all([
|
|
171
|
+
this.freeLimiter.delete(key),
|
|
172
|
+
this.blockLimiter.delete(key),
|
|
173
|
+
this.counterLimiter.delete(key),
|
|
174
|
+
]).then(() => {
|
|
175
|
+
if (typeof callback === 'function') {
|
|
176
|
+
process.nextTick(() => {
|
|
177
|
+
callback();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}).catch((err) => {
|
|
181
|
+
if (typeof callback === 'function') {
|
|
182
|
+
process.nextTick(() => {
|
|
183
|
+
callback(err);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (req.brute && req.brute.reset) {
|
|
190
|
+
// wrap existing reset if one exists
|
|
191
|
+
const oldReset = req.brute.reset;
|
|
192
|
+
const newReset = reset;
|
|
193
|
+
reset = function (callback) {
|
|
194
|
+
oldReset(() => {
|
|
195
|
+
newReset(callback);
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
req.brute = {
|
|
200
|
+
reset,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.freeLimiter.consume(key)
|
|
205
|
+
.then(() => {
|
|
206
|
+
if (typeof next === 'function') {
|
|
207
|
+
next();
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
.catch(() => {
|
|
211
|
+
Promise.all([
|
|
212
|
+
this.blockLimiter.get(key),
|
|
213
|
+
this.counterLimiter.get(key),
|
|
214
|
+
])
|
|
215
|
+
.then((allRes) => {
|
|
216
|
+
const [blockRes, counterRes] = allRes;
|
|
217
|
+
|
|
218
|
+
if (blockRes === null) {
|
|
219
|
+
const msDelay = getDelayMs(
|
|
220
|
+
counterRes ? counterRes.consumedPoints + 1 : 1,
|
|
221
|
+
this.delays,
|
|
222
|
+
// eslint-disable-next-line
|
|
223
|
+
this.options.maxWait
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
this.blockLimiter.penalty(key, 1, { customDuration: Math.ceil(msDelay / 1000) })
|
|
227
|
+
.then((blockPenaltyRes) => {
|
|
228
|
+
if (blockPenaltyRes.consumedPoints === 1) {
|
|
229
|
+
this.counterLimiter.penalty(key)
|
|
230
|
+
.then(() => {
|
|
231
|
+
if (typeof next === 'function') {
|
|
232
|
+
next();
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
.catch((err) => {
|
|
236
|
+
this.options.handleStoreError(Object.assign({}, cannotIncrementErrorObjectBase, { parent: err }));
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
const nextValidDate = new Date(Date.now() + blockPenaltyRes.msBeforeNext);
|
|
240
|
+
|
|
241
|
+
const failCallback = getFailCallback();
|
|
242
|
+
if (typeof failCallback === 'function') {
|
|
243
|
+
failCallback(req, res, next, nextValidDate);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
.catch((err) => {
|
|
248
|
+
this.options.handleStoreError(Object.assign({}, cannotIncrementErrorObjectBase, { parent: err }));
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
const nextValidDate = new Date(Date.now() + blockRes.msBeforeNext);
|
|
252
|
+
|
|
253
|
+
const failCallback = getFailCallback();
|
|
254
|
+
if (typeof failCallback === 'function') {
|
|
255
|
+
failCallback(req, res, next, nextValidDate);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
.catch((err) => {
|
|
260
|
+
this.options.handleStoreError(Object.assign({}, cannotIncrementErrorObjectBase, { parent: err }));
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
ExpressBruteFlexible.prototype.reset = function (ip, key, callback) {
|
|
268
|
+
let keyArgs = [];
|
|
269
|
+
if (ip) {
|
|
270
|
+
keyArgs.push(ip)
|
|
271
|
+
}
|
|
272
|
+
keyArgs.push(this.name);
|
|
273
|
+
keyArgs.push(key);
|
|
274
|
+
const ebKey = ExpressBruteFlexible._getKey(keyArgs);
|
|
275
|
+
|
|
276
|
+
Promise.all([
|
|
277
|
+
this.freeLimiter.delete(ebKey),
|
|
278
|
+
this.blockLimiter.delete(ebKey),
|
|
279
|
+
this.counterLimiter.delete(ebKey),
|
|
280
|
+
]).then(() => {
|
|
281
|
+
if (typeof callback === 'function') {
|
|
282
|
+
process.nextTick(() => {
|
|
283
|
+
callback();
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}).catch((err) => {
|
|
287
|
+
this.options.handleStoreError({
|
|
288
|
+
message: 'Cannot reset request count',
|
|
289
|
+
parent: err,
|
|
290
|
+
key,
|
|
291
|
+
ip,
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
ExpressBruteFlexible._getKey = function (arr) {
|
|
297
|
+
let key = '';
|
|
298
|
+
|
|
299
|
+
arr.forEach((part) => {
|
|
300
|
+
if (part) {
|
|
301
|
+
key += crypto.createHash('sha256').update(part).digest('base64');
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return crypto.createHash('sha256').update(key).digest('base64');
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const setRetryAfter = function (res, nextValidRequestDate) {
|
|
309
|
+
const secondUntilNextRequest = Math.ceil((nextValidRequestDate.getTime() - Date.now()) / 1000);
|
|
310
|
+
res.header('Retry-After', secondUntilNextRequest);
|
|
311
|
+
};
|
|
312
|
+
ExpressBruteFlexible.FailTooManyRequests = function (req, res, next, nextValidRequestDate) {
|
|
313
|
+
setRetryAfter(res, nextValidRequestDate);
|
|
314
|
+
res.status(429);
|
|
315
|
+
res.send({
|
|
316
|
+
error: {
|
|
317
|
+
text: 'Too many requests in this time frame.',
|
|
318
|
+
nextValidRequestDate,
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
};
|
|
322
|
+
ExpressBruteFlexible.FailForbidden = function (req, res, next, nextValidRequestDate) {
|
|
323
|
+
setRetryAfter(res, nextValidRequestDate);
|
|
324
|
+
res.status(403);
|
|
325
|
+
res.send({
|
|
326
|
+
error: {
|
|
327
|
+
text: 'Too many requests in this time frame.',
|
|
328
|
+
nextValidRequestDate,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
ExpressBruteFlexible.FailMark = function (req, res, next, nextValidRequestDate) {
|
|
333
|
+
res.status(429);
|
|
334
|
+
setRetryAfter(res, nextValidRequestDate);
|
|
335
|
+
res.nextValidRequestDate = nextValidRequestDate;
|
|
336
|
+
next();
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
ExpressBruteFlexible.defaults = {
|
|
340
|
+
freeRetries: 2,
|
|
341
|
+
attachResetToRequest: true,
|
|
342
|
+
minWait: 500,
|
|
343
|
+
maxWait: 1000 * 60 * 15,
|
|
344
|
+
failCallback: ExpressBruteFlexible.FailTooManyRequests,
|
|
345
|
+
handleStoreError(err) {
|
|
346
|
+
// eslint-disable-next-line
|
|
347
|
+
throw {
|
|
348
|
+
message: err.message,
|
|
349
|
+
parent: err.parent,
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
ExpressBruteFlexible.LIMITER_TYPES = LIMITER_TYPES;
|
|
355
|
+
|
|
356
|
+
ExpressBruteFlexible.instanceCount = 0;
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
module.exports = ExpressBruteFlexible;
|