@universis/janitor 1.5.0 → 1.6.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 CHANGED
@@ -295,3 +295,51 @@ app.use('/api', passport.authenticate('bearer', {session: false}), validateScope
295
295
  ```
296
296
 
297
297
  A `403 - Access denied due to authorization scopes` error will be thrown if the user does not have access to the resource.
298
+
299
+ ## RemoteAddressValidator
300
+
301
+ `RemoteAddressValidator` is a configurable application service for validating access to service endpoints based on remote address provided by OAuth2 token.
302
+
303
+ Register service under application services:
304
+
305
+ ```json
306
+ {
307
+ "services": [
308
+ {
309
+ "serviceType": "@universis/janitor#RemoteAddressValidator"
310
+ }
311
+ ]
312
+ }
313
+ ```
314
+
315
+ `RemoteAddressValidator` validates the remote address of the request with the remote address provided by the OAuth2 token. If the addresses do not match, a `403 - Access denied due to remote address` error will be thrown. Token remote address is provided by the `remoteAddress` claim in the token payload. It can be configured in the OAuth2 server configuration and may have a different name. This name may be configured in the `settings/universis/janitor/remoteAddress` configuration e.g.
316
+
317
+ ```json
318
+ {
319
+ "settings": {
320
+ "universis": {
321
+ "janitor": {
322
+ "remoteAddress": {
323
+ "claim": "ipAddress"
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }
329
+ ```
330
+
331
+ where `claim` is the name of the remote address claim in the token payload.
332
+
333
+ **Important Note**: If api server is served by a proxy, the remote address may be different from the client address. In this case, the proxy should be configured to forward the client address to the server. This scenario should be configured in application settings under `settings/universis/api/` section e.g.
334
+
335
+ ```json
336
+ {
337
+ "settings": {
338
+ "universis": {
339
+ "api": {
340
+ "proxyAddressForwarding": true
341
+ }
342
+ }
343
+ }
344
+ }
345
+ ```
@@ -0,0 +1,98 @@
1
+ import { ApplicationService, ApplicationBase } from '@themost/common';
2
+ import { DataContext } from '@themost/data';
3
+
4
+ export declare interface OAuth2MethodOptions {
5
+ access_token: string;
6
+ }
7
+
8
+ export declare interface OAuth2AuthorizeUser {
9
+ client_id?: string;
10
+ client_secret?: string;
11
+ username: string;
12
+ password: string;
13
+ grant_type: string;
14
+ scope?: string;
15
+ }
16
+
17
+ export declare interface OAuth2ServiceSettings {
18
+ unattendedExecutionAccount?: string;
19
+ client_id: string;
20
+ client_secret?: string;
21
+ server_uri: string;
22
+ userinfo_uri?: string;
23
+ introspect_uri?: string;
24
+ admin_uri?: string;
25
+ well_known_configuration_uri?: string;
26
+ adminAccount: {
27
+ username: string;
28
+ password: string;
29
+ client_id: string;
30
+ client_secret?: string;
31
+ scope?: string;
32
+ }
33
+ }
34
+
35
+ export declare interface OAuth2UserProfile {
36
+ sub: string;
37
+ name: string;
38
+ preferred_username: string;
39
+ given_name: string;
40
+ family_name: string;
41
+ email: string;
42
+ }
43
+
44
+ export declare interface GenericUser {
45
+ id?: any;
46
+ additionalType?: string;
47
+ alternateName?: string;
48
+ description?: string;
49
+ givenName?: string;
50
+ familyName?: string;
51
+ image?: string;
52
+ name?: string;
53
+ url?: string;
54
+ dateCreated?: Date;
55
+ dateModified?: Date;
56
+ createdBy?: any;
57
+ modifiedBy?: any;
58
+ lockoutTime?: Date;
59
+ logonCount?: number;
60
+ enabled?: boolean;
61
+ lastLogon?: Date;
62
+ userCredentials?: {
63
+ userPassword?: string;
64
+ userActivated?: boolean;
65
+ temporary?: boolean;
66
+ }
67
+ }
68
+
69
+ export declare interface OAuth2User {
70
+ id?: any;
71
+ username?: string;
72
+ email?: string;
73
+ enabled?: boolean;
74
+ emailVerified?: boolean;
75
+ firstName?: string;
76
+ lastName?: string;
77
+ credentials?: {
78
+ algorithm?: string,
79
+ temporary?: boolean,
80
+ type?: string,
81
+ value?: string
82
+ }
83
+ }
84
+
85
+ export declare class OAuth2ClientService extends ApplicationService {
86
+ get settings(): OAuth2ServiceSettings;
87
+ constructor(app: ApplicationBase)
88
+ getUserInfo(context: DataContext, token: string): Promise<OAuth2UserProfile>;
89
+ getTokenInfo(context: DataContext, token: string): Promise<any>;
90
+ getContextTokenInfo(context: DataContext): Promise<any>;
91
+ authorize(authorizeUser: OAuth2AuthorizeUser): Promise<{ access_token?: string, refresh_token?: string}>;
92
+ getUser(username: string, options: OAuth2MethodOptions): Promise<any>;
93
+ getUserById(user_id: any, options: OAuth2MethodOptions): Promise<any>;
94
+ getUserByEmail(email: string, options: OAuth2MethodOptions): Promise<any>;
95
+ updateUser(user: GenericUser | any, options: OAuth2MethodOptions): Promise<any>;
96
+ createUser(user: GenericUser | any, options: OAuth2MethodOptions): Promise<any>;
97
+ deleteUser(user: { id: any }, options: OAuth2MethodOptions): Promise<any>;
98
+ }
@@ -0,0 +1,251 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.OAuth2ClientService = void 0;var _superagent = require("superagent");
2
+ var _url = require("url");
3
+ var _common = require("@themost/common");
4
+
5
+ function responseHander(resolve, reject) {
6
+ return function (err, response) {
7
+ if (err) {
8
+ /**
9
+ * @type {import('superagent').Response}
10
+ */
11
+ const response = err.response;
12
+ if (response && response.headers['content-type'] === 'application/json') {
13
+ // get body
14
+ const clientError = response.body;
15
+ const error = new _common.HttpError(response.status);
16
+ return reject(Object.assign(error, {
17
+ clientError
18
+ }));
19
+ }
20
+ return reject(err);
21
+ }
22
+ if (response.status === 204 && response.headers['content-type'] === 'application/json') {
23
+ return resolve(null);
24
+ }
25
+ return resolve(response.body);
26
+ };
27
+ }
28
+
29
+ /**
30
+ * @class
31
+ */
32
+ class OAuth2ClientService extends _common.ApplicationService {
33
+ /**
34
+ * @param {import('@themost/express').ExpressDataApplication} app
35
+ */
36
+ constructor(app) {
37
+ super(app);
38
+ /**
39
+ * @name OAuth2ClientService#settings
40
+ * @type {{server_uri:string,token_uri?:string}}
41
+ */
42
+ Object.defineProperty(this, 'settings', {
43
+ writable: false,
44
+ value: app.getConfiguration().getSourceAt('settings/auth'),
45
+ enumerable: false,
46
+ configurable: false
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Gets keycloak server root
52
+ * @returns {string}
53
+ */
54
+ getServer() {
55
+ return this.settings.server_uri;
56
+ }
57
+
58
+ /**
59
+ * Gets keycloak server root
60
+ * @returns {string}
61
+ */
62
+ getAdminRoot() {
63
+ return this.settings.admin_uri;
64
+ }
65
+
66
+ // noinspection JSUnusedGlobalSymbols
67
+ /**
68
+ * Gets user's profile by calling OAuth2 server profile endpoint
69
+ * @param {ExpressDataContext} context
70
+ * @param {string} token
71
+ */
72
+ getUserInfo(token) {
73
+ return new Promise((resolve, reject) => {
74
+ const userinfo_uri = this.settings.userinfo_uri ? new _url.URL(this.settings.userinfo_uri, this.getServer()) : new _url.URL('me', this.getServer());
75
+ return new _superagent.Request('GET', userinfo_uri).
76
+ set({
77
+ 'Authorization': `Bearer ${token}`,
78
+ 'Accept': 'application/json'
79
+ }).
80
+ query({
81
+ 'access_token': token
82
+ }).end(responseHander(resolve, reject));
83
+ });
84
+ }
85
+
86
+ // noinspection JSUnusedGlobalSymbols
87
+ /**
88
+ * Gets the token info of the current context
89
+ * @param {ExpressDataContext} context
90
+ */
91
+ getContextTokenInfo(context) {
92
+ if (context.user == null) {
93
+ return Promise.reject(new Error('Context user may not be null'));
94
+ }
95
+ if (context.user.authenticationType !== 'Bearer') {
96
+ return Promise.reject(new Error('Invalid context authentication type'));
97
+ }
98
+ if (context.user.authenticationToken == null) {
99
+ return Promise.reject(new Error('Context authentication data may not be null'));
100
+ }
101
+ return this.getTokenInfo(context, context.user.authenticationToken);
102
+ }
103
+ /**
104
+ * Gets token info by calling OAuth2 server endpoint
105
+ * @param {ExpressDataContext} _context
106
+ * @param {string} token
107
+ */
108
+ getTokenInfo(_context, token) {
109
+ return new Promise((resolve, reject) => {
110
+ const introspection_uri = this.settings.introspection_uri ? new _url.URL(this.settings.introspection_uri, this.getServer()) : new _url.URL('tokeninfo', this.getServer());
111
+ return new _superagent.Request('POST', introspection_uri).
112
+ auth(this.settings.client_id, this.settings.client_secret).
113
+ set('Accept', 'application/json').
114
+ type('form').
115
+ send({
116
+ 'token_type_hint': 'access_token',
117
+ 'token': token
118
+ }).end(responseHander(resolve, reject));
119
+ });
120
+ }
121
+
122
+ /**
123
+ * @param {AuthorizeUser} authorizeUser
124
+ */
125
+ authorize(authorizeUser) {
126
+ const tokenURL = this.settings.token_uri ? new _url.URL(this.settings.token_uri) : new _url.URL('authorize', this.getServer());
127
+ return new Promise((resolve, reject) => {
128
+ return new _superagent.Request('POST', tokenURL).
129
+ type('form').
130
+ send(authorizeUser).end(responseHander(resolve, reject));
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Gets a user by name
136
+ * @param {*} user_id
137
+ * @param {AdminMethodOptions} options
138
+ */
139
+ getUserById(user_id, options) {
140
+ return new Promise((resolve, reject) => {
141
+ return new _superagent.Request('GET', new _url.URL(`users/${user_id}`, this.getAdminRoot())).
142
+ set('Authorization', `Bearer ${options.access_token}`).
143
+ end(responseHander(resolve, reject));
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Gets a user by name
149
+ * @param {string} username
150
+ * @param {AdminMethodOptions} options
151
+ */
152
+ getUser(username, options) {
153
+ return new Promise((resolve, reject) => {
154
+ return new _superagent.Request('GET', new _url.URL('users', this.getAdminRoot())).
155
+ set('Authorization', `Bearer ${options.access_token}`).
156
+ query({
157
+ '$filter': `name eq '${username}'`
158
+ }).
159
+ end(responseHander(resolve, reject));
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Gets a user by email address
165
+ * @param {string} email
166
+ * @param {AdminMethodOptions} options
167
+ */
168
+ getUserByEmail(email, options) {
169
+ return new Promise((resolve, reject) => {
170
+ return new _superagent.Request('GET', new _url.URL('users', this.getAdminRoot())).
171
+ set('Authorization', `Bearer ${options.access_token}`).
172
+ query({
173
+ '$filter': `alternateName eq '${email}'`
174
+ }).
175
+ end(responseHander(resolve, reject));
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Updates an existing user
181
+ * @param {*} user
182
+ * @param {AdminMethodOptions} options
183
+ */
184
+ updateUser(user, options) {
185
+ return new Promise((resolve, reject) => {
186
+ if (user.id == null) {
187
+ return reject(new _common.DataError('E_IDENTIFIER', 'User may not be empty at this context.', null, 'User', 'id'));
188
+ }
189
+ const request = new _superagent.Request('PUT', new _url.URL(`users/${user.id}`, this.getAdminRoot()));
190
+ return request.set('Authorization', `Bearer ${options.access_token}`).
191
+ set('Content-Type', 'application/json').
192
+ send(user).
193
+ end(responseHander(resolve, reject));
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Creates a new user
199
+ * @param {*} user
200
+ * @param {AdminMethodOptions} options
201
+ */
202
+ createUser(user, options) {
203
+ return new Promise((resolve, reject) => {
204
+ const request = new _superagent.Request('POST', new _url.URL('users', this.getAdminRoot()));
205
+ return request.set('Authorization', `Bearer ${options.access_token}`).
206
+ set('Content-Type', 'application/json').
207
+ send(Object.assign({}, user, {
208
+ $state: 1 // for create
209
+ })).
210
+ end(responseHander(resolve, reject));
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Deletes a user
216
+ * @param {{id: any}} user
217
+ * @param {AdminMethodOptions} options
218
+ */
219
+ deleteUser(user, options) {
220
+ return new Promise((resolve, reject) => {
221
+ if (user.id == null) {
222
+ return reject(new _common.DataError('E_IDENTIFIER', 'User may not be empty at this context.', null, 'User', 'id'));
223
+ }
224
+ const request = new _superagent.Request('DELETE', new _url.URL(`users/${user.id}`, this.getAdminRoot()));
225
+ return request.set('Authorization', `Bearer ${options.access_token}`).
226
+ end(responseHander(resolve, reject));
227
+ });
228
+ }
229
+
230
+ /**
231
+ * @param {boolean=} force
232
+ * @returns {*}
233
+ */
234
+ getWellKnownConfiguration(force) {
235
+ if (force) {
236
+ this.well_known_configuration = null;
237
+ }
238
+ if (this.well_known_configuration) {
239
+ return Promise.resolve(this.well_known_configuration);
240
+ }
241
+ return new Promise((resolve, reject) => {
242
+ const well_known_configuration_uri = this.settings.well_known_configuration_uri ? new _url.URL(this.settings.well_known_configuration_uri, this.getServer()) : new _url.URL('.well-known/openid-configuration', this.getServer());
243
+ return new _superagent.Request('GET', well_known_configuration_uri).
244
+ end(responseHander(resolve, reject));
245
+ }).then((configuration) => {
246
+ this.well_known_configuration = configuration;
247
+ return configuration;
248
+ });
249
+ }
250
+ }exports.OAuth2ClientService = OAuth2ClientService;
251
+ //# sourceMappingURL=OAuth2ClientService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuth2ClientService.js","names":["_superagent","require","_url","_common","responseHander","resolve","reject","err","response","headers","clientError","body","error","HttpError","status","Object","assign","OAuth2ClientService","ApplicationService","constructor","app","defineProperty","writable","value","getConfiguration","getSourceAt","enumerable","configurable","getServer","settings","server_uri","getAdminRoot","admin_uri","getUserInfo","token","Promise","userinfo_uri","URL","Request","set","query","end","getContextTokenInfo","context","user","Error","authenticationType","authenticationToken","getTokenInfo","_context","introspection_uri","auth","client_id","client_secret","type","send","authorize","authorizeUser","tokenURL","token_uri","getUserById","user_id","options","access_token","getUser","username","getUserByEmail","email","updateUser","id","DataError","request","createUser","$state","deleteUser","getWellKnownConfiguration","force","well_known_configuration","well_known_configuration_uri","then","configuration","exports"],"sources":["../src/OAuth2ClientService.js"],"sourcesContent":["import {Request} from 'superagent'\nimport {URL} from 'url';\nimport {ApplicationService, DataError, HttpError} from '@themost/common';\n\nfunction responseHander(resolve, reject) {\n return function (err, response) {\n if (err) {\n /**\n * @type {import('superagent').Response}\n */\n const response = err.response\n if (response && response.headers['content-type'] === 'application/json') {\n // get body\n const clientError = response.body;\n const error = new HttpError(response.status);\n return reject(Object.assign(error, {\n clientError\n }));\n }\n return reject(err);\n }\n if (response.status === 204 && response.headers['content-type'] === 'application/json') {\n return resolve(null);\n }\n return resolve(response.body);\n };\n}\n\n/**\n * @class\n */\nclass OAuth2ClientService extends ApplicationService {\n /**\n * @param {import('@themost/express').ExpressDataApplication} app\n */\n constructor(app) {\n super(app);\n /**\n * @name OAuth2ClientService#settings\n * @type {{server_uri:string,token_uri?:string}}\n */\n Object.defineProperty(this, 'settings', {\n writable: false,\n value: app.getConfiguration().getSourceAt('settings/auth'),\n enumerable: false,\n configurable: false\n });\n }\n\n /**\n * Gets keycloak server root\n * @returns {string}\n */\n getServer() {\n return this.settings.server_uri;\n }\n\n /**\n * Gets keycloak server root\n * @returns {string}\n */\n getAdminRoot() {\n return this.settings.admin_uri;\n }\n\n // noinspection JSUnusedGlobalSymbols\n /**\n * Gets user's profile by calling OAuth2 server profile endpoint\n * @param {ExpressDataContext} context\n * @param {string} token\n */\n getUserInfo(token) {\n return new Promise((resolve, reject) => {\n const userinfo_uri = this.settings.userinfo_uri ? new URL(this.settings.userinfo_uri, this.getServer()) : new URL('me', this.getServer());\n return new Request('GET', userinfo_uri)\n .set({\n 'Authorization': `Bearer ${token}`,\n 'Accept': 'application/json'\n })\n .query({\n 'access_token':token\n }).end(responseHander(resolve, reject));\n });\n }\n\n // noinspection JSUnusedGlobalSymbols\n /**\n * Gets the token info of the current context\n * @param {ExpressDataContext} context\n */\n getContextTokenInfo(context) {\n if (context.user == null) {\n return Promise.reject(new Error('Context user may not be null'));\n }\n if (context.user.authenticationType !== 'Bearer') {\n return Promise.reject(new Error('Invalid context authentication type'));\n }\n if (context.user.authenticationToken == null) {\n return Promise.reject(new Error('Context authentication data may not be null'));\n }\n return this.getTokenInfo(context, context.user.authenticationToken);\n }\n /**\n * Gets token info by calling OAuth2 server endpoint\n * @param {ExpressDataContext} _context\n * @param {string} token\n */\n getTokenInfo(_context, token) {\n return new Promise((resolve, reject) => {\n const introspection_uri = this.settings.introspection_uri ? new URL(this.settings.introspection_uri, this.getServer()) : new URL('tokeninfo', this.getServer());\n return new Request('POST', introspection_uri)\n .auth(this.settings.client_id, this.settings.client_secret)\n .set('Accept', 'application/json')\n .type('form')\n .send({\n 'token_type_hint': 'access_token',\n 'token': token,\n }).end(responseHander(resolve, reject));\n });\n }\n\n /**\n * @param {AuthorizeUser} authorizeUser\n */\n authorize(authorizeUser) {\n const tokenURL = this.settings.token_uri ? new URL(this.settings.token_uri) : new URL('authorize', this.getServer());\n return new Promise((resolve, reject)=> {\n return new Request('POST', tokenURL)\n .type('form')\n .send(authorizeUser).end(responseHander(resolve, reject));\n });\n }\n\n /**\n * Gets a user by name\n * @param {*} user_id \n * @param {AdminMethodOptions} options \n */\n getUserById(user_id, options) {\n return new Promise((resolve, reject) => {\n return new Request('GET', new URL(`users/${user_id}`, this.getAdminRoot()))\n .set('Authorization', `Bearer ${options.access_token}`)\n .end(responseHander(resolve, reject));\n });\n }\n\n /**\n * Gets a user by name\n * @param {string} username \n * @param {AdminMethodOptions} options \n */\n getUser(username, options) {\n return new Promise((resolve, reject)=> {\n return new Request('GET', new URL('users', this.getAdminRoot()))\n .set('Authorization', `Bearer ${options.access_token}`)\n .query({\n '$filter': `name eq '${username}'`\n })\n .end(responseHander(resolve, reject));\n });\n }\n\n /**\n * Gets a user by email address\n * @param {string} email \n * @param {AdminMethodOptions} options \n */\n getUserByEmail(email, options) {\n return new Promise((resolve, reject)=> {\n return new Request('GET', new URL('users', this.getAdminRoot()))\n .set('Authorization', `Bearer ${options.access_token}`)\n .query({\n '$filter': `alternateName eq '${email}'`\n })\n .end(responseHander(resolve, reject));\n });\n }\n\n /**\n * Updates an existing user\n * @param {*} user \n * @param {AdminMethodOptions} options \n */\n updateUser(user, options) {\n return new Promise((resolve, reject)=> {\n if (user.id == null) {\n return reject(new DataError('E_IDENTIFIER', 'User may not be empty at this context.', null, 'User', 'id'));\n }\n const request = new Request('PUT', new URL(`users/${user.id}`, this.getAdminRoot()));\n return request.set('Authorization', `Bearer ${options.access_token}`)\n .set('Content-Type', 'application/json')\n .send(user)\n .end(responseHander(resolve, reject));\n });\n }\n\n /**\n * Creates a new user\n * @param {*} user \n * @param {AdminMethodOptions} options \n */\n createUser(user, options) {\n return new Promise((resolve, reject)=> {\n const request = new Request('POST', new URL('users', this.getAdminRoot()));\n return request.set('Authorization', `Bearer ${options.access_token}`)\n .set('Content-Type', 'application/json')\n .send(Object.assign({}, user, {\n $state: 1 // for create\n }))\n .end(responseHander(resolve, reject));\n });\n }\n\n /**\n * Deletes a user\n * @param {{id: any}} user \n * @param {AdminMethodOptions} options \n */\n deleteUser(user, options) {\n return new Promise((resolve, reject)=> {\n if (user.id == null) {\n return reject(new DataError('E_IDENTIFIER', 'User may not be empty at this context.', null, 'User', 'id'));\n }\n const request = new Request('DELETE', new URL(`users/${user.id}`, this.getAdminRoot()));\n return request.set('Authorization', `Bearer ${options.access_token}`)\n .end(responseHander(resolve, reject));\n });\n }\n\n /**\n * @param {boolean=} force \n * @returns {*}\n */\n getWellKnownConfiguration(force) {\n if (force) {\n this.well_known_configuration = null;\n }\n if (this.well_known_configuration) {\n return Promise.resolve(this.well_known_configuration);\n }\n return new Promise((resolve, reject) => {\n const well_known_configuration_uri = this.settings.well_known_configuration_uri ? new URL(this.settings.well_known_configuration_uri, this.getServer()) : new URL('.well-known/openid-configuration', this.getServer());\n return new Request('GET', well_known_configuration_uri)\n .end(responseHander(resolve, reject));\n }).then((configuration) => {\n this.well_known_configuration = configuration;\n return configuration;\n });\n }\n}\n\nexport {\n OAuth2ClientService\n}\n\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { ApplicationService } from '@themost/common';
2
+ import { Request } from 'express';
3
+
4
+ export declare class RemoteAddressValidator extends ApplicationService {
5
+
6
+ constructor(app: ApplicationService);
7
+ validateRemoteAddress(request: Request): Promise<boolean>;
8
+ getRemoteAddress(request: Request): string;
9
+
10
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.RemoteAddressValidator = exports.HttpRemoteAddrForbiddenError = void 0;var _common = require("@themost/common");
2
+ var _express = _interopRequireDefault(require("express"));
3
+ var _jsonwebtoken = _interopRequireDefault(require("jsonwebtoken"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}
4
+
5
+ class HttpRemoteAddrForbiddenError extends _common.HttpForbiddenError {
6
+ constructor() {
7
+ super('Access is denied due to remote address conflict. The client network has been changed or cannot be determined.');
8
+ this.statusCode = 403.6;
9
+ }
10
+
11
+ }exports.HttpRemoteAddrForbiddenError = HttpRemoteAddrForbiddenError;
12
+
13
+ class RemoteAddressValidator extends _common.ApplicationService {
14
+ constructor(app) {
15
+ super(app);
16
+
17
+ // get proxy address forwarding option
18
+ this.proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
19
+ if (typeof proxyAddressForwarding !== 'boolean') {
20
+ this.proxyAddressForwarding = false;
21
+ }
22
+ // get token claim name
23
+ this.claim = app.getConfiguration().getSourceAt('settings/universis/janitor/remoteAddress/claim') || 'remoteAddress';
24
+
25
+ app.serviceRouter.subscribe((serviceRouter) => {
26
+ if (serviceRouter == null) {
27
+ return;
28
+ }
29
+ const addRouter = _express.default.Router();
30
+ addRouter.use((req, res, next) => {
31
+ void this.validateRemoteAddress(req).then((value) => {
32
+ if (value === false) {
33
+ return next(new HttpRemoteAddrForbiddenError());
34
+ }
35
+ return next();
36
+ }).catch((err) => {
37
+ return next(err);
38
+ });
39
+ });
40
+ // insert router at the beginning of serviceRouter.stack
41
+ serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Gets remote address from request
47
+ * @param {import('express').Request} req
48
+ * @returns
49
+ */
50
+ getRemoteAddress(req) {
51
+ let remoteAddress;
52
+ if (this.proxyAddressForwarding) {
53
+ // get proxy headers or remote address
54
+ remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
55
+ } else {
56
+ remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
57
+ }
58
+ return remoteAddress;
59
+ }
60
+
61
+ /**
62
+ * Validates token remote address with request remote address
63
+ * @param {import('express').Request} req
64
+ * @returns {Promise<boolean>}
65
+ */
66
+ async validateRemoteAddress(req) {var _req$context, _req$context$user;
67
+ const authenticationToken = (_req$context = req.context) === null || _req$context === void 0 ? void 0 : (_req$context$user = _req$context.user) === null || _req$context$user === void 0 ? void 0 : _req$context$user.authenticationToken;
68
+ if (authenticationToken != null) {
69
+ const access_token = _jsonwebtoken.default.decode(authenticationToken);
70
+ const remoteAddress = access_token[this.claim];
71
+ if (remoteAddress == null) {
72
+ _common.TraceUtils.warn(`Remote address validation failed. Expected a valid remote address claimed by using "${this.claim}" attribute but got none.`);
73
+ return false;
74
+ }
75
+ // get context remote address
76
+ const requestRemoteAddress = this.getRemoteAddress(req);
77
+ if (remoteAddress !== requestRemoteAddress) {
78
+ _common.TraceUtils.warn(`Remote address validation failed. Expected remote address is ${remoteAddress || 'Uknown'} but request remote address is ${requestRemoteAddress}`);
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+ _common.TraceUtils.warn('Remote address validation cannot be completed because authentication token is not available.');
84
+ return false;
85
+ }
86
+
87
+ }exports.RemoteAddressValidator = RemoteAddressValidator;
88
+ //# sourceMappingURL=RemoteAddressValidator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemoteAddressValidator.js","names":["_common","require","_express","_interopRequireDefault","_jsonwebtoken","obj","__esModule","default","HttpRemoteAddrForbiddenError","HttpForbiddenError","constructor","statusCode","exports","RemoteAddressValidator","ApplicationService","app","proxyAddressForwarding","getConfiguration","getSourceAt","claim","serviceRouter","subscribe","addRouter","express","Router","use","req","res","next","validateRemoteAddress","then","value","catch","err","stack","unshift","apply","getRemoteAddress","remoteAddress","headers","connection","socket","_req$context","_req$context$user","authenticationToken","context","user","access_token","jwt","decode","TraceUtils","warn","requestRemoteAddress"],"sources":["../src/RemoteAddressValidator.js"],"sourcesContent":["import { ApplicationService, HttpForbiddenError, TraceUtils } from '@themost/common';\nimport express from 'express';\nimport jwt from 'jsonwebtoken';\n\nclass HttpRemoteAddrForbiddenError extends HttpForbiddenError {\n constructor() {\n super('Access is denied due to remote address conflict. The client network has been changed or cannot be determined.');\n this.statusCode = 403.6;\n }\n \n}\n\nclass RemoteAddressValidator extends ApplicationService {\n constructor(app) {\n super(app);\n\n // get proxy address forwarding option\n this.proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');\n if (typeof proxyAddressForwarding !== 'boolean') {\n this.proxyAddressForwarding = false;\n }\n // get token claim name\n this.claim = app.getConfiguration().getSourceAt('settings/universis/janitor/remoteAddress/claim') || 'remoteAddress';\n\n app.serviceRouter.subscribe((serviceRouter) => {\n if (serviceRouter == null) {\n return;\n }\n const addRouter = express.Router();\n addRouter.use((req, res, next) => {\n void this.validateRemoteAddress(req).then((value) => {\n if (value === false) {\n return next(new HttpRemoteAddrForbiddenError());\n }\n return next();\n }).catch((err) => {\n return next(err);\n });\n });\n // insert router at the beginning of serviceRouter.stack\n serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);\n });\n }\n\n /**\n * Gets remote address from request\n * @param {import('express').Request} req \n * @returns \n */\n getRemoteAddress(req) {\n let remoteAddress;\n if (this.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 remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;\n }\n return remoteAddress;\n }\n\n /**\n * Validates token remote address with request remote address\n * @param {import('express').Request} req \n * @returns {Promise<boolean>}\n */\n async validateRemoteAddress(req) {\n const authenticationToken = req.context?.user?.authenticationToken;\n if (authenticationToken != null) {\n const access_token = jwt.decode(authenticationToken);\n const remoteAddress = access_token[this.claim];\n if (remoteAddress == null) {\n TraceUtils.warn(`Remote address validation failed. Expected a valid remote address claimed by using \"${this.claim}\" attribute but got none.`);\n return false;\n }\n // get context remote address\n const requestRemoteAddress = this.getRemoteAddress(req);\n if (remoteAddress !== requestRemoteAddress) {\n TraceUtils.warn(`Remote address validation failed. Expected remote address is ${remoteAddress || 'Uknown'} but request remote address is ${requestRemoteAddress}`);\n return false;\n }\n return true;\n }\n TraceUtils.warn('Remote address validation cannot be completed because authentication token is not available.');\n return false;\n }\n\n}\n\nexport {\n HttpRemoteAddrForbiddenError,\n RemoteAddressValidator\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,3 +3,5 @@ export * from './SpeedLimitService';
3
3
  export * from './RedisClientStore';
4
4
  export * from './ScopeAccessConfiguration';
5
5
  export * from './validateScope';
6
+ export * from './OAuth2ClientService';
7
+ export * from './RemoteAddressValidator';
package/dist/index.js CHANGED
@@ -4,4 +4,6 @@ var _SpeedLimitService = require("./SpeedLimitService");Object.keys(_SpeedLimitS
4
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
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];} });});
6
6
  var _validateScope = require("./validateScope");Object.keys(_validateScope).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _validateScope[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _validateScope[key];} });});
7
+ var _OAuth2ClientService = require("./OAuth2ClientService");Object.keys(_OAuth2ClientService).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _OAuth2ClientService[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _OAuth2ClientService[key];} });});
8
+ var _RemoteAddressValidator = require("./RemoteAddressValidator");Object.keys(_RemoteAddressValidator).forEach(function (key) {if (key === "default" || key === "__esModule") return;if (key in exports && exports[key] === _RemoteAddressValidator[key]) return;Object.defineProperty(exports, key, { enumerable: true, get: function () {return _RemoteAddressValidator[key];} });});
7
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["require","_RateLimitService","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_SpeedLimitService","_RedisClientStore","_ScopeAccessConfiguration","_validateScope"],"sources":["../src/index.js"],"sourcesContent":["import './polyfills';\nexport * from './RateLimitService';\nexport * from './SpeedLimitService';\nexport * from './RedisClientStore';\nexport * from './ScopeAccessConfiguration';\nexport * from './validateScope';\n"],"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;AACA,IAAAQ,cAAA,GAAAb,OAAA,oBAAAE,MAAA,CAAAC,IAAA,CAAAU,cAAA,EAAAT,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAQ,cAAA,CAAAR,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAI,cAAA,CAAAR,GAAA"}
1
+ {"version":3,"file":"index.js","names":["require","_RateLimitService","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_SpeedLimitService","_RedisClientStore","_ScopeAccessConfiguration","_validateScope","_OAuth2ClientService","_RemoteAddressValidator"],"sources":["../src/index.js"],"sourcesContent":["import './polyfills';\nexport * from './RateLimitService';\nexport * from './SpeedLimitService';\nexport * from './RedisClientStore';\nexport * from './ScopeAccessConfiguration';\nexport * from './validateScope';\nexport * from './OAuth2ClientService';\nexport * from './RemoteAddressValidator';\n"],"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;AACA,IAAAQ,cAAA,GAAAb,OAAA,oBAAAE,MAAA,CAAAC,IAAA,CAAAU,cAAA,EAAAT,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAQ,cAAA,CAAAR,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAI,cAAA,CAAAR,GAAA;AACA,IAAAS,oBAAA,GAAAd,OAAA,0BAAAE,MAAA,CAAAC,IAAA,CAAAW,oBAAA,EAAAV,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAS,oBAAA,CAAAT,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAK,oBAAA,CAAAT,GAAA;AACA,IAAAU,uBAAA,GAAAf,OAAA,6BAAAE,MAAA,CAAAC,IAAA,CAAAY,uBAAA,EAAAX,OAAA,WAAAC,GAAA,OAAAA,GAAA,kBAAAA,GAAA,8BAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAU,uBAAA,CAAAV,GAAA,UAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA,IAAAG,UAAA,QAAAC,GAAA,WAAAA,CAAA,UAAAM,uBAAA,CAAAV,GAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@universis/janitor",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Universis api plugin for rate limiting requests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -32,7 +32,9 @@
32
32
  "express-rate-limit": "^7.1.1",
33
33
  "express-slow-down": "^1.6.0",
34
34
  "ioredis": "^5.3.2",
35
- "rate-limit-redis": "^4.2.0"
35
+ "jsonwebtoken": "^9.0.2",
36
+ "rate-limit-redis": "^4.2.0",
37
+ "superagent": "^8.1.2"
36
38
  },
37
39
  "peerDependencies": {
38
40
  "@themost/common": "^2",
@@ -0,0 +1,98 @@
1
+ import { ApplicationService, ApplicationBase } from '@themost/common';
2
+ import { DataContext } from '@themost/data';
3
+
4
+ export declare interface OAuth2MethodOptions {
5
+ access_token: string;
6
+ }
7
+
8
+ export declare interface OAuth2AuthorizeUser {
9
+ client_id?: string;
10
+ client_secret?: string;
11
+ username: string;
12
+ password: string;
13
+ grant_type: string;
14
+ scope?: string;
15
+ }
16
+
17
+ export declare interface OAuth2ServiceSettings {
18
+ unattendedExecutionAccount?: string;
19
+ client_id: string;
20
+ client_secret?: string;
21
+ server_uri: string;
22
+ userinfo_uri?: string;
23
+ introspect_uri?: string;
24
+ admin_uri?: string;
25
+ well_known_configuration_uri?: string;
26
+ adminAccount: {
27
+ username: string;
28
+ password: string;
29
+ client_id: string;
30
+ client_secret?: string;
31
+ scope?: string;
32
+ }
33
+ }
34
+
35
+ export declare interface OAuth2UserProfile {
36
+ sub: string;
37
+ name: string;
38
+ preferred_username: string;
39
+ given_name: string;
40
+ family_name: string;
41
+ email: string;
42
+ }
43
+
44
+ export declare interface GenericUser {
45
+ id?: any;
46
+ additionalType?: string;
47
+ alternateName?: string;
48
+ description?: string;
49
+ givenName?: string;
50
+ familyName?: string;
51
+ image?: string;
52
+ name?: string;
53
+ url?: string;
54
+ dateCreated?: Date;
55
+ dateModified?: Date;
56
+ createdBy?: any;
57
+ modifiedBy?: any;
58
+ lockoutTime?: Date;
59
+ logonCount?: number;
60
+ enabled?: boolean;
61
+ lastLogon?: Date;
62
+ userCredentials?: {
63
+ userPassword?: string;
64
+ userActivated?: boolean;
65
+ temporary?: boolean;
66
+ }
67
+ }
68
+
69
+ export declare interface OAuth2User {
70
+ id?: any;
71
+ username?: string;
72
+ email?: string;
73
+ enabled?: boolean;
74
+ emailVerified?: boolean;
75
+ firstName?: string;
76
+ lastName?: string;
77
+ credentials?: {
78
+ algorithm?: string,
79
+ temporary?: boolean,
80
+ type?: string,
81
+ value?: string
82
+ }
83
+ }
84
+
85
+ export declare class OAuth2ClientService extends ApplicationService {
86
+ get settings(): OAuth2ServiceSettings;
87
+ constructor(app: ApplicationBase)
88
+ getUserInfo(context: DataContext, token: string): Promise<OAuth2UserProfile>;
89
+ getTokenInfo(context: DataContext, token: string): Promise<any>;
90
+ getContextTokenInfo(context: DataContext): Promise<any>;
91
+ authorize(authorizeUser: OAuth2AuthorizeUser): Promise<{ access_token?: string, refresh_token?: string}>;
92
+ getUser(username: string, options: OAuth2MethodOptions): Promise<any>;
93
+ getUserById(user_id: any, options: OAuth2MethodOptions): Promise<any>;
94
+ getUserByEmail(email: string, options: OAuth2MethodOptions): Promise<any>;
95
+ updateUser(user: GenericUser | any, options: OAuth2MethodOptions): Promise<any>;
96
+ createUser(user: GenericUser | any, options: OAuth2MethodOptions): Promise<any>;
97
+ deleteUser(user: { id: any }, options: OAuth2MethodOptions): Promise<any>;
98
+ }
@@ -0,0 +1,255 @@
1
+ import {Request} from 'superagent'
2
+ import {URL} from 'url';
3
+ import {ApplicationService, DataError, HttpError} from '@themost/common';
4
+
5
+ function responseHander(resolve, reject) {
6
+ return function (err, response) {
7
+ if (err) {
8
+ /**
9
+ * @type {import('superagent').Response}
10
+ */
11
+ const response = err.response
12
+ if (response && response.headers['content-type'] === 'application/json') {
13
+ // get body
14
+ const clientError = response.body;
15
+ const error = new HttpError(response.status);
16
+ return reject(Object.assign(error, {
17
+ clientError
18
+ }));
19
+ }
20
+ return reject(err);
21
+ }
22
+ if (response.status === 204 && response.headers['content-type'] === 'application/json') {
23
+ return resolve(null);
24
+ }
25
+ return resolve(response.body);
26
+ };
27
+ }
28
+
29
+ /**
30
+ * @class
31
+ */
32
+ class OAuth2ClientService extends ApplicationService {
33
+ /**
34
+ * @param {import('@themost/express').ExpressDataApplication} app
35
+ */
36
+ constructor(app) {
37
+ super(app);
38
+ /**
39
+ * @name OAuth2ClientService#settings
40
+ * @type {{server_uri:string,token_uri?:string}}
41
+ */
42
+ Object.defineProperty(this, 'settings', {
43
+ writable: false,
44
+ value: app.getConfiguration().getSourceAt('settings/auth'),
45
+ enumerable: false,
46
+ configurable: false
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Gets keycloak server root
52
+ * @returns {string}
53
+ */
54
+ getServer() {
55
+ return this.settings.server_uri;
56
+ }
57
+
58
+ /**
59
+ * Gets keycloak server root
60
+ * @returns {string}
61
+ */
62
+ getAdminRoot() {
63
+ return this.settings.admin_uri;
64
+ }
65
+
66
+ // noinspection JSUnusedGlobalSymbols
67
+ /**
68
+ * Gets user's profile by calling OAuth2 server profile endpoint
69
+ * @param {ExpressDataContext} context
70
+ * @param {string} token
71
+ */
72
+ getUserInfo(token) {
73
+ return new Promise((resolve, reject) => {
74
+ const userinfo_uri = this.settings.userinfo_uri ? new URL(this.settings.userinfo_uri, this.getServer()) : new URL('me', this.getServer());
75
+ return new Request('GET', userinfo_uri)
76
+ .set({
77
+ 'Authorization': `Bearer ${token}`,
78
+ 'Accept': 'application/json'
79
+ })
80
+ .query({
81
+ 'access_token':token
82
+ }).end(responseHander(resolve, reject));
83
+ });
84
+ }
85
+
86
+ // noinspection JSUnusedGlobalSymbols
87
+ /**
88
+ * Gets the token info of the current context
89
+ * @param {ExpressDataContext} context
90
+ */
91
+ getContextTokenInfo(context) {
92
+ if (context.user == null) {
93
+ return Promise.reject(new Error('Context user may not be null'));
94
+ }
95
+ if (context.user.authenticationType !== 'Bearer') {
96
+ return Promise.reject(new Error('Invalid context authentication type'));
97
+ }
98
+ if (context.user.authenticationToken == null) {
99
+ return Promise.reject(new Error('Context authentication data may not be null'));
100
+ }
101
+ return this.getTokenInfo(context, context.user.authenticationToken);
102
+ }
103
+ /**
104
+ * Gets token info by calling OAuth2 server endpoint
105
+ * @param {ExpressDataContext} _context
106
+ * @param {string} token
107
+ */
108
+ getTokenInfo(_context, token) {
109
+ return new Promise((resolve, reject) => {
110
+ const introspection_uri = this.settings.introspection_uri ? new URL(this.settings.introspection_uri, this.getServer()) : new URL('tokeninfo', this.getServer());
111
+ return new Request('POST', introspection_uri)
112
+ .auth(this.settings.client_id, this.settings.client_secret)
113
+ .set('Accept', 'application/json')
114
+ .type('form')
115
+ .send({
116
+ 'token_type_hint': 'access_token',
117
+ 'token': token,
118
+ }).end(responseHander(resolve, reject));
119
+ });
120
+ }
121
+
122
+ /**
123
+ * @param {AuthorizeUser} authorizeUser
124
+ */
125
+ authorize(authorizeUser) {
126
+ const tokenURL = this.settings.token_uri ? new URL(this.settings.token_uri) : new URL('authorize', this.getServer());
127
+ return new Promise((resolve, reject)=> {
128
+ return new Request('POST', tokenURL)
129
+ .type('form')
130
+ .send(authorizeUser).end(responseHander(resolve, reject));
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Gets a user by name
136
+ * @param {*} user_id
137
+ * @param {AdminMethodOptions} options
138
+ */
139
+ getUserById(user_id, options) {
140
+ return new Promise((resolve, reject) => {
141
+ return new Request('GET', new URL(`users/${user_id}`, this.getAdminRoot()))
142
+ .set('Authorization', `Bearer ${options.access_token}`)
143
+ .end(responseHander(resolve, reject));
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Gets a user by name
149
+ * @param {string} username
150
+ * @param {AdminMethodOptions} options
151
+ */
152
+ getUser(username, options) {
153
+ return new Promise((resolve, reject)=> {
154
+ return new Request('GET', new URL('users', this.getAdminRoot()))
155
+ .set('Authorization', `Bearer ${options.access_token}`)
156
+ .query({
157
+ '$filter': `name eq '${username}'`
158
+ })
159
+ .end(responseHander(resolve, reject));
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Gets a user by email address
165
+ * @param {string} email
166
+ * @param {AdminMethodOptions} options
167
+ */
168
+ getUserByEmail(email, options) {
169
+ return new Promise((resolve, reject)=> {
170
+ return new Request('GET', new URL('users', this.getAdminRoot()))
171
+ .set('Authorization', `Bearer ${options.access_token}`)
172
+ .query({
173
+ '$filter': `alternateName eq '${email}'`
174
+ })
175
+ .end(responseHander(resolve, reject));
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Updates an existing user
181
+ * @param {*} user
182
+ * @param {AdminMethodOptions} options
183
+ */
184
+ updateUser(user, options) {
185
+ return new Promise((resolve, reject)=> {
186
+ if (user.id == null) {
187
+ return reject(new DataError('E_IDENTIFIER', 'User may not be empty at this context.', null, 'User', 'id'));
188
+ }
189
+ const request = new Request('PUT', new URL(`users/${user.id}`, this.getAdminRoot()));
190
+ return request.set('Authorization', `Bearer ${options.access_token}`)
191
+ .set('Content-Type', 'application/json')
192
+ .send(user)
193
+ .end(responseHander(resolve, reject));
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Creates a new user
199
+ * @param {*} user
200
+ * @param {AdminMethodOptions} options
201
+ */
202
+ createUser(user, options) {
203
+ return new Promise((resolve, reject)=> {
204
+ const request = new Request('POST', new URL('users', this.getAdminRoot()));
205
+ return request.set('Authorization', `Bearer ${options.access_token}`)
206
+ .set('Content-Type', 'application/json')
207
+ .send(Object.assign({}, user, {
208
+ $state: 1 // for create
209
+ }))
210
+ .end(responseHander(resolve, reject));
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Deletes a user
216
+ * @param {{id: any}} user
217
+ * @param {AdminMethodOptions} options
218
+ */
219
+ deleteUser(user, options) {
220
+ return new Promise((resolve, reject)=> {
221
+ if (user.id == null) {
222
+ return reject(new DataError('E_IDENTIFIER', 'User may not be empty at this context.', null, 'User', 'id'));
223
+ }
224
+ const request = new Request('DELETE', new URL(`users/${user.id}`, this.getAdminRoot()));
225
+ return request.set('Authorization', `Bearer ${options.access_token}`)
226
+ .end(responseHander(resolve, reject));
227
+ });
228
+ }
229
+
230
+ /**
231
+ * @param {boolean=} force
232
+ * @returns {*}
233
+ */
234
+ getWellKnownConfiguration(force) {
235
+ if (force) {
236
+ this.well_known_configuration = null;
237
+ }
238
+ if (this.well_known_configuration) {
239
+ return Promise.resolve(this.well_known_configuration);
240
+ }
241
+ return new Promise((resolve, reject) => {
242
+ const well_known_configuration_uri = this.settings.well_known_configuration_uri ? new URL(this.settings.well_known_configuration_uri, this.getServer()) : new URL('.well-known/openid-configuration', this.getServer());
243
+ return new Request('GET', well_known_configuration_uri)
244
+ .end(responseHander(resolve, reject));
245
+ }).then((configuration) => {
246
+ this.well_known_configuration = configuration;
247
+ return configuration;
248
+ });
249
+ }
250
+ }
251
+
252
+ export {
253
+ OAuth2ClientService
254
+ }
255
+
@@ -0,0 +1,10 @@
1
+ import { ApplicationService } from '@themost/common';
2
+ import { Request } from 'express';
3
+
4
+ export declare class RemoteAddressValidator extends ApplicationService {
5
+
6
+ constructor(app: ApplicationService);
7
+ validateRemoteAddress(request: Request): Promise<boolean>;
8
+ getRemoteAddress(request: Request): string;
9
+
10
+ }
@@ -0,0 +1,92 @@
1
+ import { ApplicationService, HttpForbiddenError, TraceUtils } from '@themost/common';
2
+ import express from 'express';
3
+ import jwt from 'jsonwebtoken';
4
+
5
+ class HttpRemoteAddrForbiddenError extends HttpForbiddenError {
6
+ constructor() {
7
+ super('Access is denied due to remote address conflict. The client network has been changed or cannot be determined.');
8
+ this.statusCode = 403.6;
9
+ }
10
+
11
+ }
12
+
13
+ class RemoteAddressValidator extends ApplicationService {
14
+ constructor(app) {
15
+ super(app);
16
+
17
+ // get proxy address forwarding option
18
+ this.proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding');
19
+ if (typeof proxyAddressForwarding !== 'boolean') {
20
+ this.proxyAddressForwarding = false;
21
+ }
22
+ // get token claim name
23
+ this.claim = app.getConfiguration().getSourceAt('settings/universis/janitor/remoteAddress/claim') || 'remoteAddress';
24
+
25
+ app.serviceRouter.subscribe((serviceRouter) => {
26
+ if (serviceRouter == null) {
27
+ return;
28
+ }
29
+ const addRouter = express.Router();
30
+ addRouter.use((req, res, next) => {
31
+ void this.validateRemoteAddress(req).then((value) => {
32
+ if (value === false) {
33
+ return next(new HttpRemoteAddrForbiddenError());
34
+ }
35
+ return next();
36
+ }).catch((err) => {
37
+ return next(err);
38
+ });
39
+ });
40
+ // insert router at the beginning of serviceRouter.stack
41
+ serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack);
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Gets remote address from request
47
+ * @param {import('express').Request} req
48
+ * @returns
49
+ */
50
+ getRemoteAddress(req) {
51
+ let remoteAddress;
52
+ if (this.proxyAddressForwarding) {
53
+ // get proxy headers or remote address
54
+ remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress);
55
+ } else {
56
+ remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress;
57
+ }
58
+ return remoteAddress;
59
+ }
60
+
61
+ /**
62
+ * Validates token remote address with request remote address
63
+ * @param {import('express').Request} req
64
+ * @returns {Promise<boolean>}
65
+ */
66
+ async validateRemoteAddress(req) {
67
+ const authenticationToken = req.context?.user?.authenticationToken;
68
+ if (authenticationToken != null) {
69
+ const access_token = jwt.decode(authenticationToken);
70
+ const remoteAddress = access_token[this.claim];
71
+ if (remoteAddress == null) {
72
+ TraceUtils.warn(`Remote address validation failed. Expected a valid remote address claimed by using "${this.claim}" attribute but got none.`);
73
+ return false;
74
+ }
75
+ // get context remote address
76
+ const requestRemoteAddress = this.getRemoteAddress(req);
77
+ if (remoteAddress !== requestRemoteAddress) {
78
+ TraceUtils.warn(`Remote address validation failed. Expected remote address is ${remoteAddress || 'Uknown'} but request remote address is ${requestRemoteAddress}`);
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+ TraceUtils.warn('Remote address validation cannot be completed because authentication token is not available.');
84
+ return false;
85
+ }
86
+
87
+ }
88
+
89
+ export {
90
+ HttpRemoteAddrForbiddenError,
91
+ RemoteAddressValidator
92
+ }
package/src/index.d.ts CHANGED
@@ -3,3 +3,5 @@ export * from './SpeedLimitService';
3
3
  export * from './RedisClientStore';
4
4
  export * from './ScopeAccessConfiguration';
5
5
  export * from './validateScope';
6
+ export * from './OAuth2ClientService';
7
+ export * from './RemoteAddressValidator';
package/src/index.js CHANGED
@@ -4,3 +4,5 @@ export * from './SpeedLimitService';
4
4
  export * from './RedisClientStore';
5
5
  export * from './ScopeAccessConfiguration';
6
6
  export * from './validateScope';
7
+ export * from './OAuth2ClientService';
8
+ export * from './RemoteAddressValidator';