@universis/janitor 1.9.0 → 1.11.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 +106 -0
- package/dist/index.d.ts +176 -5
- package/dist/index.esm.js +591 -154
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +598 -153
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/public/schemas/v1/rate-limit-service.json +4 -4
- package/public/schemas/v1/speed-limit-service.json +4 -4
- package/src/AppRateLimitService.d.ts +5 -0
- package/src/AppRateLimitService.js +21 -0
- package/src/AppSpeedLimitService.d.ts +5 -0
- package/src/AppSpeedLimitService.js +21 -0
- package/src/HttpBearerStategy.js +1 -1
- package/src/PassportService.d.ts +6 -0
- package/src/PassportService.js +27 -0
- package/src/RateLimitService.d.ts +20 -1
- package/src/RateLimitService.js +201 -70
- package/src/SpeedLimitService.d.ts +20 -2
- package/src/SpeedLimitService.js +207 -83
- package/src/index.d.ts +4 -0
- package/src/index.js +4 -0
package/README.md
CHANGED
|
@@ -82,6 +82,57 @@ Rate limit headers will be available only if the request is made from the same o
|
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
## AppRateLimitService
|
|
86
|
+
|
|
87
|
+
`AppRateLimitService` is a configurable extension of [express-rate-limit](https://www.npmjs.com/package/express-rate-limit) for limiting service requests based on application settings.
|
|
88
|
+
`AppRateLimitService` is an alternative to `RateLimitService` that allows you to configure rate limits based on container paths instead of service paths. This allows you to configure rate limits for all services in a container.
|
|
89
|
+
|
|
90
|
+
You can register `AppRateLimitService` under application services:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"services": [
|
|
95
|
+
{
|
|
96
|
+
"serviceType": "@universis/janitor#RateLimitService",
|
|
97
|
+
"strategyType": "@universis/janitor#AppRateLimitService"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The configuration should use the same structure as `RateLimitService` but under `settings/universis/rateLimit`:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"settings": {
|
|
108
|
+
"universis": {
|
|
109
|
+
"appRateLimit": {
|
|
110
|
+
"profiles": [
|
|
111
|
+
[
|
|
112
|
+
"appRateLimitProfile",
|
|
113
|
+
{
|
|
114
|
+
"windowMs": 300000,
|
|
115
|
+
"limit": 50,
|
|
116
|
+
"legacyHeaders": true
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
],
|
|
120
|
+
"paths": [
|
|
121
|
+
[
|
|
122
|
+
"/api/users/me",
|
|
123
|
+
{
|
|
124
|
+
"profile": "appRateLimitProfile"
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
where paths are the container paths instead of service paths. The `AppRateLimitService` will apply the rate limit to all services in the container that match the path.
|
|
135
|
+
|
|
85
136
|
|
|
86
137
|
## SpeedLimitService
|
|
87
138
|
|
|
@@ -230,6 +281,61 @@ Speed limit headers will be available only if the request is made from the same
|
|
|
230
281
|
}
|
|
231
282
|
```
|
|
232
283
|
|
|
284
|
+
## AppSpeedLimitService
|
|
285
|
+
|
|
286
|
+
`AppSpeedLimitService` is a configurable extension of [express-slow-down](https://www.npmjs.com/package/express-slow-down) for slowing down service responses based on application settings.
|
|
287
|
+
|
|
288
|
+
`AppSpeedLimitService` is an alternative to `SpeedLimitService` that allows you to configure speed limits based on container paths instead of service paths. This allows you to configure speed limits for all services in a container.
|
|
289
|
+
|
|
290
|
+
You can register `AppSpeedLimitService` under application services:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"services": [
|
|
295
|
+
{
|
|
296
|
+
"serviceType": "@universis/janitor#SpeedLimitService",
|
|
297
|
+
"strategyType": "@universis/janitor#AppSpeedLimitService"
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The configuration should use the same structure as `SpeedLimitService` but under `settings/universis/speedLimit`:
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"settings": {
|
|
308
|
+
"universis": {
|
|
309
|
+
"appSpeedLimit": {
|
|
310
|
+
"profiles": [
|
|
311
|
+
[
|
|
312
|
+
"appSpeedLimitProfile",
|
|
313
|
+
{
|
|
314
|
+
"windowMs": 300000,
|
|
315
|
+
"delayAfter": 5,
|
|
316
|
+
"delayMs": 500,
|
|
317
|
+
"maxDelayMs": 20000,
|
|
318
|
+
"headers": true
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
],
|
|
322
|
+
"paths": [
|
|
323
|
+
[
|
|
324
|
+
"/api/users/me",
|
|
325
|
+
{
|
|
326
|
+
"profile": "appSpeedLimitProfile"
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
]
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
where paths are the container paths instead of service paths. The `AppSpeedLimitService` will apply the speed limit to all services in the container that match the path.
|
|
337
|
+
|
|
338
|
+
|
|
233
339
|
## ScopeAccessConfiguration
|
|
234
340
|
|
|
235
341
|
`ScopeAccessConfiguration` is a configurable application configuration strategy for limiting access to service endpoints based on user scopes.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,49 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _themost_common from '@themost/common';
|
|
2
|
+
import { ApplicationService, ApplicationBase, ConfigurationStrategy, ConfigurationBase, HttpForbiddenError, HttpError } from '@themost/common';
|
|
3
|
+
import { Router, Application, Handler, Request as Request$1 } from 'express';
|
|
4
|
+
import { Store, Options } from 'express-rate-limit';
|
|
5
|
+
import { BehaviorSubject } from 'rxjs';
|
|
6
|
+
import { Store as Store$1, Options as Options$1 } from 'express-slow-down';
|
|
2
7
|
import RedisStore from 'rate-limit-redis';
|
|
3
|
-
import { Handler, Request as Request$1 } from 'express';
|
|
4
8
|
import { DataContext } from '@themost/data';
|
|
9
|
+
import BearerStrategy from 'passport-http-bearer';
|
|
10
|
+
import { Authenticator } from 'passport';
|
|
11
|
+
|
|
12
|
+
declare type RateLimitStoreConstructor = new (...arg: unknown[]) => Store;
|
|
13
|
+
|
|
14
|
+
declare interface RateLimitServiceConfiguration {
|
|
15
|
+
extends?: string;
|
|
16
|
+
storeType?: string;
|
|
17
|
+
profiles?: [string, Options][];
|
|
18
|
+
paths?: [string, ({ profile: string } | Options)][];
|
|
19
|
+
}
|
|
5
20
|
|
|
6
21
|
declare class RateLimitService extends ApplicationService {
|
|
22
|
+
constructor(app: _themost_common.ApplicationBase);
|
|
23
|
+
readonly loaded: BehaviorSubject<Router>;
|
|
24
|
+
getServiceContainer(): BehaviorSubject<Router | Application>;
|
|
25
|
+
getServiceName(): string;
|
|
26
|
+
getServiceConfiguration(): RateLimitServiceConfiguration;
|
|
27
|
+
set(path: string, options: { profile?: string } | Options): this;
|
|
28
|
+
unset(path: string): this;
|
|
29
|
+
}
|
|
7
30
|
|
|
31
|
+
declare type SpeedLimitStoreConstructor = new (...arg: unknown[]) => Store$1;
|
|
32
|
+
|
|
33
|
+
declare interface SpeedLimitServiceConfiguration {
|
|
34
|
+
extends?: string;
|
|
35
|
+
storeType?: string;
|
|
36
|
+
profiles?: Map<string, Options$1>;
|
|
37
|
+
paths?: Map<string, ({ profile: string } | Options$1)>;
|
|
8
38
|
}
|
|
9
39
|
|
|
10
40
|
declare class SpeedLimitService extends ApplicationService {
|
|
11
|
-
|
|
41
|
+
constructor(app: ApplicationBase);
|
|
42
|
+
getServiceContainer(): BehaviorSubject<Router | Application>;
|
|
43
|
+
getServiceName(): string;
|
|
44
|
+
getServiceConfiguration(): SpeedLimitServiceConfiguration;
|
|
45
|
+
set(path: string, options: { profile?: string } | Options$1): this;
|
|
46
|
+
unset(path: string): this;
|
|
12
47
|
}
|
|
13
48
|
|
|
14
49
|
declare class RedisClientStore extends RedisStore {
|
|
@@ -209,5 +244,141 @@ declare class RemoteAddressValidator extends ApplicationService {
|
|
|
209
244
|
|
|
210
245
|
}
|
|
211
246
|
|
|
212
|
-
|
|
213
|
-
|
|
247
|
+
declare class AppRateLimitService extends RateLimitService {
|
|
248
|
+
constructor(app: _themost_common.ApplicationBase);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
declare class AppSpeedLimitService extends SpeedLimitService {
|
|
252
|
+
constructor(app: _themost_common.ApplicationBase);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
declare class HttpBearerTokenRequired extends HttpError {
|
|
256
|
+
constructor() {
|
|
257
|
+
super(499, 'A token is required to fulfill the request.');
|
|
258
|
+
this.code = 'E_TOKEN_REQUIRED';
|
|
259
|
+
this.title = 'Token Required';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
declare class HttpBearerTokenNotFound extends HttpError {
|
|
264
|
+
constructor() {
|
|
265
|
+
super(498, 'Token was not found.');
|
|
266
|
+
this.code = 'E_TOKEN_NOT_FOUND';
|
|
267
|
+
this.title = 'Invalid token';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
declare class HttpBearerTokenExpired extends HttpError {
|
|
272
|
+
constructor() {
|
|
273
|
+
super(498, 'Token was expired or is in invalid state.');
|
|
274
|
+
this.code = 'E_TOKEN_EXPIRED';
|
|
275
|
+
this.title = 'Invalid token';
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
declare class HttpAccountDisabled extends HttpForbiddenError {
|
|
280
|
+
constructor() {
|
|
281
|
+
super('Access is denied. User account is disabled.');
|
|
282
|
+
this.code = 'E_ACCOUNT_DISABLED';
|
|
283
|
+
this.statusCode = 403.2;
|
|
284
|
+
this.title = 'Disabled account';
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
declare class HttpBearerStrategy extends BearerStrategy {
|
|
289
|
+
constructor() {
|
|
290
|
+
super({
|
|
291
|
+
passReqToCallback: true
|
|
292
|
+
},
|
|
293
|
+
/**
|
|
294
|
+
* @param {Request} req
|
|
295
|
+
* @param {string} token
|
|
296
|
+
* @param {Function} done
|
|
297
|
+
*/
|
|
298
|
+
function(req, token, done) {
|
|
299
|
+
/**
|
|
300
|
+
* Gets OAuth2 client services
|
|
301
|
+
* @type {import('./OAuth2ClientService').OAuth2ClientService}
|
|
302
|
+
*/
|
|
303
|
+
let client = req.context.getApplication().getStrategy(function OAuth2ClientService() {});
|
|
304
|
+
// if client cannot be found
|
|
305
|
+
if (client == null) {
|
|
306
|
+
// throw configuration error
|
|
307
|
+
return done(new Error('Invalid application configuration. OAuth2 client service cannot be found.'))
|
|
308
|
+
}
|
|
309
|
+
if (token == null) {
|
|
310
|
+
// throw 499 Token Required error
|
|
311
|
+
return done(new HttpBearerTokenRequired());
|
|
312
|
+
}
|
|
313
|
+
// get token info
|
|
314
|
+
client.getTokenInfo(req.context, token).then(info => {
|
|
315
|
+
if (info == null) {
|
|
316
|
+
// the specified token cannot be found - 498 invalid token with specific code
|
|
317
|
+
return done(new HttpBearerTokenNotFound());
|
|
318
|
+
}
|
|
319
|
+
// if the given token is not active throw token expired - 498 invalid token with specific code
|
|
320
|
+
if (!info.active) {
|
|
321
|
+
return done(new HttpBearerTokenExpired());
|
|
322
|
+
}
|
|
323
|
+
// find user from token info
|
|
324
|
+
return (function () {
|
|
325
|
+
/**
|
|
326
|
+
* @type {import('./services/user-provisioning-mapper-service').UserProvisioningMapperService}
|
|
327
|
+
*/
|
|
328
|
+
const mapper = req.context.getApplication().getService(function UserProvisioningMapperService() {});
|
|
329
|
+
if (mapper == null) {
|
|
330
|
+
return req.context.model('User').where('name').equal(info.username).silent().getItem();
|
|
331
|
+
}
|
|
332
|
+
return mapper.getUser(req.context, info);
|
|
333
|
+
})().then((user) => {
|
|
334
|
+
// check if userProvisioning service is installed and try to find related user only if user not found
|
|
335
|
+
if (user == null) {
|
|
336
|
+
/**
|
|
337
|
+
* @type {import('./services/user-provisioning-service').UserProvisioningService}
|
|
338
|
+
*/
|
|
339
|
+
const service = req.context.getApplication().getService(function UserProvisioningService() {});
|
|
340
|
+
if (service == null) {
|
|
341
|
+
return user;
|
|
342
|
+
}
|
|
343
|
+
return service.validateUser(req.context, info);
|
|
344
|
+
}
|
|
345
|
+
return user;
|
|
346
|
+
}).then( user => {
|
|
347
|
+
// user cannot be found and of course cannot be authenticated (throw forbidden error)
|
|
348
|
+
if (user == null) {
|
|
349
|
+
// write access log for forbidden
|
|
350
|
+
return done(new HttpForbiddenError());
|
|
351
|
+
}
|
|
352
|
+
// check if user has enabled attribute
|
|
353
|
+
if (Object.prototype.hasOwnProperty.call(user, 'enabled') && !user.enabled) {
|
|
354
|
+
//if user.enabled is off throw forbidden error
|
|
355
|
+
return done(new HttpAccountDisabled('Access is denied. User account is disabled.'));
|
|
356
|
+
}
|
|
357
|
+
// otherwise return user data
|
|
358
|
+
return done(null, {
|
|
359
|
+
'name': user.name,
|
|
360
|
+
'authenticationProviderKey': user.id,
|
|
361
|
+
'authenticationType':'Bearer',
|
|
362
|
+
'authenticationToken': token,
|
|
363
|
+
'authenticationScope': info.scope
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}).catch(err => {
|
|
367
|
+
// end log token info request with error
|
|
368
|
+
if (err && err.statusCode === 404) {
|
|
369
|
+
// revert 404 not found returned by auth server to 498 invalid token
|
|
370
|
+
return done(new HttpBearerTokenNotFound());
|
|
371
|
+
}
|
|
372
|
+
// otherwise continue with error
|
|
373
|
+
return done(err);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
declare class PassportService extends ApplicationService {
|
|
380
|
+
getInstance(): Authenticator
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export { AppRateLimitService, AppSpeedLimitService, DefaultScopeAccessConfiguration, EnableScopeAccessConfiguration, ExtendScopeAccessConfiguration, HttpAccountDisabled, HttpBearerStrategy, HttpBearerTokenExpired, HttpBearerTokenNotFound, HttpBearerTokenRequired, OAuth2ClientService, PassportService, RateLimitService, RedisClientStore, RemoteAddressValidator, ScopeAccessConfiguration, ScopeString, SpeedLimitService, validateScope };
|
|
384
|
+
export type { GenericUser, OAuth2AuthorizeUser, OAuth2MethodOptions, OAuth2ServiceSettings, OAuth2User, OAuth2UserProfile, RateLimitServiceConfiguration, RateLimitStoreConstructor, ScopeAccessConfigurationElement, ScopeAccessConfigurationSection, SpeedLimitServiceConfiguration, SpeedLimitStoreConstructor, UniversisConfigurationSection };
|