@w3-commons/js-build-resources 0.0.1-security → 1.0.4

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.

Potentially problematic release.


This version of @w3-commons/js-build-resources might be problematic. Click here for more details.

Files changed (98) hide show
  1. package/package.json +9 -3
  2. package/settings-service/.eslintrc.js +4 -0
  3. package/settings-service/.node-version +1 -0
  4. package/settings-service/.prettierrc +6 -0
  5. package/settings-service/.whitesource +15 -0
  6. package/settings-service/LICENSE +4 -0
  7. package/settings-service/README.md +50 -0
  8. package/settings-service/build.yml +56 -0
  9. package/settings-service/collectCodeCoverage.js +9 -0
  10. package/settings-service/db/cassandra/Dockerfile +3 -0
  11. package/settings-service/db/cassandra/createkeyspace.dev.cql +4 -0
  12. package/settings-service/db/cassandra/createkeyspace.dev.sh +3 -0
  13. package/settings-service/db/cassandra/schema_001.cql +15 -0
  14. package/settings-service/db/cassandra/schema_002.cql +8 -0
  15. package/settings-service/db/cassandra/schema_003.cql +10 -0
  16. package/settings-service/db/cassandra/schema_004.cql +1 -0
  17. package/settings-service/db/cassandra/schema_005.cql +39 -0
  18. package/settings-service/db/cassandra/schema_006.cql +255 -0
  19. package/settings-service/db/cassandra/schema_007.cql +40 -0
  20. package/settings-service/db/cassandra/schema_008.cql +2 -0
  21. package/settings-service/db/cassandra/schema_009.cql +143 -0
  22. package/settings-service/db/cassandra/schema_010.cql +143 -0
  23. package/settings-service/db/cassandra/schema_011.cql +2 -0
  24. package/settings-service/db/cassandra/schema_012.cql +8 -0
  25. package/settings-service/jest.config.fn.js +3 -0
  26. package/settings-service/jest.config.it.js +3 -0
  27. package/settings-service/jest.config.js +14 -0
  28. package/settings-service/jest.config.unit.js +19 -0
  29. package/settings-service/jest.setup.js +11 -0
  30. package/settings-service/package-lock.json +11772 -0
  31. package/settings-service/package.json +101 -0
  32. package/settings-service/scripts/run-fn-tests.sh +3 -0
  33. package/settings-service/sonar-project.properties +3 -0
  34. package/settings-service/src/__tests__/functional/controller/ApiKeyController.fn.ts +132 -0
  35. package/settings-service/src/__tests__/functional/middleware/AuthMiddlewareNextGenSSO.fn.ts +82 -0
  36. package/settings-service/src/__tests__/functional/repo/settingsRepo.fn.ts +302 -0
  37. package/settings-service/src/__tests__/functional/unified-profile/unified-profile.fn.ts +66 -0
  38. package/settings-service/src/__tests__/integration/repo/ApiKeyRepo.it.ts +43 -0
  39. package/settings-service/src/__tests__/integration/repo/settingsRepo.it.ts +142 -0
  40. package/settings-service/src/__tests__/integration/unified-profile/unified-profile.it.ts +31 -0
  41. package/settings-service/src/__tests__/unit/ErrResponse.ts +4 -0
  42. package/settings-service/src/__tests__/unit/JWTResponse.ts +18 -0
  43. package/settings-service/src/__tests__/unit/bluepagesResponse.ts +25 -0
  44. package/settings-service/src/__tests__/unit/controller/ApiKeyController.spec.ts +217 -0
  45. package/settings-service/src/__tests__/unit/controller/AppSettingsController.spec.ts +133 -0
  46. package/settings-service/src/__tests__/unit/controller/UserSettingsController.spec.ts +328 -0
  47. package/settings-service/src/__tests__/unit/controller/getAllSettings.spec.ts +83 -0
  48. package/settings-service/src/__tests__/unit/middleware/AuthMiddlewareNextGenSSO.spec.ts +282 -0
  49. package/settings-service/src/__tests__/unit/middleware/AuthenticationMiddleware.spec.ts +494 -0
  50. package/settings-service/src/__tests__/unit/repo/ApiKeyRepo.spec.ts +194 -0
  51. package/settings-service/src/__tests__/unit/repo/getAllSettings.spec.ts +100 -0
  52. package/settings-service/src/__tests__/unit/repo/getUserSettingsRepo.spec.ts +249 -0
  53. package/settings-service/src/__tests__/unit/repo/settingsRepo.spec.ts +614 -0
  54. package/settings-service/src/__tests__/unit/unified-profile/UnifiedProfileClient.spec.ts +31 -0
  55. package/settings-service/src/__tests__/unit/unified-profile/unifiedProfileUtils.spec.ts +36 -0
  56. package/settings-service/src/__tests__/utils/test-utils.ts +41 -0
  57. package/settings-service/src/config/config.ts +190 -0
  58. package/settings-service/src/controller/ApiKeyController.ts +114 -0
  59. package/settings-service/src/controller/AppSettingsController.ts +137 -0
  60. package/settings-service/src/controller/UserSettingsController.ts +202 -0
  61. package/settings-service/src/helpers/commons.ts +69 -0
  62. package/settings-service/src/logger/logger.ts +17 -0
  63. package/settings-service/src/middleware/AuthenticationMiddleware.ts +486 -0
  64. package/settings-service/src/middleware/AuthenticationMiddlewareFactory.ts +10 -0
  65. package/settings-service/src/repo/ApiKeyRepo.ts +135 -0
  66. package/settings-service/src/repo/ApiKeyRepoFactory.ts +10 -0
  67. package/settings-service/src/repo/CassandraClient.ts +33 -0
  68. package/settings-service/src/repo/CassandraClientFactory.ts +11 -0
  69. package/settings-service/src/repo/apiKeyQueries.ts +64 -0
  70. package/settings-service/src/repo/cassandraDBHelpers.ts +119 -0
  71. package/settings-service/src/repo/settingsRepo.ts +388 -0
  72. package/settings-service/src/repo/settingsRepoFactory.ts +10 -0
  73. package/settings-service/src/repo/settingsRepoQueries.ts +62 -0
  74. package/settings-service/src/routes/apiKeyRoutes.ts +27 -0
  75. package/settings-service/src/routes/appSettingsRoutes.ts +30 -0
  76. package/settings-service/src/routes/healthCheck.ts +10 -0
  77. package/settings-service/src/routes/swagger.ts +8 -0
  78. package/settings-service/src/routes/userSettingsRoutes.ts +30 -0
  79. package/settings-service/src/server.ts +77 -0
  80. package/settings-service/src/swagger.json +732 -0
  81. package/settings-service/src/types/ApiKey.ts +19 -0
  82. package/settings-service/src/types/IRequest.ts +9 -0
  83. package/settings-service/src/types/IRequestAuthorization.ts +5 -0
  84. package/settings-service/src/types/IRouteOptions.ts +5 -0
  85. package/settings-service/src/types/QueryResultsTypes.ts +6 -0
  86. package/settings-service/src/types/UserSettingsControllerTypes.ts +5 -0
  87. package/settings-service/src/types/W3IdUser.ts +36 -0
  88. package/settings-service/src/types/settingsRepoTypes.ts +61 -0
  89. package/settings-service/src/types/unifiedProfileTypes.ts +10 -0
  90. package/settings-service/src/unified-profile/UnifiedProfileClient.ts +29 -0
  91. package/settings-service/src/unified-profile/UnifiedProfileClientFactory.ts +10 -0
  92. package/settings-service/src/unified-profile/unifiedProfileUtils.ts +22 -0
  93. package/settings-service/src/util/downloadCassandra.ts +34 -0
  94. package/settings-service/src/util/isocodeMapper.ts +22 -0
  95. package/settings-service/src/util/languages.ts +1457 -0
  96. package/settings-service/test_resources/mockApiKeyDBResult.json +8 -0
  97. package/settings-service/tsconfig.json +40 -0
  98. package/README.md +0 -5
@@ -0,0 +1,69 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
2
+ import randomstring from 'randomstring';
3
+ import { logger } from '../logger/logger';
4
+
5
+ const IV_LENGTH = 16; /** For AES, this is always 16 */
6
+ const CLASS_NAME = 'commons';
7
+
8
+ export function createRandomToken(): string {
9
+ return randomstring.generate({
10
+ length: 48,
11
+ charset: 'alphanumeric',
12
+ });
13
+ }
14
+
15
+ export function encryptSync(text: string, secret: string): string {
16
+ if (secret.length !== 32) {
17
+ throw new Error('Invalid length of secret. Must be 256 bytes or 32 characters long');
18
+ }
19
+ const iv = randomBytes(IV_LENGTH);
20
+ const cipher = createCipheriv('aes-256-cbc', Buffer.from(secret), iv);
21
+ const cipherInitial = cipher.update(Buffer.from(text));
22
+ const encrypted = Buffer.concat([cipherInitial, cipher.final()]);
23
+ return `${iv.toString('hex')}:${encrypted.toString('hex')}`;
24
+ }
25
+
26
+ export function decryptSync(text: string, secret: string): string {
27
+ if (secret.length !== 32) {
28
+ throw new Error('Invalid length of secret. Must be 256 bytes or 32 characters long');
29
+ }
30
+ const [iv, encrypted]: string[] = text.split(':');
31
+ const decipher = createDecipheriv('aes-256-cbc', Buffer.from(secret), Buffer.from(iv, 'hex'));
32
+ const decipherInitial = decipher.update(Buffer.from(encrypted, 'hex'));
33
+ const decrypted = Buffer.concat([decipherInitial, decipher.final()]);
34
+
35
+ return decrypted.toString();
36
+ }
37
+
38
+ export async function encrypt(text: string, secret: string): Promise<string> {
39
+ const encryptedValue: string = encryptSync(text, secret);
40
+ logger.info(`${CLASS_NAME} | ${encrypt.name}() | Encrypted value: ${encryptedValue}`);
41
+ return encryptedValue;
42
+ }
43
+
44
+ export async function decrypt(text: string, secret: string): Promise<string> {
45
+ const decryptedValue: string = decryptSync(text, secret);
46
+ logger.debug(`${CLASS_NAME} | ${decrypt.name}() | Decrypted value: ${decryptedValue}`);
47
+ return decryptedValue;
48
+ }
49
+
50
+ /**
51
+ * This utility method returns a string array with parsed values of appId & token
52
+ * from apiKey
53
+ *
54
+ * @param apiKey
55
+ */
56
+ export function parseAppIdAndToken(apiKey: string): string[] {
57
+ const parsedValues: string[] = [];
58
+ if (apiKey) {
59
+ const index: number = apiKey.lastIndexOf(':');
60
+ if (index > -1) {
61
+ const appId: string = apiKey.substring(0, index);
62
+ const token: string = apiKey.split(':').pop() || '';
63
+ parsedValues[0] = appId;
64
+ parsedValues[1] = token;
65
+ }
66
+ }
67
+ logger.debug(`${CLASS_NAME} | ${parseAppIdAndToken.name}() | Parsed appid & token: ${parsedValues}`);
68
+ return parsedValues;
69
+ }
@@ -0,0 +1,17 @@
1
+ import appRootPath from 'app-root-path';
2
+ import * as winston from 'winston';
3
+ import { settings } from '../config/config';
4
+
5
+ export const logger: winston.Logger = winston.createLogger({
6
+ level: settings.logLevel,
7
+ exitOnError: false,
8
+ transports: [
9
+ new winston.transports.Console({
10
+ format: winston.format.json(),
11
+ silent: process.argv.indexOf('--silent') >= 0,
12
+ }),
13
+ new winston.transports.File({
14
+ filename: `${appRootPath}/settings-service.log`,
15
+ }),
16
+ ],
17
+ });
@@ -0,0 +1,486 @@
1
+ import _ from 'lodash';
2
+ import requestPromise from 'request-promise';
3
+ import { Next, Response, Route } from 'restify';
4
+ import { NotFoundError } from 'restify-errors';
5
+ import { ApiKeyRepoFactory } from '../repo/ApiKeyRepoFactory';
6
+ import W3IdUser from '../types/W3IdUser';
7
+ import { settings } from '../config/config';
8
+ import { logger } from '../logger/logger';
9
+ import { ApiKey } from '../types/ApiKey';
10
+ import { ApiKeyRepo } from '../repo/ApiKeyRepo';
11
+ import { parseAppIdAndToken } from '../helpers/commons';
12
+ import { IRequest } from '../types/IRequest';
13
+ import { IRouteOptions } from '../types/IRouteOptions';
14
+
15
+ interface UserDetails {
16
+ email: string;
17
+ id: string;
18
+ }
19
+
20
+ enum OperationType {
21
+ QUERY = 'QUERY',
22
+ MUTATION = 'MUTATION',
23
+ REST = 'REST',
24
+ }
25
+
26
+ interface OperationDetails {
27
+ operationName: string;
28
+ operationType: OperationType;
29
+ }
30
+
31
+ /**
32
+ * This is a middleware Class which contains methods to perform authentication header validation
33
+ * and read authentication plugin response.
34
+ */
35
+ export class AuthenticationMiddleware {
36
+ public static healthCheckPath: IRouteOptions = {
37
+ path: '/health/ping',
38
+ authRequired: false,
39
+ version: settings.apiVersion,
40
+ };
41
+
42
+ public static settingsPath: IRouteOptions = {
43
+ path: `/v1/settings/users/:userID/apps/:appID`,
44
+ authRequired: true,
45
+ version: settings.apiVersion,
46
+ };
47
+
48
+ public static allSettingsPath: IRouteOptions = {
49
+ path: `/v1/settings/users/all`,
50
+ authRequired: true,
51
+ version: settings.apiVersion,
52
+ };
53
+
54
+ public static allAppSettingsPath: IRouteOptions = {
55
+ path: `/v1/settings/app/:appID`,
56
+ authRequired: true,
57
+ version: settings.apiVersion,
58
+ };
59
+
60
+ public static singleAppSettingPath: IRouteOptions = {
61
+ path: `/v1/settings/app/:appID/setting/:settingName`,
62
+ authRequired: true,
63
+ version: settings.apiVersion,
64
+ };
65
+
66
+ public static apiKeyPath: IRouteOptions = {
67
+ path: `/v1/settings/apiKey/app/:appID`,
68
+ authRequired: true,
69
+ version: settings.apiVersion,
70
+ };
71
+
72
+ public static deleteSettingsPath: IRouteOptions = {
73
+ path: `/v1/settings/users/:userId`,
74
+ authRequired: true,
75
+ version: settings.apiVersion,
76
+ };
77
+
78
+ public static AUTH_POLICY_BEARER = 'bearer';
79
+
80
+ public static AUTH_POLICY_API_KEY = 'apiKey';
81
+
82
+ static HEADER_AUTHORIZATION = 'headers.authorization';
83
+ /**
84
+ * This method checks for the required authentication headers.
85
+ * @param req
86
+ * @param res
87
+ * @param next
88
+ */
89
+
90
+ public static checkAuthentication(req: IRequest, res: Response, next: Next): void {
91
+ const w3idAuthPluginRequest: IRequest = req;
92
+ const route: IRouteOptions = (w3idAuthPluginRequest.getRoute() as Route).spec;
93
+
94
+ if (!route.authRequired) {
95
+ return next();
96
+ }
97
+ if (process.env.APP_ENVIRONMENT === 'production') {
98
+ AuthenticationMiddleware.ensureAuthenticated(req, res, next);
99
+ return undefined;
100
+ }
101
+ try {
102
+ const authorization = _.get(req, AuthenticationMiddleware.HEADER_AUTHORIZATION);
103
+
104
+ logger.debug(
105
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.checkAuthentication.name}() | Authorization: ${authorization}`,
106
+ );
107
+ if (!_.isEmpty(authorization) && authorization.includes('id') && authorization.includes('email')) {
108
+ const spaceIndex: number = _.indexOf(authorization, ' ');
109
+ const token = authorization.substring(spaceIndex + 1);
110
+ const details: UserDetails = JSON.parse(token);
111
+ req.username = details.id;
112
+ req.isAuthenticated = true;
113
+ req.auth_policy = AuthenticationMiddleware.AUTH_POLICY_BEARER;
114
+ return next();
115
+ }
116
+ AuthenticationMiddleware.ensureAuthenticated(req, res, next);
117
+ return undefined;
118
+ } catch (err) {
119
+ logger.error(
120
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.checkAuthentication.name}() | Invalid token format: ${err}`,
121
+ );
122
+ res.send(400, { code: 400, message: `Bad Request token format: ${err}` });
123
+ return next(false);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * This method checks rules based on authentication headers before authentication.
129
+ * It can respond with 401 code.
130
+ * Example: API key based authentication strategy must be used for deleteCascade Mutation.
131
+ * @param req
132
+ * @param res
133
+ * @param next
134
+ */
135
+ public static checkPreAuthentication(req: IRequest, res: Response, next: Next): void {
136
+ const operationDetails: OperationDetails | undefined = AuthenticationMiddleware.getOperationDetails(req);
137
+ if (operationDetails === undefined) {
138
+ return next();
139
+ }
140
+ const errMessage: string | undefined = AuthenticationMiddleware.getPreAuthenticationErrorMessage(
141
+ req,
142
+ operationDetails,
143
+ );
144
+ if (errMessage === undefined) {
145
+ return next();
146
+ }
147
+ res.send(401, { code: 401, message: errMessage });
148
+ return next(false);
149
+ }
150
+
151
+ /**
152
+ * This method validates user's authentication token using the IBM Security Verify approach
153
+ *
154
+ * @param req
155
+ * @param res
156
+ * @param next
157
+ * @returns whether to forward to next layer or not
158
+ */
159
+ public static async ensureAuthenticated(req: IRequest, res: Response, next: Next): Promise<void> {
160
+ try {
161
+ if (!req.headers.authorization) {
162
+ logger.info(
163
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Missing required authentication header in the request.`,
164
+ );
165
+ res.send(401, { code: 401, message: 'Missing required authentication header' });
166
+ return next(false);
167
+ }
168
+ const authorization: string = _.get(req, AuthenticationMiddleware.HEADER_AUTHORIZATION);
169
+ const bearerToken: string[] = _.split(authorization, ' ');
170
+ if (bearerToken.length < 2) {
171
+ logger.error(
172
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Missing required authentication token`,
173
+ );
174
+ res.send(401, { code: 401, message: 'Missing required authentication token' });
175
+ return next(false);
176
+ }
177
+
178
+ // Check if request to be authenticated by API key
179
+ if (bearerToken[0] === AuthenticationMiddleware.AUTH_POLICY_API_KEY) {
180
+ try {
181
+ const appId = req.params.appID;
182
+ const isValidApiKey: boolean = await AuthenticationMiddleware.verifyApiKeyWithAppId(
183
+ res,
184
+ bearerToken[1],
185
+ appId,
186
+ );
187
+ if (isValidApiKey) {
188
+ req.isAuthenticated = true;
189
+ req.auth_policy = AuthenticationMiddleware.AUTH_POLICY_API_KEY;
190
+
191
+ logger.info(
192
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | ** Authenticated successfully by api key **`,
193
+ );
194
+ return next();
195
+ }
196
+ res.send(401, { code: 401, message: 'Api key is not valid. Please verify and try again' });
197
+
198
+ logger.warn(
199
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Unable to authenticate with the provided api key`,
200
+ );
201
+ return next(false);
202
+ } catch (err) {
203
+ logger.error(
204
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Error while validating api token: ${err}`,
205
+ );
206
+ return next(false);
207
+ }
208
+ }
209
+
210
+ try {
211
+ const authUserInfo = await AuthenticationMiddleware.introspectISV(res, bearerToken[1]);
212
+ let parsedAuthUserInfo: W3IdUser = {};
213
+ try {
214
+ parsedAuthUserInfo = JSON.parse(authUserInfo);
215
+ } catch (err) {
216
+ logger.error(
217
+ `${AuthenticationMiddleware.name} | ${
218
+ AuthenticationMiddleware.ensureAuthenticated.name
219
+ }() | JSON parse error: ${JSON.stringify(authUserInfo)}`,
220
+ );
221
+ }
222
+ logger.debug(
223
+ `${AuthenticationMiddleware.name} | ${
224
+ AuthenticationMiddleware.ensureAuthenticated.name
225
+ }() | Response from ISV : ${JSON.stringify(authUserInfo)}`,
226
+ );
227
+ const userId = _.get(parsedAuthUserInfo, 'uid');
228
+ if (!_.isEmpty(userId)) {
229
+ req.username = userId;
230
+ req.isAuthenticated = true;
231
+ req.auth_policy = AuthenticationMiddleware.AUTH_POLICY_BEARER;
232
+ return next();
233
+ }
234
+ res.send(401, { code: 401, message: 'User token is expired or is invalid' });
235
+ logger.error(
236
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Invalid token. Unable to retrieve userId from JWT response.`,
237
+ );
238
+ return next(false);
239
+ } catch (err) {
240
+ logger.error(
241
+ `${AuthenticationMiddleware.name} | ${
242
+ AuthenticationMiddleware.ensureAuthenticated.name
243
+ }() | Error while validating oidc token: ${JSON.stringify(err)}`,
244
+ );
245
+ return next(false);
246
+ }
247
+ } catch (err) {
248
+ logger.error(
249
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.ensureAuthenticated.name}() | Error in ensureAuthenticatedv1: ${err}`,
250
+ );
251
+ return next(false);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * This method validates the JWT based token
257
+ *
258
+ * @param res
259
+ * @param token
260
+ * @returns userdetails
261
+ */
262
+ public static async introspectISV(res: Response, bearerToken: string): Promise<string> {
263
+ try {
264
+ const authRes = await requestPromise.post({
265
+ method: 'POST',
266
+ uri: settings.isv_introspectionUrl,
267
+ form: {
268
+ token: bearerToken,
269
+ client_id: settings.isv_clientId,
270
+ client_secret: settings.isv_clientSecret,
271
+ },
272
+ });
273
+ return authRes;
274
+ } catch (err) {
275
+ logger.error(
276
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.introspectISV.name}() | introspectJWT error: ${err}`,
277
+ );
278
+ res.send(401, { code: 401, message: 'Unable to authenticate the user' });
279
+ throw new Error('Unable to authenticate the user');
280
+ }
281
+ }
282
+
283
+ /**
284
+ * This method validates the apikey of the incoming request
285
+ *
286
+ * @param res
287
+ * @param appId
288
+ * @param apiKey
289
+ */
290
+ public static async verifyApiKeyWithAppId(
291
+ res: Response,
292
+ apiKey: string,
293
+ appIdFromParams: string,
294
+ ): Promise<boolean> {
295
+ let flag = false;
296
+ try {
297
+ const decodedBase64Key = Buffer.from(apiKey, 'base64').toString('utf-8');
298
+ const [appIdFromToken, apiToken]: string[] = parseAppIdAndToken(decodedBase64Key);
299
+ if (appIdFromToken !== appIdFromParams && !settings.restrictedAppIdWhitelist.includes(appIdFromToken)) {
300
+ logger.error(
301
+ `${AuthenticationMiddleware.name} | ${this.verifyApiKeyWithAppId.name}() | appId from query params does not match appId from bearer token`,
302
+ );
303
+ res.send(400, {
304
+ code: 400,
305
+ message: `Unable to verify API key: appId you are trying to access does not match bearer token`,
306
+ });
307
+ throw new Error('appId you are trying to access does not match bearer token');
308
+ }
309
+
310
+ logger.debug(
311
+ `${AuthenticationMiddleware.name} | ${this.verifyApiKeyWithAppId.name}() | app id: ${appIdFromToken}`,
312
+ );
313
+ logger.debug(
314
+ `${AuthenticationMiddleware.name} | ${this.verifyApiKeyWithAppId.name}() | app token: ${apiToken}`,
315
+ );
316
+
317
+ if (appIdFromToken && apiToken) {
318
+ const apiKeyRepo: ApiKeyRepo = ApiKeyRepoFactory.accessOrCreateSingleton();
319
+ try {
320
+ if (settings.restrictedAppIdWhitelist.includes(appIdFromToken)) {
321
+ const masterPersistedKey: ApiKey = await apiKeyRepo.getApiKey(appIdFromToken);
322
+ if (masterPersistedKey.key === apiToken) {
323
+ flag = true;
324
+ }
325
+ }
326
+ // Fetch api key from DB and compare with incoming token
327
+ const persistedKey: ApiKey = await apiKeyRepo.getApiKey(appIdFromToken);
328
+
329
+ if (persistedKey.key === apiToken) {
330
+ flag = true;
331
+ }
332
+ } catch (err) {
333
+ if (err instanceof NotFoundError) {
334
+ return flag;
335
+ }
336
+ throw err;
337
+ }
338
+ }
339
+ return flag;
340
+ } catch (err) {
341
+ logger.error(
342
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.verifyApiKeyWithAppId.name}() | Api key verification error: ${err}`,
343
+ );
344
+ res.send(500, { code: 500, message: `Unable to verify API key: ${err.message}` });
345
+ throw new Error(`Unable to verify API key: ${err.message}`);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * This method checks rules based on authentication headers after authentication.
351
+ * It can respond with 403 code.
352
+ * Example: Authorization for operations related to API keys.
353
+ * @param req
354
+ * @param res
355
+ * @param next
356
+ */
357
+ public static checkPostAuthentication(req: IRequest, res: Response, next: Next): void {
358
+ const operationDetails: OperationDetails | undefined = AuthenticationMiddleware.getOperationDetails(req);
359
+ if (operationDetails === undefined) {
360
+ return next();
361
+ }
362
+ if (operationDetails.operationName === 'apiKey') {
363
+ logger.info(
364
+ `${AuthenticationMiddleware.name} | ${AuthenticationMiddleware.checkPostAuthentication.name}() | Checking for user authorization`,
365
+ );
366
+ if (!AuthenticationMiddleware.isAuthorizedToRequestApiKey(req)) {
367
+ res.send(403, {
368
+ code: 403,
369
+ message: `You are not authorized for ${operationDetails.operationName} operations.`,
370
+ });
371
+ return next(false);
372
+ }
373
+ }
374
+ return next();
375
+ }
376
+
377
+ /**
378
+ * This method verifies if the api key requestor is authorized or not.
379
+ *
380
+ * @param req
381
+ */
382
+ public static isAuthorizedToRequestApiKey(req: IRequest): boolean {
383
+ const authenticatedUser: string = _.toUpper(req.username);
384
+ const apiKeyAuthorizedUserIds: string[] = _.split(settings.apiKeyAuthorizedUserIds, ',');
385
+ const flag: boolean = req.isAuthenticated && apiKeyAuthorizedUserIds.indexOf(authenticatedUser) !== -1;
386
+ return flag;
387
+ }
388
+
389
+ private static getPreAuthenticationErrorMessage(
390
+ req: IRequest,
391
+ operationDetails: OperationDetails,
392
+ ): string | undefined {
393
+ switch (operationDetails.operationType) {
394
+ case OperationType.REST: {
395
+ switch (operationDetails.operationName) {
396
+ case 'settings': {
397
+ return !AuthenticationMiddleware.usesAuthPolicyBearer(req) &&
398
+ !AuthenticationMiddleware.usesAuthPolicyApiKey(req)
399
+ ? `w3id/jwt token or API key based authentication strategy must be used for ${operationDetails.operationName} Query.`
400
+ : undefined;
401
+ }
402
+ case 'appSettings': {
403
+ return !AuthenticationMiddleware.usesAuthPolicyApiKey(req)
404
+ ? `API key based authentication strategy must be used for ${operationDetails.operationName} Query.`
405
+ : undefined;
406
+ }
407
+ case 'settingsDelete': {
408
+ return !AuthenticationMiddleware.usesAuthPolicyApiKey(req)
409
+ ? // tslint:disable-next-line:max-line-length
410
+ `API key based authentication strategy must be used for delete settings operation.`
411
+ : undefined;
412
+ }
413
+ case 'getAllUsers': {
414
+ return !AuthenticationMiddleware.usesAuthPolicyApiKey(req)
415
+ ? // tslint:disable-next-line:max-line-length
416
+ `API key based authentication strategy must be used for get all user settings operation.`
417
+ : undefined;
418
+ }
419
+ default:
420
+ return undefined;
421
+ }
422
+ }
423
+ default:
424
+ return undefined;
425
+ }
426
+ }
427
+
428
+ private static getOperationDetails(req: IRequest): OperationDetails | undefined {
429
+ const pathValue: string = req.path();
430
+ if (pathValue && pathValue.startsWith('/v1/settings/users/all')) {
431
+ logger.debug(
432
+ `${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
433
+ );
434
+ return { operationName: 'getAllUsers', operationType: OperationType.REST };
435
+ }
436
+ if (pathValue && pathValue.startsWith('/v1/settings/users') && req.method === 'DELETE') {
437
+ logger.debug(
438
+ `${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
439
+ );
440
+ return { operationName: 'settingsDelete', operationType: OperationType.REST };
441
+ }
442
+ if (pathValue && pathValue.startsWith('/v1/settings/users')) {
443
+ logger.debug(
444
+ `${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
445
+ );
446
+ return { operationName: 'settings', operationType: OperationType.REST };
447
+ }
448
+ if (pathValue && pathValue.startsWith('/v1/settings/app')) {
449
+ logger.debug(
450
+ `${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
451
+ );
452
+ return { operationName: 'appSettings', operationType: OperationType.REST };
453
+ }
454
+ if (pathValue && pathValue.startsWith('/v1/settings/apiKey')) {
455
+ logger.debug(
456
+ `${AuthenticationMiddleware.name} | ${this.getOperationDetails.name}() | Path value: ${req.path()}`,
457
+ );
458
+ return { operationName: 'apiKey', operationType: OperationType.REST };
459
+ }
460
+ return undefined;
461
+ }
462
+
463
+ private static usesAuthPolicyBearer(req: IRequest): boolean {
464
+ if (!req.headers.authorization) {
465
+ return false;
466
+ }
467
+ const authorization: string = _.get(req, AuthenticationMiddleware.HEADER_AUTHORIZATION);
468
+ const bearerToken: string[] = _.split(authorization, ' ');
469
+ if (bearerToken.length < 2) {
470
+ return false;
471
+ }
472
+ return bearerToken[0].toLowerCase() === AuthenticationMiddleware.AUTH_POLICY_BEARER.toLowerCase();
473
+ }
474
+
475
+ private static usesAuthPolicyApiKey(req: IRequest): boolean {
476
+ if (!req.headers.authorization) {
477
+ return false;
478
+ }
479
+ const authorization: string = _.get(req, AuthenticationMiddleware.HEADER_AUTHORIZATION);
480
+ const bearerToken: string[] = _.split(authorization, ' ');
481
+ if (bearerToken.length < 2) {
482
+ return false;
483
+ }
484
+ return bearerToken[0] === AuthenticationMiddleware.AUTH_POLICY_API_KEY;
485
+ }
486
+ }
@@ -0,0 +1,10 @@
1
+ import { AuthenticationMiddleware } from './AuthenticationMiddleware';
2
+
3
+ export class AuthenticationMiddlewareFactory {
4
+ private static singleton: AuthenticationMiddleware;
5
+
6
+ static accessOrCreateSingleton(): AuthenticationMiddleware {
7
+ this.singleton = this.singleton || new AuthenticationMiddleware();
8
+ return this.singleton;
9
+ }
10
+ }