@villedemontreal/jwt-validator 5.7.7

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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -0
  3. package/dist/scripts/index.d.ts +6 -0
  4. package/dist/scripts/index.js +16 -0
  5. package/dist/scripts/index.js.map +1 -0
  6. package/dist/scripts/lint.d.ts +6 -0
  7. package/dist/scripts/lint.js +18 -0
  8. package/dist/scripts/lint.js.map +1 -0
  9. package/dist/scripts/lintFix.d.ts +6 -0
  10. package/dist/scripts/lintFix.js +21 -0
  11. package/dist/scripts/lintFix.js.map +1 -0
  12. package/dist/scripts/showCoverage.d.ts +13 -0
  13. package/dist/scripts/showCoverage.js +40 -0
  14. package/dist/scripts/showCoverage.js.map +1 -0
  15. package/dist/scripts/test.d.ts +13 -0
  16. package/dist/scripts/test.js +29 -0
  17. package/dist/scripts/test.js.map +1 -0
  18. package/dist/scripts/testUnits.d.ts +15 -0
  19. package/dist/scripts/testUnits.js +95 -0
  20. package/dist/scripts/testUnits.js.map +1 -0
  21. package/dist/scripts/watch.d.ts +14 -0
  22. package/dist/scripts/watch.js +96 -0
  23. package/dist/scripts/watch.js.map +1 -0
  24. package/dist/src/config/configs.d.ts +88 -0
  25. package/dist/src/config/configs.js +123 -0
  26. package/dist/src/config/configs.js.map +1 -0
  27. package/dist/src/config/constants.d.ts +56 -0
  28. package/dist/src/config/constants.js +66 -0
  29. package/dist/src/config/constants.js.map +1 -0
  30. package/dist/src/config/init.d.ts +15 -0
  31. package/dist/src/config/init.js +48 -0
  32. package/dist/src/config/init.js.map +1 -0
  33. package/dist/src/index.d.ts +10 -0
  34. package/dist/src/index.js +32 -0
  35. package/dist/src/index.js.map +1 -0
  36. package/dist/src/jwtValidator.d.ts +21 -0
  37. package/dist/src/jwtValidator.js +129 -0
  38. package/dist/src/jwtValidator.js.map +1 -0
  39. package/dist/src/jwtValidator.test.d.ts +1 -0
  40. package/dist/src/jwtValidator.test.js +500 -0
  41. package/dist/src/jwtValidator.test.js.map +1 -0
  42. package/dist/src/middleware/jwtMiddleware.d.ts +7 -0
  43. package/dist/src/middleware/jwtMiddleware.js +27 -0
  44. package/dist/src/middleware/jwtMiddleware.js.map +1 -0
  45. package/dist/src/models/customError.d.ts +11 -0
  46. package/dist/src/models/customError.js +38 -0
  47. package/dist/src/models/customError.js.map +1 -0
  48. package/dist/src/models/expressRequest.d.ts +15 -0
  49. package/dist/src/models/expressRequest.js +17 -0
  50. package/dist/src/models/expressRequest.js.map +1 -0
  51. package/dist/src/models/gluuUserType.d.ts +9 -0
  52. package/dist/src/models/gluuUserType.js +14 -0
  53. package/dist/src/models/gluuUserType.js.map +1 -0
  54. package/dist/src/models/jwtPayload.d.ts +30 -0
  55. package/dist/src/models/jwtPayload.js +19 -0
  56. package/dist/src/models/jwtPayload.js.map +1 -0
  57. package/dist/src/models/pagination.d.ts +16 -0
  58. package/dist/src/models/pagination.js +16 -0
  59. package/dist/src/models/pagination.js.map +1 -0
  60. package/dist/src/models/publicKey.d.ts +29 -0
  61. package/dist/src/models/publicKey.js +13 -0
  62. package/dist/src/models/publicKey.js.map +1 -0
  63. package/dist/src/repositories/cachedPublicKeyRepository.d.ts +53 -0
  64. package/dist/src/repositories/cachedPublicKeyRepository.js +102 -0
  65. package/dist/src/repositories/cachedPublicKeyRepository.js.map +1 -0
  66. package/dist/src/repositories/publicKeyRepository.d.ts +19 -0
  67. package/dist/src/repositories/publicKeyRepository.js +44 -0
  68. package/dist/src/repositories/publicKeyRepository.js.map +1 -0
  69. package/dist/src/userValidator.d.ts +30 -0
  70. package/dist/src/userValidator.js +35 -0
  71. package/dist/src/userValidator.js.map +1 -0
  72. package/dist/src/userValidator.test.d.ts +1 -0
  73. package/dist/src/userValidator.test.js +251 -0
  74. package/dist/src/userValidator.test.js.map +1 -0
  75. package/dist/src/utils/jwtMock.d.ts +31 -0
  76. package/dist/src/utils/jwtMock.js +221 -0
  77. package/dist/src/utils/jwtMock.js.map +1 -0
  78. package/dist/src/utils/logger.d.ts +11 -0
  79. package/dist/src/utils/logger.js +54 -0
  80. package/dist/src/utils/logger.js.map +1 -0
  81. package/dist/src/utils/testingConfigurations.d.ts +7 -0
  82. package/dist/src/utils/testingConfigurations.js +16 -0
  83. package/dist/src/utils/testingConfigurations.js.map +1 -0
  84. package/package.json +82 -0
  85. package/src/config/configs.ts +145 -0
  86. package/src/config/constants.ts +83 -0
  87. package/src/config/init.ts +58 -0
  88. package/src/index.ts +15 -0
  89. package/src/jwtValidator.test.ts +607 -0
  90. package/src/jwtValidator.ts +162 -0
  91. package/src/middleware/jwtMiddleware.ts +33 -0
  92. package/src/models/customError.ts +37 -0
  93. package/src/models/expressRequest.ts +27 -0
  94. package/src/models/gluuUserType.ts +9 -0
  95. package/src/models/jwtPayload.ts +58 -0
  96. package/src/models/pagination.ts +26 -0
  97. package/src/models/publicKey.ts +33 -0
  98. package/src/repositories/cachedPublicKeyRepository.ts +121 -0
  99. package/src/repositories/publicKeyRepository.ts +75 -0
  100. package/src/userValidator.test.ts +279 -0
  101. package/src/userValidator.ts +54 -0
  102. package/src/utils/jwtMock.ts +243 -0
  103. package/src/utils/logger.ts +60 -0
  104. package/src/utils/testingConfigurations.ts +12 -0
@@ -0,0 +1,279 @@
1
+ import { assert } from 'chai';
2
+ import * as nock from 'nock';
3
+ import * as validator from 'validator';
4
+
5
+ import { configs } from './config/configs';
6
+ import { IPaginatedResult } from './models/pagination';
7
+ import { IPublicKey, IPublicKeys, PublicKeyState } from './models/publicKey';
8
+ import { cachedPublicKeyRepository } from './repositories/cachedPublicKeyRepository';
9
+ import { UserValidator } from './userValidator';
10
+ import { setTestingConfigurations } from './utils/testingConfigurations';
11
+
12
+ // ==========================================
13
+ // Set Testing configurations
14
+ // ==========================================
15
+ setTestingConfigurations();
16
+
17
+ /*
18
+ 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
67
+ -----END RSA PRIVATE KEY-----`;
68
+ */
69
+
70
+ const nockPublicKey = `-----BEGIN PUBLIC KEY-----
71
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzVZZXij6LgAUuH6ZcvGM
72
+ vskXj+a8T5uBkvFFEPnTdRN7dYdWrhxeqsDkvwpyxJehMRAANCP+AYXgLE2BA7qt
73
+ u0SCW+Hj+1+ZubQqwaD/EQtk72yU04rJw4YfDg6VyQbSdUQRQ7ktjVitglj4dBZA
74
+ x99O+4guId52bRZrqSLe4OUVeRSI6tSV6n7ta+vsvPxUdAylFI9mAlh2we7vAu18
75
+ X7cC/5Rzq4NIuHhWpkOwwE6h7RUu7oqWfpNYl+Ugzp7qgd/vosP4FtDTW0+N82Jj
76
+ iuKILc1bWicynv5ka22R4wNrGewPGDCyTckogxrUYkx80YFad4UBF2hGzTDfXng4
77
+ gIU61eJTiB5gy3t6AgHHaVbDPnGZIefPlE+4A35Ics4uIV1z5Kbmq2+aaP+YXE42
78
+ a/WoYJfkXqOHs1PjIJmVj/szVRKyKbpq9Qp3xIM+YZQlYUvgql/O6vbl5N1NbkV3
79
+ Cjpp+zknfYXmSUJ+0FGK/ZGcFRmOSeLyJnULeFS5EAF7CUnLMEfi0hFEiyOXSngT
80
+ BIPL2GKsvtliZvPS78ik2mfXyG0YJQiVM7hYyRV+AnudI0Pz/zjX2DvjM0s3jMeI
81
+ om+l3zpAZ4SmFDkKzhAvuxoSl2koH+VXm9QtsRFSZQwF3/K5l3Zx0yi1e16dMOWw
82
+ YPtMOiCbEausVW7wgzdoj+kCAwEAAQ==
83
+ -----END PUBLIC KEY-----`;
84
+
85
+ let date: Date = new Date();
86
+ date.setHours(-1);
87
+
88
+ date.setMonth(date.getMonth() - 1);
89
+ const lastMonth = date.toISOString();
90
+
91
+ date.setMonth(date.getMonth() - 1);
92
+ const expiredDate = date.toISOString();
93
+
94
+ date = new Date();
95
+ date.setMonth(date.getMonth() + 1);
96
+ const newCreatedDate = date.toISOString();
97
+
98
+ date.setHours(date.getHours() + 1);
99
+ const newExpiredDate = date.toISOString();
100
+
101
+ const nocKPublicKeyExpired = {
102
+ id: 1,
103
+ algorithm: 'RSA256',
104
+ publicKey: nockPublicKey,
105
+ state: PublicKeyState.EXPIRED,
106
+ createdAt: expiredDate,
107
+ expiresAt: lastMonth,
108
+ };
109
+ const nocKPublicKeyExpiredNotState: IPublicKey = {
110
+ id: 2,
111
+ algorithm: 'RSA256',
112
+ publicKey: nockPublicKey,
113
+ state: PublicKeyState.ACTIVE,
114
+ createdAt: expiredDate,
115
+ expiresAt: lastMonth,
116
+ };
117
+ const nocKPublicKeyRevoked: IPublicKey = {
118
+ id: 3,
119
+ algorithm: 'RSA256',
120
+ publicKey: nockPublicKey,
121
+ state: PublicKeyState.REVOKED,
122
+ createdAt: lastMonth,
123
+ expiresAt: newExpiredDate,
124
+ };
125
+ const nocKPublicKeyActiveOld: IPublicKey = {
126
+ id: 4,
127
+ algorithm: 'RSA256',
128
+ publicKey: nockPublicKey,
129
+ state: PublicKeyState.ACTIVE,
130
+ createdAt: lastMonth,
131
+ expiresAt: newExpiredDate,
132
+ };
133
+ const nocKPublicKeyActive: IPublicKey = {
134
+ id: 5,
135
+ algorithm: 'RSA256',
136
+ publicKey: nockPublicKey,
137
+ state: PublicKeyState.ACTIVE,
138
+ createdAt: newCreatedDate,
139
+ };
140
+
141
+ const nockListPublicKeys: IPaginatedResult<IPublicKey> = {
142
+ paging: {
143
+ limit: 25,
144
+ offset: 0,
145
+ totalCount: 5,
146
+ },
147
+ items: [
148
+ nocKPublicKeyExpired,
149
+ nocKPublicKeyExpiredNotState,
150
+ nocKPublicKeyRevoked,
151
+ nocKPublicKeyActiveOld,
152
+ nocKPublicKeyActive,
153
+ ],
154
+ };
155
+
156
+ // ==========================================
157
+ // User Validator
158
+ // ==========================================
159
+ let publicKeys: IPublicKeys;
160
+
161
+ const regExpEscape = (s: any) => {
162
+ return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
163
+ };
164
+
165
+ const pathRegex = new RegExp(`${regExpEscape(configs.getEndpoint())}(.*)`);
166
+
167
+ it('User Validator - init app & get jwt public key', async () => {
168
+ nock.cleanAll();
169
+
170
+ cachedPublicKeyRepository.clearCache();
171
+
172
+ // Intercept request
173
+ nock(configs.getHost()).get(pathRegex).reply(200, nockListPublicKeys);
174
+
175
+ publicKeys = await cachedPublicKeyRepository.getAll();
176
+
177
+ assert.match(publicKeys[1].publicKey, /^-----BEGIN PUBLIC KEY-----\n/m);
178
+ assert.match(publicKeys[1].publicKey, /^-----BEGIN PUBLIC KEY-----\n/m);
179
+ assert.match(publicKeys[1].publicKey, /\n-----END PUBLIC KEY-----$/m);
180
+
181
+ const key: string = publicKeys[1].publicKey
182
+ .replace(/^-----BEGIN PUBLIC KEY-----\n/m, '')
183
+ .replace(/\n-----END PUBLIC KEY-----$/m, '')
184
+ .split('\n')
185
+ .join('');
186
+ assert.isTrue(validator.default.isBase64(key));
187
+ });
188
+
189
+ const mockRequest = require('mock-express-request');
190
+
191
+ it('should consider mtlIdentityId field when verifying user', async () => {
192
+ let hasThrown = false;
193
+
194
+ try {
195
+ const userId = 'voodoo';
196
+ const request = new mockRequest();
197
+
198
+ request.jwt = {
199
+ mtlIdentityId: 'voodoo',
200
+ };
201
+
202
+ const userValidator: UserValidator = new UserValidator(request);
203
+ userValidator.verifyUser(userId);
204
+ } catch (err) {
205
+ hasThrown = true;
206
+ }
207
+
208
+ assert.isFalse(hasThrown);
209
+ });
210
+
211
+ it('should throw when either fields are matching', async () => {
212
+ let hasThrown = false;
213
+
214
+ try {
215
+ const userId = 'wrongVoodoo';
216
+ const request = new mockRequest();
217
+
218
+ request.jwt = {
219
+ mtlIdentityId: 'voodoo',
220
+ sub: 'voodoo',
221
+ };
222
+
223
+ const userValidator: UserValidator = new UserValidator(request);
224
+ userValidator.verifyUser(userId);
225
+ } catch (err) {
226
+ hasThrown = true;
227
+ }
228
+
229
+ assert.isTrue(hasThrown);
230
+ });
231
+
232
+ /*
233
+ it('User Validator - verifyHeaderUser - should reject missing inum ', async function () {
234
+
235
+ let token: string = jwt.sign('{"a":"a", "keyId": 4}', nockPrivateKey, { algorithm: 'RS256' });
236
+
237
+ let response = await jwtValidator.verifyHeaderUser('Bearer ' + token, 'MyInum')
238
+ .catch((err) => {
239
+ assert.strictEqual(err.error.code, constants.errors.codes.INVALID_JWT);
240
+ assert.strictEqual(err.error.target, 'Authorization header');
241
+ assert.strictEqual(err.error.message, "Invalid JWT");
242
+ assert.strictEqual(err.error.details[0].code, constants.errors.codes.INVALID_VALUE);
243
+ assert.strictEqual(err.error.details[0].target, 'jwt');
244
+ assert.strictEqual(err.error.details[0].message, 'Invalid JWT content');
245
+ });
246
+
247
+ assert.isUndefined(response);
248
+ });
249
+
250
+ it('User Validator - verifyHeaderUser - should reject bad inum ', async function () {
251
+
252
+ let token: string = jwt.sign('{"inum":"a", "keyId": 4}', nockPrivateKey, { algorithm: 'RS256' });
253
+
254
+ let response = await jwtValidator.verifyHeaderUser('Bearer ' + token, 'MyInum')
255
+ .catch((err) => {
256
+ assert.strictEqual(err.error.code, constants.errors.codes.INVALID_JWT);
257
+ assert.strictEqual(err.error.target, 'Authorization header');
258
+ assert.strictEqual(err.error.message, "Invalid JWT");
259
+ assert.strictEqual(err.error.details[0].code, constants.errors.codes.UNAUTHORIZED_ACCESS);
260
+ assert.strictEqual(err.error.details[0].target, 'jwt');
261
+ assert.strictEqual(err.error.details[0].message, 'Unauthorized access');
262
+ });
263
+
264
+ assert.isUndefined(response);
265
+ });
266
+
267
+ it('User Validator - verifyHeaderUser - should accept good token ', async function () {
268
+
269
+ let payload: any = {
270
+ inum: "MyInum",
271
+ keyId: 4
272
+ };
273
+ let token: string = jwt.sign(JSON.stringify(payload), nockPrivateKey, { algorithm: 'RS256' });
274
+
275
+ let response = await jwtValidator.verifyHeaderUser('Bearer ' + token, 'MyInum');
276
+
277
+ assert.deepEqual(response, payload);
278
+ });
279
+ */
@@ -0,0 +1,54 @@
1
+ import * as express from 'express';
2
+ import { constants } from './config/constants';
3
+ import { createInvalidJwtError } from './models/customError';
4
+ import { IRequestWithJwt, isRequestWithJwt } from './models/expressRequest';
5
+
6
+ export interface IUserValidator {
7
+ /**
8
+ * TODO commentaire pas à jour
9
+ * Verify a header containg a jwt and check the jwt with the public key and returns the decoded payload
10
+ * @param {string} userId
11
+ * @return {void}
12
+ */
13
+ isUser(userId: string): boolean;
14
+
15
+ /**
16
+ * TODO commentaire pas à jour
17
+ * Verify a header containg a jwt and check the jwt with the public key and returns the decoded payload
18
+ * @param {string} userId
19
+ * @return {void}
20
+ */
21
+ verifyUser(userId: string): void;
22
+ }
23
+
24
+ /**
25
+ * User Validator
26
+ */
27
+ export class UserValidator implements IUserValidator {
28
+ private _request: IRequestWithJwt;
29
+
30
+ /**
31
+ * Create a new instance of UserValidator
32
+ * @param {express.Request} req
33
+ */
34
+ public constructor(req: express.Request) {
35
+ if (!isRequestWithJwt(req)) {
36
+ throw new Error(`Expecting a request with a '.jwt' here! : ${req}`);
37
+ }
38
+ this._request = req;
39
+ }
40
+
41
+ public isUser(userId: string) {
42
+ return this._request.jwt.sub !== userId;
43
+ }
44
+
45
+ public verifyUser(userId: string) {
46
+ if (this._request.jwt.sub !== userId && this._request.jwt.mtlIdentityId !== userId) {
47
+ throw createInvalidJwtError({
48
+ code: constants.errors.codes.UNAUTHORIZED_ACCESS,
49
+ target: 'jwt',
50
+ message: 'Unauthorized access',
51
+ });
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,243 @@
1
+ import * as jwt from 'jsonwebtoken';
2
+ import { defaults } from 'lodash';
3
+ // import * as nock from 'nock';
4
+ import { configs } from '../config/configs';
5
+ import { IJWTPayload } from '../models/jwtPayload';
6
+ import { IPublicKey, PublicKeyState } from '../models/publicKey';
7
+ import { cachedPublicKeyRepository } from '../repositories/cachedPublicKeyRepository';
8
+
9
+ let nock: any;
10
+
11
+ const nockPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
12
+ 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
61
+ -----END RSA PRIVATE KEY-----`;
62
+
63
+ const nockPublicKey = `-----BEGIN PUBLIC KEY-----
64
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzVZZXij6LgAUuH6ZcvGM
65
+ vskXj+a8T5uBkvFFEPnTdRN7dYdWrhxeqsDkvwpyxJehMRAANCP+AYXgLE2BA7qt
66
+ u0SCW+Hj+1+ZubQqwaD/EQtk72yU04rJw4YfDg6VyQbSdUQRQ7ktjVitglj4dBZA
67
+ x99O+4guId52bRZrqSLe4OUVeRSI6tSV6n7ta+vsvPxUdAylFI9mAlh2we7vAu18
68
+ X7cC/5Rzq4NIuHhWpkOwwE6h7RUu7oqWfpNYl+Ugzp7qgd/vosP4FtDTW0+N82Jj
69
+ iuKILc1bWicynv5ka22R4wNrGewPGDCyTckogxrUYkx80YFad4UBF2hGzTDfXng4
70
+ gIU61eJTiB5gy3t6AgHHaVbDPnGZIefPlE+4A35Ics4uIV1z5Kbmq2+aaP+YXE42
71
+ a/WoYJfkXqOHs1PjIJmVj/szVRKyKbpq9Qp3xIM+YZQlYUvgql/O6vbl5N1NbkV3
72
+ Cjpp+zknfYXmSUJ+0FGK/ZGcFRmOSeLyJnULeFS5EAF7CUnLMEfi0hFEiyOXSngT
73
+ BIPL2GKsvtliZvPS78ik2mfXyG0YJQiVM7hYyRV+AnudI0Pz/zjX2DvjM0s3jMeI
74
+ om+l3zpAZ4SmFDkKzhAvuxoSl2koH+VXm9QtsRFSZQwF3/K5l3Zx0yi1e16dMOWw
75
+ YPtMOiCbEausVW7wgzdoj+kCAwEAAQ==
76
+ -----END PUBLIC KEY-----`;
77
+
78
+ let date = new Date();
79
+
80
+ date.setHours(-1);
81
+
82
+ date.setMonth(date.getMonth() - 1);
83
+ const lastMonth = date.toISOString();
84
+
85
+ date.setMonth(date.getMonth() - 1);
86
+ const expiredDate = date.toISOString();
87
+
88
+ date = new Date();
89
+ date.setMonth(date.getMonth() + 1);
90
+ const newCreatedDate = date.toISOString();
91
+ date.setHours(date.getHours() + 1);
92
+ const newExpiredDate = date.toISOString();
93
+
94
+ const nocKPublicKeyExpired: IPublicKey = {
95
+ id: 1,
96
+ algorithm: 'RSA256',
97
+ publicKey: nockPublicKey,
98
+ state: PublicKeyState.EXPIRED,
99
+ createdAt: expiredDate,
100
+ expiresAt: lastMonth,
101
+ };
102
+ const nocKPublicKeyExpiredNotState: IPublicKey = {
103
+ id: 2,
104
+ algorithm: 'RSA256',
105
+ publicKey: nockPublicKey,
106
+ state: PublicKeyState.ACTIVE,
107
+ createdAt: expiredDate,
108
+ expiresAt: lastMonth,
109
+ };
110
+ const nocKPublicKeyRevoked: IPublicKey = {
111
+ id: 3,
112
+ algorithm: 'RSA256',
113
+ publicKey: nockPublicKey,
114
+ state: PublicKeyState.REVOKED,
115
+ createdAt: lastMonth,
116
+ expiresAt: newExpiredDate,
117
+ };
118
+ const nocKPublicKeyActiveOld: IPublicKey = {
119
+ id: 4,
120
+ algorithm: 'RSA256',
121
+ publicKey: nockPublicKey,
122
+ state: PublicKeyState.ACTIVE,
123
+ createdAt: lastMonth,
124
+ expiresAt: newExpiredDate,
125
+ };
126
+ const nocKPublicKeyActive: IPublicKey = {
127
+ id: 5,
128
+ algorithm: 'RSA256',
129
+ publicKey: nockPublicKey,
130
+ state: PublicKeyState.ACTIVE,
131
+ createdAt: newCreatedDate,
132
+ };
133
+
134
+ const nockListPublicKeys: any = {
135
+ paging: {
136
+ limit: 25,
137
+ offset: 0,
138
+ totalCount: 5,
139
+ },
140
+ items: [
141
+ nocKPublicKeyExpired,
142
+ nocKPublicKeyExpiredNotState,
143
+ nocKPublicKeyRevoked,
144
+ nocKPublicKeyActiveOld,
145
+ nocKPublicKeyActive,
146
+ ],
147
+ };
148
+
149
+ const regExpEscape = (s: any) => {
150
+ return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
151
+ };
152
+
153
+ const pathRegex = new RegExp(`${regExpEscape(configs.getEndpoint())}(.*)`);
154
+
155
+ /**
156
+ * Tools to mock JWT
157
+ */
158
+ export class JwtMock {
159
+ /**
160
+ * Return a public key from the mock db
161
+ */
162
+ public getPublicKey(keyId: number): IPublicKey {
163
+ const key: IPublicKey = nockListPublicKeys.items[keyId - 1];
164
+
165
+ return key;
166
+ }
167
+
168
+ /**
169
+ * Return the public keys from the mock db
170
+ */
171
+ public getPublicKeys(): IPublicKey[] {
172
+ return nockListPublicKeys.items;
173
+ }
174
+
175
+ /**
176
+ * Return the private keys from the mock db
177
+ */
178
+ public getPrivateKey(): string {
179
+ return nockPrivateKey;
180
+ }
181
+
182
+ /**
183
+ * Flush the public keys in cache and add the mocked public keys
184
+ */
185
+ public async mockPublicKeys(options?: any): Promise<void> {
186
+ cachedPublicKeyRepository.clearCache();
187
+
188
+ // Intercept request
189
+ if (!nock) {
190
+ // Note that we lazy-load the nock module
191
+ // until we really need it, to avoid patching
192
+ // the http module for nothing.
193
+ nock = require('nock');
194
+ }
195
+ nock(configs.getHost(), options).get(pathRegex).once().reply(200, nockListPublicKeys);
196
+
197
+ await cachedPublicKeyRepository.getAll();
198
+ }
199
+
200
+ /**
201
+ * Generate a fake JWT signed with the mocked keys
202
+ */
203
+ public generateJwt(jwtProperties = {}): string {
204
+ const jwtPayload: IJWTPayload = defaults(jwtProperties, {
205
+ accessToken: 'c9ba5a95-d7f9-41f9-9a24-a7e41882f7ef',
206
+ iss: 'jwt-mock',
207
+
208
+ // From Introspect
209
+ exp: Date.now() + 3600,
210
+ iat: Date.now(),
211
+ keyId: 5,
212
+
213
+ // From ClientInfo
214
+ displayName: 'Service Account',
215
+ aud: '@!4025.CA62.9BB6.16C5!0001!2212.0010!0008!2212.0010',
216
+
217
+ // From UserInfo
218
+ name: 'Guillaume Smaha',
219
+ sub: '@!4025.CA62.9BB6.16C5!0001!2212.0010!0000!0000.0001',
220
+ inum: '@!4025.CA62.9BB6.16C5!0001!2212.0010!0000!0000.0001',
221
+ userName: 'xsmahgu@ville.montreal.qc.ca',
222
+ givenName: 'Guillaume',
223
+ familyName: 'Smaha',
224
+ userType: '',
225
+ mtlIdentityId: '100-qwerty-azerty-dvorak',
226
+ });
227
+
228
+ const jwtSigned: string = jwt.sign(jwtPayload, nockPrivateKey, {
229
+ algorithm: 'RS256',
230
+ });
231
+
232
+ return jwtSigned;
233
+ }
234
+
235
+ /**
236
+ * Deletes all mocks and also clears the cache
237
+ */
238
+ public cleanAll(): void {
239
+ nock.cleanAll();
240
+ cachedPublicKeyRepository.clearCache();
241
+ }
242
+ }
243
+ export const jwtMock: JwtMock = new JwtMock();
@@ -0,0 +1,60 @@
1
+ import {
2
+ ILogger,
3
+ initLogger,
4
+ LazyLogger,
5
+ Logger,
6
+ LoggerConfigs,
7
+ LogLevel,
8
+ } from '@villedemontreal/logger';
9
+ import { configs } from '../config/configs';
10
+
11
+ let testingLoggerLibInitialised = false;
12
+
13
+ /**
14
+ * Creates a Logger.
15
+ */
16
+ export function createLogger(name: string): ILogger {
17
+ // ==========================================
18
+ // We use a LazyLogger so the real Logger
19
+ // is only created when the first
20
+ // log is actually performed... At that point,
21
+ // our "configs.loggerCreator" configuration
22
+ // must have been set by the code using our library!
23
+ //
24
+ // This pattern allows calling code to import
25
+ // modules from us in which a logger is
26
+ // created in the global scope :
27
+ //
28
+ // let logger = createLogger('someName');
29
+ //
30
+ // Without a Lazy Logger, the library configurations
31
+ // would at that moment *not* have been set yet
32
+ // (by the calling code) and an Error would be thrown
33
+ // because the "configs.loggerCreator" is required.
34
+ // ==========================================
35
+ return new LazyLogger(name, (nameArg: string) => {
36
+ return configs.loggerCreator(nameArg);
37
+ });
38
+ }
39
+
40
+ function initTestingLoggerConfigs() {
41
+ const loggerConfig: LoggerConfigs = new LoggerConfigs(() => 'test-cid');
42
+ loggerConfig.setLogLevel(LogLevel.DEBUG);
43
+ initLogger(loggerConfig);
44
+ }
45
+
46
+ /**
47
+ * A Logger that uses a dummy cid provider.
48
+ *
49
+ * Only use this when running the tests!
50
+ */
51
+ export function getTestingLoggerCreator(): (name: string) => ILogger {
52
+ return (name: string): ILogger => {
53
+ if (!testingLoggerLibInitialised) {
54
+ initTestingLoggerConfigs();
55
+ testingLoggerLibInitialised = true;
56
+ }
57
+
58
+ return new Logger(name);
59
+ };
60
+ }
@@ -0,0 +1,12 @@
1
+ import { init } from '../config/init';
2
+ import { getTestingLoggerCreator } from '../utils/logger';
3
+
4
+ /**
5
+ * Call this when your need to set
6
+ * *Testing* configurations to the current
7
+ * library, without the need for a calling code
8
+ * to do so.
9
+ */
10
+ export function setTestingConfigurations(): void {
11
+ init(getTestingLoggerCreator(), () => 'test-cid', 'https://api.dev.interne.montreal.ca');
12
+ }