-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
executable file
·175 lines (130 loc) · 4.08 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
'use strict';
//core
const assert = require('assert');
//npm
const Redis = require('ioredis');
const pathToRegexp = require('path-to-regexp');
const parseUrl = require('parseurl');
//project
const workWithRedis = require('./lib/work-with-redis');
const setOptions = require('./lib/set-options');
const filterIncludeExclude = require('./lib/filter-request');
const debug = require('debug')('curtain');
///////////////////////////////////////////////////
/*
This is the simple version of this module that only rate limits requests by user/request id using Redis
note: https://developer.mozilla.org/en-US/docs/Mozilla/Redis_Tips
TODO: allow ability to block users if rate is exceeded by far too much or too many consecutive times
TODO: need to allow for rate limiting on particular routes
=> e.g., if the user has two different routes for which they want a different rate limit,
then the Redis key has to be different
*/
////////////////////////////////////////////////
function Curtain (conf) {
this.verbose = conf.verbose !== false; //default to true
if (conf.redis) {
if (conf.redis.client) {
this.client = conf.redis.client;
} else {
this.client = new Redis(conf.redis);
}
} else {
throw new Error('No Redis configuration provided to Curtain module constructor');
}
}
Curtain.errors = Curtain.prototype.errors = Object.freeze({
'NO_KEY': 'NO_KEY',
'RATE_EXCEEDED': 'RATE_EXCEEDED',
'REDIS_ERROR': 'REDIS_ERROR',
'BAD_ARGUMENTS': 'BAD_ARGUMENTS'
});
Curtain.opts = Curtain.prototype.opts = Object.freeze({
'req': 'req',
'maxReqsPerPeriod': 'maxReqsPerPeriod',
'periodMillis': 'periodMillis',
'excludeRoutes': 'excludeRoutes',
'includeRoutes': 'includeRoutes',
'log': 'log'
});
Curtain.prototype.limitMiddleware = function ($opts) {
let opts;
try {
opts = setOptions($opts);
} catch (err) {
throw new Error(' => Curtain usage error => Bad arguments =>\n' + (err.stack || err));
}
const self = this;
const client = this.client;
const {
logFn,
excludeRoutesRegexp,
includeOnlyRoutesRegexp,
isIncludeOnly,
maxReqsPerPeriod,
identifier,
periodMillis
} = opts;
return function (req, res, next) {
// assume same request cannot be in two middleware functions concurrently
req.curtain = {};
var filter;
try {
if (filter = filterIncludeExclude(opts, parseUrl(req).pathname)) {
return next(filter);
}
}
catch (err) {
return next({
curtainError: true,
error: err
});
}
logFn('=> Incoming request with url path:', req.path, 'was *processed* by Curtain rate-limiting library.');
req.__curtained = req.__curtained ? req.__curtained++ : 1;
if (req.__curtained > 1 && self.verbose) {
logFn('Warning: Curtain rate limiter invoked twice for this same request.');
}
//req, optz, client, resolve, reject
workWithRedis(req, opts, client, next.bind(null, null), next);
}
};
Curtain.prototype.limit = function rateLimitWithCurtain ($opts) {
const self = this;
let opts;
try {
opts = setOptions($opts);
} catch (err) {
throw new Error(' => Curtain usage error => Bad arguments =>\n' + (err.stack || err));
}
const client = this.client;
const {
req,
logFn,
excludeRoutesRegexp,
includeOnlyRoutesRegexp,
isIncludeOnly,
maxReqsPerPeriod,
identifier,
periodMillis
} = opts;
return new Promise(function (resolve, reject) {
req.curtain = {};
var filter;
try {
if (filter = filterIncludeExclude(opts, parseUrl(req).pathname)) {
return resolve(filter);
}
}
catch (err) {
//explicit for your pleasure
return reject(err);
}
logFn('=> Incoming request with url path:', req.path, 'was *processed* by Curtain rate-limiting library.');
req.__curtained = req.__curtained ? req.__curtained++ : 1;
if (req.__curtained > 1 && self.verbose) {
logFn('Warning: Curtain rate limiter invoked twice for this same request.');
}
workWithRedis(req, opts, client, resolve, reject);
});
};
module.exports = Curtain;