@sudoplatform/sudo-common 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/.gitignore +1 -0
  2. package/dependencies-report.json +117 -7
  3. package/docs/assets/highlight.css +1 -1
  4. package/docs/assets/main.js +2 -2
  5. package/docs/assets/search.js +1 -1
  6. package/docs/assets/style.css +29 -0
  7. package/docs/classes/AccountLockedError.html +3 -3
  8. package/docs/classes/AuthenticationError.html +3 -3
  9. package/docs/classes/Base64.html +2 -2
  10. package/docs/classes/Buffer.html +2 -2
  11. package/docs/classes/ConfigurationNotSetError.html +3 -3
  12. package/docs/classes/ConfigurationSetNotFoundError.html +3 -3
  13. package/docs/classes/DecodeError.html +3 -3
  14. package/docs/classes/DefaultConfigurationManager.html +13 -6
  15. package/docs/classes/DefaultLogger.html +7 -7
  16. package/docs/classes/DefaultSudoKeyArchive.html +13 -13
  17. package/docs/classes/DefaultSudoKeyManager.html +28 -28
  18. package/docs/classes/FatalError.html +3 -3
  19. package/docs/classes/IllegalArgumentError.html +3 -3
  20. package/docs/classes/IllegalStateError.html +3 -3
  21. package/docs/classes/InsufficientEntitlementsError.html +3 -3
  22. package/docs/classes/InvalidOwnershipProofError.html +3 -3
  23. package/docs/classes/InvalidTokenError.html +3 -3
  24. package/docs/classes/KeyArchiveDecodingError.html +3 -3
  25. package/docs/classes/KeyArchiveIncorrectPasswordError.html +3 -3
  26. package/docs/classes/KeyArchiveMissingError.html +3 -3
  27. package/docs/classes/KeyArchiveNoPasswordRequiredError.html +3 -3
  28. package/docs/classes/KeyArchivePasswordRequiredError.html +3 -3
  29. package/docs/classes/KeyArchiveTypeError.html +3 -3
  30. package/docs/classes/KeyArchiveUnknownKeyTypeError.html +3 -3
  31. package/docs/classes/KeyArchiveVersionError.html +3 -3
  32. package/docs/classes/KeyNotFoundError.html +3 -3
  33. package/docs/classes/KeyStoreNotExportableError.html +3 -3
  34. package/docs/classes/LimitExceededError.html +3 -3
  35. package/docs/classes/NoEntitlementsError.html +3 -3
  36. package/docs/classes/NotAuthorizedError.html +3 -3
  37. package/docs/classes/NotRegisteredError.html +3 -3
  38. package/docs/classes/NotSignedInError.html +3 -3
  39. package/docs/classes/OperationNotImplementedError.html +3 -3
  40. package/docs/classes/RegisterError.html +3 -3
  41. package/docs/classes/RequestFailedError.html +5 -5
  42. package/docs/classes/ServiceError.html +3 -3
  43. package/docs/classes/SignOutError.html +3 -3
  44. package/docs/classes/SudoCryptoProviderDefaults.html +1 -1
  45. package/docs/classes/UnknownGraphQLError.html +3 -3
  46. package/docs/classes/UnrecognizedAlgorithmError.html +3 -3
  47. package/docs/classes/UserNotConfirmedError.html +3 -3
  48. package/docs/classes/VersionMismatchError.html +3 -3
  49. package/docs/enums/CachePolicy.html +2 -2
  50. package/docs/enums/EncryptionAlgorithm.html +2 -2
  51. package/docs/enums/KeyArchiveKeyType.html +2 -2
  52. package/docs/enums/KeyDataKeyFormat.html +3 -3
  53. package/docs/enums/KeyDataKeyType.html +2 -2
  54. package/docs/enums/ListOperationResultStatus.html +4 -4
  55. package/docs/enums/PublicKeyFormat.html +1 -1
  56. package/docs/index.html +2 -2
  57. package/docs/interfaces/AsymmetricEncryptionOptions.html +2 -2
  58. package/docs/interfaces/BooleanFilter.html +2 -2
  59. package/docs/interfaces/ConfigurationManager.html +13 -6
  60. package/docs/interfaces/KeyData.html +2 -2
  61. package/docs/interfaces/ListOperationFailureResult.html +2 -2
  62. package/docs/interfaces/ListOperationPartialResult.html +4 -4
  63. package/docs/interfaces/ListOperationSuccessResult.html +3 -3
  64. package/docs/interfaces/ListOutput.html +2 -2
  65. package/docs/interfaces/Logger.html +7 -7
  66. package/docs/interfaces/Owner.html +1 -1
  67. package/docs/interfaces/PublicKey.html +1 -1
  68. package/docs/interfaces/ServiceCompatibilityInfo.html +17 -0
  69. package/docs/interfaces/StringFilter.html +2 -2
  70. package/docs/interfaces/SudoCryptoProvider.html +28 -28
  71. package/docs/interfaces/SudoKeyArchive.html +10 -10
  72. package/docs/interfaces/SudoKeyManager.html +28 -28
  73. package/docs/interfaces/SymmetricEncryptionOptions.html +2 -2
  74. package/docs/interfaces/ValidationResult.html +11 -0
  75. package/docs/modules.html +5 -5
  76. package/github/.gitignore +1 -0
  77. package/github/docs/assets/highlight.css +1 -1
  78. package/github/docs/assets/main.js +2 -2
  79. package/github/docs/assets/search.js +1 -1
  80. package/github/docs/assets/style.css +29 -0
  81. package/github/docs/classes/AccountLockedError.html +3 -3
  82. package/github/docs/classes/AuthenticationError.html +3 -3
  83. package/github/docs/classes/Base64.html +2 -2
  84. package/github/docs/classes/Buffer.html +2 -2
  85. package/github/docs/classes/ConfigurationNotSetError.html +3 -3
  86. package/github/docs/classes/ConfigurationSetNotFoundError.html +3 -3
  87. package/github/docs/classes/DecodeError.html +3 -3
  88. package/github/docs/classes/DefaultConfigurationManager.html +13 -6
  89. package/github/docs/classes/DefaultLogger.html +7 -7
  90. package/github/docs/classes/DefaultSudoKeyArchive.html +13 -13
  91. package/github/docs/classes/DefaultSudoKeyManager.html +28 -28
  92. package/github/docs/classes/FatalError.html +3 -3
  93. package/github/docs/classes/IllegalArgumentError.html +3 -3
  94. package/github/docs/classes/IllegalStateError.html +3 -3
  95. package/github/docs/classes/InsufficientEntitlementsError.html +3 -3
  96. package/github/docs/classes/InvalidOwnershipProofError.html +3 -3
  97. package/github/docs/classes/InvalidTokenError.html +3 -3
  98. package/github/docs/classes/KeyArchiveDecodingError.html +3 -3
  99. package/github/docs/classes/KeyArchiveIncorrectPasswordError.html +3 -3
  100. package/github/docs/classes/KeyArchiveMissingError.html +3 -3
  101. package/github/docs/classes/KeyArchiveNoPasswordRequiredError.html +3 -3
  102. package/github/docs/classes/KeyArchivePasswordRequiredError.html +3 -3
  103. package/github/docs/classes/KeyArchiveTypeError.html +3 -3
  104. package/github/docs/classes/KeyArchiveUnknownKeyTypeError.html +3 -3
  105. package/github/docs/classes/KeyArchiveVersionError.html +3 -3
  106. package/github/docs/classes/KeyNotFoundError.html +3 -3
  107. package/github/docs/classes/KeyStoreNotExportableError.html +3 -3
  108. package/github/docs/classes/LimitExceededError.html +3 -3
  109. package/github/docs/classes/NoEntitlementsError.html +3 -3
  110. package/github/docs/classes/NotAuthorizedError.html +3 -3
  111. package/github/docs/classes/NotRegisteredError.html +3 -3
  112. package/github/docs/classes/NotSignedInError.html +3 -3
  113. package/github/docs/classes/OperationNotImplementedError.html +3 -3
  114. package/github/docs/classes/RegisterError.html +3 -3
  115. package/github/docs/classes/RequestFailedError.html +5 -5
  116. package/github/docs/classes/ServiceError.html +3 -3
  117. package/github/docs/classes/SignOutError.html +3 -3
  118. package/github/docs/classes/SudoCryptoProviderDefaults.html +1 -1
  119. package/github/docs/classes/UnknownGraphQLError.html +3 -3
  120. package/github/docs/classes/UnrecognizedAlgorithmError.html +3 -3
  121. package/github/docs/classes/UserNotConfirmedError.html +3 -3
  122. package/github/docs/classes/VersionMismatchError.html +3 -3
  123. package/github/docs/enums/CachePolicy.html +2 -2
  124. package/github/docs/enums/EncryptionAlgorithm.html +2 -2
  125. package/github/docs/enums/KeyArchiveKeyType.html +2 -2
  126. package/github/docs/enums/KeyDataKeyFormat.html +3 -3
  127. package/github/docs/enums/KeyDataKeyType.html +2 -2
  128. package/github/docs/enums/ListOperationResultStatus.html +4 -4
  129. package/github/docs/enums/PublicKeyFormat.html +1 -1
  130. package/github/docs/index.html +2 -2
  131. package/github/docs/interfaces/AsymmetricEncryptionOptions.html +2 -2
  132. package/github/docs/interfaces/BooleanFilter.html +2 -2
  133. package/github/docs/interfaces/ConfigurationManager.html +13 -6
  134. package/github/docs/interfaces/KeyData.html +2 -2
  135. package/github/docs/interfaces/ListOperationFailureResult.html +2 -2
  136. package/github/docs/interfaces/ListOperationPartialResult.html +4 -4
  137. package/github/docs/interfaces/ListOperationSuccessResult.html +3 -3
  138. package/github/docs/interfaces/ListOutput.html +2 -2
  139. package/github/docs/interfaces/Logger.html +7 -7
  140. package/github/docs/interfaces/Owner.html +1 -1
  141. package/github/docs/interfaces/PublicKey.html +1 -1
  142. package/github/docs/interfaces/ServiceCompatibilityInfo.html +17 -0
  143. package/github/docs/interfaces/StringFilter.html +2 -2
  144. package/github/docs/interfaces/SudoCryptoProvider.html +28 -28
  145. package/github/docs/interfaces/SudoKeyArchive.html +10 -10
  146. package/github/docs/interfaces/SudoKeyManager.html +28 -28
  147. package/github/docs/interfaces/SymmetricEncryptionOptions.html +2 -2
  148. package/github/docs/interfaces/ValidationResult.html +11 -0
  149. package/github/docs/modules.html +5 -5
  150. package/github/package.json +27 -23
  151. package/github/src/configurationManager/defaultConfigurationManager.ts +156 -7
  152. package/github/test/integration/defaultConfigurationManager.spec.ts +85 -0
  153. package/github/test/unit/configurationManager/defaultConfigurationManager.spec.ts +506 -0
  154. package/{test → github/test/unit}/errors/error.spec.ts +1 -1
  155. package/github/test/{sudoKeyArchive → unit}/keyType.spec.ts +0 -0
  156. package/{test → github/test/unit}/logging/logger.spec.ts +1 -1
  157. package/github/test/{sudoKeyArchive → unit}/sudoKeyArchive.spec.ts +0 -0
  158. package/github/test/{sudoKeyManager → unit}/sudoKeyManager.spec.ts +0 -0
  159. package/github/test/{utils → unit/utils}/base64.spec.ts +1 -1
  160. package/github/test/{utils → unit/utils}/buffer.spec.ts +1 -1
  161. package/{test → github/test/unit}/utils/listOperationResult.spec.ts +1 -1
  162. package/github/tsconfig.test.json +3 -1
  163. package/github/yarn.lock +1628 -1299
  164. package/lib/configurationManager/defaultConfigurationManager.d.ts +57 -0
  165. package/lib/configurationManager/defaultConfigurationManager.js +102 -7
  166. package/package.json +27 -23
  167. package/src/configurationManager/defaultConfigurationManager.ts +156 -7
  168. package/test/integration/defaultConfigurationManager.spec.ts +85 -0
  169. package/test/unit/configurationManager/defaultConfigurationManager.spec.ts +506 -0
  170. package/{github/test → test/unit}/errors/error.spec.ts +1 -1
  171. package/test/{sudoKeyArchive → unit}/keyType.spec.ts +0 -0
  172. package/{github/test → test/unit}/logging/logger.spec.ts +1 -1
  173. package/test/{sudoKeyArchive → unit}/sudoKeyArchive.spec.ts +0 -0
  174. package/test/{sudoKeyManager → unit}/sudoKeyManager.spec.ts +0 -0
  175. package/test/{utils → unit/utils}/base64.spec.ts +1 -1
  176. package/test/{utils → unit/utils}/buffer.spec.ts +1 -1
  177. package/{github/test → test/unit}/utils/listOperationResult.spec.ts +1 -1
  178. package/tsconfig.test.json +3 -1
  179. package/yarn.lock +1628 -1299
  180. package/github/test/configurationManager/defaultConfigurationManager.spec.ts +0 -185
  181. package/test/configurationManager/defaultConfigurationManager.spec.ts +0 -185
@@ -1,4 +1,50 @@
1
1
  import * as t from 'io-ts';
2
+ /**
3
+ * Service compatibility information.
4
+ */
5
+ export interface ServiceCompatibilityInfo {
6
+ /**
7
+ * The name of the service associated with the compatibility info. This matches one of the
8
+ * service name present in sudoplatformconfig.json.
9
+ */
10
+ name: string;
11
+ /**
12
+ * The version of the service config present in sudoplatformconfig.json. It defaults
13
+ * to 1 if not present.
14
+ */
15
+ configVersion: number;
16
+ /**
17
+ * The minimum supported service config version currently supported by the backend.
18
+ */
19
+ minSupportedVersion?: number;
20
+ /**
21
+ * Any service config version less than or equal to this version is considered deprecated
22
+ * and the backend may remove the support for those versions after a grace period.
23
+ */
24
+ deprecatedVersion?: number;
25
+ /**
26
+ * After this time any deprecated service config versions will no longer be compatible
27
+ * with the backend. It is recommended to warn the user prior to the deprecation grace.
28
+ */
29
+ deprecationGrace?: Date;
30
+ }
31
+ /**
32
+ * Result returned by `validateConfig` API if an incompatible client config is found when compared
33
+ * to the deployed backend services.
34
+ */
35
+ export interface ValidationResult {
36
+ /**
37
+ * The list of incompatible services. The client must be upgraded to the latest
38
+ * version in order to use these services.
39
+ */
40
+ incompatible: ServiceCompatibilityInfo[];
41
+ /**
42
+ * The list of services that will be made incompatible with the current version of the
43
+ * client. The users should be warned that after the specified grace period these services will be
44
+ * made incompatible.
45
+ */
46
+ deprecated: ServiceCompatibilityInfo[];
47
+ }
2
48
  /**
3
49
  * Interface that encapsulates the APIs common to all configuration manager implementations.
4
50
  * A configuration manager should be a singleton that holds reference to a parsed config
@@ -50,6 +96,16 @@ export interface ConfigurationManager {
50
96
  * @throws {@link DecodeError}
51
97
  */
52
98
  bindConfigSet<T>(codec: t.Mixed, namespace?: string): T;
99
+ /**
100
+ * Validates the client configuration (sudoplatformconfig.json) against the currently deployed set of
101
+ * backend services. If the client configuration is valid, i.e. the client is compatible will all deployed
102
+ * backend services, then the call will complete with `success` result. If any part of the client
103
+ * configuration is incompatible then a detailed information on the incompatible service will be
104
+ * returned in `failure` result. See `SudoConfigManagerError.compatibilityIssueFound` for more details.
105
+ *
106
+ * @return validation result with the details of incompatible or deprecated service configurations.
107
+ */
108
+ validateConfig(): Promise<ValidationResult>;
53
109
  }
54
110
  /**
55
111
  * Singleton to manage configuration
@@ -63,4 +119,5 @@ export declare class DefaultConfigurationManager implements ConfigurationManager
63
119
  getConfigSet(namespace?: string): string;
64
120
  bind<T>(configSet: unknown, codec: t.Mixed): T;
65
121
  bindConfigSet<T>(codec: t.Mixed, namespace?: string): T;
122
+ validateConfig(): Promise<ValidationResult>;
66
123
  }
@@ -9,10 +9,16 @@ var _Either = require("fp-ts/lib/Either");
9
9
 
10
10
  var _pipeable = require("fp-ts/lib/pipeable");
11
11
 
12
+ var AWS = _interopRequireWildcard(require("aws-sdk"));
13
+
12
14
  var _PathReporter = require("io-ts/lib/PathReporter");
13
15
 
14
16
  var _error = require("../errors/error");
15
17
 
18
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
19
+
20
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
21
+
16
22
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
17
23
 
18
24
  /**
@@ -40,14 +46,10 @@ class DefaultConfigurationManager {
40
46
  getConfigSet(namespace) {
41
47
  if (!this._config) {
42
48
  throw new _error.ConfigurationNotSetError();
43
- } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
44
-
45
-
46
- const parsed = JSON.parse(this._config); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
47
-
48
- const configSet = namespace ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
49
- parsed[namespace] : parsed; // eslint-disable-next-line @typescript-eslint/no-unsafe-return
49
+ }
50
50
 
51
+ const parsed = JSON.parse(this._config);
52
+ const configSet = namespace ? parsed[namespace] : parsed;
51
53
  return configSet;
52
54
  }
53
55
 
@@ -68,6 +70,99 @@ class DefaultConfigurationManager {
68
70
  return this.bind(configSet, codec);
69
71
  }
70
72
 
73
+ async validateConfig() {
74
+ var _config$identityServi, _config$identityServi2, _objects$Contents;
75
+
76
+ if (!this._config) {
77
+ return {
78
+ incompatible: [],
79
+ deprecated: []
80
+ };
81
+ }
82
+
83
+ const config = JSON.parse(this._config);
84
+ const region = (_config$identityServi = config.identityService) === null || _config$identityServi === void 0 ? void 0 : _config$identityServi.region;
85
+ const bucket = (_config$identityServi2 = config.identityService) === null || _config$identityServi2 === void 0 ? void 0 : _config$identityServi2.serviceInfoBucket;
86
+
87
+ if (!(region && bucket)) {
88
+ return {
89
+ incompatible: [],
90
+ deprecated: []
91
+ };
92
+ }
93
+
94
+ const s3Client = new AWS.S3({
95
+ region: region
96
+ });
97
+ const incompatible = [];
98
+ const deprecated = [];
99
+ const objects = await s3Client.makeUnauthenticatedRequest('listObjects', {
100
+ Bucket: bucket
101
+ }).promise();
102
+
103
+ if (!((_objects$Contents = objects.Contents) !== null && _objects$Contents !== void 0 && _objects$Contents.length)) {
104
+ return {
105
+ incompatible: [],
106
+ deprecated: []
107
+ };
108
+ }
109
+
110
+ const keys = objects.Contents.map(object => object.Key); // Only fetch the service info docs for the services that are present in client config
111
+ // to minimize the network calls.
112
+
113
+ const keysToFetch = keys.filter(key => key.endsWith('.json') && config[key.replace('.json', '')]);
114
+
115
+ for (const key of keysToFetch) {
116
+ const object = await s3Client.makeUnauthenticatedRequest('getObject', {
117
+ Key: key,
118
+ Bucket: bucket
119
+ }).promise();
120
+ let body;
121
+
122
+ if (typeof object.Body === 'string') {
123
+ body = object.Body;
124
+ } else if (ArrayBuffer.isView(object.Body)) {
125
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
126
+ body = new TextDecoder().decode(object.Body);
127
+ } else {
128
+ continue;
129
+ }
130
+
131
+ const json = JSON.parse(body);
132
+ const serviceName = key.replace('.json', '');
133
+ const serviceInfo = json[serviceName];
134
+ const serviceConfig = config[serviceName];
135
+
136
+ if (serviceInfo && serviceConfig) {
137
+ const currentVersion = serviceConfig.version ?? 1;
138
+ const deprecationGrace = serviceInfo.deprecationGrace ?? -1;
139
+ const compatibilityInfo = {
140
+ name: serviceName,
141
+ configVersion: currentVersion,
142
+ minSupportedVersion: serviceInfo.minVersion,
143
+ deprecatedVersion: serviceInfo.deprecated,
144
+ deprecationGrace: deprecationGrace != -1 ? new Date(deprecationGrace) : undefined
145
+ }; // If the service config in `sudoplatformconfig.json` is less than the
146
+ // minimum supported version then the client is incompatible.
147
+
148
+ if (currentVersion < (compatibilityInfo.minSupportedVersion ?? 0)) {
149
+ incompatible.push(compatibilityInfo);
150
+ } // If the service config is less than or equal to the deprecated version
151
+ // then it will be made incompatible after the deprecation grace.
152
+
153
+
154
+ if (currentVersion <= (compatibilityInfo.deprecatedVersion ?? 0)) {
155
+ deprecated.push(compatibilityInfo);
156
+ }
157
+ }
158
+ }
159
+
160
+ return {
161
+ incompatible,
162
+ deprecated
163
+ };
164
+ }
165
+
71
166
  }
72
167
 
73
168
  exports.DefaultConfigurationManager = DefaultConfigurationManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudoplatform/sudo-common",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "author": "Anonyome Labs, Inc.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,9 @@
14
14
  "lint": "yarn lint:eslint && yarn lint:prettier",
15
15
  "lint:eslint": "eslint \"{bin,integration-tests,src,test}/**/*.{ts,tsx}\"",
16
16
  "lint:prettier": "prettier -l \"{bin,integration-tests,src,test}/**/*.{js,json,jsx,ts,tsx}\"",
17
- "test": "jest",
17
+ "unit-test": "jest test/unit",
18
+ "integration-test": "jest test/integration",
19
+ "test": "yarn unit-test && yarn integration-test",
18
20
  "build": "yarn clean && yarn build:transpile && yarn build:verify && yarn build:declarations && yarn build:doc",
19
21
  "build:verify": "yarn audit-with-suppressions && yarn lint && yarn test --coverage",
20
22
  "build:transpile": "babel src --out-dir lib --extensions '.ts'",
@@ -41,38 +43,40 @@
41
43
  },
42
44
  "dependencies": {
43
45
  "apollo-client": "^2.6.10",
44
- "browser-bunyan": "^1.6.3",
45
- "fflate": "^0.7.1",
46
+ "aws-sdk": "^2.973.0",
47
+ "browser-bunyan": "^1.8.0",
48
+ "fflate": "^0.7.3",
46
49
  "graphql": "^15.6.1"
47
50
  },
48
51
  "peerDependencies": {
49
52
  "io-ts": "^2.2.10"
50
53
  },
51
54
  "devDependencies": {
52
- "@babel/cli": "^7.15.7",
53
- "@babel/core": "^7.15.8",
54
- "@babel/plugin-proposal-class-properties": "^7.14.5",
55
- "@babel/plugin-proposal-private-methods": "^7.14.5",
56
- "@babel/preset-env": "^7.15.8",
57
- "@babel/preset-typescript": "^7.15.0",
58
- "@types/jest": "^27.0.2",
59
- "@typescript-eslint/eslint-plugin": "^5.1.0",
60
- "@typescript-eslint/parser": "^5.1.0",
61
- "eslint": "^8.0.1",
62
- "eslint-config-prettier": "^8.3.0",
63
- "eslint-plugin-import": "^2.25.2",
55
+ "@babel/cli": "^7.16.6",
56
+ "@babel/core": "^7.17.5",
57
+ "@babel/plugin-proposal-class-properties": "^7.16.7",
58
+ "@babel/plugin-proposal-private-methods": "^7.16.11",
59
+ "@babel/preset-env": "^7.16.11",
60
+ "@babel/preset-typescript": "^7.16.7",
61
+ "@types/jest": "^27.4.0",
62
+ "@typescript-eslint/eslint-plugin": "^5.12.1",
63
+ "@typescript-eslint/parser": "^5.12.1",
64
+ "aws-sdk-mock": "^5.6.2",
65
+ "eslint": "^8.9.0",
66
+ "eslint-config-prettier": "^8.4.0",
67
+ "eslint-plugin-import": "^2.25.4",
64
68
  "eslint-plugin-prettier": "^4.0.0",
65
- "fp-ts": "^2.11.5",
69
+ "fp-ts": "^2.11.8",
66
70
  "io-ts": "^2.2.16",
67
71
  "io-ts-types": "^0.5.16",
68
72
  "isomorphic-webcrypto": "^2.3.8",
69
- "jest": "^27.3.1",
70
- "monocle-ts": "^2.3.11",
71
- "newtype-ts": "^0.3.2",
72
- "prettier": "^2.4.1",
73
+ "jest": "^27.5.1",
74
+ "monocle-ts": "^2.3.12",
75
+ "newtype-ts": "^0.3.5",
76
+ "prettier": "^2.5.1",
73
77
  "rimraf": "^3.0.2",
74
78
  "ts-mockito": "^2.6.1",
75
- "typedoc": "^0.22.6",
76
- "typescript": "^4.4.4"
79
+ "typedoc": "^0.22.12",
80
+ "typescript": "^4.5.5"
77
81
  }
78
82
  }
@@ -1,6 +1,12 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
4
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
5
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
1
6
  import { fold, left } from 'fp-ts/lib/Either'
2
7
  import { pipe } from 'fp-ts/lib/pipeable'
3
8
  import * as t from 'io-ts'
9
+ import * as AWS from 'aws-sdk'
4
10
  import { PathReporter } from 'io-ts/lib/PathReporter'
5
11
  import {
6
12
  ConfigurationNotSetError,
@@ -8,6 +14,54 @@ import {
8
14
  ConfigurationSetNotFoundError,
9
15
  } from '../errors/error'
10
16
 
17
+ /**
18
+ * Service compatibility information.
19
+ */
20
+ export interface ServiceCompatibilityInfo {
21
+ /**
22
+ * The name of the service associated with the compatibility info. This matches one of the
23
+ * service name present in sudoplatformconfig.json.
24
+ */
25
+ name: string
26
+ /**
27
+ * The version of the service config present in sudoplatformconfig.json. It defaults
28
+ * to 1 if not present.
29
+ */
30
+ configVersion: number
31
+ /**
32
+ * The minimum supported service config version currently supported by the backend.
33
+ */
34
+ minSupportedVersion?: number
35
+ /**
36
+ * Any service config version less than or equal to this version is considered deprecated
37
+ * and the backend may remove the support for those versions after a grace period.
38
+ */
39
+ deprecatedVersion?: number
40
+ /**
41
+ * After this time any deprecated service config versions will no longer be compatible
42
+ * with the backend. It is recommended to warn the user prior to the deprecation grace.
43
+ */
44
+ deprecationGrace?: Date
45
+ }
46
+
47
+ /**
48
+ * Result returned by `validateConfig` API if an incompatible client config is found when compared
49
+ * to the deployed backend services.
50
+ */
51
+ export interface ValidationResult {
52
+ /**
53
+ * The list of incompatible services. The client must be upgraded to the latest
54
+ * version in order to use these services.
55
+ */
56
+ incompatible: ServiceCompatibilityInfo[]
57
+ /**
58
+ * The list of services that will be made incompatible with the current version of the
59
+ * client. The users should be warned that after the specified grace period these services will be
60
+ * made incompatible.
61
+ */
62
+ deprecated: ServiceCompatibilityInfo[]
63
+ }
64
+
11
65
  /**
12
66
  * Interface that encapsulates the APIs common to all configuration manager implementations.
13
67
  * A configuration manager should be a singleton that holds reference to a parsed config
@@ -62,6 +116,17 @@ export interface ConfigurationManager {
62
116
  * @throws {@link DecodeError}
63
117
  */
64
118
  bindConfigSet<T>(codec: t.Mixed, namespace?: string): T
119
+
120
+ /**
121
+ * Validates the client configuration (sudoplatformconfig.json) against the currently deployed set of
122
+ * backend services. If the client configuration is valid, i.e. the client is compatible will all deployed
123
+ * backend services, then the call will complete with `success` result. If any part of the client
124
+ * configuration is incompatible then a detailed information on the incompatible service will be
125
+ * returned in `failure` result. See `SudoConfigManagerError.compatibilityIssueFound` for more details.
126
+ *
127
+ * @return validation result with the details of incompatible or deprecated service configurations.
128
+ */
129
+ validateConfig(): Promise<ValidationResult>
65
130
  }
66
131
 
67
132
  /**
@@ -95,15 +160,9 @@ export class DefaultConfigurationManager implements ConfigurationManager {
95
160
  throw new ConfigurationNotSetError()
96
161
  }
97
162
 
98
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
99
163
  const parsed = JSON.parse(this._config)
100
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
101
- const configSet = namespace
102
- ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
103
- parsed[namespace]
104
- : parsed
164
+ const configSet = namespace ? parsed[namespace] : parsed
105
165
 
106
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
107
166
  return configSet
108
167
  }
109
168
 
@@ -129,4 +188,94 @@ export class DefaultConfigurationManager implements ConfigurationManager {
129
188
 
130
189
  return this.bind<T>(configSet, codec)
131
190
  }
191
+
192
+ public async validateConfig(): Promise<ValidationResult> {
193
+ if (!this._config) {
194
+ return { incompatible: [], deprecated: [] }
195
+ }
196
+
197
+ const config = JSON.parse(this._config)
198
+
199
+ const region = config.identityService?.region
200
+ const bucket = config.identityService?.serviceInfoBucket
201
+
202
+ if (!(region && bucket)) {
203
+ return { incompatible: [], deprecated: [] }
204
+ }
205
+
206
+ const s3Client = new AWS.S3({ region: region })
207
+
208
+ const incompatible: ServiceCompatibilityInfo[] = []
209
+ const deprecated: ServiceCompatibilityInfo[] = []
210
+
211
+ const objects = await s3Client
212
+ .makeUnauthenticatedRequest('listObjects', {
213
+ Bucket: bucket,
214
+ })
215
+ .promise()
216
+
217
+ if (!objects.Contents?.length) {
218
+ return { incompatible: [], deprecated: [] }
219
+ }
220
+
221
+ const keys = objects.Contents.map((object: any) => object.Key)
222
+
223
+ // Only fetch the service info docs for the services that are present in client config
224
+ // to minimize the network calls.
225
+ const keysToFetch = keys.filter(
226
+ (key: string) =>
227
+ key.endsWith('.json') && config[key.replace('.json', '')],
228
+ )
229
+
230
+ for (const key of keysToFetch) {
231
+ const object = await s3Client
232
+ .makeUnauthenticatedRequest('getObject', {
233
+ Key: key,
234
+ Bucket: bucket,
235
+ })
236
+ .promise()
237
+
238
+ let body: string
239
+
240
+ if (typeof object.Body === 'string') {
241
+ body = object.Body
242
+ } else if (ArrayBuffer.isView(object.Body)) {
243
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
244
+ body = new TextDecoder().decode(object.Body)
245
+ } else {
246
+ continue
247
+ }
248
+
249
+ const json = JSON.parse(body)
250
+ const serviceName = key.replace('.json', '')
251
+ const serviceInfo: any = json[serviceName]
252
+ const serviceConfig: any = config[serviceName]
253
+ if (serviceInfo && serviceConfig) {
254
+ const currentVersion: number = serviceConfig.version ?? 1
255
+ const deprecationGrace: number = serviceInfo.deprecationGrace ?? -1
256
+ const compatibilityInfo: ServiceCompatibilityInfo = {
257
+ name: serviceName,
258
+ configVersion: currentVersion,
259
+ minSupportedVersion: serviceInfo.minVersion,
260
+ deprecatedVersion: serviceInfo.deprecated,
261
+ deprecationGrace:
262
+ deprecationGrace != -1 ? new Date(deprecationGrace) : undefined,
263
+ }
264
+
265
+ // If the service config in `sudoplatformconfig.json` is less than the
266
+ // minimum supported version then the client is incompatible.
267
+ if (currentVersion < (compatibilityInfo.minSupportedVersion ?? 0)) {
268
+ incompatible.push(compatibilityInfo)
269
+ }
270
+
271
+ // If the service config is less than or equal to the deprecated version
272
+ // then it will be made incompatible after the deprecation grace.
273
+ if (currentVersion <= (compatibilityInfo.deprecatedVersion ?? 0)) {
274
+ deprecated.push(compatibilityInfo)
275
+ }
276
+ }
277
+ }
278
+
279
+ return { incompatible, deprecated }
280
+ }
132
281
  }
@@ -0,0 +1,85 @@
1
+ import { DefaultConfigurationManager } from '../../src/configurationManager/defaultConfigurationManager'
2
+ import { readFileSync, existsSync } from 'fs'
3
+ import { TextEncoder, TextDecoder } from 'util'
4
+
5
+ global.TextEncoder = TextEncoder
6
+ global.TextDecoder = TextDecoder
7
+
8
+ describe('configuration manager', () => {
9
+ const configFilePath = 'config/sudoplatformconfig.json'
10
+ if (existsSync(configFilePath)) {
11
+ const config = readFileSync(configFilePath, 'utf-8')
12
+ const json = JSON.parse(config)
13
+
14
+ describe('validateConfig', () => {
15
+ it('should return deprecated service info if equal to deprecated version', async () => {
16
+ const config = {
17
+ identityService: {
18
+ region: json.identityService.region,
19
+ serviceInfoBucket: json.identityService.serviceInfoBucket,
20
+ },
21
+ vcService: {
22
+ version: 3,
23
+ },
24
+ }
25
+
26
+ const manager = DefaultConfigurationManager.getInstance().setConfig(
27
+ JSON.stringify(config),
28
+ )
29
+
30
+ await expect(manager.validateConfig()).resolves.toEqual({
31
+ deprecated: [
32
+ {
33
+ configVersion: 3,
34
+ minSupportedVersion: 2,
35
+ deprecatedVersion: 3,
36
+ name: 'vcService',
37
+ },
38
+ ],
39
+ incompatible: [],
40
+ })
41
+ })
42
+
43
+ it('should return incompatible service info if less than min version', async () => {
44
+ const config = {
45
+ identityService: {
46
+ region: json.identityService.region,
47
+ serviceInfoBucket: json.identityService.serviceInfoBucket,
48
+ },
49
+ vcService: {
50
+ version: 1,
51
+ },
52
+ }
53
+
54
+ const manager = DefaultConfigurationManager.getInstance().setConfig(
55
+ JSON.stringify(config),
56
+ )
57
+
58
+ await expect(manager.validateConfig()).resolves.toEqual({
59
+ deprecated: [
60
+ {
61
+ configVersion: 1,
62
+ minSupportedVersion: 2,
63
+ deprecatedVersion: 3,
64
+ name: 'vcService',
65
+ },
66
+ ],
67
+ incompatible: [
68
+ {
69
+ configVersion: 1,
70
+ minSupportedVersion: 2,
71
+ deprecatedVersion: 3,
72
+ name: 'vcService',
73
+ },
74
+ ],
75
+ })
76
+ })
77
+ })
78
+ } else {
79
+ it('Skip all tests.', () => {
80
+ console.log(
81
+ 'No sudoplatformconfig.json, test key and test key ID file found. Skipping all integration tests.',
82
+ )
83
+ })
84
+ }
85
+ })