@universis/janitor 1.0.0 → 1.1.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 +73 -1
- package/dist/RateLimitService.js +32 -1
- package/dist/RateLimitService.js.map +1 -1
- package/dist/RedisClientStore.d.ts +5 -0
- package/dist/RedisClientStore.js +98 -0
- package/dist/RedisClientStore.js.map +1 -0
- package/dist/ScopeAccessConfiguration.d.ts +60 -0
- package/dist/ScopeAccessConfiguration.js +93 -0
- package/dist/ScopeAccessConfiguration.js.map +1 -0
- package/dist/SpeedLimitService.js +46 -3
- package/dist/SpeedLimitService.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/polyfills.js +11 -0
- package/dist/polyfills.js.map +1 -0
- package/package.json +4 -2
- package/public/favicon.ico +0 -0
- package/public/schemas/v1/speed-limit-service.json +25 -2
- package/public/universis_janitor.svg +25 -0
- package/public/universis_janitor_128.png +0 -0
- package/public/universis_janitor_256.png +0 -0
- package/src/RateLimitService.js +32 -1
- package/src/RedisClientStore.d.ts +5 -0
- package/src/RedisClientStore.js +101 -0
- package/src/ScopeAccessConfiguration.d.ts +60 -0
- package/src/ScopeAccessConfiguration.js +97 -0
- package/src/SpeedLimitService.js +46 -3
- package/src/index.d.ts +3 -1
- package/src/index.js +4 -1
- package/src/polyfills.js +10 -0
- package/dist/models/TestAction.d.ts +0 -5
- package/dist/models/TestAction.js +0 -5
- package/dist/models/TestAction.js.map +0 -1
- package/public/janitor-logo-128.png +0 -0
- package/public/janitor-logo-text.png +0 -0
- package/public/janitor-logo.svg +0 -13
- package/src/models/TestAction.d.ts +0 -5
- package/src/models/TestAction.js +0 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @universis/janitor
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
Universis api plugin for rate limiting requests or slowing down service responses.
|
|
6
6
|
|
|
@@ -111,3 +111,75 @@ and start configuring speed limited endpoints under `settings/universis/speedLim
|
|
|
111
111
|
|
|
112
112
|
Read more about speed limit configuration at [express-slow-down documentation](https://github.com/express-rate-limit/express-slow-down#configuration)
|
|
113
113
|
|
|
114
|
+
`SpeedLimitService` offers two additional options for delaying response: `randomDelayMs` and `randomMaxDelayMs`. These options are used for adding random delay to the response. They define the range of random delay in milliseconds.
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"settings": {
|
|
118
|
+
"universis": {
|
|
119
|
+
"speedLimit": {
|
|
120
|
+
"profiles": [
|
|
121
|
+
[
|
|
122
|
+
"userSpeedLimitProfile",
|
|
123
|
+
{
|
|
124
|
+
"windowMs": 120000,
|
|
125
|
+
"delayAfter": 5,
|
|
126
|
+
"randomDelayMs": [
|
|
127
|
+
500,
|
|
128
|
+
1000
|
|
129
|
+
],
|
|
130
|
+
"headers": false
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
],
|
|
134
|
+
"paths": [
|
|
135
|
+
[
|
|
136
|
+
"/users/me",
|
|
137
|
+
{
|
|
138
|
+
"profile": "userSpeedLimitProfile"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
If `randomDelayMs` is set, `delayMs` is ignored.
|
|
148
|
+
|
|
149
|
+
`randomMaxDelayMs` is used for setting the maximum random delay. If `randomMaxDelayMs` is not set, the maximum delay is set to `maxDelayMs` is ignored e.g.
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"settings": {
|
|
154
|
+
"universis": {
|
|
155
|
+
"speedLimit": {
|
|
156
|
+
"profiles": [
|
|
157
|
+
[
|
|
158
|
+
"userSpeedLimitProfile",
|
|
159
|
+
{
|
|
160
|
+
"windowMs": 120000,
|
|
161
|
+
"delayAfter": 5,
|
|
162
|
+
"delayMs": 500,
|
|
163
|
+
"randomMaxDelayMs": [
|
|
164
|
+
7000,
|
|
165
|
+
12000
|
|
166
|
+
],
|
|
167
|
+
"headers": false
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
],
|
|
171
|
+
"paths": [
|
|
172
|
+
[
|
|
173
|
+
"/users/me",
|
|
174
|
+
{
|
|
175
|
+
"profile": "userSpeedLimitProfile"
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
package/dist/RateLimitService.js
CHANGED
|
@@ -34,6 +34,11 @@ class RateLimitService extends _common.ApplicationService {
|
|
|
34
34
|
if (paths.size === 0) {
|
|
35
35
|
_common.TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');
|
|
36
36
|
}
|
|
37
|
+
// get proxy address forwarding option
|
|
38
|
+
let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
|
|
39
|
+
if (typeof proxyAddressForwarding !== 'boolean') {
|
|
40
|
+
proxyAddressForwarding = false;
|
|
41
|
+
}
|
|
37
42
|
paths.forEach((value, path) => {
|
|
38
43
|
let profile;
|
|
39
44
|
// get profile
|
|
@@ -50,9 +55,35 @@ class RateLimitService extends _common.ApplicationService {
|
|
|
50
55
|
legacyHeaders: true // send headers
|
|
51
56
|
}, profile, {
|
|
52
57
|
keyGenerator: (req) => {
|
|
53
|
-
|
|
58
|
+
let remoteAddress;
|
|
59
|
+
if (proxyAddressForwarding) {
|
|
60
|
+
// get proxy headers or remote address
|
|
61
|
+
remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
62
|
+
} else {
|
|
63
|
+
// get remote address
|
|
64
|
+
remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
|
|
65
|
+
}
|
|
66
|
+
return `${path}:${remoteAddress}`;
|
|
54
67
|
}
|
|
55
68
|
});
|
|
69
|
+
if (typeof rateLimitOptions.store === 'string') {
|
|
70
|
+
// load store
|
|
71
|
+
const store = rateLimitOptions.store.split('#');
|
|
72
|
+
let StoreClass;
|
|
73
|
+
if (store.length === 2) {
|
|
74
|
+
const storeModule = require(store[0]);
|
|
75
|
+
if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
|
|
76
|
+
StoreClass = storeModule[store[1]];
|
|
77
|
+
rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error(`${store} cannot be found or is inaccessible`);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
StoreClass = require(store[0]);
|
|
83
|
+
// create store
|
|
84
|
+
rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
56
87
|
addRouter.use(path, (0, _expressRateLimit.rateLimit)(rateLimitOptions));
|
|
57
88
|
}
|
|
58
89
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimitService.js","names":["_common","require","_expressRateLimit","_express","_interopRequireDefault","_path","obj","__esModule","default","RateLimitService","ApplicationService","constructor","app","serviceRouter","subscribe","addRouter","express","Router","serviceConfiguration","getConfiguration","getSourceAt","profiles","paths","extends","configurationPath","getConfigurationPath","extendsPath","path","resolve","TraceUtils","log","pathsArray","profilesArray","Map","size","warn","forEach","value","profile","get","rateLimitOptions","Object","assign","windowMs","limit","legacyHeaders","keyGenerator","req","headers","connection","
|
|
1
|
+
{"version":3,"file":"RateLimitService.js","names":["_common","require","_expressRateLimit","_express","_interopRequireDefault","_path","obj","__esModule","default","RateLimitService","ApplicationService","constructor","app","serviceRouter","subscribe","addRouter","express","Router","serviceConfiguration","getConfiguration","getSourceAt","profiles","paths","extends","configurationPath","getConfigurationPath","extendsPath","path","resolve","TraceUtils","log","pathsArray","profilesArray","Map","size","warn","proxyAddressForwarding","forEach","value","profile","get","rateLimitOptions","Object","assign","windowMs","limit","legacyHeaders","keyGenerator","req","remoteAddress","headers","connection","socket","store","split","StoreClass","length","storeModule","prototype","hasOwnProperty","call","Error","use","rateLimit","stack","unshift","apply","err","error","exports"],"sources":["../src/RateLimitService.js"],"sourcesContent":["import { ApplicationService, TraceUtils } from '@themost/common';\nimport { rateLimit } from 'express-rate-limit';\nimport express from 'express';\nimport path from 'path';\n\nexport class RateLimitService extends ApplicationService {\n /**\n * @param {import('@themost/express').ExpressDataApplication} app \n */\n constructor(app) {\n super(app);\n app.serviceRouter.subscribe(serviceRouter => {\n if (serviceRouter == null) {\n return;\n }\n try {\n const addRouter = express.Router();\n let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/rateLimit') || {\n profiles: [],\n paths: []\n };\n if (serviceConfiguration.extends) {\n // get additional configuration\n const configurationPath = app.getConfiguration().getConfigurationPath();\n const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);\n TraceUtils.log(`@universis/janitor#RateLimitService will try to extend service configuration from ${extendsPath}`);\n serviceConfiguration = require(extendsPath);\n }\n const pathsArray = serviceConfiguration.paths || [];\n const profilesArray = serviceConfiguration.profiles || [];\n // create maps\n const paths = new Map(pathsArray);\n const profiles = new Map(profilesArray);\n if (paths.size === 0) {\n TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');\n }\n // get proxy address forwarding option\n let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');\n if (typeof proxyAddressForwarding !== 'boolean') {\n proxyAddressForwarding = false;\n }\n paths.forEach((value, path) => {\n let profile;\n // get profile\n if (value.profile) {\n profile = profiles.get(value.profile);\n } else {\n // or options defined inline\n profile = value\n }\n if (profile != null) {\n const rateLimitOptions = Object.assign({\n windowMs: 5 * 60 * 1000, // 5 minutes\n limit: 50, // 50 requests\n legacyHeaders: true // send headers\n }, profile, {\n keyGenerator: (req) => {\n let remoteAddress;\n if (proxyAddressForwarding) {\n // get proxy headers or remote address\n remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n } else {\n // get remote address\n remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n }\n return `${path}:${remoteAddress}`;\n }\n });\n if (typeof rateLimitOptions.store === 'string') {\n // load store\n const store = rateLimitOptions.store.split('#');\n let StoreClass;\n if (store.length === 2) {\n const storeModule = require(store[0]);\n if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {\n StoreClass = storeModule[store[1]];\n rateLimitOptions.store = new StoreClass(this, rateLimitOptions);\n } else {\n throw new Error(`${store} cannot be found or is inaccessible`);\n }\n } else {\n StoreClass = require(store[0]);\n // create store\n rateLimitOptions.store = new StoreClass(this, rateLimitOptions);\n }\n }\n addRouter.use(path, rateLimit(rateLimitOptions));\n }\n });\n if (addRouter.stack.length) {\n serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);\n }\n } catch (err) {\n TraceUtils.error('An error occurred while validating rate limit configuration.');\n TraceUtils.error(err);\n TraceUtils.warn('Rate limit service is inactive due to an error occured while loading configuration.')\n }\n });\n }\n\n}"],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.RedisClientStore = void 0;var _common = require("@themost/common");
|
|
2
|
+
var _rateLimitRedis = _interopRequireDefault(require("rate-limit-redis"));
|
|
3
|
+
var _redis = require("redis");function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _defineProperty(obj, key, value) {key = _toPropertyKey(key);if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}function _toPropertyKey(arg) {var key = _toPrimitive(arg, "string");return typeof key === "symbol" ? key : String(key);}function _toPrimitive(input, hint) {if (typeof input !== "object" || input === null) return input;var prim = input[Symbol.toPrimitive];if (prim !== undefined) {var res = prim.call(input, hint || "default");if (typeof res !== "object") return res;throw new TypeError("@@toPrimitive must return a primitive value.");}return (hint === "string" ? String : Number)(input);}
|
|
4
|
+
|
|
5
|
+
const superLoadIncrementScript = _rateLimitRedis.default.prototype.loadIncrementScript;
|
|
6
|
+
const superLoadGetScript = _rateLimitRedis.default.prototype.loadGetScript;
|
|
7
|
+
|
|
8
|
+
function noLoadGetScript() {
|
|
9
|
+
|
|
10
|
+
//
|
|
11
|
+
}
|
|
12
|
+
function noLoadIncrementScript() {
|
|
13
|
+
|
|
14
|
+
//
|
|
15
|
+
}
|
|
16
|
+
if (superLoadIncrementScript != noLoadIncrementScript) {
|
|
17
|
+
_rateLimitRedis.default.prototype.loadIncrementScript = noLoadIncrementScript;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (superLoadGetScript != noLoadGetScript) {
|
|
21
|
+
_rateLimitRedis.default.prototype.loadGetScript = noLoadGetScript;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class RedisClientStore extends _rateLimitRedis.default {
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @param {import('@themost/common').ApplicationService} service
|
|
34
|
+
* @param {{windowMs: number}} options
|
|
35
|
+
*/
|
|
36
|
+
constructor(service, options) {
|
|
37
|
+
super({
|
|
38
|
+
/**
|
|
39
|
+
* @param {...string} args
|
|
40
|
+
* @returns {Promise<*>}
|
|
41
|
+
*/
|
|
42
|
+
sendCommand: function () {
|
|
43
|
+
const args = Array.from(arguments);
|
|
44
|
+
const self = this;
|
|
45
|
+
if (args[0] === 'SCRIPT') {
|
|
46
|
+
_common.TraceUtils.debug('RedisClientStore: Creating new client for sending script command');
|
|
47
|
+
const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {
|
|
48
|
+
host: '127.0.0.1',
|
|
49
|
+
port: 6379
|
|
50
|
+
};
|
|
51
|
+
const client = (0, _redis.createClient)(connectOptions);
|
|
52
|
+
return client.connect().then(() => {
|
|
53
|
+
return client.sendCommand(args);
|
|
54
|
+
}).catch((error) => {
|
|
55
|
+
_common.TraceUtils.error(error);
|
|
56
|
+
return Promise.reject(error);
|
|
57
|
+
}).finally(() => {
|
|
58
|
+
if (client.isOpen) {
|
|
59
|
+
_common.TraceUtils.debug('RedisClientStore: Closing client of sending script command');
|
|
60
|
+
client.disconnect().catch((errDisconnect) => {
|
|
61
|
+
_common.TraceUtils.error(errDisconnect);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (self.client == null) {
|
|
67
|
+
_common.TraceUtils.debug('RedisClientStore: Creating store client for sending commands');
|
|
68
|
+
const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {
|
|
69
|
+
host: '127.0.0.1',
|
|
70
|
+
port: 6379
|
|
71
|
+
};
|
|
72
|
+
self.client = (0, _redis.createClient)(connectOptions);
|
|
73
|
+
}
|
|
74
|
+
if (self.client.isOpen) {
|
|
75
|
+
return self.client.sendCommand(args);
|
|
76
|
+
}
|
|
77
|
+
_common.TraceUtils.debug('RedisClientStore: Opening redis store client for sending commands');
|
|
78
|
+
return self.client.connect().then(() => {
|
|
79
|
+
// send load script commands once
|
|
80
|
+
return Promise.all([
|
|
81
|
+
superLoadIncrementScript.call(self),
|
|
82
|
+
superLoadGetScript.call(self)]).
|
|
83
|
+
then((results) => {
|
|
84
|
+
self.incrementScriptSha = results[0];
|
|
85
|
+
self.getScriptSha = results[1];
|
|
86
|
+
// send command
|
|
87
|
+
args[1] = self.incrementScriptSha;
|
|
88
|
+
return self.client.sendCommand(args);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}); /**
|
|
93
|
+
* @type {import('redis').RedisClientType}
|
|
94
|
+
*/_defineProperty(this, "client", void 0);this.init(options);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}exports.RedisClientStore = RedisClientStore;
|
|
98
|
+
//# sourceMappingURL=RedisClientStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedisClientStore.js","names":["_common","require","_rateLimitRedis","_interopRequireDefault","_redis","obj","__esModule","default","_defineProperty","key","value","_toPropertyKey","Object","defineProperty","enumerable","configurable","writable","arg","_toPrimitive","String","input","hint","prim","Symbol","toPrimitive","undefined","res","call","TypeError","Number","superLoadIncrementScript","RedisStore","prototype","loadIncrementScript","superLoadGetScript","loadGetScript","noLoadGetScript","noLoadIncrementScript","RedisClientStore","constructor","service","options","sendCommand","args","Array","from","arguments","self","TraceUtils","debug","connectOptions","getApplication","getConfiguration","getSourceAt","host","port","client","createClient","connect","then","catch","error","Promise","reject","finally","isOpen","disconnect","errDisconnect","all","results","incrementScriptSha","getScriptSha","init","exports"],"sources":["../src/RedisClientStore.js"],"sourcesContent":["import { TraceUtils } from '@themost/common';\nimport RedisStore from 'rate-limit-redis';\nimport { createClient } from 'redis';\n\nconst superLoadIncrementScript = RedisStore.prototype.loadIncrementScript;\nconst superLoadGetScript = RedisStore.prototype.loadGetScript;\n\nfunction noLoadGetScript() {\n //\n}\n\nfunction noLoadIncrementScript() {\n //\n}\n\nif (superLoadIncrementScript != noLoadIncrementScript) {\n RedisStore.prototype.loadIncrementScript = noLoadIncrementScript;\n}\n\nif (superLoadGetScript != noLoadGetScript) {\n RedisStore.prototype.loadGetScript = noLoadGetScript;\n}\n\nclass RedisClientStore extends RedisStore {\n\n /**\n * @type {import('redis').RedisClientType}\n */\n client;\n\n /**\n * \n * @param {import('@themost/common').ApplicationService} service\n * @param {{windowMs: number}} options\n */\n constructor(service, options) {\n super({\n /**\n * @param {...string} args\n * @returns {Promise<*>}\n */\n sendCommand: function () {\n const args = Array.from(arguments);\n const self = this;\n if (args[0] === 'SCRIPT') {\n TraceUtils.debug('RedisClientStore: Creating new client for sending script command');\n const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {\n host: '127.0.0.1',\n port: 6379\n };\n const client = createClient(connectOptions);\n return client.connect().then(() => {\n return client.sendCommand(args);\n }).catch((error) => {\n TraceUtils.error(error);\n return Promise.reject(error);\n }).finally(() => {\n if (client.isOpen) {\n TraceUtils.debug('RedisClientStore: Closing client of sending script command');\n client.disconnect().catch((errDisconnect) => {\n TraceUtils.error(errDisconnect);\n });\n }\n });\n }\n if (self.client == null) {\n TraceUtils.debug('RedisClientStore: Creating store client for sending commands');\n const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {\n host: '127.0.0.1',\n port: 6379\n };\n self.client = createClient(connectOptions);\n }\n if (self.client.isOpen) {\n return self.client.sendCommand(args);\n }\n TraceUtils.debug('RedisClientStore: Opening redis store client for sending commands');\n return self.client.connect().then(() => {\n // send load script commands once\n return Promise.all([\n superLoadIncrementScript.call(self),\n superLoadGetScript.call(self)\n ]).then((results) => {\n self.incrementScriptSha = results[0];\n self.getScriptSha = results[1];\n // send command \n args[1] = self.incrementScriptSha;\n return self.client.sendCommand(args);\n });\n });\n }\n });\n this.init(options)\n \n }\n\n}\n\nexport {\n RedisClientStore\n}"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Universis Project Version 1.0
|
|
4
|
+
* Copyright (c) 2018, Universis Project All rights reserved
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an LGPL 3.0 license that can be
|
|
7
|
+
* found in the LICENSE file at https://universis.io/license
|
|
8
|
+
*/
|
|
9
|
+
import {ConfigurationBase, ConfigurationStrategy} from '@themost/common';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Declares a configuration element for managing scope-based permissions on server resources
|
|
13
|
+
*/
|
|
14
|
+
declare interface ScopeAccessConfigurationElement {
|
|
15
|
+
/**
|
|
16
|
+
* Gets or sets an array of strings that holds an array of scopes e.g. students or students:read or students,teachers etc
|
|
17
|
+
*/
|
|
18
|
+
scope: Array<string>,
|
|
19
|
+
/**
|
|
20
|
+
* Gets or sets a string which represents the regular expression that is going to used to validate endpoint
|
|
21
|
+
*/
|
|
22
|
+
resource: string;
|
|
23
|
+
/**
|
|
24
|
+
* Gets or sets an array of strings which represents the access levels for the given scopes e.g. READ or READ,WRITE etc
|
|
25
|
+
*/
|
|
26
|
+
access: Array<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Gets or sets a string which represents a short description for this item
|
|
29
|
+
*/
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare class ScopeAccessConfiguration extends ConfigurationStrategy {
|
|
34
|
+
|
|
35
|
+
constructor(configuration: ConfigurationBase);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Gets an array of scope access configuration elements
|
|
39
|
+
*/
|
|
40
|
+
public elements: Array<ScopeAccessConfigurationElement>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Verifies the given request and returns a promise that resolves with a scope access configuration element
|
|
44
|
+
*/
|
|
45
|
+
verify(req: Request): Promise<ScopeAccessConfigurationElement>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export declare class DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {
|
|
49
|
+
|
|
50
|
+
constructor(configuration: ConfigurationBase);
|
|
51
|
+
/**
|
|
52
|
+
* Gets an array of scope access configuration elements
|
|
53
|
+
*/
|
|
54
|
+
public elements: Array<ScopeAccessConfigurationElement>;
|
|
55
|
+
/**
|
|
56
|
+
* Verifies the given request and returns a promise that resolves with a scope access configuration element
|
|
57
|
+
*/
|
|
58
|
+
verify(req: Request): Promise<ScopeAccessConfigurationElement>;
|
|
59
|
+
|
|
60
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.ScopeAccessConfiguration = exports.DefaultScopeAccessConfiguration = void 0;var _common = require("@themost/common");
|
|
2
|
+
var _path = _interopRequireDefault(require("path"));
|
|
3
|
+
var _url = _interopRequireDefault(require("url"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}
|
|
4
|
+
|
|
5
|
+
const HTTP_METHOD_REGEXP = /^\b(POST|PUT|PATCH|DELETE)\b$/i;
|
|
6
|
+
|
|
7
|
+
class ScopeAccessConfiguration extends _common.ConfigurationStrategy {
|
|
8
|
+
/**
|
|
9
|
+
* @param {ConfigurationBase} configuration
|
|
10
|
+
*/
|
|
11
|
+
constructor(configuration) {
|
|
12
|
+
super(configuration);
|
|
13
|
+
let elements = [];
|
|
14
|
+
// define property
|
|
15
|
+
Object.defineProperty(this, 'elements', {
|
|
16
|
+
get: () => {
|
|
17
|
+
return elements;
|
|
18
|
+
},
|
|
19
|
+
enumerable: true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {Request} req
|
|
25
|
+
* @returns Promise<ScopeAccessConfigurationElement>
|
|
26
|
+
*/
|
|
27
|
+
verify(req) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
try {
|
|
30
|
+
// validate request context
|
|
31
|
+
_common.Args.notNull(req.context, 'Context');
|
|
32
|
+
// validate request context user
|
|
33
|
+
_common.Args.notNull(req.context.user, 'User');
|
|
34
|
+
if (req.context.user.authenticationScope && req.context.user.authenticationScope.length > 0) {
|
|
35
|
+
// get original url
|
|
36
|
+
let reqUrl = _url.default.parse(req.originalUrl).pathname;
|
|
37
|
+
// get user context scopes as array e.g, ['students', 'students:read']
|
|
38
|
+
let reqScopes = req.context.user.authenticationScope.split(',');
|
|
39
|
+
// get user access based on HTTP method e.g. GET -> read access
|
|
40
|
+
let reqAccess = HTTP_METHOD_REGEXP.test(req.method) ? 'write' : 'read';
|
|
41
|
+
let result = this.elements.find((x) => {
|
|
42
|
+
// filter element by access level
|
|
43
|
+
return x.access.indexOf(reqAccess) >= 0
|
|
44
|
+
// resource path
|
|
45
|
+
&& new RegExp('^' + x.resource, 'i').test(reqUrl)
|
|
46
|
+
// and scopes
|
|
47
|
+
&& x.scope.find((y) => {
|
|
48
|
+
// search user scopes (validate wildcard scope)
|
|
49
|
+
return y === '*' || reqScopes.indexOf(y) >= 0;
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return resolve(result);
|
|
53
|
+
}
|
|
54
|
+
return resolve();
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return reject(err);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @class
|
|
67
|
+
*/exports.ScopeAccessConfiguration = ScopeAccessConfiguration;
|
|
68
|
+
class DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {
|
|
69
|
+
/**
|
|
70
|
+
* @param {ConfigurationBase} configuration
|
|
71
|
+
*/
|
|
72
|
+
constructor(configuration) {
|
|
73
|
+
super(configuration);
|
|
74
|
+
let defaults = [];
|
|
75
|
+
// load scope access from configuration resource
|
|
76
|
+
try {
|
|
77
|
+
/**
|
|
78
|
+
* @type {Array<ScopeAccessConfigurationElement>}
|
|
79
|
+
*/
|
|
80
|
+
defaults = require(_path.default.resolve(configuration.getConfigurationPath(), 'scope.access.json'));
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// if an error occurred other than module not found (there are no default access policies)
|
|
84
|
+
if (err.code !== 'MODULE_NOT_FOUND') {
|
|
85
|
+
// throw error
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
// otherwise continue
|
|
89
|
+
}
|
|
90
|
+
this.elements.push.apply(this.elements, defaults);
|
|
91
|
+
}
|
|
92
|
+
}exports.DefaultScopeAccessConfiguration = DefaultScopeAccessConfiguration;
|
|
93
|
+
//# sourceMappingURL=ScopeAccessConfiguration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScopeAccessConfiguration.js","names":["_common","require","_path","_interopRequireDefault","_url","obj","__esModule","default","HTTP_METHOD_REGEXP","ScopeAccessConfiguration","ConfigurationStrategy","constructor","configuration","elements","Object","defineProperty","get","enumerable","verify","req","Promise","resolve","reject","Args","notNull","context","user","authenticationScope","length","reqUrl","url","parse","originalUrl","pathname","reqScopes","split","reqAccess","test","method","result","find","x","access","indexOf","RegExp","resource","scope","y","err","exports","DefaultScopeAccessConfiguration","defaults","path","getConfigurationPath","code","push","apply"],"sources":["../src/ScopeAccessConfiguration.js"],"sourcesContent":["import {ConfigurationStrategy, Args} from '@themost/common';\nimport path from 'path';\nimport url from 'url';\n\nconst HTTP_METHOD_REGEXP = /^\\b(POST|PUT|PATCH|DELETE)\\b$/i;\n\nclass ScopeAccessConfiguration extends ConfigurationStrategy {\n /**\n * @param {ConfigurationBase} configuration\n */\n constructor(configuration) {\n super(configuration);\n let elements = [];\n // define property\n Object.defineProperty(this, 'elements', {\n get: () => {\n return elements;\n },\n enumerable: true\n });\n }\n\n /**\n * @param {Request} req\n * @returns Promise<ScopeAccessConfigurationElement>\n */\n verify(req) {\n return new Promise((resolve, reject) => {\n try {\n // validate request context\n Args.notNull(req.context,'Context');\n // validate request context user\n Args.notNull(req.context.user,'User');\n if (req.context.user.authenticationScope && req.context.user.authenticationScope.length>0) {\n // get original url\n let reqUrl = url.parse(req.originalUrl).pathname;\n // get user context scopes as array e.g, ['students', 'students:read']\n let reqScopes = req.context.user.authenticationScope.split(',');\n // get user access based on HTTP method e.g. GET -> read access\n let reqAccess = HTTP_METHOD_REGEXP.test(req.method) ? 'write' : 'read';\n let result = this.elements.find(x => {\n // filter element by access level\n return x.access.indexOf(reqAccess)>=0\n // resource path\n && new RegExp( '^' + x.resource, 'i').test(reqUrl)\n // and scopes\n && x.scope.find(y => {\n // search user scopes (validate wildcard scope)\n return y === '*' || reqScopes.indexOf(y)>=0;\n });\n });\n return resolve(result);\n }\n return resolve();\n }\n catch(err) {\n return reject(err);\n }\n\n });\n }\n\n}\n\n/**\n * @class\n */\nclass DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {\n /**\n * @param {ConfigurationBase} configuration\n */\n constructor(configuration) {\n super(configuration);\n let defaults = [];\n // load scope access from configuration resource\n try {\n /**\n * @type {Array<ScopeAccessConfigurationElement>}\n */\n defaults = require(path.resolve(configuration.getConfigurationPath(), 'scope.access.json'));\n }\n catch(err) {\n // if an error occurred other than module not found (there are no default access policies)\n if (err.code !== 'MODULE_NOT_FOUND') {\n // throw error\n throw err;\n }\n // otherwise continue\n }\n this.elements.push.apply(this.elements, defaults);\n }\n}\n\nexport {\n ScopeAccessConfiguration,\n DefaultScopeAccessConfiguration\n}\n"],"mappings":"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"}
|
|
@@ -32,6 +32,11 @@ class SpeedLimitService extends _common.ApplicationService {
|
|
|
32
32
|
if (paths.size === 0) {
|
|
33
33
|
_common.TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');
|
|
34
34
|
}
|
|
35
|
+
// get proxy address forwarding option
|
|
36
|
+
let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
|
|
37
|
+
if (typeof proxyAddressForwarding !== 'boolean') {
|
|
38
|
+
proxyAddressForwarding = false;
|
|
39
|
+
}
|
|
35
40
|
paths.forEach((value, path) => {
|
|
36
41
|
let profile;
|
|
37
42
|
// get profile
|
|
@@ -44,14 +49,52 @@ class SpeedLimitService extends _common.ApplicationService {
|
|
|
44
49
|
if (profile != null) {
|
|
45
50
|
const slowDownOptions = Object.assign({
|
|
46
51
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
47
|
-
delayAfter:
|
|
52
|
+
delayAfter: 20, // 20 requests
|
|
48
53
|
delayMs: 500, // 500 ms
|
|
49
|
-
maxDelayMs:
|
|
54
|
+
maxDelayMs: 10000 // 10 seconds
|
|
50
55
|
}, profile, {
|
|
51
56
|
keyGenerator: (req) => {
|
|
52
|
-
|
|
57
|
+
let remoteAddress;
|
|
58
|
+
if (proxyAddressForwarding) {
|
|
59
|
+
// get proxy headers or remote address
|
|
60
|
+
remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
61
|
+
} else {
|
|
62
|
+
// get remote address
|
|
63
|
+
remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
|
|
64
|
+
}
|
|
65
|
+
return `${path}:${remoteAddress}`;
|
|
53
66
|
}
|
|
54
67
|
});
|
|
68
|
+
if (Array.isArray(slowDownOptions.randomDelayMs)) {
|
|
69
|
+
slowDownOptions.delayMs = () => {
|
|
70
|
+
const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
|
|
71
|
+
return delayMs;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
|
|
75
|
+
slowDownOptions.maxDelayMs = () => {
|
|
76
|
+
const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
|
|
77
|
+
return maxDelayMs;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (typeof slowDownOptions.store === 'string') {
|
|
81
|
+
// load store
|
|
82
|
+
const store = slowDownOptions.store.split('#');
|
|
83
|
+
let StoreClass;
|
|
84
|
+
if (store.length === 2) {
|
|
85
|
+
const storeModule = require(store[0]);
|
|
86
|
+
if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
|
|
87
|
+
StoreClass = storeModule[store[1]];
|
|
88
|
+
slowDownOptions.store = new StoreClass(this, slowDownOptions);
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error(`${store} cannot be found or is inaccessible`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
StoreClass = require(store[0]);
|
|
94
|
+
// create store
|
|
95
|
+
slowDownOptions.store = new StoreClass(this, slowDownOptions);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
55
98
|
addRouter.use(path, (0, _expressSlowDown.default)(slowDownOptions));
|
|
56
99
|
}
|
|
57
100
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpeedLimitService.js","names":["_common","require","_expressSlowDown","_interopRequireDefault","_express","_path","obj","__esModule","default","SpeedLimitService","ApplicationService","constructor","app","serviceRouter","subscribe","addRouter","express","Router","serviceConfiguration","getConfiguration","getSourceAt","profiles","paths","extends","configurationPath","getConfigurationPath","extendsPath","path","resolve","TraceUtils","log","pathsArray","profilesArray","Map","size","warn","forEach","value","profile","get","slowDownOptions","Object","assign","windowMs","delayAfter","delayMs","maxDelayMs","keyGenerator","req","headers","connection","remoteAddress","socket","use","slowDown","stack","length","unshift","apply","err","error","exports"],"sources":["../src/SpeedLimitService.js"],"sourcesContent":["import { ApplicationService, TraceUtils } from '@themost/common';\nimport slowDown from 'express-slow-down';\nimport express from 'express';\nimport path from 'path';\n\nexport class SpeedLimitService extends ApplicationService {\n constructor(app) {\n super(app);\n\n app.serviceRouter.subscribe(serviceRouter => {\n if (serviceRouter == null) {\n return;\n }\n try {\n const addRouter = express.Router();\n let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/speedLimit') || {\n profiles: [],\n paths: []\n };\n if (serviceConfiguration.extends) {\n // get additional configuration\n const configurationPath = app.getConfiguration().getConfigurationPath();\n const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);\n TraceUtils.log(`@universis/janitor#SpeedLimitService will try to extend service configuration from ${extendsPath}`);\n serviceConfiguration = require(extendsPath);\n }\n const pathsArray = serviceConfiguration.paths || [];\n const profilesArray = serviceConfiguration.profiles || [];\n // create maps\n const paths = new Map(pathsArray);\n const profiles = new Map(profilesArray);\n if (paths.size === 0) {\n TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');\n }\n paths.forEach((value, path) => {\n let profile;\n // get profile\n if (value.profile) {\n profile = profiles.get(value.profile);\n } else {\n // or options defined inline\n profile = value\n }\n if (profile != null) {\n const slowDownOptions = Object.assign({\n windowMs: 5 * 60 * 1000, // 5 minutes\n delayAfter: 5, // 5 requests\n delayMs: 500, // 500 ms\n maxDelayMs: 20000 // 20 seconds\n }, profile, {\n keyGenerator: (req) => {\n return req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n }\n });\n addRouter.use(path, slowDown(slowDownOptions));\n }\n });\n if (addRouter.stack.length) {\n serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);\n }\n } catch (err) {\n TraceUtils.error('An error occurred while validating speed limit configuration.');\n TraceUtils.error(err);\n TraceUtils.warn('Speed limit service is inactive due to an error occured while loading configuration.')\n }\n });\n }\n\n}"],"mappings":"8GAAA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,gBAAA,GAAAC,sBAAA,CAAAF,OAAA;AACA,IAAAG,QAAA,GAAAD,sBAAA,CAAAF,OAAA;AACA,IAAAI,KAAA,GAAAF,sBAAA,CAAAF,OAAA,UAAwB,SAAAE,uBAAAG,GAAA,UAAAA,GAAA,IAAAA,GAAA,CAAAC,UAAA,GAAAD,GAAA,KAAAE,OAAA,EAAAF,GAAA;;AAEjB,MAAMG,iBAAiB,SAASC,0BAAkB,CAAC;EACtDC,WAAWA,CAACC,GAAG,EAAE;IACb,KAAK,CAACA,GAAG,CAAC;;IAEVA,GAAG,CAACC,aAAa,CAACC,SAAS,CAAC,CAAAD,aAAa,KAAI;MACzC,IAAIA,aAAa,IAAI,IAAI,EAAE;QACvB;MACJ;MACA,IAAI;QACA,MAAME,SAAS,GAAGC,gBAAO,CAACC,MAAM,EAAE;QAClC,IAAIC,oBAAoB,GAAGN,GAAG,CAACO,gBAAgB,EAAE,CAACC,WAAW,CAAC,uCAAuC,CAAC,IAAI;UACtGC,QAAQ,EAAE,EAAE;UACZC,KAAK,EAAE;QACX,CAAC;QACD,IAAIJ,oBAAoB,CAACK,OAAO,EAAE;UAC9B;UACA,MAAMC,iBAAiB,GAAGZ,GAAG,CAACO,gBAAgB,EAAE,CAACM,oBAAoB,EAAE;UACvE,MAAMC,WAAW,GAAGC,aAAI,CAACC,OAAO,CAACJ,iBAAiB,EAAEN,oBAAoB,CAACK,OAAO,CAAC;UACjFM,kBAAU,CAACC,GAAG,CAAE,sFAAqFJ,WAAY,EAAC,CAAC;UACnHR,oBAAoB,GAAGjB,OAAO,CAACyB,WAAW,CAAC;QAC/C;QACA,MAAMK,UAAU,GAAGb,oBAAoB,CAACI,KAAK,IAAI,EAAE;QACnD,MAAMU,aAAa,GAAGd,oBAAoB,CAACG,QAAQ,IAAI,EAAE;QACzD;QACA,MAAMC,KAAK,GAAG,IAAIW,GAAG,CAACF,UAAU,CAAC;QACjC,MAAMV,QAAQ,GAAG,IAAIY,GAAG,CAACD,aAAa,CAAC;QACvC,IAAIV,KAAK,CAACY,IAAI,KAAK,CAAC,EAAE;UAClBL,kBAAU,CAACM,IAAI,CAAC,6FAA6F,CAAC;QAClH;QACAb,KAAK,CAACc,OAAO,CAAC,CAACC,KAAK,EAAEV,IAAI,KAAK;UAC3B,IAAIW,OAAO;UACX;UACA,IAAID,KAAK,CAACC,OAAO,EAAE;YACfA,OAAO,GAAGjB,QAAQ,CAACkB,GAAG,CAACF,KAAK,CAACC,OAAO,CAAC;UACzC,CAAC,MAAM;YACH;YACAA,OAAO,GAAGD,KAAK;UACnB;UACA,IAAIC,OAAO,IAAI,IAAI,EAAE;YACjB,MAAME,eAAe,GAAGC,MAAM,CAACC,MAAM,CAAC;cAClCC,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE;cACzBC,UAAU,EAAE,CAAC,EAAE;cACfC,OAAO,EAAE,GAAG,EAAE;cACdC,UAAU,EAAE,KAAK,CAAC;YACtB,CAAC,EAAER,OAAO,EAAE;cACRS,YAAY,EAAEA,CAACC,GAAG,KAAK;gBACnB,OAAOA,GAAG,CAACC,OAAO,CAAC,WAAW,CAAC,IAAID,GAAG,CAACC,OAAO,CAAC,iBAAiB,CAAC,KAAKD,GAAG,CAACE,UAAU,GAAGF,GAAG,CAACE,UAAU,CAACC,aAAa,GAAGH,GAAG,CAACI,MAAM,CAACD,aAAa,CAAC;cACnJ;YACJ,CAAC,CAAC;YACFpC,SAAS,CAACsC,GAAG,CAAC1B,IAAI,EAAE,IAAA2B,wBAAQ,EAACd,eAAe,CAAC,CAAC;UAClD;QACJ,CAAC,CAAC;QACF,IAAIzB,SAAS,CAACwC,KAAK,CAACC,MAAM,EAAE;UACxB3C,aAAa,CAAC0C,KAAK,CAACE,OAAO,CAACC,KAAK,CAAC7C,aAAa,CAAC0C,KAAK,EAAExC,SAAS,CAACwC,KAAK,CAAC;QAC3E;MACJ,CAAC,CAAC,OAAOI,GAAG,EAAE;QACV9B,kBAAU,CAAC+B,KAAK,CAAC,+DAA+D,CAAC;QACjF/B,kBAAU,CAAC+B,KAAK,CAACD,GAAG,CAAC;QACrB9B,kBAAU,CAACM,IAAI,CAAC,sFAAsF,CAAC;MAC3G;IACJ,CAAC,CAAC;EACN;;AAEJ,CAAC0B,OAAA,CAAApD,iBAAA,GAAAA,iBAAA"}
|
|
1
|
+
{"version":3,"file":"SpeedLimitService.js","names":["_common","require","_expressSlowDown","_interopRequireDefault","_express","_path","obj","__esModule","default","SpeedLimitService","ApplicationService","constructor","app","serviceRouter","subscribe","addRouter","express","Router","serviceConfiguration","getConfiguration","getSourceAt","profiles","paths","extends","configurationPath","getConfigurationPath","extendsPath","path","resolve","TraceUtils","log","pathsArray","profilesArray","Map","size","warn","proxyAddressForwarding","forEach","value","profile","get","slowDownOptions","Object","assign","windowMs","delayAfter","delayMs","maxDelayMs","keyGenerator","req","remoteAddress","headers","connection","socket","Array","isArray","randomDelayMs","Math","floor","random","randomMaxDelayMs","store","split","StoreClass","length","storeModule","prototype","hasOwnProperty","call","Error","use","slowDown","stack","unshift","apply","err","error","exports"],"sources":["../src/SpeedLimitService.js"],"sourcesContent":["import { ApplicationService, TraceUtils } from '@themost/common';\nimport slowDown from 'express-slow-down';\nimport express from 'express';\nimport path from 'path';\n\nexport class SpeedLimitService extends ApplicationService {\n constructor(app) {\n super(app);\n\n app.serviceRouter.subscribe(serviceRouter => {\n if (serviceRouter == null) {\n return;\n }\n try {\n const addRouter = express.Router();\n let serviceConfiguration = app.getConfiguration().getSourceAt('settings/universis/janitor/speedLimit') || {\n profiles: [],\n paths: []\n };\n if (serviceConfiguration.extends) {\n // get additional configuration\n const configurationPath = app.getConfiguration().getConfigurationPath();\n const extendsPath = path.resolve(configurationPath, serviceConfiguration.extends);\n TraceUtils.log(`@universis/janitor#SpeedLimitService will try to extend service configuration from ${extendsPath}`);\n serviceConfiguration = require(extendsPath);\n }\n const pathsArray = serviceConfiguration.paths || [];\n const profilesArray = serviceConfiguration.profiles || [];\n // create maps\n const paths = new Map(pathsArray);\n const profiles = new Map(profilesArray);\n if (paths.size === 0) {\n TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');\n }\n // get proxy address forwarding option\n let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');\n if (typeof proxyAddressForwarding !== 'boolean') {\n proxyAddressForwarding = false;\n }\n paths.forEach((value, path) => {\n let profile;\n // get profile\n if (value.profile) {\n profile = profiles.get(value.profile);\n } else {\n // or options defined inline\n profile = value\n }\n if (profile != null) {\n const slowDownOptions = Object.assign({\n windowMs: 5 * 60 * 1000, // 5 minutes\n delayAfter: 20, // 20 requests\n delayMs: 500, // 500 ms\n maxDelayMs: 10000 // 10 seconds\n }, profile, {\n keyGenerator: (req) => {\n let remoteAddress;\n if (proxyAddressForwarding) {\n // get proxy headers or remote address\n remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n } else {\n // get remote address\n remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);\n }\n return `${path}:${remoteAddress}`;\n }\n });\n if (Array.isArray(slowDownOptions.randomDelayMs)) {\n slowDownOptions.delayMs = () => {\n const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);\n return delayMs;\n }\n }\n if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {\n slowDownOptions.maxDelayMs = () => {\n const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);\n return maxDelayMs;\n }\n }\n if (typeof slowDownOptions.store === 'string') {\n // load store\n const store = slowDownOptions.store.split('#');\n let StoreClass;\n if (store.length === 2) {\n const storeModule = require(store[0]);\n if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {\n StoreClass = storeModule[store[1]];\n slowDownOptions.store = new StoreClass(this, slowDownOptions);\n } else {\n throw new Error(`${store} cannot be found or is inaccessible`);\n }\n } else {\n StoreClass = require(store[0]);\n // create store\n slowDownOptions.store = new StoreClass(this, slowDownOptions);\n }\n }\n addRouter.use(path, slowDown(slowDownOptions));\n }\n });\n if (addRouter.stack.length) {\n serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);\n }\n } catch (err) {\n TraceUtils.error('An error occurred while validating speed limit configuration.');\n TraceUtils.error(err);\n TraceUtils.warn('Speed limit service is inactive due to an error occured while loading configuration.')\n }\n });\n }\n\n}"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", { value: true });require("./polyfills");
|
|
2
|
+
var _RateLimitService = require("./RateLimitService");Object.keys(_RateLimitService).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _RateLimitService[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _RateLimitService[key];} });});
|
|
2
3
|
var _SpeedLimitService = require("./SpeedLimitService");Object.keys(_SpeedLimitService).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _SpeedLimitService[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _SpeedLimitService[key];} });});
|
|
4
|
+
var _RedisClientStore = require("./RedisClientStore");Object.keys(_RedisClientStore).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _RedisClientStore[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _RedisClientStore[key];} });});
|
|
5
|
+
var _ScopeAccessConfiguration = require("./ScopeAccessConfiguration");Object.keys(_ScopeAccessConfiguration).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _ScopeAccessConfiguration[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _ScopeAccessConfiguration[key];} });});
|
|
3
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["
|
|
1
|
+
{"version":3,"file":"index.js","names":["require","_RateLimitService","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_SpeedLimitService","_RedisClientStore","_ScopeAccessConfiguration"],"sources":["../src/index.js"],"sourcesContent":["import './polyfills';\nexport * from './RateLimitService';\nexport * from './SpeedLimitService';\nexport * from './RedisClientStore';\nexport * from './ScopeAccessConfiguration';"],"mappings":"2EAAAA,OAAA;AACA,IAAAC,iBAAA,GAAAD,OAAA,uBAAAE,MAAA,CAAAC,IAAA,CAAAF,iBAAA,EAAAG,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAJ,iBAAA,CAAAI,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAR,iBAAA,CAAAI,GAAA;AACA,IAAAK,kBAAA,GAAAV,OAAA,wBAAAE,MAAA,CAAAC,IAAA,CAAAO,kBAAA,EAAAN,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,kBAAA,CAAAL,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAC,kBAAA,CAAAL,GAAA;AACA,IAAAM,iBAAA,GAAAX,OAAA,uBAAAE,MAAA,CAAAC,IAAA,CAAAQ,iBAAA,EAAAP,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,iBAAA,CAAAN,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAE,iBAAA,CAAAN,GAAA;AACA,IAAAO,yBAAA,GAAAZ,OAAA,+BAAAE,MAAA,CAAAC,IAAA,CAAAS,yBAAA,EAAAR,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAO,yBAAA,CAAAP,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAG,yBAAA,CAAAP,GAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";if (!String.prototype.replaceAll) {
|
|
2
|
+
String.prototype.replaceAll = function (str, newStr) {
|
|
3
|
+
// If a regex pattern
|
|
4
|
+
if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
|
|
5
|
+
return this.replace(str, newStr);
|
|
6
|
+
}
|
|
7
|
+
// If a string
|
|
8
|
+
return this.replace(new RegExp(str, 'g'), newStr);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=polyfills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polyfills.js","names":["String","prototype","replaceAll","str","newStr","Object","toString","call","toLowerCase","replace","RegExp"],"sources":["../src/polyfills.js"],"sourcesContent":["if (!String.prototype.replaceAll) {\n\tString.prototype.replaceAll = function(str, newStr){\n\t\t// If a regex pattern\n\t\tif (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {\n\t\t\treturn this.replace(str, newStr);\n\t\t}\n\t\t// If a string\n\t\treturn this.replace(new RegExp(str, 'g'), newStr);\n\t};\n}"],"mappings":"aAAA,IAAI,CAACA,MAAM,CAACC,SAAS,CAACC,UAAU,EAAE;EACjCF,MAAM,CAACC,SAAS,CAACC,UAAU,GAAG,UAASC,GAAG,EAAEC,MAAM,EAAC;IAClD;IACA,IAAIC,MAAM,CAACJ,SAAS,CAACK,QAAQ,CAACC,IAAI,CAACJ,GAAG,CAAC,CAACK,WAAW,EAAE,KAAK,iBAAiB,EAAE;MAC5E,OAAO,IAAI,CAACC,OAAO,CAACN,GAAG,EAAEC,MAAM,CAAC;IACjC;IACA;IACA,OAAO,IAAI,CAACK,OAAO,CAAC,IAAIC,MAAM,CAACP,GAAG,EAAE,GAAG,CAAC,EAAEC,MAAM,CAAC;EAClD,CAAC;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@universis/janitor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Universis api plugin for rate limiting requests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"express-rate-limit": "^7.1.1",
|
|
33
|
-
"express-slow-down": "^1.6.0"
|
|
33
|
+
"express-slow-down": "^1.6.0",
|
|
34
|
+
"rate-limit-redis": "^4.1.2",
|
|
35
|
+
"redis": "^4.6.10"
|
|
34
36
|
},
|
|
35
37
|
"peerDependencies": {
|
|
36
38
|
"@themost/common": "^2",
|
|
Binary file
|
|
@@ -30,10 +30,34 @@
|
|
|
30
30
|
"description": "max number of connections during windowMs before starting to delay responses. Number or function that returns a number. Defaults to 1.",
|
|
31
31
|
"type": "integer"
|
|
32
32
|
},
|
|
33
|
+
"randomDelayMs": {
|
|
34
|
+
"description": "random milliseconds - a random value between the given range for defining delayMs",
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": [
|
|
37
|
+
{
|
|
38
|
+
"type": "integer"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "integer"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
33
45
|
"maxDelayMs": {
|
|
34
46
|
"description": "milliseconds - maximum value for delayMs after many consecutive attempts, that is, after the n-th request, the delay will be always maxDelayMs. Important when your application is running behind a load balancer or reverse proxy that has a request timeout. Defaults to Infinity.",
|
|
35
47
|
"type": "integer"
|
|
36
48
|
},
|
|
49
|
+
"randomMaxDelayMs": {
|
|
50
|
+
"description": "random milliseconds - a random value between the given range for delayMs after many consecutive attempts, that is, after the n-th request, the delay will be always maxDelayMs.",
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": [
|
|
53
|
+
{
|
|
54
|
+
"type": "integer"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "integer"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
37
61
|
"skipFailedRequests": {
|
|
38
62
|
"description": "when true failed requests (response status >= 400) won't be counted. Defaults to false.",
|
|
39
63
|
"type": "boolean"
|
|
@@ -53,8 +77,7 @@
|
|
|
53
77
|
},
|
|
54
78
|
"required": [
|
|
55
79
|
"windowMs",
|
|
56
|
-
"delayAfter"
|
|
57
|
-
"delayMs"
|
|
80
|
+
"delayAfter"
|
|
58
81
|
]
|
|
59
82
|
}
|
|
60
83
|
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
3
|
+
<!-- Created with Vectornator (http://vectornator.io/) -->
|
|
4
|
+
<svg height="100%" stroke-miterlimit="10" style="fill-rule:nonzero;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;" version="1.1" viewBox="0 0 1024 1024" width="100%" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
5
|
+
<defs/>
|
|
6
|
+
<g id="Untitled">
|
|
7
|
+
<g opacity="1">
|
|
8
|
+
<g opacity="1">
|
|
9
|
+
<path d="M133.315 232.814L350.631 231.553L500.788 230.681" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
10
|
+
<path d="M790.685 232.814L849.823 231.553L890.685 230.681" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
11
|
+
<path d="M558.158 231.747C558.158 182.042 598.452 141.747 648.158 141.747C697.864 141.747 738.158 182.042 738.158 231.747C738.158 281.453 697.864 321.747 648.158 321.747C598.452 321.747 558.158 281.453 558.158 231.747Z" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
12
|
+
</g>
|
|
13
|
+
<g opacity="1">
|
|
14
|
+
<path d="M890.685 516.481L673.369 517.742L523.212 518.614" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
15
|
+
<path d="M233.315 516.481L174.177 517.742L133.315 518.614" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
16
|
+
<path d="M465.842 517.547C465.842 567.253 425.548 607.547 375.842 607.547C326.136 607.547 285.842 567.253 285.842 517.547C285.842 467.842 326.136 427.547 375.842 427.547C425.548 427.547 465.842 467.842 465.842 517.547Z" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
17
|
+
</g>
|
|
18
|
+
<g opacity="1">
|
|
19
|
+
<path d="M133.315 793.319L350.631 792.058L500.788 791.186" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
20
|
+
<path d="M790.685 793.319L849.823 792.058L890.685 791.186" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
21
|
+
<path d="M558.158 792.253C558.158 742.547 598.452 702.253 648.158 702.253C697.864 702.253 738.158 742.547 738.158 792.253C738.158 841.958 697.864 882.253 648.158 882.253C598.452 882.253 558.158 841.958 558.158 792.253Z" fill="none" opacity="1" stroke="#c73850" stroke-linecap="butt" stroke-linejoin="round" stroke-width="48"/>
|
|
22
|
+
</g>
|
|
23
|
+
</g>
|
|
24
|
+
</g>
|
|
25
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
package/src/RateLimitService.js
CHANGED
|
@@ -34,6 +34,11 @@ export class RateLimitService extends ApplicationService {
|
|
|
34
34
|
if (paths.size === 0) {
|
|
35
35
|
TraceUtils.warn('@universis/janitor#RateLimitService is being started but the collection of paths is empty.');
|
|
36
36
|
}
|
|
37
|
+
// get proxy address forwarding option
|
|
38
|
+
let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
|
|
39
|
+
if (typeof proxyAddressForwarding !== 'boolean') {
|
|
40
|
+
proxyAddressForwarding = false;
|
|
41
|
+
}
|
|
37
42
|
paths.forEach((value, path) => {
|
|
38
43
|
let profile;
|
|
39
44
|
// get profile
|
|
@@ -50,9 +55,35 @@ export class RateLimitService extends ApplicationService {
|
|
|
50
55
|
legacyHeaders: true // send headers
|
|
51
56
|
}, profile, {
|
|
52
57
|
keyGenerator: (req) => {
|
|
53
|
-
|
|
58
|
+
let remoteAddress;
|
|
59
|
+
if (proxyAddressForwarding) {
|
|
60
|
+
// get proxy headers or remote address
|
|
61
|
+
remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
62
|
+
} else {
|
|
63
|
+
// get remote address
|
|
64
|
+
remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
65
|
+
}
|
|
66
|
+
return `${path}:${remoteAddress}`;
|
|
54
67
|
}
|
|
55
68
|
});
|
|
69
|
+
if (typeof rateLimitOptions.store === 'string') {
|
|
70
|
+
// load store
|
|
71
|
+
const store = rateLimitOptions.store.split('#');
|
|
72
|
+
let StoreClass;
|
|
73
|
+
if (store.length === 2) {
|
|
74
|
+
const storeModule = require(store[0]);
|
|
75
|
+
if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
|
|
76
|
+
StoreClass = storeModule[store[1]];
|
|
77
|
+
rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error(`${store} cannot be found or is inaccessible`);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
StoreClass = require(store[0]);
|
|
83
|
+
// create store
|
|
84
|
+
rateLimitOptions.store = new StoreClass(this, rateLimitOptions);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
56
87
|
addRouter.use(path, rateLimit(rateLimitOptions));
|
|
57
88
|
}
|
|
58
89
|
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { TraceUtils } from '@themost/common';
|
|
2
|
+
import RedisStore from 'rate-limit-redis';
|
|
3
|
+
import { createClient } from 'redis';
|
|
4
|
+
|
|
5
|
+
const superLoadIncrementScript = RedisStore.prototype.loadIncrementScript;
|
|
6
|
+
const superLoadGetScript = RedisStore.prototype.loadGetScript;
|
|
7
|
+
|
|
8
|
+
function noLoadGetScript() {
|
|
9
|
+
//
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function noLoadIncrementScript() {
|
|
13
|
+
//
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (superLoadIncrementScript != noLoadIncrementScript) {
|
|
17
|
+
RedisStore.prototype.loadIncrementScript = noLoadIncrementScript;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (superLoadGetScript != noLoadGetScript) {
|
|
21
|
+
RedisStore.prototype.loadGetScript = noLoadGetScript;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class RedisClientStore extends RedisStore {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @type {import('redis').RedisClientType}
|
|
28
|
+
*/
|
|
29
|
+
client;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @param {import('@themost/common').ApplicationService} service
|
|
34
|
+
* @param {{windowMs: number}} options
|
|
35
|
+
*/
|
|
36
|
+
constructor(service, options) {
|
|
37
|
+
super({
|
|
38
|
+
/**
|
|
39
|
+
* @param {...string} args
|
|
40
|
+
* @returns {Promise<*>}
|
|
41
|
+
*/
|
|
42
|
+
sendCommand: function () {
|
|
43
|
+
const args = Array.from(arguments);
|
|
44
|
+
const self = this;
|
|
45
|
+
if (args[0] === 'SCRIPT') {
|
|
46
|
+
TraceUtils.debug('RedisClientStore: Creating new client for sending script command');
|
|
47
|
+
const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {
|
|
48
|
+
host: '127.0.0.1',
|
|
49
|
+
port: 6379
|
|
50
|
+
};
|
|
51
|
+
const client = createClient(connectOptions);
|
|
52
|
+
return client.connect().then(() => {
|
|
53
|
+
return client.sendCommand(args);
|
|
54
|
+
}).catch((error) => {
|
|
55
|
+
TraceUtils.error(error);
|
|
56
|
+
return Promise.reject(error);
|
|
57
|
+
}).finally(() => {
|
|
58
|
+
if (client.isOpen) {
|
|
59
|
+
TraceUtils.debug('RedisClientStore: Closing client of sending script command');
|
|
60
|
+
client.disconnect().catch((errDisconnect) => {
|
|
61
|
+
TraceUtils.error(errDisconnect);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (self.client == null) {
|
|
67
|
+
TraceUtils.debug('RedisClientStore: Creating store client for sending commands');
|
|
68
|
+
const connectOptions = service.getApplication().getConfiguration().getSourceAt('settings/redis/options') || {
|
|
69
|
+
host: '127.0.0.1',
|
|
70
|
+
port: 6379
|
|
71
|
+
};
|
|
72
|
+
self.client = createClient(connectOptions);
|
|
73
|
+
}
|
|
74
|
+
if (self.client.isOpen) {
|
|
75
|
+
return self.client.sendCommand(args);
|
|
76
|
+
}
|
|
77
|
+
TraceUtils.debug('RedisClientStore: Opening redis store client for sending commands');
|
|
78
|
+
return self.client.connect().then(() => {
|
|
79
|
+
// send load script commands once
|
|
80
|
+
return Promise.all([
|
|
81
|
+
superLoadIncrementScript.call(self),
|
|
82
|
+
superLoadGetScript.call(self)
|
|
83
|
+
]).then((results) => {
|
|
84
|
+
self.incrementScriptSha = results[0];
|
|
85
|
+
self.getScriptSha = results[1];
|
|
86
|
+
// send command
|
|
87
|
+
args[1] = self.incrementScriptSha;
|
|
88
|
+
return self.client.sendCommand(args);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
this.init(options)
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
RedisClientStore
|
|
101
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Universis Project Version 1.0
|
|
4
|
+
* Copyright (c) 2018, Universis Project All rights reserved
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an LGPL 3.0 license that can be
|
|
7
|
+
* found in the LICENSE file at https://universis.io/license
|
|
8
|
+
*/
|
|
9
|
+
import {ConfigurationBase, ConfigurationStrategy} from '@themost/common';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Declares a configuration element for managing scope-based permissions on server resources
|
|
13
|
+
*/
|
|
14
|
+
declare interface ScopeAccessConfigurationElement {
|
|
15
|
+
/**
|
|
16
|
+
* Gets or sets an array of strings that holds an array of scopes e.g. students or students:read or students,teachers etc
|
|
17
|
+
*/
|
|
18
|
+
scope: Array<string>,
|
|
19
|
+
/**
|
|
20
|
+
* Gets or sets a string which represents the regular expression that is going to used to validate endpoint
|
|
21
|
+
*/
|
|
22
|
+
resource: string;
|
|
23
|
+
/**
|
|
24
|
+
* Gets or sets an array of strings which represents the access levels for the given scopes e.g. READ or READ,WRITE etc
|
|
25
|
+
*/
|
|
26
|
+
access: Array<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Gets or sets a string which represents a short description for this item
|
|
29
|
+
*/
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare class ScopeAccessConfiguration extends ConfigurationStrategy {
|
|
34
|
+
|
|
35
|
+
constructor(configuration: ConfigurationBase);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Gets an array of scope access configuration elements
|
|
39
|
+
*/
|
|
40
|
+
public elements: Array<ScopeAccessConfigurationElement>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Verifies the given request and returns a promise that resolves with a scope access configuration element
|
|
44
|
+
*/
|
|
45
|
+
verify(req: Request): Promise<ScopeAccessConfigurationElement>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export declare class DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {
|
|
49
|
+
|
|
50
|
+
constructor(configuration: ConfigurationBase);
|
|
51
|
+
/**
|
|
52
|
+
* Gets an array of scope access configuration elements
|
|
53
|
+
*/
|
|
54
|
+
public elements: Array<ScopeAccessConfigurationElement>;
|
|
55
|
+
/**
|
|
56
|
+
* Verifies the given request and returns a promise that resolves with a scope access configuration element
|
|
57
|
+
*/
|
|
58
|
+
verify(req: Request): Promise<ScopeAccessConfigurationElement>;
|
|
59
|
+
|
|
60
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {ConfigurationStrategy, Args} from '@themost/common';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import url from 'url';
|
|
4
|
+
|
|
5
|
+
const HTTP_METHOD_REGEXP = /^\b(POST|PUT|PATCH|DELETE)\b$/i;
|
|
6
|
+
|
|
7
|
+
class ScopeAccessConfiguration extends ConfigurationStrategy {
|
|
8
|
+
/**
|
|
9
|
+
* @param {ConfigurationBase} configuration
|
|
10
|
+
*/
|
|
11
|
+
constructor(configuration) {
|
|
12
|
+
super(configuration);
|
|
13
|
+
let elements = [];
|
|
14
|
+
// define property
|
|
15
|
+
Object.defineProperty(this, 'elements', {
|
|
16
|
+
get: () => {
|
|
17
|
+
return elements;
|
|
18
|
+
},
|
|
19
|
+
enumerable: true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {Request} req
|
|
25
|
+
* @returns Promise<ScopeAccessConfigurationElement>
|
|
26
|
+
*/
|
|
27
|
+
verify(req) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
try {
|
|
30
|
+
// validate request context
|
|
31
|
+
Args.notNull(req.context,'Context');
|
|
32
|
+
// validate request context user
|
|
33
|
+
Args.notNull(req.context.user,'User');
|
|
34
|
+
if (req.context.user.authenticationScope && req.context.user.authenticationScope.length>0) {
|
|
35
|
+
// get original url
|
|
36
|
+
let reqUrl = url.parse(req.originalUrl).pathname;
|
|
37
|
+
// get user context scopes as array e.g, ['students', 'students:read']
|
|
38
|
+
let reqScopes = req.context.user.authenticationScope.split(',');
|
|
39
|
+
// get user access based on HTTP method e.g. GET -> read access
|
|
40
|
+
let reqAccess = HTTP_METHOD_REGEXP.test(req.method) ? 'write' : 'read';
|
|
41
|
+
let result = this.elements.find(x => {
|
|
42
|
+
// filter element by access level
|
|
43
|
+
return x.access.indexOf(reqAccess)>=0
|
|
44
|
+
// resource path
|
|
45
|
+
&& new RegExp( '^' + x.resource, 'i').test(reqUrl)
|
|
46
|
+
// and scopes
|
|
47
|
+
&& x.scope.find(y => {
|
|
48
|
+
// search user scopes (validate wildcard scope)
|
|
49
|
+
return y === '*' || reqScopes.indexOf(y)>=0;
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return resolve(result);
|
|
53
|
+
}
|
|
54
|
+
return resolve();
|
|
55
|
+
}
|
|
56
|
+
catch(err) {
|
|
57
|
+
return reject(err);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @class
|
|
67
|
+
*/
|
|
68
|
+
class DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {
|
|
69
|
+
/**
|
|
70
|
+
* @param {ConfigurationBase} configuration
|
|
71
|
+
*/
|
|
72
|
+
constructor(configuration) {
|
|
73
|
+
super(configuration);
|
|
74
|
+
let defaults = [];
|
|
75
|
+
// load scope access from configuration resource
|
|
76
|
+
try {
|
|
77
|
+
/**
|
|
78
|
+
* @type {Array<ScopeAccessConfigurationElement>}
|
|
79
|
+
*/
|
|
80
|
+
defaults = require(path.resolve(configuration.getConfigurationPath(), 'scope.access.json'));
|
|
81
|
+
}
|
|
82
|
+
catch(err) {
|
|
83
|
+
// if an error occurred other than module not found (there are no default access policies)
|
|
84
|
+
if (err.code !== 'MODULE_NOT_FOUND') {
|
|
85
|
+
// throw error
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
// otherwise continue
|
|
89
|
+
}
|
|
90
|
+
this.elements.push.apply(this.elements, defaults);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
ScopeAccessConfiguration,
|
|
96
|
+
DefaultScopeAccessConfiguration
|
|
97
|
+
}
|
package/src/SpeedLimitService.js
CHANGED
|
@@ -32,6 +32,11 @@ export class SpeedLimitService extends ApplicationService {
|
|
|
32
32
|
if (paths.size === 0) {
|
|
33
33
|
TraceUtils.warn('@universis/janitor#SpeedLimitService is being started but the collection of paths is empty.');
|
|
34
34
|
}
|
|
35
|
+
// get proxy address forwarding option
|
|
36
|
+
let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
|
|
37
|
+
if (typeof proxyAddressForwarding !== 'boolean') {
|
|
38
|
+
proxyAddressForwarding = false;
|
|
39
|
+
}
|
|
35
40
|
paths.forEach((value, path) => {
|
|
36
41
|
let profile;
|
|
37
42
|
// get profile
|
|
@@ -44,14 +49,52 @@ export class SpeedLimitService extends ApplicationService {
|
|
|
44
49
|
if (profile != null) {
|
|
45
50
|
const slowDownOptions = Object.assign({
|
|
46
51
|
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
47
|
-
delayAfter:
|
|
52
|
+
delayAfter: 20, // 20 requests
|
|
48
53
|
delayMs: 500, // 500 ms
|
|
49
|
-
maxDelayMs:
|
|
54
|
+
maxDelayMs: 10000 // 10 seconds
|
|
50
55
|
}, profile, {
|
|
51
56
|
keyGenerator: (req) => {
|
|
52
|
-
|
|
57
|
+
let remoteAddress;
|
|
58
|
+
if (proxyAddressForwarding) {
|
|
59
|
+
// get proxy headers or remote address
|
|
60
|
+
remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
61
|
+
} else {
|
|
62
|
+
// get remote address
|
|
63
|
+
remoteAddress = (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
|
|
64
|
+
}
|
|
65
|
+
return `${path}:${remoteAddress}`;
|
|
53
66
|
}
|
|
54
67
|
});
|
|
68
|
+
if (Array.isArray(slowDownOptions.randomDelayMs)) {
|
|
69
|
+
slowDownOptions.delayMs = () => {
|
|
70
|
+
const delayMs = Math.floor(Math.random() * (slowDownOptions.randomDelayMs[1] - slowDownOptions.randomDelayMs[0] + 1) + slowDownOptions.randomDelayMs[0]);
|
|
71
|
+
return delayMs;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(slowDownOptions.randomMaxDelayMs)) {
|
|
75
|
+
slowDownOptions.maxDelayMs = () => {
|
|
76
|
+
const maxDelayMs = Math.floor(Math.random() * (slowDownOptions.randomMaxDelayMs[1] - slowDownOptions.randomMaxDelayMs[0] + 1) + slowDownOptions.randomMaxDelayMs[0]);
|
|
77
|
+
return maxDelayMs;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (typeof slowDownOptions.store === 'string') {
|
|
81
|
+
// load store
|
|
82
|
+
const store = slowDownOptions.store.split('#');
|
|
83
|
+
let StoreClass;
|
|
84
|
+
if (store.length === 2) {
|
|
85
|
+
const storeModule = require(store[0]);
|
|
86
|
+
if (Object.prototype.hasOwnProperty.call(storeModule, store[1])) {
|
|
87
|
+
StoreClass = storeModule[store[1]];
|
|
88
|
+
slowDownOptions.store = new StoreClass(this, slowDownOptions);
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error(`${store} cannot be found or is inaccessible`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
StoreClass = require(store[0]);
|
|
94
|
+
// create store
|
|
95
|
+
slowDownOptions.store = new StoreClass(this, slowDownOptions);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
55
98
|
addRouter.use(path, slowDown(slowDownOptions));
|
|
56
99
|
}
|
|
57
100
|
});
|
package/src/index.d.ts
CHANGED
package/src/index.js
CHANGED
package/src/polyfills.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
if (!String.prototype.replaceAll) {
|
|
2
|
+
String.prototype.replaceAll = function(str, newStr){
|
|
3
|
+
// If a regex pattern
|
|
4
|
+
if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
|
|
5
|
+
return this.replace(str, newStr);
|
|
6
|
+
}
|
|
7
|
+
// If a string
|
|
8
|
+
return this.replace(new RegExp(str, 'g'), newStr);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.TestAction = void 0;var _data = require("@themost/data");var _dec, _class;let
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
TestAction = (_dec = _data.EdmMapping.entityType(), _dec(_class = class TestAction extends _data.DataObject {}) || _class);exports.TestAction = TestAction;
|
|
5
|
-
//# sourceMappingURL=TestAction.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TestAction.js","names":["_data","require","_dec","_class","TestAction","EdmMapping","entityType","DataObject","exports"],"sources":["../../src/models/TestAction.js"],"sourcesContent":["import { DataObject, EdmMapping } from \"@themost/data\";\n\n@EdmMapping.entityType()\nclass TestAction extends DataObject {\n\n}\n\nexport {\n TestAction\n}"],"mappings":"uGAAA,IAAAA,KAAA,GAAAC,OAAA,kBAAuD,IAAAC,IAAA,EAAAC,MAAA;;;AAGjDC,UAAU,IAAAF,IAAA,GADfG,gBAAU,CAACC,UAAU,EAAE,EAAAJ,IAAA,CAAAC,MAAA,GAAxB,MACMC,UAAU,SAASG,gBAAU,CAAC,EAEnC,KAAAJ,MAAA,EAAAK,OAAA,CAAAJ,UAAA,GAAAA,UAAA"}
|
|
Binary file
|
|
Binary file
|
package/public/janitor-logo.svg
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
-
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
3
|
-
<!-- Created with Vectornator (http://vectornator.io/) -->
|
|
4
|
-
<svg height="1451.5199999999998mm" stroke-miterlimit="10" style="fill-rule:nonzero;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;" version="1.1" viewBox="0 0 4115.06 4115.06" width="1451.5199999999998mm" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
5
|
-
<defs/>
|
|
6
|
-
<g id="Layer-1">
|
|
7
|
-
<path d="M723.486 744.868L1942.84 744.767L3392.53 744.647" fill="none" opacity="1" stroke="#23b7cc" stroke-linecap="round" stroke-linejoin="round" stroke-width="283.5"/>
|
|
8
|
-
<path d="M735.141 1225.03L1408.88 1968.33L1629.62 2205.76L2290.13 2916.22" fill="none" opacity="1" stroke="#23b7cc" stroke-linecap="round" stroke-linejoin="round" stroke-width="283.5"/>
|
|
9
|
-
<path d="M2381.55 2272.88L2826.88 1797.13L3362.09 1218.8" fill="none" opacity="1" stroke="#23b7cc" stroke-linecap="round" stroke-linejoin="round" stroke-width="283.5"/>
|
|
10
|
-
<path d="M2806.2 3518.14L2806.83 3058.9L2806.03 2876.36L2804.41 2504.02" fill="none" opacity="1" stroke="#23b7cc" stroke-linecap="round" stroke-linejoin="round" stroke-width="283.5"/>
|
|
11
|
-
<path d="M817.89 2946.06L1633.75 2942.44" fill="none" opacity="1" stroke="#23b7cc" stroke-linecap="round" stroke-linejoin="round" stroke-width="283.5"/>
|
|
12
|
-
</g>
|
|
13
|
-
</svg>
|