@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,162 @@
1
+ import { utils } from '@villedemontreal/general-utils';
2
+ import * as jwt from 'jsonwebtoken';
3
+ import * as moment from 'moment';
4
+ import { constants } from './config/constants';
5
+ import { createInvalidAuthHeaderError, createInvalidJwtError } from './models/customError';
6
+ import { IJWTPayload, isJWTPayload } from './models/jwtPayload';
7
+ import { IPublicKey, PublicKeyState } from './models/publicKey';
8
+ import { cachedPublicKeyRepository } from './repositories/cachedPublicKeyRepository';
9
+
10
+ /**
11
+ * JWT validator
12
+ */
13
+ export interface IJwtValidator {
14
+ /**
15
+ * Verifies an "Authorization" header containing a JWT, checks
16
+ * the JWT with the public key and returns the decoded payload.
17
+ *
18
+ * @param {string} header
19
+ * @return {Promise<IJWTPayload>}
20
+ */
21
+ verifyAuthorizationHeader(header: string): Promise<IJWTPayload>;
22
+
23
+ /**
24
+ * Verifies a JWT with the public key and returns the decoded payload.
25
+ * @param {string} token
26
+ * @return {Promise<IJWTPayload>}
27
+ */
28
+ verifyToken(token: string): Promise<IJWTPayload>;
29
+ }
30
+
31
+ /**
32
+ * JWT Validator
33
+ */
34
+ class JwtValidator implements IJwtValidator {
35
+ public async verifyAuthorizationHeader(header: string): Promise<IJWTPayload> {
36
+ if (utils.isBlank(header)) {
37
+ throw createInvalidAuthHeaderError({
38
+ code: constants.errors.codes.NULL_VALUE,
39
+ target: 'Authorization header',
40
+ message: 'Empty Authorization header',
41
+ });
42
+ }
43
+
44
+ const parts: string[] = header.trim().split(' ');
45
+ if (parts[0] !== 'Bearer') {
46
+ throw createInvalidAuthHeaderError({
47
+ code: constants.errors.codes.INVALID_VALUE,
48
+ target: 'Authorization header',
49
+ message: 'Bad authentication scheme, "Bearer" required',
50
+ });
51
+ }
52
+
53
+ return await this.verifyToken(parts[1]);
54
+ }
55
+
56
+ public async verifyToken(token: string): Promise<IJWTPayload> {
57
+ const payload = this.parseJwt(token);
58
+
59
+ const key = await this.getJwtPublicKey(payload);
60
+
61
+ this.validateJwtCreationTimestamp(payload, key);
62
+ this.validateJwtExpirationTimestamp(payload, key);
63
+
64
+ return this.verifyJwt(token, key.publicKey);
65
+ }
66
+
67
+ private parseJwt(token: string): IJWTPayload {
68
+ const payload: IJWTPayload = jwt.decode(token) as IJWTPayload;
69
+ if (!payload) {
70
+ throw createInvalidJwtError({
71
+ code: constants.errors.codes.INVALID_VALUE,
72
+ target: 'jwt',
73
+ message: 'jwt malformed',
74
+ });
75
+ }
76
+ return payload;
77
+ }
78
+
79
+ private verifyJwt(token: string, publicKey: string): IJWTPayload {
80
+ let payload: any;
81
+ try {
82
+ payload = jwt.verify(token, publicKey);
83
+ } catch (err) {
84
+ throw createInvalidJwtError({
85
+ code: constants.errors.codes.INVALID_VALUE,
86
+ target: 'jwt',
87
+ message: err.message,
88
+ });
89
+ }
90
+
91
+ if (isJWTPayload(payload)) {
92
+ return payload;
93
+ }
94
+ throw createInvalidJwtError({
95
+ code: constants.errors.codes.INVALID_VALUE,
96
+ target: 'jwt',
97
+ message: 'expected a valid JWT payload',
98
+ });
99
+ }
100
+
101
+ private async getJwtPublicKey(payload: IJWTPayload): Promise<IPublicKey> {
102
+ const keyId = payload.keyId;
103
+ if (!keyId || keyId <= 0) {
104
+ throw createInvalidJwtError({
105
+ code: constants.errors.codes.INVALID_VALUE,
106
+ target: 'jwt',
107
+ message: 'missing public key ID',
108
+ });
109
+ }
110
+
111
+ const key: IPublicKey = await cachedPublicKeyRepository.getOne(keyId);
112
+
113
+ // Check key state
114
+ if (!key || key.state !== PublicKeyState.ACTIVE) {
115
+ throw createInvalidJwtError({
116
+ code: constants.errors.codes.INVALID_VALUE,
117
+ target: 'jwt',
118
+ message: 'this keyId is no longer active',
119
+ });
120
+ }
121
+ return key;
122
+ }
123
+
124
+ private validateJwtCreationTimestamp(payload: IJWTPayload, key: IPublicKey) {
125
+ // Check the jwt was not created before the creation date of the key
126
+ const payloadIat: moment.Moment = moment.utc(payload.iat * 1000);
127
+ const keyCreatedAt: moment.Moment = moment.utc(key.createdAt);
128
+ if (payloadIat.diff(keyCreatedAt) < 0) {
129
+ throw createInvalidJwtError({
130
+ code: constants.errors.codes.INVALID_VALUE,
131
+ target: 'jwt',
132
+ message: "this jwt can't be created before the public key",
133
+ });
134
+ }
135
+ }
136
+
137
+ private validateJwtExpirationTimestamp(payload: IJWTPayload, key: IPublicKey) {
138
+ // Check expiration date
139
+ if (key.expiresAt) {
140
+ const keyexpiresAt: moment.Moment = moment.utc(key.expiresAt);
141
+ if (moment.utc().diff(keyexpiresAt) > 0) {
142
+ throw createInvalidJwtError({
143
+ code: constants.errors.codes.INVALID_VALUE,
144
+ target: 'jwt',
145
+ message: 'this keyId is expired',
146
+ });
147
+ }
148
+
149
+ // Check the jwt was not created after the expiration date of the key
150
+ const payloadIat: moment.Moment = moment.utc(payload.iat * 1000);
151
+ if (payloadIat.diff(keyexpiresAt) > 0) {
152
+ throw createInvalidJwtError({
153
+ code: constants.errors.codes.INVALID_VALUE,
154
+ target: 'jwt',
155
+ message: "this jwt can't be created after the expiration of the public key",
156
+ });
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ export const jwtValidator: IJwtValidator = new JwtValidator();
@@ -0,0 +1,33 @@
1
+ import * as express from 'express';
2
+ import httpHeaderFieldsTyped from 'http-header-fields-typed';
3
+ import { constants } from '../config/constants';
4
+ import { jwtValidator } from '../jwtValidator';
5
+
6
+ /**
7
+ * JWT validation Middleware
8
+ *
9
+ * @param {boolean} mandatoryValidation Defines if the JWT is mandatory. Defaults to true.
10
+ */
11
+ export const jwtValidationMiddleware: (
12
+ mandatoryValidation?: boolean
13
+ ) => (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<void> = (
14
+ mandatoryValidation = true
15
+ ) => {
16
+ return async (
17
+ req: express.Request,
18
+ res: express.Response,
19
+ next: express.NextFunction
20
+ ): Promise<void> => {
21
+ try {
22
+ const authHeader: string = req.get(httpHeaderFieldsTyped.AUTHORIZATION);
23
+ if (mandatoryValidation || authHeader) {
24
+ req[constants.requestExtraVariables.JWT] = await jwtValidator.verifyAuthorizationHeader(
25
+ authHeader
26
+ );
27
+ }
28
+ next();
29
+ } catch (err) {
30
+ next(err);
31
+ }
32
+ };
33
+ };
@@ -0,0 +1,37 @@
1
+ import { createError, IApiError, IApiErrorAndInfo } from '@villedemontreal/general-utils';
2
+ import { LogLevel } from '@villedemontreal/logger';
3
+ import * as HttpStatusCodes from 'http-status-codes';
4
+ import { constants } from '../config/constants';
5
+
6
+ /**
7
+ * Easily creates an "InvalidJWT" error (401). To throw when
8
+ * the user has a bad/expired JWT
9
+ */
10
+ export function createInvalidJwtError(detail: IApiError): IApiErrorAndInfo {
11
+ return createError(constants.errors.codes.INVALID_JWT, 'Invalid JWT')
12
+ .httpStatus(HttpStatusCodes.UNAUTHORIZED)
13
+ .target('Authorization header')
14
+ .publicMessage('Invalid JWT')
15
+ .logLevel(LogLevel.ERROR)
16
+ .logStackTrace(false)
17
+ .addDetail(detail)
18
+ .build();
19
+ }
20
+
21
+ /**
22
+ * Easily creates an "invalidAuthHedaer" error (401). To throw when
23
+ * the user has a bad authorization header
24
+ */
25
+ export function createInvalidAuthHeaderError(detail: IApiError): IApiErrorAndInfo {
26
+ return createError(
27
+ constants.errors.codes.INVALID_AUTHORIZATION_HEADER,
28
+ 'Invalid Authorization header'
29
+ )
30
+ .httpStatus(HttpStatusCodes.UNAUTHORIZED)
31
+ .target('Authorization header')
32
+ .publicMessage('Invalid Authorization header')
33
+ .logLevel(LogLevel.ERROR)
34
+ .logStackTrace(false)
35
+ .addDetail(detail)
36
+ .build();
37
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Extend Express Request object
3
+ */
4
+ import * as express from 'express';
5
+ import * as _ from 'lodash';
6
+ import { constants } from '../config/constants';
7
+ import { IJWTPayload } from '../models/jwtPayload';
8
+
9
+ export interface IRequestWithJwt extends express.Request {
10
+ /**
11
+ * The JSON Web Token of the request.
12
+ */
13
+ jwt: IJWTPayload;
14
+ }
15
+
16
+ /**
17
+ * Request with JWT Type Guard
18
+ */
19
+ export const isRequestWithJwt = (obj: any): obj is IRequestWithJwt => {
20
+ return (
21
+ obj &&
22
+ _.isObject(obj) &&
23
+ 'get' in obj &&
24
+ 'headers' in obj &&
25
+ constants.requestExtraVariables.JWT in obj
26
+ );
27
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The possible values of the "userType", in a JWT.
3
+ * Note that an empty (or non existent) "userType"
4
+ * means it is the JWT of a regular citizen.
5
+ */
6
+ export enum GluuUserType {
7
+ EMPLOYEE = 'employee',
8
+ SERVICE_ACCOUNT = 'serviceAccount',
9
+ }
@@ -0,0 +1,58 @@
1
+ import * as _ from 'lodash';
2
+
3
+ /**
4
+ * A JWT payload
5
+ */
6
+ export interface IJWTPayload {
7
+ // TODO more comments on those properties!
8
+
9
+ accessToken: string;
10
+ iss: string;
11
+
12
+ // JWT information
13
+ // From Introspect
14
+ exp: number;
15
+ iat: number;
16
+ keyId: number;
17
+
18
+ // From ClientInfo
19
+ displayName: string;
20
+ aud: string;
21
+
22
+ // From UserInfo
23
+ name: string;
24
+ sub: string;
25
+
26
+ /**
27
+ * @deprecated Please use mtlIdentityId or userName instead.
28
+ */
29
+ inum: string;
30
+
31
+ userName: string;
32
+ givenName: string;
33
+ familyName: string;
34
+ userType: string;
35
+ email?: string;
36
+
37
+ // Corresponds to oro identity-id or inum
38
+ mtlIdentityId?: string;
39
+ // available only for employees
40
+ employeeNumber?: string;
41
+ customData?: any;
42
+ }
43
+
44
+ /**
45
+ * IJWTPayload Type Guard
46
+ */
47
+ export const isJWTPayload = (obj: any): obj is IJWTPayload => {
48
+ return (
49
+ obj &&
50
+ _.isObject(obj) &&
51
+ 'accessToken' in obj &&
52
+ 'iss' in obj &&
53
+ 'exp' in obj &&
54
+ 'iat' in obj &&
55
+ 'sub' in obj &&
56
+ 'keyId' in obj
57
+ );
58
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Represents a paginated result set. This follows the doc :
3
+ * https://villemontreal.atlassian.net/wiki/display/AES/REST+API#RESTAPI-Pagination.1
4
+ */
5
+ export interface IPaginatedResult<T> {
6
+ paging: {
7
+ offset: number;
8
+ limit: number;
9
+ totalCount: number;
10
+ };
11
+ items: T[];
12
+ }
13
+
14
+ /**
15
+ * IPaginatedResult Type Guard
16
+ */
17
+ export const isPaginatedResult = (obj: any): obj is IPaginatedResult<any> => {
18
+ return (
19
+ obj &&
20
+ 'paging' in obj &&
21
+ 'offset' in obj.paging &&
22
+ 'limit' in obj.paging &&
23
+ 'totalCount' in obj.paging &&
24
+ 'items' in obj
25
+ );
26
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The content of public key
3
+ */
4
+ export interface IPublicKey {
5
+ id: number;
6
+ algorithm: string;
7
+ publicKey: string;
8
+ state: PublicKeyState;
9
+ createdAt?: string;
10
+ expiresAt?: string;
11
+ }
12
+
13
+ /**
14
+ * List of public key with keyId as key
15
+ */
16
+ export interface IPublicKeys {
17
+ [keyId: number]: IPublicKey;
18
+ }
19
+
20
+ /**
21
+ * Array of public key
22
+ */
23
+ // tslint:disable-next-line:no-empty-interface
24
+ export type IPublicKeysList = Array<IPublicKey>;
25
+
26
+ /**
27
+ * Public key state
28
+ */
29
+ export enum PublicKeyState {
30
+ ACTIVE = 'active',
31
+ EXPIRED = 'expired',
32
+ REVOKED = 'revoked',
33
+ }
@@ -0,0 +1,121 @@
1
+ import { ApiErrorAndInfo } from '@villedemontreal/general-utils/dist/src';
2
+ import { extend } from 'lodash';
3
+ import * as moment from 'moment';
4
+ import { configs } from '../config/configs';
5
+ import { IPublicKey, IPublicKeys } from '../models/publicKey';
6
+ import { createLogger } from '../utils/logger';
7
+ import { IPublicKeyRepository, publicKeyRepository } from './publicKeyRepository';
8
+ const logger = createLogger('CachedPublicKeyRepository');
9
+
10
+ /**
11
+ * Cached Public Key repository
12
+ */
13
+ export interface ICachedPublicKeyRepository extends IPublicKeyRepository {
14
+ /**
15
+ * Clears the public keys in cache
16
+ */
17
+ clearCache(): void;
18
+ }
19
+
20
+ export class CachedPublicKeyRepository implements ICachedPublicKeyRepository {
21
+ /**
22
+ * Next update
23
+ */
24
+ private _nextUpdate: moment.Moment;
25
+
26
+ /**
27
+ * Cached public keys
28
+ */
29
+ private _cachedKeys: IPublicKeys = {};
30
+
31
+ /**
32
+ * Clears the public keys in cache
33
+ */
34
+ public clearCache(): void {
35
+ this._cachedKeys = {};
36
+ this._nextUpdate = moment.utc();
37
+ }
38
+
39
+ /**
40
+ * Return the last public keys
41
+ * @return {Promise<IPublicKeys>}
42
+ */
43
+ public async getAll(): Promise<IPublicKeys> {
44
+ if (!this.isValidCache()) {
45
+ const keysList: IPublicKeys = await publicKeyRepository.getAll();
46
+
47
+ if (keysList) {
48
+ this.updateNextCacheUpdate();
49
+ this._cachedKeys = extend(this._cachedKeys, keysList);
50
+ }
51
+ }
52
+ return this._cachedKeys;
53
+ }
54
+
55
+ /**
56
+ * Return one public key
57
+ * @param {number} keyId
58
+ * @return {Promise<IPublicKey>}
59
+ */
60
+ public async getOne(keyId: number): Promise<IPublicKey> {
61
+ if (!this._cachedKeys[keyId] || !this.isValidCache()) {
62
+ // In case of network error while getting key, return the cached one
63
+ try {
64
+ const key: IPublicKey = await publicKeyRepository.getOne(keyId);
65
+ if (key) {
66
+ this.updateNextCacheUpdate();
67
+ this._cachedKeys[keyId] = key;
68
+ }
69
+ } catch (err) {
70
+ if (!this._cachedKeys[keyId] || !this.isTransientError(err)) {
71
+ throw err;
72
+ }
73
+ logger.error(
74
+ JSON.stringify(err),
75
+ `[getOne] There was an error getting key ${keyId}, cached value was sent as result`
76
+ );
77
+ }
78
+ }
79
+ return this._cachedKeys[keyId];
80
+ }
81
+
82
+ /**
83
+ * Check if the error is a network error, server error or
84
+ * If true, do not throw error
85
+ * @return {boolean}
86
+ */
87
+ private isTransientError(err: any): boolean {
88
+ if (err instanceof ApiErrorAndInfo) {
89
+ if (err.httpStatus >= 500 || err.httpStatus === 429) return true;
90
+ return false;
91
+ }
92
+ // No status on a superagent network error
93
+ if (!err.status) return true;
94
+ const errStatus: number = +err.status;
95
+ if (errStatus >= 500 || errStatus === 429) return true;
96
+ return false;
97
+ }
98
+
99
+ /**
100
+ * Check if the cache is stil valid
101
+ * @return {boolean}
102
+ */
103
+ private isValidCache(): boolean {
104
+ if (!this._nextUpdate || moment.utc().diff(this._nextUpdate) >= 0) {
105
+ return false;
106
+ }
107
+
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * Update the date of the next update
113
+ * @return
114
+ */
115
+ private updateNextCacheUpdate(): void {
116
+ this._nextUpdate = moment.utc().add(configs.getCacheDuration(), 'seconds');
117
+ }
118
+ }
119
+
120
+ export const cachedPublicKeyRepository: ICachedPublicKeyRepository =
121
+ new CachedPublicKeyRepository();
@@ -0,0 +1,75 @@
1
+ import { httpUtils } from '@villedemontreal/http-request';
2
+ import { isArray, keyBy } from 'lodash';
3
+ import * as superagent from 'superagent';
4
+
5
+ import { createError } from '@villedemontreal/general-utils';
6
+ import { configs } from '../config/configs';
7
+ import { constants } from '../config/constants';
8
+ import { IPaginatedResult } from '../models/pagination';
9
+ import { IPublicKey, IPublicKeys } from '../models/publicKey';
10
+
11
+ /**
12
+ * Public Key repository
13
+ */
14
+ export interface IPublicKeyRepository {
15
+ /**
16
+ * Return the last public keys
17
+ * @return {Promise<IPublicKeys>}
18
+ */
19
+ getAll(): Promise<IPublicKeys>;
20
+
21
+ /**
22
+ * Returns one public key.
23
+ * Returns null if not found.
24
+ * @param {number} keyId
25
+ * @return {Promise<IPublicKey>}
26
+ */
27
+ getOne(keyId: number): Promise<IPublicKey>;
28
+ }
29
+
30
+ class PublicKeyRepository implements IPublicKeyRepository {
31
+ public async getAll(): Promise<IPublicKeys> {
32
+ const url = `${httpUtils.urlJoin(
33
+ configs.getHost(),
34
+ configs.getEndpoint()
35
+ )}?${configs.getFetchKeysParameters()}`;
36
+ const request = superagent.get(url);
37
+
38
+ const response = await httpUtils.send(request);
39
+ if (!response.ok) {
40
+ throw new Error(`An error occured calling ${url} : ${response.error}`);
41
+ }
42
+
43
+ const data: IPaginatedResult<IPublicKey> = response.body;
44
+ if (data && data.items && isArray(data.items)) {
45
+ const newKeys: IPublicKeys = keyBy(data.items, (item) => item.id);
46
+ return newKeys;
47
+ }
48
+ return null;
49
+ }
50
+
51
+ public async getOne(keyId: number): Promise<IPublicKey> {
52
+ const url = httpUtils.urlJoin(configs.getHost(), configs.getEndpoint(), keyId.toString());
53
+ const request = superagent.get(url);
54
+
55
+ const response = await httpUtils.send(request);
56
+ if (!response.ok) {
57
+ // ==========================================
58
+ // Not found: we return null
59
+ // ==========================================
60
+ if (response.status === 404) {
61
+ return null;
62
+ }
63
+
64
+ throw createError(
65
+ constants.errors.codes.UNABLE_TO_GET_PUBLIC_KEY,
66
+ `An error occured calling ${url} : ${response.error}`
67
+ )
68
+ .httpStatus(response.status)
69
+ .build();
70
+ }
71
+ return response.body;
72
+ }
73
+ }
74
+
75
+ export const publicKeyRepository: IPublicKeyRepository = new PublicKeyRepository();