@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,494 @@
1
+ import { Response } from 'restify';
2
+ import { NotFoundError } from 'restify-errors';
3
+ import { AuthenticationMiddlewareFactory } from '../../../middleware/AuthenticationMiddlewareFactory';
4
+ import { settings } from '../../../config/config';
5
+ import { AuthenticationMiddleware } from '../../../middleware/AuthenticationMiddleware';
6
+ import { ApiKey } from '../../../types/ApiKey';
7
+ import { ApiKeyRepoImpl } from '../../../repo/ApiKeyRepo';
8
+ import * as commons from '../../../helpers/commons';
9
+ import { TEST_API_KEY, TEST_APP_ID, TEST_USER_ID } from '../../utils/test-utils';
10
+ import { ErrResponse } from '../ErrResponse';
11
+
12
+ jest.mock('axios');
13
+ jest.mock('request-promise');
14
+
15
+ const W3ID_TOKEN = `Bearer token`;
16
+
17
+ const TEST_API_KEY_TOKEN =
18
+ 'dzNub3RpZmljYXRpb25zLXRlc3Q6c29MVFlJQnFzUjFKYjlmb0FTbjRwYURVeW1Ca0dudlQ0ZHRXanZMN1BhakNMbWM4';
19
+
20
+ // INCORRECT_APP_ID matches the appId in the INVALID_API_KEY_TOKEN to pass the match appId condition before failing
21
+ const INCORRECT_APP_ID = 'test';
22
+ const INVALID_API_KEY_TOKEN = `dGVzdDpEOXQwOVAycllyM1lzeXh4RW1YTllaeHc1WHVnZ09MT3FzaDJNdUNKelBidHladVE=`;
23
+
24
+ const apiKeyAuthorizedIds: string = settings.apiKeyAuthorizedUserIds;
25
+
26
+ afterAll(() => {
27
+ settings.apiKeyAuthorizedUserIds = apiKeyAuthorizedIds;
28
+ });
29
+ /**
30
+ * Below are the test cases for Authentication Middleware
31
+ */
32
+ describe('requestHandler', () => {
33
+ afterEach(() => {
34
+ process.env.APP_ENVIRONMENT = 'dev';
35
+ jest.clearAllMocks();
36
+ });
37
+
38
+ it('verify singleton working', async () => {
39
+ let middleware = AuthenticationMiddlewareFactory.accessOrCreateSingleton();
40
+ expect(middleware).toBeDefined();
41
+ middleware = AuthenticationMiddlewareFactory.accessOrCreateSingleton();
42
+ expect(middleware).toBeDefined();
43
+ });
44
+
45
+ it('pre-authentication should work for valid input', async () => {
46
+ const route = { path: '/v1/settings/users/:userID/apps/:appID', spec: { authRequired: true } };
47
+ const req = {
48
+ getRoute: jest.fn().mockReturnValue(route),
49
+ } as any;
50
+ req.headers = {};
51
+ req.path = () => '/v1/settings/users/:userID/apps/:appID';
52
+ const res = {} as any;
53
+ let respObj = {} as any;
54
+ let statusCode = 0;
55
+ res.send = (code: number, obj: any) => {
56
+ statusCode = code;
57
+ respObj = obj;
58
+ return obj;
59
+ };
60
+ const next = jest.fn() as any;
61
+ await AuthenticationMiddleware.checkPreAuthentication(req, res, next);
62
+ expect(statusCode).toEqual(401);
63
+ expect(respObj.code).toEqual(401);
64
+ expect(respObj.message).toEqual(
65
+ 'w3id/jwt token or API key based authentication strategy must be used for settings Query.',
66
+ );
67
+ });
68
+
69
+ /**
70
+ * This test case handles the missing authoriztion header scenario.
71
+ */
72
+ it('missing authorization header', async () => {
73
+ const route = { path: '/settings/users/:userID/apps/:appID', spec: { authRequired: true } };
74
+ const req = {
75
+ getRoute: jest.fn().mockReturnValue(route),
76
+ } as any;
77
+ req.headers = {};
78
+ const res = {} as any;
79
+ let respObj = {} as any;
80
+ let statusCode = 0;
81
+ res.send = (code: number, obj: any) => {
82
+ statusCode = code;
83
+ respObj = obj;
84
+ return obj;
85
+ };
86
+ const next = jest.fn() as any;
87
+ await AuthenticationMiddleware.checkAuthentication(req, res, next);
88
+ expect(statusCode).toEqual(401);
89
+ expect(respObj.code).toEqual(401);
90
+ expect(respObj.message).toEqual('Missing required authentication header');
91
+ });
92
+
93
+ /**
94
+ * This test case checks for authentication is disabled.
95
+ */
96
+ it('authRequired is disabled and should call next middleware', async () => {
97
+ const route = { path: '/settings/users/:userID/apps/:appID', spec: { authRequired: false } };
98
+ const req = {
99
+ getRoute: jest.fn().mockReturnValue(route) as any,
100
+ } as any;
101
+ const res: Response = {} as any;
102
+ const next = jest.fn() as any;
103
+ await AuthenticationMiddleware.checkAuthentication(req, res, next);
104
+ expect(next).toBeCalled();
105
+ });
106
+
107
+ /**
108
+ * This test case checks for valid test token in the headers present in the
109
+ * incoming request.
110
+ */
111
+ it('authRequired is enabled and should authorize for fake valid token format for test environment', async () => {
112
+ const route = { path: '/settings/users/:userID/apps/:appID', spec: { authRequired: true } };
113
+ const req = {
114
+ getRoute: jest.fn().mockReturnValue(route) as any,
115
+ } as any;
116
+ req.headers = { authorization: `Bearer {"email":"xxxxxx@us.ibm.com","id":"AAAABBCCC"}` };
117
+ const res: Response = {} as any;
118
+ const next = jest.fn() as any;
119
+ await AuthenticationMiddleware.checkAuthentication(req, res, next);
120
+ expect(req.isAuthenticated).toEqual(true);
121
+ expect(req.username).toEqual('AAAABBCCC');
122
+ expect(next).toHaveBeenCalled();
123
+ });
124
+
125
+ /**
126
+ * This test case checks for valid test token in the headers present in the
127
+ * incoming request.
128
+ */
129
+ it('authRequired is enabled and should authorize for valid w3Id/JWT token format for test environment', async () => {
130
+ const route = { path: '/settings/users/:userID/apps/:appID', spec: { authRequired: true } };
131
+ const req = {
132
+ getRoute: jest.fn().mockReturnValue(route) as any,
133
+ } as any;
134
+ req.headers = { authorization: `Bearer ${W3ID_TOKEN}` };
135
+ const res: Response = {} as any;
136
+ const next = jest.fn() as any;
137
+ const spy = jest.spyOn(AuthenticationMiddleware, 'ensureAuthenticated');
138
+ await AuthenticationMiddleware.checkAuthentication(req, res, next);
139
+ expect(spy).toHaveBeenCalled();
140
+ });
141
+
142
+ /**
143
+ * This test case handles the missing token in header scenario.
144
+ */
145
+ it('missing authorization token in header', async () => {
146
+ const route = { path: '/settings/users/:userID/apps/:appID', spec: { authRequired: true } };
147
+ const req = {
148
+ getRoute: jest.fn().mockReturnValue(route),
149
+ } as any;
150
+ req.headers = { authorization: 'Bearer' };
151
+
152
+ const res: Response = {} as any;
153
+ let respObj: ErrResponse = {} as any;
154
+ let statusCode = 0;
155
+ res.send = (code: number, obj: any) => {
156
+ statusCode = code;
157
+ respObj = obj;
158
+ return obj;
159
+ };
160
+ const next = jest.fn() as any;
161
+ await AuthenticationMiddleware.checkAuthentication(req, res, next);
162
+ expect(statusCode).toEqual(401);
163
+ expect(respObj.code).toEqual(401);
164
+ expect(respObj.message).toEqual('Missing required authentication token');
165
+ expect(next).toHaveBeenCalled();
166
+ });
167
+
168
+ describe('verifyApiKeyWithAppId tests', () => {
169
+ const res: Response = {
170
+ send: jest.fn(),
171
+ } as any;
172
+
173
+ it('check verifyApiKeyWithAppId returns true for valid api key', async () => {
174
+ const getApiKeySpy = jest
175
+ .spyOn(ApiKeyRepoImpl.prototype, 'getApiKey')
176
+ .mockResolvedValue(new ApiKey(TEST_APP_ID, TEST_API_KEY, TEST_USER_ID, new Date(), new Date()));
177
+
178
+ expect(
179
+ await AuthenticationMiddleware.verifyApiKeyWithAppId(res, TEST_API_KEY_TOKEN, TEST_APP_ID),
180
+ ).toBeTruthy();
181
+ expect(getApiKeySpy).toHaveBeenCalled();
182
+ });
183
+
184
+ it('check verifyApiKeyWithAppId returns true for w3 (master key)', async () => {
185
+ const parseAppIdSpy = jest
186
+ .spyOn(commons, 'parseAppIdAndToken')
187
+ .mockReturnValueOnce(['w3', TEST_API_KEY]);
188
+ const getApiKeySpy = jest
189
+ .spyOn(ApiKeyRepoImpl.prototype, 'getApiKey')
190
+ .mockResolvedValue(new ApiKey('w3', TEST_API_KEY, TEST_USER_ID, new Date(), new Date()));
191
+
192
+ expect(
193
+ await AuthenticationMiddleware.verifyApiKeyWithAppId(res, TEST_API_KEY_TOKEN, 'w3'),
194
+ ).toBeTruthy();
195
+ expect(getApiKeySpy).toHaveBeenCalled();
196
+ expect(parseAppIdSpy).toHaveBeenCalled();
197
+ });
198
+
199
+ it('check verifyApiKeyWithAppId throws an error if token appId does not match appId from query params', async () => {
200
+ expect.assertions(2);
201
+ try {
202
+ await AuthenticationMiddleware.verifyApiKeyWithAppId(res, INVALID_API_KEY_TOKEN, TEST_APP_ID);
203
+ } catch (err) {
204
+ expect(err).toBeInstanceOf(Error);
205
+ expect(err.message).toEqual(
206
+ `Unable to verify API key: appId you are trying to access does not match bearer token`,
207
+ );
208
+ }
209
+ });
210
+
211
+ it('check verifyApiKeyWithAppId should return false with invalid api key', async () => {
212
+ expect(
213
+ await AuthenticationMiddleware.verifyApiKeyWithAppId(res, INVALID_API_KEY_TOKEN, INCORRECT_APP_ID),
214
+ ).toBeFalsy();
215
+ });
216
+
217
+ it('check verifyApiKeyWithAppId returns false if NotFoundError when getting apiKey', async () => {
218
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockImplementationOnce(() => {
219
+ throw new NotFoundError();
220
+ });
221
+
222
+ expect(
223
+ await AuthenticationMiddleware.verifyApiKeyWithAppId(res, TEST_API_KEY_TOKEN, TEST_APP_ID),
224
+ ).toBeFalsy();
225
+ expect(getApiKeySpy).toHaveBeenCalled();
226
+ });
227
+
228
+ it('check verifyApiKeyWithAppId should throw error if Error caught in getApiKey', async () => {
229
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockImplementationOnce(() => {
230
+ throw new Error('error getting apiKey from DB');
231
+ });
232
+
233
+ expect.assertions(3);
234
+ try {
235
+ await AuthenticationMiddleware.verifyApiKeyWithAppId(res, TEST_API_KEY_TOKEN, TEST_APP_ID);
236
+ } catch (err) {
237
+ expect(err).toBeInstanceOf(Error);
238
+ expect(err.message).toEqual('Unable to verify API key: error getting apiKey from DB');
239
+ expect(getApiKeySpy).toHaveBeenCalled();
240
+ }
241
+ });
242
+ });
243
+
244
+ /**
245
+ * Below test case checks for the successful api key verification from ensureAuthenticated method
246
+ */
247
+ describe('ensureAuthenticated API key tests', () => {
248
+ it('ensureAuthenticated with right API key', async () => {
249
+ const req = {} as any;
250
+ req.params = { appID: 'w3notifications-test' };
251
+ req.headers = { authorization: `apiKey ${TEST_API_KEY_TOKEN}` };
252
+ AuthenticationMiddleware.verifyApiKeyWithAppId = jest.fn().mockReturnValue(true);
253
+ const res: Response = {} as any;
254
+ res.send = (code: number, obj) => {
255
+ return obj;
256
+ };
257
+ const next = jest.fn() as any;
258
+ await AuthenticationMiddleware.ensureAuthenticated(req, res, next);
259
+ expect(req.username).toBeUndefined();
260
+ expect(req.isAuthenticated).toEqual(true);
261
+ expect(req.auth_policy).toEqual(AuthenticationMiddleware.AUTH_POLICY_API_KEY);
262
+ expect(next).toHaveBeenCalled();
263
+ });
264
+
265
+ /**
266
+ * Below test case checks for the successful invalid api key verification from ensureAuthenticated
267
+ */
268
+ it('ensureAuthenticated with invalid API key', async () => {
269
+ const req = {} as any;
270
+ req.params = { appID: 'w3notifications-test' };
271
+ req.headers = { authorization: `apiKey ${INVALID_API_KEY_TOKEN}` };
272
+ AuthenticationMiddleware.verifyApiKeyWithAppId = jest.fn().mockReturnValue(false);
273
+ const res: Response = {} as any;
274
+ let errorCode;
275
+ res.send = (code: number, obj) => {
276
+ errorCode = code;
277
+ return obj;
278
+ };
279
+ const next = jest.fn() as any;
280
+ await AuthenticationMiddleware.ensureAuthenticated(req, res, next);
281
+
282
+ expect(req.username).toBeUndefined();
283
+ expect(errorCode).toEqual(401);
284
+ expect(next).toHaveBeenCalledWith(false);
285
+ });
286
+
287
+ /**
288
+ * Below test case checks for the case of invalid api key from ensureAuthenticated method
289
+ */
290
+ it('failure case validation of API key', async () => {
291
+ const req = {} as any;
292
+ req.headers = { authorization: `apiKey ${INVALID_API_KEY_TOKEN}` };
293
+ AuthenticationMiddleware.verifyApiKeyWithAppId = jest.fn().mockImplementation(() => {
294
+ throw new Error('Unable to verify API key');
295
+ });
296
+ const res: Response = {} as any;
297
+ res.send = (code: number, obj) => {
298
+ return obj;
299
+ };
300
+ const next = jest.fn() as any;
301
+ await AuthenticationMiddleware.ensureAuthenticated(req, res, next);
302
+ expect(next).toHaveBeenCalledWith(false);
303
+ jest.clearAllMocks();
304
+ });
305
+
306
+ it('Check for checkPreAuthentication for REST endpoint - invalid credentials', async () => {
307
+ const req = {
308
+ headers: {},
309
+ body: {
310
+ variables: { appId: 'general' },
311
+ operationName: 'appSettings',
312
+ },
313
+ params: { appID: 'w3notifications-test' },
314
+ path: () => '/v1/settings/app/:appID/setting/:settingName',
315
+ } as any;
316
+ const res: Response = {
317
+ send: jest.fn(),
318
+ } as any;
319
+ const next = jest.fn() as any;
320
+ AuthenticationMiddleware.checkPreAuthentication(req, res, next);
321
+ expect(next).toBeCalledWith(false);
322
+ expect(res.send).toBeCalledWith(401, {
323
+ code: 401,
324
+ message: 'API key based authentication strategy must be used for appSettings Query.',
325
+ });
326
+ });
327
+
328
+ it('Check for checkPreAuthentication for getAllSettings endpoint - invalid credentials', async () => {
329
+ const req = {
330
+ headers: {},
331
+ path: () => '/v1/settings/users/all',
332
+ } as any;
333
+ const res: Response = {
334
+ send: jest.fn(),
335
+ } as any;
336
+ const next = jest.fn() as any;
337
+ AuthenticationMiddleware.checkPreAuthentication(req, res, next);
338
+ expect(next).toBeCalledWith(false);
339
+ expect(res.send).toBeCalledWith(401, {
340
+ code: 401,
341
+ message: 'API key based authentication strategy must be used for get all user settings operation.',
342
+ });
343
+ });
344
+
345
+ it('Check for checkPreAuthentication for delete endpoint - invalid credentials', async () => {
346
+ const req = {
347
+ headers: {},
348
+ path: () => '/v1/settings/users/ABCDEFGHI',
349
+ method: 'DELETE',
350
+ } as any;
351
+ const res: Response = {
352
+ send: jest.fn(),
353
+ } as any;
354
+ const next = jest.fn() as any;
355
+ AuthenticationMiddleware.checkPreAuthentication(req, res, next);
356
+ expect(next).toBeCalledWith(false);
357
+ expect(res.send).toBeCalledWith(401, {
358
+ code: 401,
359
+ message: 'API key based authentication strategy must be used for delete settings operation.',
360
+ });
361
+ });
362
+
363
+ it('Check for checkPreAuthentication for REST endpoint - valid apikey credentials', async () => {
364
+ const req = {
365
+ headers: {
366
+ authorization: 'apiKey dummy_api_key',
367
+ },
368
+ body: {
369
+ variables: { appId: 'general' },
370
+ operationName: 'appSettings',
371
+ },
372
+ params: { appID: 'w3notifications-test' },
373
+ path: () => '/v1/settings/app/:appID/setting/:settingName',
374
+ } as any;
375
+ const res: Response = {
376
+ send: jest.fn(),
377
+ } as any;
378
+ const next = jest.fn() as any;
379
+ AuthenticationMiddleware.checkPreAuthentication(req, res, next);
380
+ expect(next).toBeCalled();
381
+ expect(next).not.toBeCalledWith(false);
382
+ });
383
+
384
+ it('Check for checkPreAuthentication for invalid REST endpoint - valid apikey credentials', async () => {
385
+ const req = {
386
+ headers: {
387
+ authorization: 'apiKey dummy_api_key',
388
+ },
389
+ body: {
390
+ variables: { appId: 'general' },
391
+ operationName: 'appSettings',
392
+ },
393
+ params: { appID: 'w3notifications-test' },
394
+ path: () => '/invalidpath',
395
+ } as any;
396
+ const res: Response = {
397
+ send: jest.fn(),
398
+ } as any;
399
+ const next = jest.fn() as any;
400
+ AuthenticationMiddleware.checkPreAuthentication(req, res, next);
401
+ expect(next).toBeCalled();
402
+ });
403
+ });
404
+
405
+ describe('checkPostAuthentication', () => {
406
+ it('should return next if operationDetails are undefined', () => {
407
+ const req = {
408
+ path: () => '/invalidpath',
409
+ } as any;
410
+ const res: Response = {
411
+ send: jest.fn(),
412
+ } as any;
413
+ const next = jest.fn() as any;
414
+
415
+ AuthenticationMiddleware.checkPostAuthentication(req, res, next);
416
+
417
+ expect(next).toHaveBeenCalled();
418
+ });
419
+
420
+ it('should return next if user is authorized to request api key', () => {
421
+ const req = {
422
+ headers: {
423
+ authorization: 'apiKey dummy_api_key',
424
+ },
425
+ body: {
426
+ variables: { appId: 'general' },
427
+ operationName: 'apiKey',
428
+ },
429
+ params: { appID: 'w3notifications-test' },
430
+ path: () => '/v1/settings/apiKey/app/:appID',
431
+ } as any;
432
+ const res: Response = {
433
+ send: jest.fn(),
434
+ } as any;
435
+ const next = jest.fn() as any;
436
+
437
+ jest.spyOn(AuthenticationMiddleware, 'isAuthorizedToRequestApiKey').mockReturnValueOnce(true);
438
+
439
+ AuthenticationMiddleware.checkPostAuthentication(req, res, next);
440
+
441
+ expect(next).toHaveBeenCalled();
442
+ });
443
+
444
+ it('should send a 403 response if user is not authorized to request api key', () => {
445
+ const req = {
446
+ headers: {
447
+ authorization: 'apiKey dummy_api_key',
448
+ },
449
+ body: {
450
+ variables: { appId: 'general' },
451
+ operationName: 'apiKey',
452
+ },
453
+ params: { appID: 'w3notifications-test' },
454
+ path: () => '/v1/settings/apiKey/app/:appID',
455
+ } as any;
456
+ const res: Response = {
457
+ send: jest.fn(),
458
+ } as any;
459
+ const next = jest.fn() as any;
460
+
461
+ jest.spyOn(AuthenticationMiddleware, 'isAuthorizedToRequestApiKey').mockReturnValueOnce(false);
462
+ res.send = jest.fn();
463
+
464
+ AuthenticationMiddleware.checkPostAuthentication(req, res, next);
465
+
466
+ expect(res.send).toHaveBeenCalledWith(403, {
467
+ code: 403,
468
+ message: `You are not authorized for apiKey operations.`,
469
+ });
470
+ expect(next).toHaveBeenCalledWith(false);
471
+ jest.restoreAllMocks();
472
+ });
473
+ });
474
+
475
+ describe('isAuthorizedToRequestApiKey method', () => {
476
+ it('should return true if username is on list of authorized user ids', () => {
477
+ const req = {} as any;
478
+ req.isAuthenticated = true;
479
+ req.username = TEST_USER_ID;
480
+ settings.apiKeyAuthorizedUserIds = TEST_USER_ID;
481
+
482
+ expect(AuthenticationMiddleware.isAuthorizedToRequestApiKey(req)).toBeTruthy();
483
+ });
484
+
485
+ it('should return false if username is on list of authorized user ids', () => {
486
+ const req = {} as any;
487
+ req.isAuthenticated = true;
488
+ req.username = 'invalidUser';
489
+ settings.apiKeyAuthorizedUserIds = TEST_USER_ID;
490
+
491
+ expect(AuthenticationMiddleware.isAuthorizedToRequestApiKey(req)).toBeFalsy();
492
+ });
493
+ });
494
+ });
@@ -0,0 +1,194 @@
1
+ import fs from 'fs';
2
+ import { NotFoundError } from 'restify-errors';
3
+ import { ApiKeyRepoFactory } from '../../../repo/ApiKeyRepoFactory';
4
+ import { settings } from '../../../config/config';
5
+ import { ApiKey } from '../../../types/ApiKey';
6
+ import { ApiKeyRepo, ApiKeyRepoImpl } from '../../../repo/ApiKeyRepo';
7
+ import { cassandraDBHelpers } from '../../../repo/cassandraDBHelpers';
8
+ import * as commons from '../../../helpers/commons';
9
+ import { mockApiKey, TEST_API_KEY, TEST_APP_ID, TEST_USER_ID } from '../../utils/test-utils';
10
+
11
+ const mockApiKeyDBRow = {
12
+ '[json]': fs.readFileSync('test_resources/mockApiKeyDBResult.json').toString(),
13
+ };
14
+
15
+ const encryptionKey: string = process.env.AES_ENCRYPTION_KEY || '';
16
+ settings.aesEncryptionKey = 'kXp2s5v8x/A?D(G+KbPeShVmYq3t6w9z';
17
+
18
+ const apiKeyRepo: ApiKeyRepo = ApiKeyRepoFactory.accessOrCreateSingleton();
19
+
20
+ afterEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+
24
+ afterAll(() => {
25
+ settings.aesEncryptionKey = encryptionKey;
26
+ });
27
+
28
+ describe('getInstance method', () => {
29
+ it('getInstance returns the current instance of the repo', async () => {
30
+ expect(apiKeyRepo).toBeDefined();
31
+ expect(apiKeyRepo).toBeInstanceOf(ApiKeyRepoImpl);
32
+ });
33
+ });
34
+
35
+ describe('getApiKey method', () => {
36
+ it('gets the api key from the database', async () => {
37
+ const results = {
38
+ first: jest.fn().mockReturnValue(mockApiKeyDBRow),
39
+ } as any;
40
+ jest.spyOn(cassandraDBHelpers, 'execute').mockResolvedValueOnce(results);
41
+
42
+ const returnedApiKey: ApiKey = await apiKeyRepo.getApiKey(TEST_APP_ID);
43
+
44
+ expect(returnedApiKey).toBeDefined();
45
+ expect(returnedApiKey.key).toEqual(TEST_API_KEY);
46
+ });
47
+
48
+ it('getApiKey throws an error if no results found in DB', async () => {
49
+ const results = {
50
+ first: jest.fn().mockReturnValue(null),
51
+ } as any;
52
+ const executeSpy = jest.spyOn(cassandraDBHelpers, 'execute').mockResolvedValueOnce(results);
53
+
54
+ expect.assertions(4);
55
+ try {
56
+ await apiKeyRepo.getApiKey(TEST_APP_ID);
57
+ } catch (err) {
58
+ expect(err).toBeDefined();
59
+ expect(err).toBeInstanceOf(NotFoundError);
60
+ expect(err.message).toEqual(`ApiKey for the appId: ${JSON.stringify(TEST_APP_ID)} doesn't exist.`);
61
+ expect(executeSpy).toHaveBeenCalled();
62
+ }
63
+ });
64
+ });
65
+
66
+ describe('createApiKey method', () => {
67
+ it('createApiKey returns api key if it already exists', async () => {
68
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockResolvedValueOnce(mockApiKey);
69
+
70
+ const returnedKey: string = await apiKeyRepo.createApiKey(TEST_APP_ID, TEST_USER_ID);
71
+
72
+ expect(returnedKey).toBeDefined();
73
+ expect(returnedKey).toEqual(mockApiKey.key);
74
+ expect(getApiKeySpy).toHaveBeenCalled();
75
+ });
76
+
77
+ it('createApiKey throws an error if there is an error in getApiKey', async () => {
78
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockImplementationOnce(() => {
79
+ throw new Error();
80
+ });
81
+
82
+ expect.assertions(4);
83
+ try {
84
+ await apiKeyRepo.createApiKey(TEST_APP_ID, TEST_USER_ID);
85
+ } catch (err) {
86
+ expect(err).toBeDefined();
87
+ expect(err).toBeInstanceOf(Error);
88
+ expect(err.message).toEqual('Unable to create API key. Please try again later.');
89
+ expect(getApiKeySpy).toHaveBeenCalled();
90
+ }
91
+ });
92
+
93
+ it('createApiKey should create and insert a new api key in database and return decrypted value', async () => {
94
+ const results = {
95
+ first: jest.fn().mockReturnValue(mockApiKeyDBRow),
96
+ } as any;
97
+ const createRandomTokenSpy = jest.spyOn(commons, 'createRandomToken').mockReturnValueOnce(TEST_API_KEY);
98
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockImplementationOnce(() => {
99
+ throw new NotFoundError();
100
+ });
101
+ const executeSpy = jest.spyOn(cassandraDBHelpers, 'execute').mockResolvedValueOnce(results);
102
+
103
+ const createdKey = await apiKeyRepo.createApiKey('newApiKey', TEST_USER_ID);
104
+
105
+ expect(createdKey).toBeDefined();
106
+ expect(createdKey).toEqual(TEST_API_KEY);
107
+ expect(executeSpy).toHaveBeenCalled();
108
+ expect(getApiKeySpy).toHaveBeenCalled();
109
+ expect(createRandomTokenSpy).toHaveBeenCalled();
110
+ });
111
+
112
+ it('createApiKey throws an error if error inserting into DB', async () => {
113
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockImplementationOnce(() => {
114
+ throw new NotFoundError();
115
+ });
116
+ jest.spyOn(cassandraDBHelpers, 'execute').mockRejectedValue(new Error());
117
+
118
+ expect.assertions(4);
119
+ try {
120
+ await apiKeyRepo.createApiKey(TEST_APP_ID, TEST_USER_ID);
121
+ } catch (err) {
122
+ expect(err).toBeDefined();
123
+ expect(err).toBeInstanceOf(Error);
124
+ expect(err.message).toEqual(`Unable to create API key. Please try again later.`);
125
+ expect(getApiKeySpy).toHaveBeenCalled();
126
+ }
127
+ });
128
+ });
129
+
130
+ describe('updateApiKey', () => {
131
+ it('updateApiKey throws an error if there is an error in getApiKey', async () => {
132
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockImplementationOnce(() => {
133
+ throw new Error();
134
+ });
135
+
136
+ expect.assertions(4);
137
+ try {
138
+ await apiKeyRepo.updateApiKey(TEST_APP_ID, TEST_USER_ID);
139
+ } catch (err) {
140
+ expect(err).toBeDefined();
141
+ expect(err).toBeInstanceOf(Error);
142
+ expect(err.message).toEqual('Unable to update API key. Please try again later.');
143
+ expect(getApiKeySpy).toHaveBeenCalled();
144
+ }
145
+ });
146
+
147
+ it('updateApiKey throws an error if the apiKey is not already in the datbase', async () => {
148
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockImplementationOnce(() => {
149
+ throw new NotFoundError();
150
+ });
151
+
152
+ expect.assertions(4);
153
+ try {
154
+ await apiKeyRepo.updateApiKey(TEST_APP_ID, TEST_USER_ID);
155
+ } catch (err) {
156
+ expect(err).toBeDefined();
157
+ expect(err).toBeInstanceOf(Error);
158
+ expect(err.message).toEqual(`API key doesn't exist for the appId`);
159
+ expect(getApiKeySpy).toHaveBeenCalled();
160
+ }
161
+ });
162
+
163
+ it('updateApiKey should update api key in database and return decrypted value', async () => {
164
+ const results = {
165
+ first: jest.fn().mockReturnValue(mockApiKeyDBRow),
166
+ } as any;
167
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockResolvedValueOnce(mockApiKey);
168
+ const createRandomTokenSpy = jest.spyOn(commons, 'createRandomToken').mockReturnValueOnce(TEST_API_KEY);
169
+ const executeSpy = jest.spyOn(cassandraDBHelpers, 'execute').mockResolvedValueOnce(results);
170
+
171
+ const updatedKey = await apiKeyRepo.updateApiKey(TEST_APP_ID, TEST_USER_ID);
172
+
173
+ expect(updatedKey).toBeDefined();
174
+ expect(updatedKey).toEqual(TEST_API_KEY);
175
+ expect(createRandomTokenSpy).toHaveBeenCalled();
176
+ expect(executeSpy).toHaveBeenCalled();
177
+ expect(getApiKeySpy).toHaveBeenCalled();
178
+ });
179
+
180
+ it('updateApiKey throws an error if error updating key in DB', async () => {
181
+ const getApiKeySpy = jest.spyOn(ApiKeyRepoImpl.prototype, 'getApiKey').mockResolvedValueOnce(mockApiKey);
182
+ jest.spyOn(cassandraDBHelpers, 'execute').mockRejectedValue(new Error());
183
+
184
+ expect.assertions(4);
185
+ try {
186
+ await apiKeyRepo.updateApiKey(TEST_APP_ID, TEST_USER_ID);
187
+ } catch (err) {
188
+ expect(err).toBeDefined();
189
+ expect(err).toBeInstanceOf(Error);
190
+ expect(err.message).toEqual(`Unable to update Api key. Please try again later.`);
191
+ expect(getApiKeySpy).toHaveBeenCalled();
192
+ }
193
+ });
194
+ });