@twin.org/node-core 0.0.2-next.2 → 0.0.2-next.21

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 (52) hide show
  1. package/dist/cjs/index.cjs +864 -391
  2. package/dist/esm/index.mjs +852 -394
  3. package/dist/types/bootstrap.d.ts +9 -9
  4. package/dist/types/builders/engineEnvBuilder.d.ts +1 -1
  5. package/dist/types/builders/engineServerEnvBuilder.d.ts +3 -2
  6. package/dist/types/builders/extensionsBuilder.d.ts +32 -0
  7. package/dist/types/defaults.d.ts +6 -0
  8. package/dist/types/index.d.ts +4 -0
  9. package/dist/types/models/IEngineEnvironmentVariables.d.ts +188 -24
  10. package/dist/types/models/IEngineServerEnvironmentVariables.d.ts +12 -3
  11. package/dist/types/models/INodeEngineConfig.d.ts +6 -0
  12. package/dist/types/models/INodeEnvironmentVariables.d.ts +1 -2
  13. package/dist/types/models/INodeOptions.d.ts +6 -1
  14. package/dist/types/models/nodeExtensionMethods.d.ts +27 -0
  15. package/dist/types/models/nodeFeatures.d.ts +4 -0
  16. package/dist/types/node.d.ts +7 -2
  17. package/dist/types/server.d.ts +4 -2
  18. package/dist/types/utils.d.ts +24 -0
  19. package/docs/changelog.md +138 -0
  20. package/docs/reference/functions/{bootstrapAttestationMethod.md → bootstrapSynchronisedStorage.md} +3 -3
  21. package/docs/reference/functions/buildConfiguration.md +2 -2
  22. package/docs/reference/functions/buildEngineConfiguration.md +2 -2
  23. package/docs/reference/functions/buildEngineServerConfiguration.md +9 -3
  24. package/docs/reference/functions/directoryExists.md +19 -0
  25. package/docs/reference/functions/extensionsConfiguration.md +25 -0
  26. package/docs/reference/functions/extensionsInitialiseEngine.md +25 -0
  27. package/docs/reference/functions/extensionsInitialiseEngineServer.md +31 -0
  28. package/docs/reference/functions/getFiles.md +19 -0
  29. package/docs/reference/functions/getSubFolders.md +19 -0
  30. package/docs/reference/functions/loadTextFile.md +19 -0
  31. package/docs/reference/functions/overrideModuleImport.md +17 -0
  32. package/docs/reference/functions/shutdownExtensions.md +25 -0
  33. package/docs/reference/functions/start.md +4 -4
  34. package/docs/reference/index.md +21 -1
  35. package/docs/reference/interfaces/IEngineEnvironmentVariables.md +374 -36
  36. package/docs/reference/interfaces/IEngineServerEnvironmentVariables.md +1673 -5
  37. package/docs/reference/interfaces/INodeEngineConfig.md +7 -0
  38. package/docs/reference/interfaces/INodeEnvironmentVariables.md +643 -137
  39. package/docs/reference/interfaces/INodeOptions.md +14 -2
  40. package/docs/reference/type-aliases/NodeExtensionInitialiseEngineMethod.md +18 -0
  41. package/docs/reference/type-aliases/NodeExtensionInitialiseEngineServerMethod.md +24 -0
  42. package/docs/reference/type-aliases/NodeExtensionInitialiseMethod.md +23 -0
  43. package/docs/reference/type-aliases/NodeExtensionShutdownMethod.md +10 -0
  44. package/docs/reference/variables/ATTESTATION_VERIFICATION_METHOD_ID.md +3 -0
  45. package/docs/reference/variables/AUTH_SIGNING_KEY_ID.md +3 -0
  46. package/docs/reference/variables/BLOB_STORAGE_ENCRYPTION_KEY_ID.md +3 -0
  47. package/docs/reference/variables/IMMUTABLE_PROOF_VERIFICATION_METHOD_ID.md +3 -0
  48. package/docs/reference/variables/NodeFeatures.md +7 -1
  49. package/docs/reference/variables/SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID.md +3 -0
  50. package/docs/reference/variables/VC_AUTHENTICATION_VERIFICATION_METHOD_ID.md +3 -0
  51. package/locales/en.json +15 -7
  52. package/package.json +18 -2
@@ -1,20 +1,32 @@
1
1
  import { PasswordHelper } from '@twin.org/api-auth-entity-storage-service';
2
- import { I18n, Is, Converter, RandomHelper, StringHelper, Coerce, Urn, GeneralError, ErrorHelper, EnvHelper } from '@twin.org/core';
2
+ import { I18n, Is, Coerce, Converter, RandomHelper, Urn, GeneralError, EnvHelper } from '@twin.org/core';
3
3
  import { PasswordGenerator, Bip39 } from '@twin.org/crypto';
4
- import { WalletConnectorType, IdentityConnectorType, EntityStorageConnectorType, BlobStorageConnectorType, BlobStorageComponentType, VaultConnectorType, DltConfigType, LoggingConnectorType, LoggingComponentType, BackgroundTaskConnectorType, EventBusConnectorType, EventBusComponentType, TelemetryConnectorType, TelemetryComponentType, MessagingEmailConnectorType, MessagingSmsConnectorType, MessagingPushNotificationConnectorType, MessagingComponentType, FaucetConnectorType, NftConnectorType, NftComponentType, VerifiableStorageConnectorType, VerifiableStorageComponentType, ImmutableProofComponentType, AuditableItemGraphComponentType, AuditableItemStreamComponentType, IdentityComponentType, IdentityResolverConnectorType, IdentityResolverComponentType, IdentityProfileConnectorType, IdentityProfileComponentType, AttestationConnectorType, AttestationComponentType, DataConverterConnectorType, DataExtractorConnectorType, DataProcessingComponentType, DocumentManagementComponentType, FederatedCatalogueComponentType, RightsManagementPapComponentType, RightsManagementComponentType, TaskSchedulerComponentType } from '@twin.org/engine-types';
4
+ import { AuthenticationComponentType, InformationComponentType, RestRouteProcessorType, SocketRouteProcessorType, AuthenticationAdminComponentType } from '@twin.org/engine-server-types';
5
+ import { WalletConnectorType, IdentityConnectorType, EntityStorageConnectorType, BlobStorageConnectorType, BlobStorageComponentType, VaultConnectorType, DltConfigType, LoggingConnectorType, LoggingComponentType, BackgroundTaskConnectorType, TaskSchedulerComponentType, EventBusConnectorType, EventBusComponentType, TelemetryConnectorType, TelemetryComponentType, MessagingEmailConnectorType, MessagingSmsConnectorType, MessagingPushNotificationConnectorType, MessagingAdminComponentType, MessagingComponentType, FaucetConnectorType, EngineTypeHelper, NftConnectorType, NftComponentType, VerifiableStorageConnectorType, VerifiableStorageComponentType, ImmutableProofComponentType, IdentityComponentType, IdentityResolverConnectorType, IdentityResolverComponentType, IdentityProfileConnectorType, IdentityProfileComponentType, AttestationConnectorType, AttestationComponentType, DataProcessingComponentType, DataConverterConnectorType, DataExtractorConnectorType, AuditableItemGraphComponentType, AuditableItemStreamComponentType, DocumentManagementComponentType, AuthenticationGeneratorComponentType, RightsManagementPapComponentType, RightsManagementPmpComponentType, RightsManagementPipComponentType, RightsManagementPxpComponentType, RightsManagementPdpComponentType, RightsManagementPepComponentType, RightsManagementPnpComponentType, RightsManagementPnapComponentType, RightsManagementDapComponentType, RightsManagementDarpComponentType, SynchronisedStorageComponentType, FederatedCatalogueComponentType, DataSpaceConnectorComponentType } from '@twin.org/engine-types';
5
6
  import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
6
7
  import { IdentityProfileConnectorFactory, IdentityConnectorFactory, IdentityResolverConnectorFactory, DocumentHelper } from '@twin.org/identity-models';
7
8
  import { VaultConnectorFactory, VaultKeyType } from '@twin.org/vault-models';
8
9
  import { WalletConnectorFactory } from '@twin.org/wallet-models';
9
- import { readFile, stat } from 'node:fs/promises';
10
+ import { readFile, stat, readdir } from 'node:fs/promises';
10
11
  import path from 'node:path';
12
+ import { CLIDisplay } from '@twin.org/cli-core';
13
+ import { PolicyNegotiationPointClient, DataAccessPointClient } from '@twin.org/rights-management-rest-client';
11
14
  import { addDefaultRestPaths, addDefaultSocketPaths, EngineServer } from '@twin.org/engine-server';
12
- import { InformationComponentType, RestRouteProcessorType, SocketRouteProcessorType, AuthenticationAdminComponentType, AuthenticationComponentType } from '@twin.org/engine-server-types';
15
+ import { ModuleHelper } from '@twin.org/modules';
13
16
  import * as dotenv from 'dotenv';
14
17
  import { Engine } from '@twin.org/engine';
15
18
  import { FileStateStorage } from '@twin.org/engine-core';
16
19
  import { EngineCoreFactory } from '@twin.org/engine-models';
17
20
 
21
+ // Copyright 2024 IOTA Stiftung.
22
+ // SPDX-License-Identifier: Apache-2.0.
23
+ const ATTESTATION_VERIFICATION_METHOD_ID = "attestation-assertion";
24
+ const IMMUTABLE_PROOF_VERIFICATION_METHOD_ID = "immutable-proof-assertion";
25
+ const BLOB_STORAGE_ENCRYPTION_KEY_ID = "blob-encryption";
26
+ const SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID = "synchronised-storage-blob-encryption";
27
+ const VC_AUTHENTICATION_VERIFICATION_METHOD_ID = "node-authentication-assertion";
28
+ const AUTH_SIGNING_KEY_ID = "auth-signing";
29
+
18
30
  // Copyright 2024 IOTA Stiftung.
19
31
  // SPDX-License-Identifier: Apache-2.0.
20
32
  /**
@@ -29,25 +41,28 @@ const NodeFeatures = {
29
41
  /**
30
42
  * NodeUser - generates a user for the node if not provided in config.
31
43
  */
32
- NodeUser: "node-user"
44
+ NodeUser: "node-user",
45
+ /**
46
+ * NodeWallet - generates a wallet for the node and funds it when there is a faucet available.
47
+ */
48
+ NodeWallet: "node-wallet"
33
49
  };
34
50
 
35
51
  // Copyright 2024 IOTA Stiftung.
36
52
  // SPDX-License-Identifier: Apache-2.0.
37
- /* eslint-disable no-console */
38
53
  /**
39
54
  * Initialise the locales for the application.
40
55
  * @param localesDirectory The directory containing the locales.
41
56
  */
42
57
  async function initialiseLocales(localesDirectory) {
43
58
  const localesFile = path.resolve(path.join(localesDirectory, "en.json"));
44
- console.info("Locales File:", localesFile);
59
+ CLIDisplay.value("Locales File", localesFile);
45
60
  if (await fileExists(localesFile)) {
46
61
  const enLangContent = await readFile(localesFile, "utf8");
47
62
  I18n.addDictionary("en", JSON.parse(enLangContent));
48
63
  }
49
64
  else {
50
- console.warn(`Locales file not found: ${localesFile}`);
65
+ CLIDisplay.error(`Locales file not found: ${localesFile}`);
51
66
  }
52
67
  }
53
68
  /**
@@ -71,13 +86,79 @@ async function fileExists(filename) {
71
86
  return false;
72
87
  }
73
88
  }
89
+ /**
90
+ * Does the specified directory exist.
91
+ * @param directory The directory to check for existence.
92
+ * @returns True if the directory exists.
93
+ */
94
+ async function directoryExists(directory) {
95
+ try {
96
+ const stats = await stat(directory);
97
+ return stats.isDirectory();
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ /**
104
+ * Get the sub folders for the folder.
105
+ * @param directory The directory to get the sub folders.
106
+ * @returns The list of sub folders.
107
+ */
108
+ async function getSubFolders(directory) {
109
+ try {
110
+ const dir = await readdir(directory);
111
+ const folders = [];
112
+ for (const dirEntry of dir) {
113
+ const fullPath = path.join(directory, dirEntry);
114
+ const stats = await stat(fullPath);
115
+ if (stats.isDirectory()) {
116
+ folders.push(fullPath);
117
+ }
118
+ }
119
+ return folders;
120
+ }
121
+ catch {
122
+ return [];
123
+ }
124
+ }
125
+ /**
126
+ * Get the files in the directory.
127
+ * @param directory The directory to get the files from.
128
+ * @returns The list of files in the directory.
129
+ */
130
+ async function getFiles(directory) {
131
+ try {
132
+ const dir = await readdir(directory);
133
+ const files = [];
134
+ for (const dirEntry of dir) {
135
+ const fullPath = path.join(directory, dirEntry);
136
+ const stats = await stat(fullPath);
137
+ if (stats.isFile()) {
138
+ files.push(fullPath);
139
+ }
140
+ }
141
+ return files;
142
+ }
143
+ catch {
144
+ return [];
145
+ }
146
+ }
147
+ /**
148
+ * Load the text file.
149
+ * @param filename The filename of the text file to load.
150
+ * @returns The contents of the text file if it could not be loaded.
151
+ */
152
+ async function loadTextFile(filename) {
153
+ return readFile(filename, "utf8");
154
+ }
74
155
  /**
75
156
  * Load the JSON file.
76
157
  * @param filename The filename of the JSON file to load.
77
158
  * @returns The contents of the JSON file or null if it could not be loaded.
78
159
  */
79
160
  async function loadJsonFile(filename) {
80
- const content = await readFile(filename, "utf8");
161
+ const content = await loadTextFile(filename);
81
162
  return JSON.parse(content);
82
163
  }
83
164
  /**
@@ -116,8 +197,18 @@ async function bootstrap(engineCore, context, envVars) {
116
197
  await bootstrapNodeUser(engineCore, context, envVars, features);
117
198
  await bootstrapAuth(engineCore, context, envVars);
118
199
  await bootstrapBlobEncryption(engineCore, context, envVars);
119
- await bootstrapAttestationMethod(engineCore, context, envVars);
120
- await bootstrapImmutableProofMethod(engineCore, context, envVars);
200
+ const defaultAttestationConnectorType = engineCore.getRegisteredInstanceTypeOptional("attestationConnector");
201
+ if (!Is.empty(defaultAttestationConnectorType)) {
202
+ await addVerificationMethod(engineCore, context, "attestation", envVars.attestationVerificationMethodId ?? ATTESTATION_VERIFICATION_METHOD_ID);
203
+ }
204
+ const defaultImmutableProofComponentType = engineCore.getRegisteredInstanceTypeOptional("immutableProofComponent");
205
+ if (!Is.empty(defaultImmutableProofComponentType)) {
206
+ await addVerificationMethod(engineCore, context, "immutable proof", envVars.immutableProofVerificationMethodId ?? IMMUTABLE_PROOF_VERIFICATION_METHOD_ID);
207
+ }
208
+ if (Coerce.boolean(envVars.vcAuthenticationEnabled) ?? false) {
209
+ await addVerificationMethod(engineCore, context, "verifiable credential authentication", envVars.vcAuthenticationVerificationMethodId ?? VC_AUTHENTICATION_VERIFICATION_METHOD_ID);
210
+ }
211
+ await bootstrapSynchronisedStorage(engineCore, context, envVars);
121
212
  }
122
213
  /**
123
214
  * Bootstrap the node creating any necessary resources.
@@ -132,23 +223,21 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
132
223
  // But we have a chicken and egg problem in that we can't create the identity
133
224
  // to store the mnemonic in the vault without an identity. We use a temporary identity
134
225
  // and then replace it with the new identity later in the process.
135
- const engineDefaultTypes = engineCore.getDefaultTypes();
136
- if (!Is.empty(engineDefaultTypes.vaultConnector)) {
137
- const vaultConnector = VaultConnectorFactory.get(engineDefaultTypes.vaultConnector);
138
- const workingIdentity = envVars.identity ??
139
- context.state.nodeIdentity ??
140
- `bootstrap-temp-${Converter.bytesToHex(RandomHelper.generate(16))}`;
141
- await bootstrapMnemonic(engineCore, envVars, features, vaultConnector, workingIdentity);
142
- const addresses = await bootstrapWallet(engineCore, envVars, features, workingIdentity);
143
- const finalIdentity = await bootstrapIdentity(engineCore, envVars, features, workingIdentity);
144
- await finaliseWallet(engineCore, envVars, features, finalIdentity, addresses);
145
- await finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity);
146
- context.state.nodeIdentity = finalIdentity;
147
- context.stateDirty = true;
148
- engineCore.logInfo(I18n.formatMessage("node.nodeIdentity", {
149
- identity: context.state.nodeIdentity
150
- }));
151
- }
226
+ const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
227
+ const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
228
+ const workingIdentity = envVars.identity ??
229
+ context.state.nodeIdentity ??
230
+ `bootstrap-temp-${Converter.bytesToHex(RandomHelper.generate(16))}`;
231
+ await bootstrapMnemonic(engineCore, envVars, features, vaultConnector, workingIdentity);
232
+ const addresses = await bootstrapWallet(engineCore, envVars, features, workingIdentity);
233
+ const finalIdentity = await bootstrapIdentity(engineCore, envVars, features, workingIdentity);
234
+ await finaliseWallet(engineCore, envVars, features, finalIdentity, addresses);
235
+ await finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity);
236
+ context.state.nodeIdentity = finalIdentity;
237
+ context.stateDirty = true;
238
+ engineCore.logInfo(I18n.formatMessage("node.nodeIdentity", {
239
+ identity: context.state.nodeIdentity
240
+ }));
152
241
  }
153
242
  }
154
243
  /**
@@ -160,12 +249,13 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
160
249
  * @returns The addresses for the wallet.
161
250
  */
162
251
  async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
163
- const engineDefaultTypes = engineCore.getDefaultTypes();
252
+ const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
164
253
  // Now create an identity for the node controlled by the address we just funded
165
- const identityConnector = IdentityConnectorFactory.get(engineDefaultTypes.identityConnector);
254
+ const identityConnector = IdentityConnectorFactory.get(defaultIdentityConnectorType);
166
255
  let identityDocument;
167
256
  try {
168
- const identityResolverConnector = IdentityResolverConnectorFactory.get(engineDefaultTypes.identityResolverConnector);
257
+ const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
258
+ const identityResolverConnector = IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
169
259
  identityDocument = await identityResolverConnector.resolveDocument(nodeIdentity);
170
260
  engineCore.logInfo(I18n.formatMessage("node.existingNodeIdentity", { identity: nodeIdentity }));
171
261
  }
@@ -175,7 +265,7 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
175
265
  identityDocument = await identityConnector.createDocument(nodeIdentity);
176
266
  engineCore.logInfo(I18n.formatMessage("node.createdNodeIdentity", { identity: identityDocument.id }));
177
267
  }
178
- if (engineDefaultTypes.identityConnector === IdentityConnectorType.Iota) {
268
+ if (defaultIdentityConnectorType.startsWith(IdentityConnectorType.Iota)) {
179
269
  const didUrn = Urn.fromValidString(identityDocument.id);
180
270
  const didParts = didUrn.parts();
181
271
  const objectId = didParts[3];
@@ -194,23 +284,26 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
194
284
  * @returns The addresses for the wallet.
195
285
  */
196
286
  async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
197
- const engineDefaultTypes = engineCore.getDefaultTypes();
198
- const walletConnector = WalletConnectorFactory.get(engineDefaultTypes.walletConnector);
199
- const addresses = await walletConnector.getAddresses(nodeIdentity, 0, 0, 5);
200
- const balance = await walletConnector.getBalance(nodeIdentity, addresses[0]);
201
- if (balance === 0n) {
202
- let address0 = addresses[0];
203
- if (engineDefaultTypes.walletConnector === WalletConnectorType.Iota) {
204
- address0 = `${envVars.iotaExplorerEndpoint}address/${address0}?network=${envVars.iotaNetwork}`;
287
+ if (features.includes(NodeFeatures.NodeWallet)) {
288
+ const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
289
+ const walletConnector = WalletConnectorFactory.get(defaultWalletConnectorType);
290
+ const addresses = await walletConnector.getAddresses(nodeIdentity, 0, 0, 5);
291
+ const balance = await walletConnector.getBalance(nodeIdentity, addresses[0]);
292
+ if (balance === 0n) {
293
+ let address0 = addresses[0];
294
+ if (defaultWalletConnectorType.startsWith(WalletConnectorType.Iota)) {
295
+ address0 = `${envVars.iotaExplorerEndpoint}address/${address0}?network=${envVars.iotaNetwork}`;
296
+ }
297
+ engineCore.logInfo(I18n.formatMessage("node.fundingWallet", { address: address0 }));
298
+ // Add some funds to the wallet from the faucet
299
+ await walletConnector.ensureBalance(nodeIdentity, addresses[0], 1000000000n);
205
300
  }
206
- engineCore.logInfo(I18n.formatMessage("node.fundingWallet", { address: address0 }));
207
- // Add some funds to the wallet from the faucet
208
- await walletConnector.ensureBalance(nodeIdentity, addresses[0], 1000000000n);
209
- }
210
- else {
211
- engineCore.logInfo(I18n.formatMessage("node.fundedWallet"));
301
+ else {
302
+ engineCore.logInfo(I18n.formatMessage("node.fundedWallet"));
303
+ }
304
+ return addresses;
212
305
  }
213
- return addresses;
306
+ return [];
214
307
  }
215
308
  /**
216
309
  * Bootstrap the identity for the node.
@@ -221,15 +314,17 @@ async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
221
314
  * @param addresses The addresses for the wallet.
222
315
  */
223
316
  async function finaliseWallet(engineCore, envVars, features, finalIdentity, addresses) {
224
- const engineDefaultTypes = engineCore.getDefaultTypes();
225
- // If we are using entity storage for wallet the identity associated with the
226
- // address will be wrong, so fix it
227
- if (engineDefaultTypes.walletConnector === "entity-storage") {
228
- const walletAddress = EntityStorageConnectorFactory.get(StringHelper.kebabCase("WalletAddress"));
229
- const addr = await walletAddress.get(addresses[0]);
230
- if (!Is.empty(addr)) {
231
- addr.identity = finalIdentity;
232
- await walletAddress.set(addr);
317
+ if (features.includes(NodeFeatures.NodeWallet)) {
318
+ const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
319
+ // If we are using entity storage for wallet the identity associated with the
320
+ // address will be wrong, so fix it
321
+ if (defaultWalletConnectorType.startsWith(WalletConnectorType.EntityStorage)) {
322
+ const walletAddress = EntityStorageConnectorFactory.get("wallet-address");
323
+ const addr = await walletAddress.get(addresses[0]);
324
+ if (!Is.empty(addr)) {
325
+ addr.identity = finalIdentity;
326
+ await walletAddress.set(addr);
327
+ }
233
328
  }
234
329
  }
235
330
  }
@@ -291,10 +386,10 @@ async function finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity)
291
386
  */
292
387
  async function bootstrapNodeUser(engineCore, context, envVars, features) {
293
388
  if (features.includes(NodeFeatures.NodeUser)) {
294
- const engineDefaultTypes = engineCore.getDefaultTypes();
295
- if (engineDefaultTypes.authenticationComponent === "authentication-entity-storage" &&
389
+ const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceType("authenticationComponent");
390
+ if (defaultAuthenticationComponentType.startsWith(AuthenticationComponentType.EntityStorage) &&
296
391
  Is.stringValue(context.state.nodeIdentity)) {
297
- const authUserEntityStorage = EntityStorageConnectorFactory.get(StringHelper.kebabCase("AuthenticationUser"));
392
+ const authUserEntityStorage = EntityStorageConnectorFactory.get("authentication-user");
298
393
  const email = envVars.username ?? DEFAULT_NODE_USERNAME;
299
394
  let nodeAdminUser = await authUserEntityStorage.get(email);
300
395
  if (Is.empty(nodeAdminUser)) {
@@ -335,7 +430,8 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
335
430
  }
336
431
  }
337
432
  // We have create a node user, now we need to create a profile for the user
338
- const identityProfileConnector = IdentityProfileConnectorFactory.get(engineDefaultTypes.identityProfileConnector);
433
+ const defaultIdentityProfileConnectorType = engineCore.getRegisteredInstanceType("identityProfileConnector");
434
+ const identityProfileConnector = IdentityProfileConnectorFactory.get(defaultIdentityProfileConnectorType);
339
435
  if (identityProfileConnector) {
340
436
  let userProfile;
341
437
  try {
@@ -365,43 +461,6 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
365
461
  }
366
462
  }
367
463
  }
368
- /**
369
- * Bootstrap the attestation verification methods.
370
- * @param engineCore The engine core for the node.
371
- * @param context The context for the node.
372
- * @param envVars The environment variables for the node.
373
- * @param features The features that are enabled on the node.
374
- */
375
- async function bootstrapAttestationMethod(engineCore, context, envVars, features) {
376
- if (Is.stringValue(context.state.nodeIdentity) &&
377
- Is.arrayValue(context.config.types.identityConnector) &&
378
- Is.stringValue(envVars.attestationVerificationMethodId)) {
379
- const engineDefaultTypes = engineCore.getDefaultTypes();
380
- const identityConnector = IdentityConnectorFactory.get(engineDefaultTypes.identityConnector);
381
- const identityResolverConnector = IdentityResolverConnectorFactory.get(engineDefaultTypes.identityResolverConnector);
382
- const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
383
- const fullMethodId = `${identityDocument.id}#${envVars.attestationVerificationMethodId}`;
384
- let createVm = true;
385
- try {
386
- DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
387
- createVm = false;
388
- }
389
- catch { }
390
- if (createVm) {
391
- // Add attestation verification method to DID, the correct node context is now in place
392
- // so the keys for the verification method will be stored correctly
393
- engineCore.logInfo(I18n.formatMessage("node.addingAttestation", {
394
- methodId: fullMethodId
395
- }));
396
- await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", envVars.attestationVerificationMethodId);
397
- }
398
- else {
399
- engineCore.logInfo(I18n.formatMessage("node.existingAttestation", {
400
- methodId: fullMethodId
401
- }));
402
- }
403
- }
404
- }
405
464
  /**
406
465
  * Bootstrap the immutable proof verification methods.
407
466
  * @param engineCore The engine core for the node.
@@ -409,36 +468,7 @@ async function bootstrapAttestationMethod(engineCore, context, envVars, features
409
468
  * @param envVars The environment variables for the node.
410
469
  * @param features The features that are enabled on the node.
411
470
  */
412
- async function bootstrapImmutableProofMethod(engineCore, context, envVars, features) {
413
- const engineDefaultTypes = engineCore.getDefaultTypes();
414
- if (Is.stringValue(context.state.nodeIdentity) &&
415
- Is.arrayValue(context.config.types.identityConnector) &&
416
- Is.stringValue(envVars.immutableProofVerificationMethodId)) {
417
- const identityConnector = IdentityConnectorFactory.get(engineDefaultTypes.identityConnector);
418
- const identityResolverConnector = IdentityResolverConnectorFactory.get(engineDefaultTypes.identityResolverConnector);
419
- const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
420
- const fullMethodId = `${identityDocument.id}#${envVars.immutableProofVerificationMethodId}`;
421
- let createVm = true;
422
- try {
423
- DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
424
- createVm = false;
425
- }
426
- catch { }
427
- if (createVm) {
428
- // Add AIG verification method to DID, the correct node context is now in place
429
- // so the keys for the verification method will be stored correctly
430
- engineCore.logInfo(I18n.formatMessage("node.addingImmutableProof", {
431
- methodId: fullMethodId
432
- }));
433
- await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", envVars.immutableProofVerificationMethodId);
434
- }
435
- else {
436
- engineCore.logInfo(I18n.formatMessage("node.existingImmutableProof", {
437
- methodId: fullMethodId
438
- }));
439
- }
440
- }
441
- }
471
+ async function bootstrapImmutableProofMethod(engineCore, context, envVars, features) { }
442
472
  /**
443
473
  * Bootstrap the keys for blob encryption.
444
474
  * @param engineCore The engine core for the node.
@@ -449,18 +479,28 @@ async function bootstrapImmutableProofMethod(engineCore, context, envVars, featu
449
479
  async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
450
480
  if ((Coerce.boolean(envVars.blobStorageEnableEncryption) ?? false) &&
451
481
  Is.stringValue(context.state.nodeIdentity)) {
452
- const engineDefaultTypes = engineCore.getDefaultTypes();
453
482
  // Create a new key for encrypting blobs
454
- const vaultConnector = VaultConnectorFactory.get(engineDefaultTypes.vaultConnector);
455
- const keyName = `${context.state.nodeIdentity}/${envVars.blobStorageEncryptionKey}`;
483
+ const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
484
+ const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
485
+ const keyName = `${context.state.nodeIdentity}/${envVars.blobStorageEncryptionKeyId ?? BLOB_STORAGE_ENCRYPTION_KEY_ID}`;
456
486
  let existingKey;
457
487
  try {
458
488
  existingKey = await vaultConnector.getKey(keyName);
459
489
  }
460
490
  catch { }
461
491
  if (Is.empty(existingKey)) {
462
- engineCore.logInfo(I18n.formatMessage("node.creatingBlobEncryptionKey", { keyName }));
463
- await vaultConnector.createKey(keyName, VaultKeyType.ChaCha20Poly1305);
492
+ if (Is.stringBase64(envVars.blobStorageSymmetricEncryptionKey)) {
493
+ engineCore.logInfo(I18n.formatMessage("node.addingBlobEncryptionKey", { keyName }));
494
+ await vaultConnector.addKey(keyName, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(envVars.blobStorageSymmetricEncryptionKey));
495
+ }
496
+ else {
497
+ engineCore.logInfo(I18n.formatMessage("node.creatingBlobEncryptionKey", { keyName }));
498
+ const key = await vaultConnector.createKey(keyName, VaultKeyType.ChaCha20Poly1305);
499
+ engineCore.logInfo(I18n.formatMessage("node.createdBlobEncryptionKey", {
500
+ keyName,
501
+ keyValue: Converter.bytesToBase64(key)
502
+ }));
503
+ }
464
504
  }
465
505
  else {
466
506
  engineCore.logInfo(I18n.formatMessage("node.existingBlobEncryptionKey", { keyName }));
@@ -475,12 +515,14 @@ async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
475
515
  * @param features The features that are enabled on the node.
476
516
  */
477
517
  async function bootstrapAuth(engineCore, context, envVars, features) {
478
- const engineDefaultTypes = engineCore.getDefaultTypes();
479
- if (engineDefaultTypes.authenticationComponent === "authentication-entity-storage" &&
518
+ const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceTypeOptional("authenticationComponent");
519
+ if (Is.stringValue(defaultAuthenticationComponentType) &&
520
+ defaultAuthenticationComponentType.startsWith(AuthenticationComponentType.EntityStorage) &&
480
521
  Is.stringValue(context.state.nodeIdentity)) {
481
522
  // Create a new JWT signing key and a user login for the node
482
- const vaultConnector = VaultConnectorFactory.get(engineDefaultTypes.vaultConnector);
483
- const keyName = `${context.state.nodeIdentity}/${envVars.authSigningKeyId}`;
523
+ const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
524
+ const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
525
+ const keyName = `${context.state.nodeIdentity}/${envVars.authSigningKeyId ?? AUTH_SIGNING_KEY_ID}`;
484
526
  let existingKey;
485
527
  try {
486
528
  existingKey = await vaultConnector.getKey(keyName);
@@ -495,6 +537,74 @@ async function bootstrapAuth(engineCore, context, envVars, features) {
495
537
  }
496
538
  }
497
539
  }
540
+ /**
541
+ * Bootstrap the synchronised storage blob encryption and verification methods.
542
+ * @param engineCore The engine core for the node.
543
+ * @param context The context for the node.
544
+ * @param envVars The environment variables for the node.
545
+ * @param features The features that are enabled on the node.
546
+ */
547
+ async function bootstrapSynchronisedStorage(engineCore, context, envVars, features) {
548
+ if (Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false) {
549
+ // If this is a trusted node we need to add the blob encryption key pair
550
+ if (Is.stringBase64(envVars.synchronisedStorageBlobStorageKey)) {
551
+ const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
552
+ const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
553
+ const keyName = envVars.synchronisedStorageBlobStorageEncryptionKeyId ??
554
+ SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID;
555
+ let existingKey;
556
+ try {
557
+ existingKey = await vaultConnector.getKey(keyName);
558
+ }
559
+ catch { }
560
+ if (Is.empty(existingKey)) {
561
+ engineCore.logInfo(I18n.formatMessage("node.addingSynchronisedStorageBlobEncryptionKey", { keyName }));
562
+ await vaultConnector.addKey(keyName, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(envVars.synchronisedStorageBlobStorageKey));
563
+ }
564
+ else {
565
+ engineCore.logInfo(I18n.formatMessage("node.existingSynchronisedStorageBlobEncryptionKey", { keyName }));
566
+ }
567
+ }
568
+ }
569
+ }
570
+ /**
571
+ * Add a verification method if it doesn't exist.
572
+ * @param engineCore The engine core for the node.
573
+ * @param context The context for the node.
574
+ * @param verificationMethodTitle The verification method title.
575
+ * @param verificationMethodId The verification method ID.
576
+ */
577
+ async function addVerificationMethod(engineCore, context, verificationMethodTitle, verificationMethodId) {
578
+ if (Is.stringValue(context.state.nodeIdentity) &&
579
+ Is.arrayValue(context.config.types.identityConnector) &&
580
+ Is.stringValue(verificationMethodId)) {
581
+ const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
582
+ const identityConnector = IdentityConnectorFactory.get(defaultIdentityConnectorType);
583
+ const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
584
+ const identityResolverConnector = IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
585
+ const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
586
+ const fullMethodId = `${identityDocument.id}#${verificationMethodId}`;
587
+ let exists = false;
588
+ try {
589
+ DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
590
+ exists = true;
591
+ }
592
+ catch { }
593
+ if (!exists) {
594
+ engineCore.logInfo(I18n.formatMessage("node.addingVerificationMethod", {
595
+ title: verificationMethodTitle,
596
+ methodId: fullMethodId
597
+ }));
598
+ await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", verificationMethodId);
599
+ }
600
+ else {
601
+ engineCore.logInfo(I18n.formatMessage("node.existingVerificationMethod", {
602
+ title: verificationMethodTitle,
603
+ methodId: fullMethodId
604
+ }));
605
+ }
606
+ }
607
+ }
498
608
 
499
609
  // Copyright 2024 IOTA Stiftung.
500
610
  // SPDX-License-Identifier: Apache-2.0.
@@ -503,61 +613,51 @@ async function bootstrapAuth(engineCore, context, envVars, features) {
503
613
  * @param envVars The environment variables.
504
614
  * @returns The config for the core.
505
615
  */
506
- function buildEngineConfiguration(envVars) {
616
+ async function buildEngineConfiguration(envVars) {
507
617
  if (Is.stringValue(envVars.storageFileRoot)) {
508
618
  envVars.stateFilename ??= "engine-state.json";
509
619
  envVars.storageFileRoot = path.resolve(envVars.storageFileRoot);
510
620
  envVars.stateFilename = path.join(envVars.storageFileRoot, envVars.stateFilename);
511
621
  }
512
- envVars.attestationVerificationMethodId ??= "attestation-assertion";
513
- envVars.immutableProofVerificationMethodId ??= "immutable-proof-assertion";
514
- envVars.blobStorageEnableEncryption ??= "false";
515
- envVars.blobStorageEncryptionKey ??= "blob-encryption";
516
622
  const coreConfig = {
517
623
  debug: Coerce.boolean(envVars.debug) ?? false,
518
624
  types: {}
519
625
  };
520
- configureEntityStorage(coreConfig, envVars);
521
- configureBlobStorage(coreConfig, envVars);
522
- configureVault(coreConfig, envVars);
523
- configureDlt(coreConfig, envVars);
524
- configureLogging(coreConfig, envVars);
525
- configureBackgroundTask(coreConfig, envVars);
526
- configureEventBus(coreConfig, envVars);
527
- configureTelemetry(coreConfig, envVars);
528
- configureMessaging(coreConfig, envVars);
529
- configureFaucet(coreConfig, envVars);
530
- configureWallet(coreConfig, envVars);
531
- configureNft(coreConfig, envVars);
532
- configureVerifiableStorage(coreConfig, envVars);
533
- configureIdentity(coreConfig, envVars);
534
- configureIdentityResolver(coreConfig, envVars);
535
- configureIdentityProfile(coreConfig, envVars);
536
- configureAttestation(coreConfig, envVars);
537
- configureDataProcessing(coreConfig, envVars);
538
- configureAuditableItemGraph(coreConfig);
539
- configureAuditableItemStream(coreConfig);
540
- configureDocumentManagement(coreConfig);
541
- configureFederatedCatalogue(coreConfig, envVars);
542
- configureRightsManagement(coreConfig, envVars);
543
- configureTaskScheduler(coreConfig, envVars);
626
+ await configureEntityStorage(coreConfig, envVars);
627
+ await configureBlobStorage(coreConfig, envVars);
628
+ await configureVault(coreConfig, envVars);
629
+ await configureDlt(coreConfig, envVars);
630
+ await configureLogging(coreConfig, envVars);
631
+ await configureBackgroundTask(coreConfig, envVars);
632
+ await configureTaskScheduler(coreConfig, envVars);
633
+ await configureEventBus(coreConfig, envVars);
634
+ await configureTelemetry(coreConfig, envVars);
635
+ await configureMessaging(coreConfig, envVars);
636
+ await configureFaucet(coreConfig, envVars);
637
+ await configureWallet(coreConfig, envVars);
638
+ await configureNft(coreConfig, envVars);
639
+ await configureVerifiableStorage(coreConfig, envVars);
640
+ await configureIdentity(coreConfig, envVars);
641
+ await configureIdentityResolver(coreConfig, envVars);
642
+ await configureIdentityProfile(coreConfig, envVars);
643
+ await configureAttestation(coreConfig, envVars);
644
+ await configureDataProcessing(coreConfig, envVars);
645
+ await configureAuditableItemGraph(coreConfig, envVars);
646
+ await configureAuditableItemStream(coreConfig, envVars);
647
+ await configureDocumentManagement(coreConfig, envVars);
648
+ await configureVerifiableCredentialAuthentication(coreConfig, envVars);
649
+ await configureRightsManagement(coreConfig, envVars);
650
+ await configureSynchronisedStorage(coreConfig, envVars);
651
+ await configureFederatedCatalogue(coreConfig, envVars);
652
+ await configureDataSpaceConnector(coreConfig, envVars);
544
653
  return coreConfig;
545
654
  }
546
- /**
547
- * Helper function to get IOTA configuration from centralized dltConfig.
548
- * @param coreConfig The core config.
549
- * @returns The IOTA configuration if found, undefined otherwise.
550
- */
551
- function getIotaConfig(coreConfig) {
552
- const dltConfig = coreConfig.types.dltConfig?.find(config => config.type === DltConfigType.Iota && config.isDefault);
553
- return dltConfig?.options?.config;
554
- }
555
655
  /**
556
656
  * Configures the entity storage.
557
657
  * @param coreConfig The core config.
558
658
  * @param envVars The environment variables.
559
659
  */
560
- function configureEntityStorage(coreConfig, envVars) {
660
+ async function configureEntityStorage(coreConfig, envVars) {
561
661
  coreConfig.types ??= {};
562
662
  coreConfig.types.entityStorageConnector ??= [];
563
663
  const entityStorageConnectorTypes = envVars.entityStorageConnectorType?.split(",") ?? [];
@@ -581,9 +681,10 @@ function configureEntityStorage(coreConfig, envVars) {
581
681
  options: {
582
682
  config: {
583
683
  region: envVars.awsDynamodbRegion ?? "",
584
- accessKeyId: envVars.awsDynamodbAccessKeyId ?? "",
585
- secretAccessKey: envVars.awsDynamodbSecretAccessKey ?? "",
586
- endpoint: envVars.awsDynamodbEndpoint ?? ""
684
+ authMode: envVars.awsDynamodbAuthMode,
685
+ accessKeyId: envVars.awsDynamodbAccessKeyId,
686
+ secretAccessKey: envVars.awsDynamodbSecretAccessKey,
687
+ endpoint: envVars.awsDynamodbEndpoint
587
688
  },
588
689
  tablePrefix: envVars.entityStorageTablePrefix
589
690
  }
@@ -662,7 +763,7 @@ function configureEntityStorage(coreConfig, envVars) {
662
763
  }
663
764
  });
664
765
  }
665
- if (entityStorageConnectorTypes) {
766
+ if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.PostgreSql)) {
666
767
  coreConfig.types.entityStorageConnector.push({
667
768
  type: EntityStorageConnectorType.PostgreSql,
668
769
  options: {
@@ -677,10 +778,19 @@ function configureEntityStorage(coreConfig, envVars) {
677
778
  }
678
779
  });
679
780
  }
781
+ const defaultEntityStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
782
+ if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.Synchronised)) {
783
+ // For synchronised storage we use the default connector as the one we wrap for real DB operations
784
+ coreConfig.types.entityStorageConnector.push({
785
+ type: EntityStorageConnectorType.Synchronised,
786
+ options: {
787
+ entityStorageConnectorType: defaultEntityStorageConnectorType
788
+ }
789
+ });
790
+ }
680
791
  if (Is.arrayValue(entityStorageConnectorTypes)) {
681
- const defaultStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
682
792
  for (const config of coreConfig.types.entityStorageConnector) {
683
- if (config.type === defaultStorageConnectorType) {
793
+ if (config.type === defaultEntityStorageConnectorType) {
684
794
  config.isDefault = true;
685
795
  break;
686
796
  }
@@ -692,7 +802,7 @@ function configureEntityStorage(coreConfig, envVars) {
692
802
  * @param coreConfig The core config.
693
803
  * @param envVars The environment variables.
694
804
  */
695
- function configureBlobStorage(coreConfig, envVars) {
805
+ async function configureBlobStorage(coreConfig, envVars) {
696
806
  coreConfig.types.blobStorageConnector ??= [];
697
807
  const blobStorageConnectorTypes = envVars.blobStorageConnectorType?.split(",") ?? [];
698
808
  if (blobStorageConnectorTypes.includes(BlobStorageConnectorType.Memory)) {
@@ -731,9 +841,10 @@ function configureBlobStorage(coreConfig, envVars) {
731
841
  config: {
732
842
  region: envVars.awsS3Region ?? "",
733
843
  bucketName: envVars.awsS3BucketName ?? "",
734
- accessKeyId: envVars.awsS3AccessKeyId ?? "",
735
- secretAccessKey: envVars.awsS3SecretAccessKey ?? "",
736
- endpoint: envVars.awsS3Endpoint ?? ""
844
+ authMode: envVars.awsS3AuthMode,
845
+ accessKeyId: envVars.awsS3AccessKeyId,
846
+ secretAccessKey: envVars.awsS3SecretAccessKey,
847
+ endpoint: envVars.awsS3Endpoint
737
848
  },
738
849
  storagePrefix: envVars.blobStoragePrefix
739
850
  }
@@ -772,6 +883,13 @@ function configureBlobStorage(coreConfig, envVars) {
772
883
  for (const config of coreConfig.types.blobStorageConnector) {
773
884
  if (config.type === defaultStorageConnectorType) {
774
885
  config.isDefault = true;
886
+ }
887
+ // If this blob storage connector is the one to use for public access
888
+ // then add it as a feature
889
+ if (Is.stringValue(envVars.blobStorageConnectorPublic) &&
890
+ config.type === envVars.blobStorageConnectorPublic) {
891
+ config.features ??= [];
892
+ config.features.push("public");
775
893
  break;
776
894
  }
777
895
  }
@@ -783,7 +901,7 @@ function configureBlobStorage(coreConfig, envVars) {
783
901
  options: {
784
902
  config: {
785
903
  vaultKeyId: (envVars.blobStorageEnableEncryption ?? false)
786
- ? envVars.blobStorageEncryptionKey
904
+ ? (envVars.blobStorageEncryptionKeyId ?? BLOB_STORAGE_ENCRYPTION_KEY_ID)
787
905
  : undefined
788
906
  }
789
907
  }
@@ -795,41 +913,43 @@ function configureBlobStorage(coreConfig, envVars) {
795
913
  * @param coreConfig The core config.
796
914
  * @param envVars The environment variables.
797
915
  */
798
- function configureLogging(coreConfig, envVars) {
916
+ async function configureLogging(coreConfig, envVars) {
799
917
  coreConfig.types.loggingConnector ??= [];
800
- const loggingConnectors = (envVars.loggingConnector ?? "").split(",");
801
- for (const loggingConnector of loggingConnectors) {
918
+ const loggingConnectorTypes = (envVars.loggingConnector ?? "").split(",");
919
+ let additionalConnectorCount = 0;
920
+ for (const loggingConnector of loggingConnectorTypes) {
802
921
  if (loggingConnector === LoggingConnectorType.Console) {
803
- coreConfig.types.loggingConnector?.push({
922
+ coreConfig.types.loggingConnector.push({
804
923
  type: LoggingConnectorType.Console,
805
924
  options: {
806
925
  config: {
807
926
  translateMessages: true,
808
927
  hideGroups: true
809
928
  }
810
- },
811
- isDefault: loggingConnectors.length === 1
929
+ }
812
930
  });
931
+ additionalConnectorCount++;
813
932
  }
814
933
  else if (loggingConnector === LoggingConnectorType.EntityStorage) {
815
- coreConfig.types.loggingConnector?.push({
816
- type: LoggingConnectorType.EntityStorage,
817
- isDefault: loggingConnectors.length === 1
934
+ coreConfig.types.loggingConnector.push({
935
+ type: LoggingConnectorType.EntityStorage
818
936
  });
937
+ additionalConnectorCount++;
819
938
  }
820
939
  }
821
- if (loggingConnectors.length > 1) {
940
+ if (additionalConnectorCount > 1) {
822
941
  coreConfig.types.loggingConnector?.push({
823
942
  type: LoggingConnectorType.Multi,
824
- isDefault: true,
825
943
  options: {
826
- loggingConnectorTypes: loggingConnectors
944
+ loggingConnectorTypes
827
945
  }
828
946
  });
829
947
  }
830
- if (loggingConnectors.length > 0) {
948
+ if (additionalConnectorCount > 0) {
831
949
  coreConfig.types.loggingComponent ??= [];
832
- coreConfig.types.loggingComponent.push({ type: LoggingComponentType.Service });
950
+ // We set the isDefault flag so that other components will get this service by default
951
+ // and not the generic one from the engine core
952
+ coreConfig.types.loggingComponent.push({ type: LoggingComponentType.Service, isDefault: true });
833
953
  }
834
954
  }
835
955
  /**
@@ -837,7 +957,7 @@ function configureLogging(coreConfig, envVars) {
837
957
  * @param coreConfig The core config.
838
958
  * @param envVars The environment variables.
839
959
  */
840
- function configureVault(coreConfig, envVars) {
960
+ async function configureVault(coreConfig, envVars) {
841
961
  coreConfig.types.vaultConnector ??= [];
842
962
  if (envVars.vaultConnector === VaultConnectorType.EntityStorage) {
843
963
  coreConfig.types.vaultConnector.push({
@@ -861,7 +981,7 @@ function configureVault(coreConfig, envVars) {
861
981
  * @param coreConfig The core config.
862
982
  * @param envVars The environment variables.
863
983
  */
864
- function configureBackgroundTask(coreConfig, envVars) {
984
+ async function configureBackgroundTask(coreConfig, envVars) {
865
985
  coreConfig.types.backgroundTaskConnector ??= [];
866
986
  if (envVars.backgroundTaskConnector === BackgroundTaskConnectorType.EntityStorage) {
867
987
  coreConfig.types.backgroundTaskConnector.push({
@@ -874,7 +994,7 @@ function configureBackgroundTask(coreConfig, envVars) {
874
994
  * @param coreConfig The core config.
875
995
  * @param envVars The environment variables.
876
996
  */
877
- function configureEventBus(coreConfig, envVars) {
997
+ async function configureEventBus(coreConfig, envVars) {
878
998
  coreConfig.types.eventBusConnector ??= [];
879
999
  if (envVars.eventBusConnector === EventBusConnectorType.Local) {
880
1000
  coreConfig.types.eventBusConnector.push({
@@ -891,7 +1011,7 @@ function configureEventBus(coreConfig, envVars) {
891
1011
  * @param coreConfig The core config.
892
1012
  * @param envVars The environment variables.
893
1013
  */
894
- function configureTelemetry(coreConfig, envVars) {
1014
+ async function configureTelemetry(coreConfig, envVars) {
895
1015
  coreConfig.types.telemetryConnector ??= [];
896
1016
  if (envVars.telemetryConnector === TelemetryConnectorType.EntityStorage) {
897
1017
  coreConfig.types.telemetryConnector.push({
@@ -908,71 +1028,84 @@ function configureTelemetry(coreConfig, envVars) {
908
1028
  * @param coreConfig The core config.
909
1029
  * @param envVars The environment variables.
910
1030
  */
911
- function configureMessaging(coreConfig, envVars) {
912
- coreConfig.types.messagingEmailConnector ??= [];
913
- coreConfig.types.messagingSmsConnector ??= [];
914
- coreConfig.types.messagingPushNotificationConnector ??= [];
915
- if (envVars.messagingEmailConnector === MessagingEmailConnectorType.EntityStorage) {
916
- coreConfig.types.messagingEmailConnector.push({
917
- type: MessagingEmailConnectorType.EntityStorage
918
- });
919
- }
920
- else if (envVars.messagingEmailConnector === MessagingEmailConnectorType.Aws) {
921
- coreConfig.types.messagingEmailConnector.push({
922
- type: MessagingEmailConnectorType.Aws,
923
- options: {
924
- config: {
925
- region: envVars.awsS3Region ?? "",
926
- accessKeyId: envVars.awsS3AccessKeyId ?? "",
927
- secretAccessKey: envVars.awsS3SecretAccessKey ?? "",
928
- endpoint: envVars.awsS3Endpoint ?? ""
1031
+ async function configureMessaging(coreConfig, envVars) {
1032
+ if (Coerce.boolean(envVars.messagingEnabled) ?? false) {
1033
+ coreConfig.types.messagingEmailConnector ??= [];
1034
+ coreConfig.types.messagingSmsConnector ??= [];
1035
+ coreConfig.types.messagingPushNotificationConnector ??= [];
1036
+ if (envVars.messagingEmailConnector === MessagingEmailConnectorType.EntityStorage) {
1037
+ coreConfig.types.messagingEmailConnector.push({
1038
+ type: MessagingEmailConnectorType.EntityStorage
1039
+ });
1040
+ }
1041
+ else if (envVars.messagingEmailConnector === MessagingEmailConnectorType.Aws) {
1042
+ coreConfig.types.messagingEmailConnector.push({
1043
+ type: MessagingEmailConnectorType.Aws,
1044
+ options: {
1045
+ config: {
1046
+ region: envVars.awsSesRegion ?? "",
1047
+ authMode: envVars.awsSesAuthMode,
1048
+ accessKeyId: envVars.awsSesAccessKeyId,
1049
+ secretAccessKey: envVars.awsSesSecretAccessKey,
1050
+ endpoint: envVars.awsSesEndpoint
1051
+ }
929
1052
  }
930
- }
931
- });
932
- }
933
- if (envVars.messagingSmsConnector === MessagingSmsConnectorType.EntityStorage) {
934
- coreConfig.types.messagingSmsConnector.push({
935
- type: MessagingSmsConnectorType.EntityStorage
936
- });
937
- }
938
- else if (envVars.messagingSmsConnector === MessagingSmsConnectorType.Aws) {
939
- coreConfig.types.messagingSmsConnector.push({
940
- type: MessagingSmsConnectorType.Aws,
941
- options: {
942
- config: {
943
- region: envVars.awsS3Region ?? "",
944
- accessKeyId: envVars.awsS3AccessKeyId ?? "",
945
- secretAccessKey: envVars.awsS3SecretAccessKey ?? "",
946
- endpoint: envVars.awsS3Endpoint ?? ""
1053
+ });
1054
+ }
1055
+ if (envVars.messagingSmsConnector === MessagingSmsConnectorType.EntityStorage) {
1056
+ coreConfig.types.messagingSmsConnector.push({
1057
+ type: MessagingSmsConnectorType.EntityStorage
1058
+ });
1059
+ }
1060
+ else if (envVars.messagingSmsConnector === MessagingSmsConnectorType.Aws) {
1061
+ coreConfig.types.messagingSmsConnector.push({
1062
+ type: MessagingSmsConnectorType.Aws,
1063
+ options: {
1064
+ config: {
1065
+ region: envVars.awsSesRegion ?? "",
1066
+ authMode: envVars.awsSesAuthMode,
1067
+ accessKeyId: envVars.awsSesAccessKeyId,
1068
+ secretAccessKey: envVars.awsSesSecretAccessKey,
1069
+ endpoint: envVars.awsSesEndpoint
1070
+ }
947
1071
  }
948
- }
949
- });
950
- }
951
- if (envVars.messagingPushNotificationConnector ===
952
- MessagingPushNotificationConnectorType.EntityStorage) {
953
- coreConfig.types.messagingPushNotificationConnector.push({
954
- type: MessagingPushNotificationConnectorType.EntityStorage
955
- });
956
- }
957
- else if (envVars.messagingPushNotificationConnector === MessagingPushNotificationConnectorType.Aws) {
958
- coreConfig.types.messagingPushNotificationConnector.push({
959
- type: MessagingPushNotificationConnectorType.Aws,
1072
+ });
1073
+ }
1074
+ if (envVars.messagingPushNotificationConnector ===
1075
+ MessagingPushNotificationConnectorType.EntityStorage) {
1076
+ coreConfig.types.messagingPushNotificationConnector.push({
1077
+ type: MessagingPushNotificationConnectorType.EntityStorage
1078
+ });
1079
+ }
1080
+ else if (envVars.messagingPushNotificationConnector === MessagingPushNotificationConnectorType.Aws) {
1081
+ coreConfig.types.messagingPushNotificationConnector.push({
1082
+ type: MessagingPushNotificationConnectorType.Aws,
1083
+ options: {
1084
+ config: {
1085
+ region: envVars.awsSesRegion ?? "",
1086
+ authMode: envVars.awsSesAuthMode,
1087
+ accessKeyId: envVars.awsSesAccessKeyId,
1088
+ secretAccessKey: envVars.awsSesSecretAccessKey,
1089
+ endpoint: envVars.awsSesEndpoint,
1090
+ applicationsSettings: Is.json(envVars.awsMessagingPushNotificationApplications)
1091
+ ? JSON.parse(envVars.awsMessagingPushNotificationApplications)
1092
+ : []
1093
+ }
1094
+ }
1095
+ });
1096
+ }
1097
+ const templates = Is.arrayValue(envVars.messagingTemplates)
1098
+ ? envVars.messagingTemplates
1099
+ : undefined;
1100
+ coreConfig.types.messagingAdminComponent ??= [];
1101
+ coreConfig.types.messagingAdminComponent.push({
1102
+ type: MessagingAdminComponentType.Service,
960
1103
  options: {
961
1104
  config: {
962
- region: envVars.awsS3Region ?? "",
963
- accessKeyId: envVars.awsS3AccessKeyId ?? "",
964
- secretAccessKey: envVars.awsS3SecretAccessKey ?? "",
965
- endpoint: envVars.awsS3Endpoint ?? "",
966
- applicationsSettings: Is.json(envVars.awsMessagingPushNotificationApplications)
967
- ? JSON.parse(envVars.awsMessagingPushNotificationApplications)
968
- : []
1105
+ templates
969
1106
  }
970
1107
  }
971
1108
  });
972
- }
973
- if (coreConfig.types.messagingEmailConnector.length > 0 ||
974
- coreConfig.types.messagingSmsConnector.length > 0 ||
975
- coreConfig.types.messagingPushNotificationConnector.length > 0) {
976
1109
  coreConfig.types.messagingComponent ??= [];
977
1110
  coreConfig.types.messagingComponent.push({ type: MessagingComponentType.Service });
978
1111
  }
@@ -982,7 +1115,7 @@ function configureMessaging(coreConfig, envVars) {
982
1115
  * @param coreConfig The core config.
983
1116
  * @param envVars The environment variables.
984
1117
  */
985
- function configureFaucet(coreConfig, envVars) {
1118
+ async function configureFaucet(coreConfig, envVars) {
986
1119
  coreConfig.types.faucetConnector ??= [];
987
1120
  if (envVars.faucetConnector === FaucetConnectorType.EntityStorage) {
988
1121
  coreConfig.types.faucetConnector.push({
@@ -990,14 +1123,14 @@ function configureFaucet(coreConfig, envVars) {
990
1123
  });
991
1124
  }
992
1125
  else if (envVars.faucetConnector === FaucetConnectorType.Iota) {
993
- const iotaConfig = getIotaConfig(coreConfig);
1126
+ const dltConfig = EngineTypeHelper.getConfigOfType(coreConfig, "dltConfig", DltConfigType.Iota);
994
1127
  coreConfig.types.faucetConnector.push({
995
1128
  type: FaucetConnectorType.Iota,
996
1129
  options: {
997
1130
  config: {
998
1131
  endpoint: envVars.iotaFaucetEndpoint ?? "",
999
- clientOptions: iotaConfig?.clientOptions ?? { url: "" },
1000
- network: iotaConfig?.network ?? ""
1132
+ clientOptions: dltConfig?.options?.config?.clientOptions ?? { url: "" },
1133
+ network: dltConfig?.options?.config?.network ?? ""
1001
1134
  }
1002
1135
  }
1003
1136
  });
@@ -1008,7 +1141,7 @@ function configureFaucet(coreConfig, envVars) {
1008
1141
  * @param coreConfig The core config.
1009
1142
  * @param envVars The environment variables.
1010
1143
  */
1011
- function configureWallet(coreConfig, envVars) {
1144
+ async function configureWallet(coreConfig, envVars) {
1012
1145
  coreConfig.types.walletConnector ??= [];
1013
1146
  if (envVars.walletConnector === WalletConnectorType.EntityStorage) {
1014
1147
  coreConfig.types.walletConnector.push({
@@ -1016,11 +1149,11 @@ function configureWallet(coreConfig, envVars) {
1016
1149
  });
1017
1150
  }
1018
1151
  else if (envVars.walletConnector === WalletConnectorType.Iota) {
1019
- const iotaConfig = getIotaConfig(coreConfig);
1152
+ const dltConfig = EngineTypeHelper.getConfigOfType(coreConfig, "dltConfig", DltConfigType.Iota);
1020
1153
  coreConfig.types.walletConnector.push({
1021
1154
  type: WalletConnectorType.Iota,
1022
1155
  options: {
1023
- config: iotaConfig ?? {}
1156
+ config: dltConfig?.options?.config ?? {}
1024
1157
  }
1025
1158
  });
1026
1159
  }
@@ -1030,7 +1163,7 @@ function configureWallet(coreConfig, envVars) {
1030
1163
  * @param coreConfig The core config.
1031
1164
  * @param envVars The environment variables.
1032
1165
  */
1033
- function configureNft(coreConfig, envVars) {
1166
+ async function configureNft(coreConfig, envVars) {
1034
1167
  coreConfig.types.nftConnector ??= [];
1035
1168
  if (envVars.nftConnector === NftConnectorType.EntityStorage) {
1036
1169
  coreConfig.types.nftConnector.push({
@@ -1038,11 +1171,11 @@ function configureNft(coreConfig, envVars) {
1038
1171
  });
1039
1172
  }
1040
1173
  else if (envVars.nftConnector === NftConnectorType.Iota) {
1041
- const iotaConfig = getIotaConfig(coreConfig);
1174
+ const dltConfig = EngineTypeHelper.getConfigOfType(coreConfig, "dltConfig", DltConfigType.Iota);
1042
1175
  coreConfig.types.nftConnector.push({
1043
1176
  type: NftConnectorType.Iota,
1044
1177
  options: {
1045
- config: iotaConfig ?? {}
1178
+ config: dltConfig?.options?.config ?? {}
1046
1179
  }
1047
1180
  });
1048
1181
  }
@@ -1056,7 +1189,7 @@ function configureNft(coreConfig, envVars) {
1056
1189
  * @param coreConfig The core config.
1057
1190
  * @param envVars The environment variables.
1058
1191
  */
1059
- function configureVerifiableStorage(coreConfig, envVars) {
1192
+ async function configureVerifiableStorage(coreConfig, envVars) {
1060
1193
  coreConfig.types.verifiableStorageConnector ??= [];
1061
1194
  if (envVars.verifiableStorageConnector === VerifiableStorageConnectorType.EntityStorage) {
1062
1195
  coreConfig.types.verifiableStorageConnector.push({
@@ -1064,11 +1197,11 @@ function configureVerifiableStorage(coreConfig, envVars) {
1064
1197
  });
1065
1198
  }
1066
1199
  else if (envVars.verifiableStorageConnector === VerifiableStorageConnectorType.Iota) {
1067
- const iotaConfig = getIotaConfig(coreConfig);
1200
+ const dltConfig = EngineTypeHelper.getConfigOfType(coreConfig, "dltConfig", DltConfigType.Iota);
1068
1201
  coreConfig.types.verifiableStorageConnector.push({
1069
1202
  type: VerifiableStorageConnectorType.Iota,
1070
1203
  options: {
1071
- config: iotaConfig ?? {}
1204
+ config: dltConfig?.options?.config ?? {}
1072
1205
  }
1073
1206
  });
1074
1207
  }
@@ -1082,18 +1215,10 @@ function configureVerifiableStorage(coreConfig, envVars) {
1082
1215
  type: ImmutableProofComponentType.Service,
1083
1216
  options: {
1084
1217
  config: {
1085
- verificationMethodId: envVars.immutableProofVerificationMethodId
1218
+ verificationMethodId: envVars.immutableProofVerificationMethodId ?? IMMUTABLE_PROOF_VERIFICATION_METHOD_ID
1086
1219
  }
1087
1220
  }
1088
1221
  });
1089
- coreConfig.types.auditableItemGraphComponent ??= [];
1090
- coreConfig.types.auditableItemGraphComponent.push({
1091
- type: AuditableItemGraphComponentType.Service
1092
- });
1093
- coreConfig.types.auditableItemStreamComponent ??= [];
1094
- coreConfig.types.auditableItemStreamComponent.push({
1095
- type: AuditableItemStreamComponentType.Service
1096
- });
1097
1222
  }
1098
1223
  }
1099
1224
  /**
@@ -1101,7 +1226,7 @@ function configureVerifiableStorage(coreConfig, envVars) {
1101
1226
  * @param coreConfig The core config.
1102
1227
  * @param envVars The environment variables.
1103
1228
  */
1104
- function configureIdentity(coreConfig, envVars) {
1229
+ async function configureIdentity(coreConfig, envVars) {
1105
1230
  coreConfig.types.identityConnector ??= [];
1106
1231
  if (envVars.identityConnector === IdentityConnectorType.EntityStorage) {
1107
1232
  coreConfig.types.identityConnector.push({
@@ -1109,11 +1234,11 @@ function configureIdentity(coreConfig, envVars) {
1109
1234
  });
1110
1235
  }
1111
1236
  else if (envVars.identityConnector === IdentityConnectorType.Iota) {
1112
- const iotaConfig = getIotaConfig(coreConfig);
1237
+ const dltConfig = EngineTypeHelper.getConfigOfType(coreConfig, "dltConfig", DltConfigType.Iota);
1113
1238
  coreConfig.types.identityConnector.push({
1114
1239
  type: IdentityConnectorType.Iota,
1115
1240
  options: {
1116
- config: iotaConfig ?? {}
1241
+ config: dltConfig?.options?.config ?? {}
1117
1242
  }
1118
1243
  });
1119
1244
  }
@@ -1127,7 +1252,7 @@ function configureIdentity(coreConfig, envVars) {
1127
1252
  * @param coreConfig The core config.
1128
1253
  * @param envVars The environment variables.
1129
1254
  */
1130
- function configureIdentityResolver(coreConfig, envVars) {
1255
+ async function configureIdentityResolver(coreConfig, envVars) {
1131
1256
  coreConfig.types.identityResolverConnector ??= [];
1132
1257
  if (envVars.identityResolverConnector === IdentityResolverConnectorType.EntityStorage) {
1133
1258
  coreConfig.types.identityResolverConnector.push({
@@ -1135,11 +1260,11 @@ function configureIdentityResolver(coreConfig, envVars) {
1135
1260
  });
1136
1261
  }
1137
1262
  else if (envVars.identityResolverConnector === IdentityResolverConnectorType.Iota) {
1138
- const iotaConfig = getIotaConfig(coreConfig);
1263
+ const dltConfig = EngineTypeHelper.getConfigOfType(coreConfig, "dltConfig", DltConfigType.Iota);
1139
1264
  coreConfig.types.identityResolverConnector.push({
1140
1265
  type: IdentityResolverConnectorType.Iota,
1141
1266
  options: {
1142
- config: iotaConfig ?? {}
1267
+ config: dltConfig?.options?.config ?? {}
1143
1268
  }
1144
1269
  });
1145
1270
  }
@@ -1165,7 +1290,7 @@ function configureIdentityResolver(coreConfig, envVars) {
1165
1290
  * @param coreConfig The core config.
1166
1291
  * @param envVars The environment variables.
1167
1292
  */
1168
- function configureIdentityProfile(coreConfig, envVars) {
1293
+ async function configureIdentityProfile(coreConfig, envVars) {
1169
1294
  coreConfig.types.identityProfileConnector ??= [];
1170
1295
  if (envVars.identityProfileConnector === IdentityConnectorType.EntityStorage) {
1171
1296
  coreConfig.types.identityProfileConnector.push({
@@ -1182,7 +1307,7 @@ function configureIdentityProfile(coreConfig, envVars) {
1182
1307
  * @param coreConfig The core config.
1183
1308
  * @param envVars The environment variables.
1184
1309
  */
1185
- function configureAttestation(coreConfig, envVars) {
1310
+ async function configureAttestation(coreConfig, envVars) {
1186
1311
  coreConfig.types.attestationConnector ??= [];
1187
1312
  if (envVars.attestationConnector === AttestationConnectorType.Nft) {
1188
1313
  coreConfig.types.attestationConnector.push({
@@ -1195,7 +1320,7 @@ function configureAttestation(coreConfig, envVars) {
1195
1320
  type: AttestationComponentType.Service,
1196
1321
  options: {
1197
1322
  config: {
1198
- verificationMethodId: envVars.attestationVerificationMethodId
1323
+ verificationMethodId: envVars.attestationVerificationMethodId ?? ATTESTATION_VERIFICATION_METHOD_ID
1199
1324
  }
1200
1325
  }
1201
1326
  });
@@ -1206,8 +1331,8 @@ function configureAttestation(coreConfig, envVars) {
1206
1331
  * @param coreConfig The core config.
1207
1332
  * @param envVars The environment variables.
1208
1333
  */
1209
- function configureAuditableItemGraph(coreConfig, envVars) {
1210
- if (Is.arrayValue(coreConfig.types.verifiableStorageConnector)) {
1334
+ async function configureAuditableItemGraph(coreConfig, envVars) {
1335
+ if (Coerce.boolean(envVars.auditableItemGraphEnabled) ?? false) {
1211
1336
  coreConfig.types.auditableItemGraphComponent ??= [];
1212
1337
  coreConfig.types.auditableItemGraphComponent.push({
1213
1338
  type: AuditableItemGraphComponentType.Service
@@ -1219,8 +1344,8 @@ function configureAuditableItemGraph(coreConfig, envVars) {
1219
1344
  * @param coreConfig The core config.
1220
1345
  * @param envVars The environment variables.
1221
1346
  */
1222
- function configureAuditableItemStream(coreConfig, envVars) {
1223
- if (Is.arrayValue(coreConfig.types.verifiableStorageConnector)) {
1347
+ async function configureAuditableItemStream(coreConfig, envVars) {
1348
+ if (Coerce.boolean(envVars.auditableItemStreamEnabled) ?? false) {
1224
1349
  coreConfig.types.auditableItemStreamComponent ??= [];
1225
1350
  coreConfig.types.auditableItemStreamComponent.push({
1226
1351
  type: AuditableItemStreamComponentType.Service
@@ -1232,34 +1357,33 @@ function configureAuditableItemStream(coreConfig, envVars) {
1232
1357
  * @param coreConfig The core config.
1233
1358
  * @param envVars The environment variables.
1234
1359
  */
1235
- function configureDataProcessing(coreConfig, envVars) {
1236
- coreConfig.types.dataConverterConnector ??= [];
1237
- coreConfig.types.dataExtractorConnector ??= [];
1238
- const converterConnectors = envVars.dataConverterConnectors?.split(",") ?? [];
1239
- for (const converterConnector of converterConnectors) {
1240
- if (converterConnector === DataConverterConnectorType.Json) {
1241
- coreConfig.types.dataConverterConnector.push({
1242
- type: DataConverterConnectorType.Json
1243
- });
1244
- }
1245
- else if (converterConnector === DataConverterConnectorType.Xml) {
1246
- coreConfig.types.dataConverterConnector.push({
1247
- type: DataConverterConnectorType.Xml
1248
- });
1249
- }
1250
- }
1251
- const extractorConnectors = envVars.dataExtractorConnectors?.split(",") ?? [];
1252
- for (const extractorConnector of extractorConnectors) {
1253
- if (extractorConnector === DataExtractorConnectorType.JsonPath) {
1254
- coreConfig.types.dataExtractorConnector.push({
1255
- type: DataExtractorConnectorType.JsonPath
1256
- });
1257
- }
1258
- }
1259
- if (coreConfig.types.dataConverterConnector.length > 0 ||
1260
- coreConfig.types.dataExtractorConnector.length > 0) {
1360
+ async function configureDataProcessing(coreConfig, envVars) {
1361
+ if (Coerce.boolean(envVars.dataProcessingEnabled) ?? false) {
1261
1362
  coreConfig.types.dataProcessingComponent ??= [];
1262
1363
  coreConfig.types.dataProcessingComponent.push({ type: DataProcessingComponentType.Service });
1364
+ coreConfig.types.dataConverterConnector ??= [];
1365
+ const converterConnectors = envVars.dataConverterConnectors?.split(",") ?? [];
1366
+ for (const converterConnector of converterConnectors) {
1367
+ if (converterConnector === DataConverterConnectorType.Json) {
1368
+ coreConfig.types.dataConverterConnector.push({
1369
+ type: DataConverterConnectorType.Json
1370
+ });
1371
+ }
1372
+ else if (converterConnector === DataConverterConnectorType.Xml) {
1373
+ coreConfig.types.dataConverterConnector.push({
1374
+ type: DataConverterConnectorType.Xml
1375
+ });
1376
+ }
1377
+ }
1378
+ coreConfig.types.dataExtractorConnector ??= [];
1379
+ const extractorConnectors = envVars.dataExtractorConnectors?.split(",") ?? [];
1380
+ for (const extractorConnector of extractorConnectors) {
1381
+ if (extractorConnector === DataExtractorConnectorType.JsonPath) {
1382
+ coreConfig.types.dataExtractorConnector.push({
1383
+ type: DataExtractorConnectorType.JsonPath
1384
+ });
1385
+ }
1386
+ }
1263
1387
  }
1264
1388
  }
1265
1389
  /**
@@ -1267,10 +1391,8 @@ function configureDataProcessing(coreConfig, envVars) {
1267
1391
  * @param coreConfig The core config.
1268
1392
  * @param envVars The environment variables.
1269
1393
  */
1270
- function configureDocumentManagement(coreConfig, envVars) {
1271
- if (Is.arrayValue(coreConfig.types.auditableItemGraphComponent) &&
1272
- Is.arrayValue(coreConfig.types.blobStorageComponent) &&
1273
- Is.arrayValue(coreConfig.types.attestationComponent)) {
1394
+ async function configureDocumentManagement(coreConfig, envVars) {
1395
+ if (Coerce.boolean(envVars.documentManagementEnabled) ?? false) {
1274
1396
  coreConfig.types.documentManagementComponent ??= [];
1275
1397
  coreConfig.types.documentManagementComponent.push({
1276
1398
  type: DocumentManagementComponentType.Service
@@ -1278,21 +1400,22 @@ function configureDocumentManagement(coreConfig, envVars) {
1278
1400
  }
1279
1401
  }
1280
1402
  /**
1281
- * Configures the federated catalogue.
1403
+ * Configures the verifiable credential authentication.
1282
1404
  * @param coreConfig The core config.
1283
1405
  * @param envVars The environment variables.
1284
1406
  */
1285
- function configureFederatedCatalogue(coreConfig, envVars) {
1286
- if (Is.arrayValue(coreConfig.types.identityResolverComponent)) {
1287
- coreConfig.types.federatedCatalogueComponent ??= [];
1288
- coreConfig.types.federatedCatalogueComponent.push({
1289
- type: FederatedCatalogueComponentType.Service,
1407
+ async function configureVerifiableCredentialAuthentication(coreConfig, envVars) {
1408
+ if (Coerce.boolean(envVars.vcAuthenticationEnabled) ?? false) {
1409
+ // Can only perform VC authentication if identity component is available
1410
+ coreConfig.types.authenticationGeneratorComponent ??= [];
1411
+ coreConfig.types.authenticationGeneratorComponent.push({
1412
+ type: AuthenticationGeneratorComponentType.VerifiableCredential,
1290
1413
  options: {
1291
1414
  config: {
1292
- subResourceCacheTtlMs: Coerce.number(envVars.federatedCatalogueCacheTtlMs),
1293
- clearingHouseApproverList: Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
1415
+ verificationMethodId: envVars.vcAuthenticationVerificationMethodId ?? VC_AUTHENTICATION_VERIFICATION_METHOD_ID
1294
1416
  }
1295
- }
1417
+ },
1418
+ features: ["verifiable-credential"]
1296
1419
  });
1297
1420
  }
1298
1421
  }
@@ -1301,15 +1424,87 @@ function configureFederatedCatalogue(coreConfig, envVars) {
1301
1424
  * @param coreConfig The core config.
1302
1425
  * @param envVars The environment variables.
1303
1426
  */
1304
- function configureRightsManagement(coreConfig, envVars) {
1427
+ async function configureRightsManagement(coreConfig, envVars) {
1305
1428
  if (Coerce.boolean(envVars.rightsManagementEnabled) ?? false) {
1306
1429
  coreConfig.types.rightsManagementPapComponent ??= [];
1307
1430
  coreConfig.types.rightsManagementPapComponent.push({
1308
1431
  type: RightsManagementPapComponentType.Service
1309
1432
  });
1310
- coreConfig.types.rightsManagementComponent ??= [];
1311
- coreConfig.types.rightsManagementComponent.push({
1312
- type: RightsManagementComponentType.Service
1433
+ coreConfig.types.rightsManagementPmpComponent ??= [];
1434
+ coreConfig.types.rightsManagementPmpComponent.push({
1435
+ type: RightsManagementPmpComponentType.Service
1436
+ });
1437
+ coreConfig.types.rightsManagementPipComponent ??= [];
1438
+ coreConfig.types.rightsManagementPipComponent.push({
1439
+ type: RightsManagementPipComponentType.Service,
1440
+ options: {
1441
+ informationModulesConfig: Is.arrayValue(envVars.rightsManagementInformationSources)
1442
+ ? envVars.rightsManagementInformationSources
1443
+ : undefined
1444
+ }
1445
+ });
1446
+ coreConfig.types.rightsManagementPxpComponent ??= [];
1447
+ coreConfig.types.rightsManagementPxpComponent.push({
1448
+ type: RightsManagementPxpComponentType.Service,
1449
+ options: {
1450
+ actionModulesConfig: Is.arrayValue(envVars.rightsManagementExecutionActions)
1451
+ ? envVars.rightsManagementExecutionActions
1452
+ : undefined
1453
+ }
1454
+ });
1455
+ coreConfig.types.rightsManagementPdpComponent ??= [];
1456
+ coreConfig.types.rightsManagementPdpComponent.push({
1457
+ type: RightsManagementPdpComponentType.Service,
1458
+ options: {
1459
+ arbiterModulesConfig: Is.arrayValue(envVars.rightsManagementArbiters)
1460
+ ? envVars.rightsManagementArbiters
1461
+ : undefined
1462
+ }
1463
+ });
1464
+ coreConfig.types.rightsManagementPepComponent ??= [];
1465
+ coreConfig.types.rightsManagementPepComponent.push({
1466
+ type: RightsManagementPepComponentType.Service,
1467
+ options: {
1468
+ processorModulesConfig: Is.arrayValue(envVars.rightsManagementEnforcementProcessors)
1469
+ ? envVars.rightsManagementEnforcementProcessors
1470
+ : undefined
1471
+ }
1472
+ });
1473
+ coreConfig.types.rightsManagementPnpComponent ??= [];
1474
+ coreConfig.types.rightsManagementPnpComponent.push({
1475
+ type: RightsManagementPnpComponentType.Service,
1476
+ options: {
1477
+ negotiatorModulesConfig: Is.arrayValue(envVars.rightsManagementNegotiators)
1478
+ ? envVars.rightsManagementNegotiators
1479
+ : undefined,
1480
+ requesterModulesConfig: Is.arrayValue(envVars.rightsManagementRequesters)
1481
+ ? envVars.rightsManagementRequesters
1482
+ : undefined,
1483
+ config: {
1484
+ baseCallbackUrl: envVars.rightsManagementBaseCallbackUrl ?? "",
1485
+ offers: Is.arrayValue(envVars.rightsManagementOffers)
1486
+ ? envVars.rightsManagementOffers
1487
+ : [],
1488
+ negotiationComponentCreator: async (url) => new PolicyNegotiationPointClient({ endpoint: url })
1489
+ }
1490
+ }
1491
+ });
1492
+ coreConfig.types.rightsManagementPnapComponent ??= [];
1493
+ coreConfig.types.rightsManagementPnapComponent.push({
1494
+ type: RightsManagementPnapComponentType.Service
1495
+ });
1496
+ coreConfig.types.rightsManagementDapComponent ??= [];
1497
+ coreConfig.types.rightsManagementDapComponent.push({
1498
+ type: RightsManagementDapComponentType.Service
1499
+ });
1500
+ coreConfig.types.rightsManagementDarpComponent ??= [];
1501
+ coreConfig.types.rightsManagementDarpComponent.push({
1502
+ type: RightsManagementDarpComponentType.Service,
1503
+ options: {
1504
+ config: {
1505
+ dataAccessComponentCreator: async (url) => new DataAccessPointClient({ endpoint: url })
1506
+ }
1507
+ }
1313
1508
  });
1314
1509
  }
1315
1510
  }
@@ -1318,11 +1513,92 @@ function configureRightsManagement(coreConfig, envVars) {
1318
1513
  * @param coreConfig The core config.
1319
1514
  * @param envVars The environment variables.
1320
1515
  */
1321
- function configureTaskScheduler(coreConfig, envVars) {
1322
- if (Coerce.boolean(envVars.taskSchedulerEnabled) ?? true) {
1516
+ async function configureTaskScheduler(coreConfig, envVars) {
1517
+ if (Coerce.boolean(envVars.taskSchedulerEnabled) ?? false) {
1323
1518
  coreConfig.types.taskSchedulerComponent ??= [];
1324
1519
  coreConfig.types.taskSchedulerComponent.push({
1325
- type: TaskSchedulerComponentType.Default
1520
+ type: TaskSchedulerComponentType.Service
1521
+ });
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Configures the synchronised storage.
1526
+ * @param coreConfig The core config.
1527
+ * @param envVars The environment variables.
1528
+ */
1529
+ async function configureSynchronisedStorage(coreConfig, envVars) {
1530
+ if (Is.arrayValue(coreConfig.types.identityResolverComponent) &&
1531
+ (Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false)) {
1532
+ // Check if the config provides a custom verifiable storage key id
1533
+ let verifiableStorageKeyId = Coerce.string(envVars.synchronisedStorageVerifiableStorageKeyId);
1534
+ if (!Is.stringValue(verifiableStorageKeyId)) {
1535
+ // No custom key so default to the network setting
1536
+ verifiableStorageKeyId = envVars.iotaNetwork;
1537
+ }
1538
+ coreConfig.types.synchronisedStorageComponent ??= [];
1539
+ coreConfig.types.synchronisedStorageComponent.push({
1540
+ type: SynchronisedStorageComponentType.Service,
1541
+ options: {
1542
+ config: {
1543
+ verifiableStorageKeyId: verifiableStorageKeyId ?? "",
1544
+ blobStorageEncryptionKeyId: envVars.synchronisedStorageBlobStorageEncryptionKeyId ??
1545
+ SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID,
1546
+ entityUpdateIntervalMinutes: Coerce.number(envVars.synchronisedStorageEntityUpdateIntervalMinutes),
1547
+ consolidationIntervalMinutes: Coerce.number(envVars.synchronisedStorageConsolidationIntervalMinutes),
1548
+ consolidationBatchSize: Coerce.number(envVars.synchronisedStorageConsolidationBatchSize),
1549
+ maxConsolidations: Coerce.number(envVars.synchronisedStorageMaxConsolidations)
1550
+ }
1551
+ }
1552
+ });
1553
+ // If there is a trusted url set, we need to add a client
1554
+ // and give it a feature of trusted so that when the synchronised
1555
+ // storage is created it can pickup the correct component
1556
+ if (Is.stringValue(envVars.synchronisedStorageTrustedUrl)) {
1557
+ coreConfig.types.synchronisedStorageComponent.push({
1558
+ type: SynchronisedStorageComponentType.RestClient,
1559
+ options: {
1560
+ endpoint: envVars.synchronisedStorageTrustedUrl
1561
+ },
1562
+ features: ["trusted"]
1563
+ });
1564
+ }
1565
+ }
1566
+ }
1567
+ /**
1568
+ * Configures the federated catalogue.
1569
+ * @param coreConfig The core config.
1570
+ * @param envVars The environment variables.
1571
+ */
1572
+ async function configureFederatedCatalogue(coreConfig, envVars) {
1573
+ if (Coerce.boolean(envVars.federatedCatalogueEnabled) ?? false) {
1574
+ coreConfig.types.federatedCatalogueComponent ??= [];
1575
+ coreConfig.types.federatedCatalogueComponent.push({
1576
+ type: FederatedCatalogueComponentType.Service,
1577
+ options: {
1578
+ config: {
1579
+ subResourceCacheTtlMs: Coerce.number(envVars.federatedCatalogueCacheTtlMs),
1580
+ clearingHouseApproverList: Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
1581
+ }
1582
+ }
1583
+ });
1584
+ }
1585
+ }
1586
+ /**
1587
+ * Configures the data space connector.
1588
+ * @param coreConfig The core config.
1589
+ * @param envVars The environment variables.
1590
+ */
1591
+ async function configureDataSpaceConnector(coreConfig, envVars) {
1592
+ if (Coerce.boolean(envVars.dataSpaceConnectorEnabled) ?? false) {
1593
+ coreConfig.types.dataSpaceConnectorComponent ??= [];
1594
+ coreConfig.types.dataSpaceConnectorComponent.push({
1595
+ type: DataSpaceConnectorComponentType.Service,
1596
+ options: {
1597
+ config: {
1598
+ retainActivityLogsFor: Coerce.number(envVars.dataSpaceConnectorRetainActivityLogsFor),
1599
+ activityLogsCleanUpInterval: Coerce.number(envVars.dataSpaceConnectorActivityLogsCleanUpInterval)
1600
+ }
1601
+ }
1326
1602
  });
1327
1603
  }
1328
1604
  }
@@ -1331,7 +1607,7 @@ function configureTaskScheduler(coreConfig, envVars) {
1331
1607
  * @param coreConfig The core config.
1332
1608
  * @param envVars The environment variables.
1333
1609
  */
1334
- function configureDlt(coreConfig, envVars) {
1610
+ async function configureDlt(coreConfig, envVars) {
1335
1611
  // Create centralized DLT configuration for IOTA if essential IOTA variables are set
1336
1612
  if (Is.stringValue(envVars.iotaNodeEndpoint) && Is.stringValue(envVars.iotaNetwork)) {
1337
1613
  coreConfig.types.dltConfig ??= [];
@@ -1365,10 +1641,10 @@ function configureDlt(coreConfig, envVars) {
1365
1641
  * @param coreEngineConfig The core engine config.
1366
1642
  * @param serverInfo The server information.
1367
1643
  * @param openApiSpecPath The path to the open api spec.
1368
- * @returns The the config for the core and the server.
1644
+ * @param favIconPath The path to the favicon.
1645
+ * @returns The config for the core and the server.
1369
1646
  */
1370
- function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, openApiSpecPath) {
1371
- envVars.authSigningKeyId ??= "auth-signing";
1647
+ async function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, openApiSpecPath, favIconPath) {
1372
1648
  const webServerOptions = {
1373
1649
  port: Coerce.number(envVars.port),
1374
1650
  host: Coerce.string(envVars.host),
@@ -1394,7 +1670,8 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
1394
1670
  options: {
1395
1671
  config: {
1396
1672
  serverInfo,
1397
- openApiSpecPath
1673
+ openApiSpecPath,
1674
+ favIconPath
1398
1675
  }
1399
1676
  }
1400
1677
  }
@@ -1414,8 +1691,9 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
1414
1691
  }
1415
1692
  serverConfig.types.restRouteProcessor ??= [];
1416
1693
  serverConfig.types.socketRouteProcessor ??= [];
1417
- const disableNodeIdentity = Coerce.boolean(envVars.disableNodeIdentity);
1418
- if (!disableNodeIdentity) {
1694
+ const features = getFeatures(envVars);
1695
+ const hasNodeIdentity = features.includes(NodeFeatures.NodeIdentity);
1696
+ if (hasNodeIdentity) {
1419
1697
  serverConfig.types.restRouteProcessor.push({
1420
1698
  type: RestRouteProcessorType.NodeIdentity
1421
1699
  });
@@ -1424,11 +1702,18 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
1424
1702
  });
1425
1703
  }
1426
1704
  if (!coreEngineConfig.silent) {
1705
+ const includeBody = Coerce.boolean(envVars.routeLoggingIncludeBody) ?? coreEngineConfig.debug;
1706
+ const fullBase64 = Coerce.boolean(envVars.routeLoggingFullBase64) ?? false;
1707
+ const obfuscateProperties = Is.stringValue(envVars.routeLoggingObfuscateProperties)
1708
+ ? envVars.routeLoggingObfuscateProperties.split(",")
1709
+ : undefined;
1427
1710
  serverConfig.types.restRouteProcessor.push({
1428
1711
  type: RestRouteProcessorType.Logging,
1429
1712
  options: {
1430
1713
  config: {
1431
- includeBody: coreEngineConfig.debug
1714
+ includeBody,
1715
+ fullBase64,
1716
+ obfuscateProperties
1432
1717
  }
1433
1718
  }
1434
1719
  });
@@ -1436,7 +1721,9 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
1436
1721
  type: SocketRouteProcessorType.Logging,
1437
1722
  options: {
1438
1723
  config: {
1439
- includeBody: coreEngineConfig.debug
1724
+ includeBody,
1725
+ fullBase64,
1726
+ obfuscateProperties
1440
1727
  }
1441
1728
  }
1442
1729
  });
@@ -1474,7 +1761,7 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
1474
1761
  type: AuthenticationComponentType.EntityStorage,
1475
1762
  options: {
1476
1763
  config: {
1477
- signingKeyName: envVars.authSigningKeyId
1764
+ signingKeyName: envVars.authSigningKeyId ?? AUTH_SIGNING_KEY_ID
1478
1765
  }
1479
1766
  }
1480
1767
  });
@@ -1482,7 +1769,7 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
1482
1769
  type: RestRouteProcessorType.AuthHeader,
1483
1770
  options: {
1484
1771
  config: {
1485
- signingKeyName: envVars.authSigningKeyId
1772
+ signingKeyName: envVars.authSigningKeyId ?? AUTH_SIGNING_KEY_ID
1486
1773
  }
1487
1774
  }
1488
1775
  });
@@ -1490,11 +1777,19 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
1490
1777
  type: SocketRouteProcessorType.AuthHeader,
1491
1778
  options: {
1492
1779
  config: {
1493
- signingKeyName: envVars.authSigningKeyId
1780
+ signingKeyName: envVars.authSigningKeyId ?? AUTH_SIGNING_KEY_ID
1494
1781
  }
1495
1782
  }
1496
1783
  });
1497
1784
  }
1785
+ if (Coerce.boolean(envVars.vcAuthenticationEnabled) ?? false) {
1786
+ serverConfig.types.restRouteProcessor.push({
1787
+ type: RestRouteProcessorType.AuthVerifiableCredential
1788
+ });
1789
+ serverConfig.types.socketRouteProcessor.push({
1790
+ type: SocketRouteProcessorType.AuthVerifiableCredential
1791
+ });
1792
+ }
1498
1793
  addDefaultRestPaths(serverConfig);
1499
1794
  addDefaultSocketPaths(serverConfig);
1500
1795
  return serverConfig;
@@ -1502,18 +1797,115 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
1502
1797
 
1503
1798
  // Copyright 2024 IOTA Stiftung.
1504
1799
  // SPDX-License-Identifier: Apache-2.0.
1505
- /* eslint-disable no-console */
1800
+ /**
1801
+ * Handles the configuration of the extensions.
1802
+ * @param envVars The environment variables for the node.
1803
+ * @param nodeEngineConfig The node engine config.
1804
+ * @returns The config for the core and the server.
1805
+ */
1806
+ async function extensionsConfiguration(envVars, nodeEngineConfig) {
1807
+ if (Is.stringValue(envVars.extensions)) {
1808
+ const extensions = envVars.extensions.split(",");
1809
+ for (const extension of extensions) {
1810
+ let initialiseConfigMethod;
1811
+ try {
1812
+ CLIDisplay.value(I18n.formatMessage("node.extensionLoading"), extension);
1813
+ initialiseConfigMethod = await ModuleHelper.getModuleMethod(extension, "extensionInitialise");
1814
+ }
1815
+ catch (err) {
1816
+ throw new GeneralError("node", "extensionLoadingError", { extension }, err);
1817
+ }
1818
+ if (Is.function(initialiseConfigMethod)) {
1819
+ await initialiseConfigMethod(envVars, nodeEngineConfig);
1820
+ }
1821
+ }
1822
+ }
1823
+ return nodeEngineConfig;
1824
+ }
1825
+ /**
1826
+ * Handles the initialisation of the extensions when the engine has been constructed.
1827
+ * @param envVars The environment variables for the node.
1828
+ * @param engineCore The engine core instance.
1829
+ * @returns Nothing.
1830
+ */
1831
+ async function extensionsInitialiseEngine(envVars, engineCore) {
1832
+ if (Is.stringValue(envVars.extensions)) {
1833
+ const extensions = envVars.extensions.split(",");
1834
+ for (const extension of extensions) {
1835
+ let initialiseEngineMethod;
1836
+ try {
1837
+ engineCore.logInfo(I18n.formatMessage("node.extensionInitialisingEngine", { extension }));
1838
+ initialiseEngineMethod =
1839
+ await ModuleHelper.getModuleMethod(extension, "extensionInitialiseEngine");
1840
+ }
1841
+ catch { }
1842
+ if (Is.function(initialiseEngineMethod)) {
1843
+ await initialiseEngineMethod(engineCore);
1844
+ }
1845
+ }
1846
+ }
1847
+ }
1848
+ /**
1849
+ * Handles the initialisation of the extensions when the engine server has been constructed.
1850
+ * @param envVars The environment variables for the node.
1851
+ * @param engineCore The engine core instance.
1852
+ * @param engineServer The engine server instance.
1853
+ * @returns Nothing.
1854
+ */
1855
+ async function extensionsInitialiseEngineServer(envVars, engineCore, engineServer) {
1856
+ if (Is.stringValue(envVars.extensions)) {
1857
+ const extensions = envVars.extensions.split(",");
1858
+ for (const extension of extensions) {
1859
+ let initialiseEngineServerMethod;
1860
+ try {
1861
+ engineCore.logInfo(I18n.formatMessage("node.extensionInitialisingEngineServer", { extension }));
1862
+ initialiseEngineServerMethod =
1863
+ await ModuleHelper.getModuleMethod(extension, "extensionInitialiseEngineServer");
1864
+ }
1865
+ catch { }
1866
+ if (Is.function(initialiseEngineServerMethod)) {
1867
+ await initialiseEngineServerMethod(engineCore, engineServer);
1868
+ }
1869
+ }
1870
+ }
1871
+ }
1872
+ /**
1873
+ * Handles the shutdown of the extensions.
1874
+ * @param envVars The environment variables for the node.
1875
+ * @param engineCore The engine core instance.
1876
+ * @returns Nothing.
1877
+ */
1878
+ async function shutdownExtensions(envVars, engineCore) {
1879
+ if (Is.stringValue(envVars.extensions)) {
1880
+ const extensions = envVars.extensions.split(",");
1881
+ for (const extension of extensions) {
1882
+ let shutdownMethod;
1883
+ try {
1884
+ engineCore.logInfo(I18n.formatMessage("node.extensionShutdown", { extension }));
1885
+ shutdownMethod = await ModuleHelper.getModuleMethod(extension, "extensionShutdown");
1886
+ }
1887
+ catch { }
1888
+ if (Is.function(shutdownMethod)) {
1889
+ await shutdownMethod();
1890
+ }
1891
+ }
1892
+ }
1893
+ }
1894
+
1895
+ // Copyright 2024 IOTA Stiftung.
1896
+ // SPDX-License-Identifier: Apache-2.0.
1506
1897
  /**
1507
1898
  * Start the engine server.
1508
1899
  * @param nodeOptions Optional run options for the engine server.
1509
- * @param engineServerConfig The configuration for the engine server.
1900
+ * @param nodeEngineConfig The configuration for the engine server.
1510
1901
  * @param envVars The environment variables.
1511
1902
  * @returns The engine server.
1512
1903
  */
1513
- async function start(nodeOptions, engineServerConfig, envVars) {
1514
- envVars.storageFileRoot ??= "";
1904
+ async function start(nodeOptions, nodeEngineConfig, envVars) {
1515
1905
  const entityStorageConnectorType = envVars.entityStorageConnectorType?.split(",") ?? [];
1516
1906
  const blobStorageConnectorType = envVars.blobStorageConnectorType?.split(",") ?? [];
1907
+ // If the blob storage or entity storage is configured with file connectors
1908
+ // then we need to make sure the storageFileRoot is set
1517
1909
  if ((entityStorageConnectorType.includes(EntityStorageConnectorType.File) ||
1518
1910
  blobStorageConnectorType.includes(BlobStorageConnectorType.File) ||
1519
1911
  Is.empty(nodeOptions?.stateStorage)) &&
@@ -1522,24 +1914,26 @@ async function start(nodeOptions, engineServerConfig, envVars) {
1522
1914
  storageFileRoot: `${nodeOptions?.envPrefix ?? ""}_STORAGE_FILE_ROOT`
1523
1915
  });
1524
1916
  }
1525
- // Create the engine instance using file state storage and custom bootstrap.
1917
+ // Create the engine instance using file state storage unless one is configured in options
1526
1918
  const engine = new Engine({
1527
- config: engineServerConfig,
1919
+ config: nodeEngineConfig,
1528
1920
  stateStorage: nodeOptions?.stateStorage ?? new FileStateStorage(envVars.stateFilename ?? ""),
1529
1921
  customBootstrap: async (core, engineContext) => bootstrap(core, engineContext, envVars)
1530
1922
  });
1923
+ // Construct the server with the engine.
1924
+ const server = new EngineServer({ engineCore: engine });
1531
1925
  // Extend the engine.
1532
1926
  if (Is.function(nodeOptions?.extendEngine)) {
1533
- console.info("Extending Engine");
1927
+ engine.logInfo(I18n.formatMessage("node.extendingEngine"));
1534
1928
  await nodeOptions.extendEngine(engine);
1535
1929
  }
1536
- // Construct the server with the engine.
1537
- const server = new EngineServer({ engineCore: engine });
1930
+ await extensionsInitialiseEngine(envVars, engine);
1538
1931
  // Extend the engine server.
1539
1932
  if (Is.function(nodeOptions?.extendEngineServer)) {
1540
- console.info("Extending Engine Server");
1933
+ engine.logInfo(I18n.formatMessage("node.extendingEngineServer"));
1541
1934
  await nodeOptions?.extendEngineServer(server);
1542
1935
  }
1936
+ await extensionsInitialiseEngineServer(envVars, engine, server);
1543
1937
  // Need to register the engine with the factory so that background tasks
1544
1938
  // can clone it to spawn new instances.
1545
1939
  EngineCoreFactory.register("engine", () => engine);
@@ -1548,14 +1942,17 @@ async function start(nodeOptions, engineServerConfig, envVars) {
1548
1942
  if (canContinue) {
1549
1943
  return {
1550
1944
  engine,
1551
- server
1945
+ server,
1946
+ shutdown: async () => {
1947
+ await server.stop();
1948
+ await shutdownExtensions(envVars, engine);
1949
+ }
1552
1950
  };
1553
1951
  }
1554
1952
  }
1555
1953
 
1556
1954
  // Copyright 2024 IOTA Stiftung.
1557
1955
  // SPDX-License-Identifier: Apache-2.0.
1558
- /* eslint-disable no-console */
1559
1956
  /**
1560
1957
  * Run the TWIN Node server.
1561
1958
  * @param nodeOptions Optional configuration options for running the server.
@@ -1566,41 +1963,60 @@ async function run(nodeOptions) {
1566
1963
  nodeOptions ??= {};
1567
1964
  const serverInfo = {
1568
1965
  name: nodeOptions?.serverName ?? "TWIN Node Server",
1569
- version: nodeOptions?.serverVersion ?? "0.0.2-next.2" // x-release-please-version
1966
+ version: nodeOptions?.serverVersion ?? "0.0.2-next.21" // x-release-please-version
1570
1967
  };
1571
- console.log(`\u001B[4m🌩️ ${serverInfo.name} v${serverInfo.version}\u001B[24m\n`);
1968
+ CLIDisplay.header(serverInfo.name, serverInfo.version, "🌩️ ");
1572
1969
  if (!Is.stringValue(nodeOptions?.executionDirectory)) {
1573
1970
  nodeOptions.executionDirectory = getExecutionDirectory();
1574
1971
  }
1575
- console.info("Execution Directory:", nodeOptions.executionDirectory);
1972
+ CLIDisplay.value("Execution Directory", nodeOptions.executionDirectory);
1576
1973
  nodeOptions.localesDirectory =
1577
1974
  nodeOptions?.localesDirectory ??
1578
1975
  path.resolve(path.join(nodeOptions.executionDirectory, "dist", "locales"));
1579
- console.info("Locales Directory:", nodeOptions.localesDirectory);
1976
+ CLIDisplay.value("Locales Directory", nodeOptions.localesDirectory);
1580
1977
  await initialiseLocales(nodeOptions.localesDirectory);
1581
1978
  if (Is.empty(nodeOptions?.openApiSpecFile)) {
1582
- const specFile = path.resolve(path.join(nodeOptions.executionDirectory, "docs", "open-api", "spec.json"));
1583
- console.info("Default OpenAPI Spec File:", specFile);
1979
+ const specFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "docs", "open-api", "spec.json"));
1980
+ CLIDisplay.value("Default OpenAPI Spec File", specFile);
1584
1981
  if (await fileExists(specFile)) {
1585
1982
  nodeOptions ??= {};
1586
1983
  nodeOptions.openApiSpecFile = specFile;
1587
1984
  }
1588
1985
  }
1986
+ else {
1987
+ CLIDisplay.value("OpenAPI Spec File", nodeOptions.openApiSpecFile);
1988
+ }
1989
+ if (Is.empty(nodeOptions?.favIconFile)) {
1990
+ const favIconFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "static", "favicon.png"));
1991
+ CLIDisplay.value("Default Favicon File", favIconFile);
1992
+ if (await fileExists(favIconFile)) {
1993
+ nodeOptions ??= {};
1994
+ nodeOptions.favIconFile = favIconFile;
1995
+ }
1996
+ }
1997
+ else {
1998
+ CLIDisplay.value("Favicon File", nodeOptions.favIconFile);
1999
+ }
1589
2000
  nodeOptions.envPrefix ??= "TWIN_NODE_";
1590
- console.info("Environment Prefix:", nodeOptions.envPrefix);
1591
- const { engineServerConfig, nodeEnvVars: envVars } = await buildConfiguration(process.env, nodeOptions, serverInfo);
1592
- console.info();
1593
- const startResult = await start(nodeOptions, engineServerConfig, envVars);
2001
+ CLIDisplay.value("Environment Variable Prefix", nodeOptions.envPrefix);
2002
+ overrideModuleImport(nodeOptions.executionDirectory ?? "");
2003
+ const { nodeEngineConfig, nodeEnvVars: envVars } = await buildConfiguration(
2004
+ // This is the only location in the code base that should access process.env directly
2005
+ // eslint-disable-next-line no-restricted-syntax
2006
+ process.env, nodeOptions, serverInfo);
2007
+ CLIDisplay.break();
2008
+ const startResult = await start(nodeOptions, nodeEngineConfig, envVars);
1594
2009
  if (!Is.empty(startResult)) {
1595
2010
  for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"]) {
1596
2011
  process.on(signal, async () => {
1597
- await startResult.server.stop();
2012
+ CLIDisplay.value("Terminate Signal", signal);
2013
+ await startResult.shutdown();
1598
2014
  });
1599
2015
  }
1600
2016
  }
1601
2017
  }
1602
2018
  catch (err) {
1603
- console.error(ErrorHelper.formatErrors(err).join("\n"));
2019
+ CLIDisplay.error(err);
1604
2020
  // eslint-disable-next-line unicorn/no-process-exit
1605
2021
  process.exit(1);
1606
2022
  }
@@ -1617,14 +2033,15 @@ async function buildConfiguration(processEnv, options, serverInfo) {
1617
2033
  let defaultEnvOnly = false;
1618
2034
  if (Is.empty(options?.envFilenames)) {
1619
2035
  const envFile = path.resolve(path.join(options.executionDirectory ?? "", ".env"));
1620
- console.info("Default Environment File:", envFile);
2036
+ CLIDisplay.value("Default Environment File", envFile);
1621
2037
  options ??= {};
1622
2038
  options.envFilenames = [envFile];
1623
2039
  defaultEnvOnly = true;
1624
2040
  }
1625
2041
  if (Is.arrayValue(options?.envFilenames)) {
1626
2042
  const output = dotenv.config({
1627
- path: options?.envFilenames
2043
+ path: options?.envFilenames,
2044
+ quiet: true
1628
2045
  });
1629
2046
  // We don't want to throw an error if the default environment file is not found.
1630
2047
  // Only if we have custom environment files.
@@ -1639,41 +2056,82 @@ async function buildConfiguration(processEnv, options, serverInfo) {
1639
2056
  // Expand any environment variables that use the @file: syntax
1640
2057
  const keys = Object.keys(envVars);
1641
2058
  for (const key of keys) {
1642
- if (Is.stringValue(envVars[key]) && envVars[key].startsWith("@file:")) {
2059
+ if (Is.stringValue(envVars[key]) &&
2060
+ (envVars[key].startsWith("@text:") || envVars[key].startsWith("@json:"))) {
1643
2061
  const filePath = envVars[key].slice(6).trim();
1644
2062
  const embeddedFile = path.resolve(path.join(options.executionDirectory ?? "", filePath));
1645
- console.info(`Expanding Environment Variable: ${key} from file: ${embeddedFile}`);
1646
- const fileContent = await loadJsonFile(embeddedFile);
1647
- envVars[key] = fileContent;
2063
+ if (envVars[key].startsWith("@text:")) {
2064
+ CLIDisplay.value(`Expanding Environment Variable: ${key} from text file`, embeddedFile);
2065
+ envVars[key] = await loadTextFile(embeddedFile);
2066
+ }
2067
+ else if (envVars[key].startsWith("@json:")) {
2068
+ CLIDisplay.value(`Expanding Environment Variable: ${key} from JSON file`, embeddedFile);
2069
+ envVars[key] = await loadJsonFile(embeddedFile);
2070
+ }
1648
2071
  }
1649
2072
  }
1650
2073
  // Extend the environment variables with any additional custom configuration.
1651
2074
  if (Is.function(options?.extendEnvVars)) {
1652
- console.info("Extending Environment Variables");
2075
+ CLIDisplay.task("Extending Environment Variables");
1653
2076
  await options.extendEnvVars(envVars);
1654
2077
  }
1655
2078
  // Build the engine configuration from the environment variables.
1656
- const coreConfig = buildEngineConfiguration(envVars);
1657
- const engineServerConfig = buildEngineServerConfiguration(envVars, coreConfig, serverInfo, options?.openApiSpecFile);
2079
+ const coreConfig = await buildEngineConfiguration(envVars);
2080
+ const engineServerConfig = await buildEngineServerConfiguration(envVars, coreConfig, serverInfo, options?.openApiSpecFile, options?.favIconFile);
1658
2081
  // Merge any custom configuration provided in the options.
1659
2082
  if (Is.arrayValue(options?.configFilenames)) {
1660
2083
  for (const configFile of options.configFilenames) {
1661
- console.info("Loading Configuration File:", configFile);
2084
+ CLIDisplay.value("Loading Configuration File", configFile);
1662
2085
  const configFilePath = path.resolve(path.join(options.executionDirectory ?? "", configFile));
1663
2086
  const config = await loadJsonFile(configFilePath);
1664
2087
  Object.assign(engineServerConfig, config);
1665
2088
  }
1666
2089
  }
1667
2090
  if (Is.objectValue(options?.config)) {
1668
- console.info("Merging Custom Configuration");
2091
+ CLIDisplay.task("Merging Custom Configuration");
1669
2092
  Object.assign(engineServerConfig, options.config);
1670
2093
  }
1671
2094
  // Merge any custom configuration provided in the options.
1672
2095
  if (Is.function(options?.extendConfig)) {
1673
- console.info("Extending Configuration");
1674
- await options.extendConfig(engineServerConfig);
2096
+ CLIDisplay.task("Extending Configuration");
2097
+ await options.extendConfig(envVars, engineServerConfig);
1675
2098
  }
1676
- return { engineServerConfig, nodeEnvVars: envVars };
2099
+ const nodeEngineConfig = await extensionsConfiguration(envVars, engineServerConfig);
2100
+ return { nodeEngineConfig, nodeEnvVars: envVars };
2101
+ }
2102
+ /**
2103
+ * Override module imports to use local files where possible.
2104
+ * @param executionDirectory The execution directory for resolving local module paths.
2105
+ */
2106
+ function overrideModuleImport(executionDirectory) {
2107
+ ModuleHelper.overrideImport(async (moduleName) => {
2108
+ // If the module path for example when dynamically loading
2109
+ // modules looks like a local file then we try to resolve
2110
+ // using the local file system
2111
+ const isLocal = ModuleHelper.isLocalModule(moduleName);
2112
+ if (isLocal) {
2113
+ // See if we can just resolve the filename locally
2114
+ let localFilename = path.resolve(moduleName);
2115
+ let exists = await fileExists(localFilename);
2116
+ if (!exists) {
2117
+ // Doesn't exist in the current directory, try the execution directory
2118
+ localFilename = path.resolve(executionDirectory, moduleName);
2119
+ exists = await fileExists(localFilename);
2120
+ }
2121
+ if (exists) {
2122
+ // If the module exists then we can load it, otherwise
2123
+ // we fallback to regular handling to see if that can import it
2124
+ return {
2125
+ module: await import(process.platform === "win32" ? `file://${localFilename}` : localFilename),
2126
+ useDefault: false
2127
+ };
2128
+ }
2129
+ }
2130
+ // The filename doesn't look like a local module, so just use default handling
2131
+ return {
2132
+ useDefault: true
2133
+ };
2134
+ });
1677
2135
  }
1678
2136
 
1679
- export { NodeFeatures, bootstrap, bootstrapAttestationMethod, bootstrapAuth, bootstrapBlobEncryption, bootstrapImmutableProofMethod, bootstrapNodeIdentity, bootstrapNodeUser, buildConfiguration, buildEngineConfiguration, buildEngineServerConfiguration, fileExists, getExecutionDirectory, getFeatures, initialiseLocales, loadJsonFile, run, start };
2137
+ export { ATTESTATION_VERIFICATION_METHOD_ID, AUTH_SIGNING_KEY_ID, BLOB_STORAGE_ENCRYPTION_KEY_ID, IMMUTABLE_PROOF_VERIFICATION_METHOD_ID, NodeFeatures, SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID, VC_AUTHENTICATION_VERIFICATION_METHOD_ID, bootstrap, bootstrapAuth, bootstrapBlobEncryption, bootstrapImmutableProofMethod, bootstrapNodeIdentity, bootstrapNodeUser, bootstrapSynchronisedStorage, buildConfiguration, buildEngineConfiguration, buildEngineServerConfiguration, directoryExists, extensionsConfiguration, extensionsInitialiseEngine, extensionsInitialiseEngineServer, fileExists, getExecutionDirectory, getFeatures, getFiles, getSubFolders, initialiseLocales, loadJsonFile, loadTextFile, overrideModuleImport, run, shutdownExtensions, start };