express-rate-limit 5.1.3 → 5.2.5

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 CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2019 Nathan Friedly
1
+ Copyright 2020 Nathan Friedly
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
package/README.md CHANGED
@@ -1,20 +1,19 @@
1
1
  # Express Rate Limit
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/nfriedly/express-rate-limit.png?branch=master)](http://travis-ci.org/nfriedly/express-rate-limit)
3
+ ![Node.js CI](https://github.com/nfriedly/express-rate-limit/workflows/Node.js%20CI/badge.svg)
4
4
  [![NPM version](http://badge.fury.io/js/express-rate-limit.png)](https://npmjs.org/package/express-rate-limit "View this project on NPM")
5
- [![Dependency Status](https://david-dm.org/nfriedly/express-rate-limit.png?theme=shields.io)](https://david-dm.org/nfriedly/express-rate-limit)
6
- [![Development Dependency Status](https://david-dm.org/nfriedly/express-rate-limit/dev-status.png?theme=shields.io)](https://david-dm.org/nfriedly/express-rate-limit#info=devDependencies)
5
+ [![npm downloads](https://img.shields.io/npm/dm/express-rate-limit)](https://www.npmjs.com/package/express-rate-limit)
6
+
7
7
 
8
8
  Basic rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.
9
9
 
10
10
  Plays nice with [express-slow-down](https://www.npmjs.com/package/express-slow-down).
11
11
 
12
- Note: this module does not share state with other processes/servers by default.
13
- If you need a more robust solution, I recommend using an external store:
12
+ Note: this module does not share state with other processes/servers by default. It also buckets all requests to an internal clock rather than starting a new timer for each end-user. It's fine for abuse-prevention but might not produce the desired effect when attempting to strictly enforce API rate-limits or similar. If you need a more robust solution, I recommend using an external store:
14
13
 
15
14
  ### Stores
16
15
 
17
- - Memory Store _(default, built-in)_ - stores hits in-memory in the Node.js process. Does not share state with other servers or processes.
16
+ - Memory Store _(default, built-in)_ - stores hits in-memory in the Node.js process. Does not share state with other servers or processes, and does not start a separate timer for each end user.
18
17
  - [Redis Store](https://npmjs.com/package/rate-limit-redis)
19
18
  - [Memcached Store](https://npmjs.org/package/rate-limit-memcached)
20
19
  - [Mongo Store](https://www.npmjs.com/package/rate-limit-mongo)
@@ -208,7 +207,7 @@ Defaults to `false`.
208
207
 
209
208
  ### skip
210
209
 
211
- Function used to skip (whitelist) requests. Returning `true` from the function will skip limiting for that request.
210
+ Function used to skip (whitelist) requests. Returning `true`, or a promise that resolves with `true`, from the function will skip limiting for that request.
212
211
 
213
212
  Defaults to always `false` (count all requests):
214
213
 
@@ -50,101 +50,117 @@ function RateLimit(options) {
50
50
  });
51
51
 
52
52
  function rateLimit(req, res, next) {
53
- if (options.skip(req, res)) {
54
- return next();
55
- }
53
+ Promise.resolve(options.skip(req, res))
54
+ .then((skip) => {
55
+ if (skip) {
56
+ return next();
57
+ }
56
58
 
57
- const key = options.keyGenerator(req, res);
58
-
59
- options.store.incr(key, function (err, current, resetTime) {
60
- if (err) {
61
- return next(err);
62
- }
63
-
64
- const maxResult =
65
- typeof options.max === "function" ? options.max(req, res) : options.max;
66
-
67
- Promise.resolve(maxResult)
68
- .then((max) => {
69
- req.rateLimit = {
70
- limit: max,
71
- current: current,
72
- remaining: Math.max(max - current, 0),
73
- resetTime: resetTime,
74
- };
75
-
76
- if (options.headers && !res.headersSent) {
77
- res.setHeader("X-RateLimit-Limit", max);
78
- res.setHeader("X-RateLimit-Remaining", req.rateLimit.remaining);
79
- if (resetTime instanceof Date) {
80
- // if we have a resetTime, also provide the current date to help avoid issues with incorrect clocks
81
- res.setHeader("Date", new Date().toGMTString());
82
- res.setHeader(
83
- "X-RateLimit-Reset",
84
- Math.ceil(resetTime.getTime() / 1000)
85
- );
86
- }
87
- }
88
- if (options.draft_polli_ratelimit_headers && !res.headersSent) {
89
- res.setHeader("RateLimit-Limit", max);
90
- res.setHeader("RateLimit-Remaining", req.rateLimit.remaining);
91
- if (resetTime) {
92
- const deltaSeconds = Math.ceil(
93
- (resetTime.getTime() - Date.now()) / 1000
94
- );
95
- res.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
96
- }
59
+ const key = options.keyGenerator(req, res);
60
+
61
+ options.store.incr(key, function (err, current, resetTime) {
62
+ if (err) {
63
+ return next(err);
97
64
  }
98
65
 
99
- if (options.skipFailedRequests || options.skipSuccessfulRequests) {
100
- let decremented = false;
101
- const decrementKey = () => {
102
- if (!decremented) {
103
- options.store.decrement(key);
104
- decremented = true;
66
+ const maxResult =
67
+ typeof options.max === "function"
68
+ ? options.max(req, res)
69
+ : options.max;
70
+
71
+ Promise.resolve(maxResult)
72
+ .then((max) => {
73
+ req.rateLimit = {
74
+ limit: max,
75
+ current: current,
76
+ remaining: Math.max(max - current, 0),
77
+ resetTime: resetTime,
78
+ };
79
+
80
+ if (options.headers && !res.headersSent) {
81
+ res.setHeader("X-RateLimit-Limit", max);
82
+ res.setHeader("X-RateLimit-Remaining", req.rateLimit.remaining);
83
+ if (resetTime instanceof Date) {
84
+ // if we have a resetTime, also provide the current date to help avoid issues with incorrect clocks
85
+ res.setHeader("Date", new Date().toGMTString());
86
+ res.setHeader(
87
+ "X-RateLimit-Reset",
88
+ Math.ceil(resetTime.getTime() / 1000)
89
+ );
90
+ }
91
+ }
92
+ if (options.draft_polli_ratelimit_headers && !res.headersSent) {
93
+ res.setHeader("RateLimit-Limit", max);
94
+ res.setHeader("RateLimit-Remaining", req.rateLimit.remaining);
95
+ if (resetTime) {
96
+ const deltaSeconds = Math.ceil(
97
+ (resetTime.getTime() - Date.now()) / 1000
98
+ );
99
+ res.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
100
+ }
105
101
  }
106
- };
107
102
 
108
- if (options.skipFailedRequests) {
109
- res.on("finish", function () {
110
- if (res.statusCode >= 400) {
111
- decrementKey();
103
+ if (
104
+ options.skipFailedRequests ||
105
+ options.skipSuccessfulRequests
106
+ ) {
107
+ let decremented = false;
108
+ const decrementKey = () => {
109
+ if (!decremented) {
110
+ options.store.decrement(key);
111
+ decremented = true;
112
+ }
113
+ };
114
+
115
+ if (options.skipFailedRequests) {
116
+ res.on("finish", function () {
117
+ if (res.statusCode >= 400) {
118
+ decrementKey();
119
+ }
120
+ });
121
+
122
+ res.on("close", () => {
123
+ if (!res.finished) {
124
+ decrementKey();
125
+ }
126
+ });
127
+
128
+ res.on("error", () => decrementKey());
112
129
  }
113
- });
114
130
 
115
- res.on("close", () => {
116
- if (!res.finished) {
117
- decrementKey();
131
+ if (options.skipSuccessfulRequests) {
132
+ res.on("finish", function () {
133
+ if (res.statusCode < 400) {
134
+ options.store.decrement(key);
135
+ }
136
+ });
118
137
  }
119
- });
138
+ }
120
139
 
121
- res.on("error", () => decrementKey());
122
- }
140
+ if (max && current === max + 1) {
141
+ options.onLimitReached(req, res, options);
142
+ }
123
143
 
124
- if (options.skipSuccessfulRequests) {
125
- res.on("finish", function () {
126
- if (res.statusCode < 400) {
127
- options.store.decrement(key);
144
+ if (max && current > max) {
145
+ if (options.headers && !res.headersSent) {
146
+ res.setHeader(
147
+ "Retry-After",
148
+ Math.ceil(options.windowMs / 1000)
149
+ );
128
150
  }
129
- });
130
- }
131
- }
151
+ return options.handler(req, res, next);
152
+ }
132
153
 
133
- if (max && current === max + 1) {
134
- options.onLimitReached(req, res, options);
135
- }
154
+ next();
136
155
 
137
- if (max && current > max) {
138
- if (options.headers && !res.headersSent) {
139
- res.setHeader("Retry-After", Math.ceil(options.windowMs / 1000));
140
- }
141
- return options.handler(req, res, next);
142
- }
156
+ return null;
157
+ })
158
+ .catch(next);
159
+ });
143
160
 
144
- next();
145
- })
146
- .catch(next);
147
- });
161
+ return null;
162
+ })
163
+ .catch(next);
148
164
  }
149
165
 
150
166
  rateLimit.resetKey = options.store.resetKey.bind(options.store);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-rate-limit",
3
- "version": "5.1.3",
3
+ "version": "5.2.5",
4
4
  "description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
5
5
  "homepage": "https://github.com/nfriedly/express-rate-limit",
6
6
  "author": {
@@ -31,17 +31,17 @@
31
31
  "brute-force",
32
32
  "attack"
33
33
  ],
34
- "dependencies": {},
35
34
  "devDependencies": {
36
- "eslint": "^6.8.0",
37
- "eslint-config-prettier": "^6.10.1",
38
- "eslint-plugin-prettier": "^3.1.2",
35
+ "bluebird": "^3.7.2",
36
+ "eslint": "^7.19.0",
37
+ "eslint-config-prettier": "^7.2.0",
38
+ "eslint-plugin-prettier": "^3.3.1",
39
39
  "express": "^4.17.1",
40
- "husky": "^4.2.3",
41
- "mocha": "^7.1.1",
42
- "prettier": "^2.0.4",
43
- "pretty-quick": "^2.0.1",
44
- "supertest": "^4.0.2"
40
+ "husky": "^4.3.8",
41
+ "mocha": "^8.2.1",
42
+ "prettier": "^2.2.1",
43
+ "pretty-quick": "^3.1.0",
44
+ "supertest": "^6.1.3"
45
45
  },
46
46
  "scripts": {
47
47
  "lint": "eslint .",