@twin.org/node-core 0.0.2-next.1 â 0.0.2-next.11
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.
- package/dist/cjs/index.cjs +438 -218
- package/dist/esm/index.mjs +438 -220
- package/dist/types/bootstrap.d.ts +9 -9
- package/dist/types/builders/engineServerEnvBuilder.d.ts +2 -1
- package/dist/types/models/IEngineEnvironmentVariables.d.ts +115 -21
- package/dist/types/models/INodeOptions.d.ts +4 -0
- package/dist/types/models/nodeFeatures.d.ts +4 -0
- package/dist/types/node.d.ts +5 -0
- package/dist/types/utils.d.ts +6 -0
- package/docs/changelog.md +73 -0
- package/docs/reference/functions/{bootstrapAttestationMethod.md â bootstrapSynchronisedStorage.md} +3 -3
- package/docs/reference/functions/buildEngineServerConfiguration.md +7 -1
- package/docs/reference/functions/loadTextFile.md +19 -0
- package/docs/reference/functions/overrideModuleImport.md +17 -0
- package/docs/reference/index.md +3 -1
- package/docs/reference/interfaces/IEngineEnvironmentVariables.md +222 -32
- package/docs/reference/interfaces/INodeEnvironmentVariables.md +309 -43
- package/docs/reference/interfaces/INodeOptions.md +8 -0
- package/docs/reference/variables/NodeFeatures.md +7 -1
- package/locales/en.json +6 -4
- package/package.json +5 -2
package/dist/esm/index.mjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { PasswordHelper } from '@twin.org/api-auth-entity-storage-service';
|
|
2
|
-
import { I18n, Is, Converter, RandomHelper, StringHelper,
|
|
2
|
+
import { I18n, Is, Coerce, Converter, RandomHelper, StringHelper, Urn, GeneralError, ErrorHelper, EnvHelper } from '@twin.org/core';
|
|
3
3
|
import { PasswordGenerator, Bip39 } from '@twin.org/crypto';
|
|
4
|
-
import {
|
|
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, MessagingComponentType, FaucetConnectorType, NftConnectorType, NftComponentType, VerifiableStorageConnectorType, VerifiableStorageComponentType, ImmutableProofComponentType, AuditableItemGraphComponentType, AuditableItemStreamComponentType, IdentityComponentType, IdentityResolverConnectorType, IdentityResolverComponentType, IdentityProfileConnectorType, IdentityProfileComponentType, AttestationConnectorType, AttestationComponentType, DataConverterConnectorType, DataExtractorConnectorType, DataProcessingComponentType, DocumentManagementComponentType, RightsManagementPapComponentType, RightsManagementPmpComponentType, RightsManagementPipComponentType, RightsManagementPxpComponentType, RightsManagementPdpComponentType, RightsManagementPepComponentType, RightsManagementPnpComponentType, RightsManagementPnapComponentType, RightsManagementPnrpComponentType, 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
10
|
import { readFile, stat } from 'node:fs/promises';
|
|
10
11
|
import path from 'node:path';
|
|
12
|
+
import { PolicyNegotiationPointClient } from '@twin.org/rights-management-rest-client';
|
|
11
13
|
import { addDefaultRestPaths, addDefaultSocketPaths, EngineServer } from '@twin.org/engine-server';
|
|
12
|
-
import {
|
|
14
|
+
import { ModuleHelper } from '@twin.org/modules';
|
|
13
15
|
import * as dotenv from 'dotenv';
|
|
14
16
|
import { Engine } from '@twin.org/engine';
|
|
15
17
|
import { FileStateStorage } from '@twin.org/engine-core';
|
|
@@ -29,7 +31,11 @@ const NodeFeatures = {
|
|
|
29
31
|
/**
|
|
30
32
|
* NodeUser - generates a user for the node if not provided in config.
|
|
31
33
|
*/
|
|
32
|
-
NodeUser: "node-user"
|
|
34
|
+
NodeUser: "node-user",
|
|
35
|
+
/**
|
|
36
|
+
* NodeWallet - generates a wallet for the node and funds it when there is a faucet available.
|
|
37
|
+
*/
|
|
38
|
+
NodeWallet: "node-wallet"
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -71,13 +77,21 @@ async function fileExists(filename) {
|
|
|
71
77
|
return false;
|
|
72
78
|
}
|
|
73
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Load the text file.
|
|
82
|
+
* @param filename The filename of the text file to load.
|
|
83
|
+
* @returns The contents of the text file if it could not be loaded.
|
|
84
|
+
*/
|
|
85
|
+
async function loadTextFile(filename) {
|
|
86
|
+
return readFile(filename, "utf8");
|
|
87
|
+
}
|
|
74
88
|
/**
|
|
75
89
|
* Load the JSON file.
|
|
76
90
|
* @param filename The filename of the JSON file to load.
|
|
77
91
|
* @returns The contents of the JSON file or null if it could not be loaded.
|
|
78
92
|
*/
|
|
79
93
|
async function loadJsonFile(filename) {
|
|
80
|
-
const content = await
|
|
94
|
+
const content = await loadTextFile(filename);
|
|
81
95
|
return JSON.parse(content);
|
|
82
96
|
}
|
|
83
97
|
/**
|
|
@@ -116,8 +130,12 @@ async function bootstrap(engineCore, context, envVars) {
|
|
|
116
130
|
await bootstrapNodeUser(engineCore, context, envVars, features);
|
|
117
131
|
await bootstrapAuth(engineCore, context, envVars);
|
|
118
132
|
await bootstrapBlobEncryption(engineCore, context, envVars);
|
|
119
|
-
await
|
|
120
|
-
await
|
|
133
|
+
await addVerificationMethod(engineCore, context, "attestation", envVars.attestationVerificationMethodId);
|
|
134
|
+
await addVerificationMethod(engineCore, context, "immutable proof", envVars.immutableProofVerificationMethodId);
|
|
135
|
+
if (Coerce.boolean(envVars.rightsManagementEnabled) ?? false) {
|
|
136
|
+
await addVerificationMethod(engineCore, context, "rights management", envVars.rightsManagementNegotiationMethodId);
|
|
137
|
+
}
|
|
138
|
+
await bootstrapSynchronisedStorage(engineCore, context, envVars);
|
|
121
139
|
}
|
|
122
140
|
/**
|
|
123
141
|
* Bootstrap the node creating any necessary resources.
|
|
@@ -132,23 +150,21 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
|
|
|
132
150
|
// But we have a chicken and egg problem in that we can't create the identity
|
|
133
151
|
// to store the mnemonic in the vault without an identity. We use a temporary identity
|
|
134
152
|
// and then replace it with the new identity later in the process.
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
153
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
154
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
155
|
+
const workingIdentity = envVars.identity ??
|
|
156
|
+
context.state.nodeIdentity ??
|
|
157
|
+
`bootstrap-temp-${Converter.bytesToHex(RandomHelper.generate(16))}`;
|
|
158
|
+
await bootstrapMnemonic(engineCore, envVars, features, vaultConnector, workingIdentity);
|
|
159
|
+
const addresses = await bootstrapWallet(engineCore, envVars, features, workingIdentity);
|
|
160
|
+
const finalIdentity = await bootstrapIdentity(engineCore, envVars, features, workingIdentity);
|
|
161
|
+
await finaliseWallet(engineCore, envVars, features, finalIdentity, addresses);
|
|
162
|
+
await finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity);
|
|
163
|
+
context.state.nodeIdentity = finalIdentity;
|
|
164
|
+
context.stateDirty = true;
|
|
165
|
+
engineCore.logInfo(I18n.formatMessage("node.nodeIdentity", {
|
|
166
|
+
identity: context.state.nodeIdentity
|
|
167
|
+
}));
|
|
152
168
|
}
|
|
153
169
|
}
|
|
154
170
|
/**
|
|
@@ -160,12 +176,13 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
|
|
|
160
176
|
* @returns The addresses for the wallet.
|
|
161
177
|
*/
|
|
162
178
|
async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
163
|
-
const
|
|
179
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
164
180
|
// Now create an identity for the node controlled by the address we just funded
|
|
165
|
-
const identityConnector = IdentityConnectorFactory.get(
|
|
181
|
+
const identityConnector = IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
166
182
|
let identityDocument;
|
|
167
183
|
try {
|
|
168
|
-
const
|
|
184
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
185
|
+
const identityResolverConnector = IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
169
186
|
identityDocument = await identityResolverConnector.resolveDocument(nodeIdentity);
|
|
170
187
|
engineCore.logInfo(I18n.formatMessage("node.existingNodeIdentity", { identity: nodeIdentity }));
|
|
171
188
|
}
|
|
@@ -175,7 +192,7 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
|
175
192
|
identityDocument = await identityConnector.createDocument(nodeIdentity);
|
|
176
193
|
engineCore.logInfo(I18n.formatMessage("node.createdNodeIdentity", { identity: identityDocument.id }));
|
|
177
194
|
}
|
|
178
|
-
if (
|
|
195
|
+
if (defaultIdentityConnectorType.startsWith(IdentityConnectorType.Iota)) {
|
|
179
196
|
const didUrn = Urn.fromValidString(identityDocument.id);
|
|
180
197
|
const didParts = didUrn.parts();
|
|
181
198
|
const objectId = didParts[3];
|
|
@@ -194,23 +211,26 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
|
194
211
|
* @returns The addresses for the wallet.
|
|
195
212
|
*/
|
|
196
213
|
async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
214
|
+
if (features.includes(NodeFeatures.NodeWallet)) {
|
|
215
|
+
const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
|
|
216
|
+
const walletConnector = WalletConnectorFactory.get(defaultWalletConnectorType);
|
|
217
|
+
const addresses = await walletConnector.getAddresses(nodeIdentity, 0, 0, 5);
|
|
218
|
+
const balance = await walletConnector.getBalance(nodeIdentity, addresses[0]);
|
|
219
|
+
if (balance === 0n) {
|
|
220
|
+
let address0 = addresses[0];
|
|
221
|
+
if (defaultWalletConnectorType.startsWith(WalletConnectorType.Iota)) {
|
|
222
|
+
address0 = `${envVars.iotaExplorerEndpoint}address/${address0}?network=${envVars.iotaNetwork}`;
|
|
223
|
+
}
|
|
224
|
+
engineCore.logInfo(I18n.formatMessage("node.fundingWallet", { address: address0 }));
|
|
225
|
+
// Add some funds to the wallet from the faucet
|
|
226
|
+
await walletConnector.ensureBalance(nodeIdentity, addresses[0], 1000000000n);
|
|
205
227
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
else {
|
|
211
|
-
engineCore.logInfo(I18n.formatMessage("node.fundedWallet"));
|
|
228
|
+
else {
|
|
229
|
+
engineCore.logInfo(I18n.formatMessage("node.fundedWallet"));
|
|
230
|
+
}
|
|
231
|
+
return addresses;
|
|
212
232
|
}
|
|
213
|
-
return
|
|
233
|
+
return [];
|
|
214
234
|
}
|
|
215
235
|
/**
|
|
216
236
|
* Bootstrap the identity for the node.
|
|
@@ -221,15 +241,17 @@ async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
|
|
|
221
241
|
* @param addresses The addresses for the wallet.
|
|
222
242
|
*/
|
|
223
243
|
async function finaliseWallet(engineCore, envVars, features, finalIdentity, addresses) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
addr
|
|
232
|
-
|
|
244
|
+
if (features.includes(NodeFeatures.NodeWallet)) {
|
|
245
|
+
const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
|
|
246
|
+
// If we are using entity storage for wallet the identity associated with the
|
|
247
|
+
// address will be wrong, so fix it
|
|
248
|
+
if (defaultWalletConnectorType.startsWith(WalletConnectorType.EntityStorage)) {
|
|
249
|
+
const walletAddress = EntityStorageConnectorFactory.get(StringHelper.kebabCase("WalletAddress"));
|
|
250
|
+
const addr = await walletAddress.get(addresses[0]);
|
|
251
|
+
if (!Is.empty(addr)) {
|
|
252
|
+
addr.identity = finalIdentity;
|
|
253
|
+
await walletAddress.set(addr);
|
|
254
|
+
}
|
|
233
255
|
}
|
|
234
256
|
}
|
|
235
257
|
}
|
|
@@ -291,8 +313,8 @@ async function finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity)
|
|
|
291
313
|
*/
|
|
292
314
|
async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
293
315
|
if (features.includes(NodeFeatures.NodeUser)) {
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
316
|
+
const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceType("authenticationComponent");
|
|
317
|
+
if (defaultAuthenticationComponentType.startsWith(AuthenticationComponentType.EntityStorage) &&
|
|
296
318
|
Is.stringValue(context.state.nodeIdentity)) {
|
|
297
319
|
const authUserEntityStorage = EntityStorageConnectorFactory.get(StringHelper.kebabCase("AuthenticationUser"));
|
|
298
320
|
const email = envVars.username ?? DEFAULT_NODE_USERNAME;
|
|
@@ -335,7 +357,8 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
|
335
357
|
}
|
|
336
358
|
}
|
|
337
359
|
// We have create a node user, now we need to create a profile for the user
|
|
338
|
-
const
|
|
360
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
361
|
+
const identityProfileConnector = IdentityProfileConnectorFactory.get(defaultIdentityConnectorType);
|
|
339
362
|
if (identityProfileConnector) {
|
|
340
363
|
let userProfile;
|
|
341
364
|
try {
|
|
@@ -365,43 +388,6 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
|
365
388
|
}
|
|
366
389
|
}
|
|
367
390
|
}
|
|
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
391
|
/**
|
|
406
392
|
* Bootstrap the immutable proof verification methods.
|
|
407
393
|
* @param engineCore The engine core for the node.
|
|
@@ -409,36 +395,7 @@ async function bootstrapAttestationMethod(engineCore, context, envVars, features
|
|
|
409
395
|
* @param envVars The environment variables for the node.
|
|
410
396
|
* @param features The features that are enabled on the node.
|
|
411
397
|
*/
|
|
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
|
-
}
|
|
398
|
+
async function bootstrapImmutableProofMethod(engineCore, context, envVars, features) { }
|
|
442
399
|
/**
|
|
443
400
|
* Bootstrap the keys for blob encryption.
|
|
444
401
|
* @param engineCore The engine core for the node.
|
|
@@ -449,18 +406,28 @@ async function bootstrapImmutableProofMethod(engineCore, context, envVars, featu
|
|
|
449
406
|
async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
|
|
450
407
|
if ((Coerce.boolean(envVars.blobStorageEnableEncryption) ?? false) &&
|
|
451
408
|
Is.stringValue(context.state.nodeIdentity)) {
|
|
452
|
-
const engineDefaultTypes = engineCore.getDefaultTypes();
|
|
453
409
|
// Create a new key for encrypting blobs
|
|
454
|
-
const
|
|
455
|
-
const
|
|
410
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
411
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
412
|
+
const keyName = `${context.state.nodeIdentity}/${envVars.blobStorageEncryptionKeyId}`;
|
|
456
413
|
let existingKey;
|
|
457
414
|
try {
|
|
458
415
|
existingKey = await vaultConnector.getKey(keyName);
|
|
459
416
|
}
|
|
460
417
|
catch { }
|
|
461
418
|
if (Is.empty(existingKey)) {
|
|
462
|
-
|
|
463
|
-
|
|
419
|
+
if (Is.stringBase64(envVars.blobStorageSymmetricEncryptionKey)) {
|
|
420
|
+
engineCore.logInfo(I18n.formatMessage("node.addingBlobEncryptionKey", { keyName }));
|
|
421
|
+
await vaultConnector.addKey(keyName, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(envVars.blobStorageSymmetricEncryptionKey));
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
engineCore.logInfo(I18n.formatMessage("node.creatingBlobEncryptionKey", { keyName }));
|
|
425
|
+
const key = await vaultConnector.createKey(keyName, VaultKeyType.ChaCha20Poly1305);
|
|
426
|
+
engineCore.logInfo(I18n.formatMessage("node.createdBlobEncryptionKey", {
|
|
427
|
+
keyName,
|
|
428
|
+
keyValue: Converter.bytesToBase64(key)
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
464
431
|
}
|
|
465
432
|
else {
|
|
466
433
|
engineCore.logInfo(I18n.formatMessage("node.existingBlobEncryptionKey", { keyName }));
|
|
@@ -475,11 +442,13 @@ async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
|
|
|
475
442
|
* @param features The features that are enabled on the node.
|
|
476
443
|
*/
|
|
477
444
|
async function bootstrapAuth(engineCore, context, envVars, features) {
|
|
478
|
-
const
|
|
479
|
-
if (
|
|
445
|
+
const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceTypeOptional("authenticationComponent");
|
|
446
|
+
if (Is.stringValue(defaultAuthenticationComponentType) &&
|
|
447
|
+
defaultAuthenticationComponentType.startsWith(AuthenticationComponentType.EntityStorage) &&
|
|
480
448
|
Is.stringValue(context.state.nodeIdentity)) {
|
|
481
449
|
// Create a new JWT signing key and a user login for the node
|
|
482
|
-
const
|
|
450
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
451
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
483
452
|
const keyName = `${context.state.nodeIdentity}/${envVars.authSigningKeyId}`;
|
|
484
453
|
let existingKey;
|
|
485
454
|
try {
|
|
@@ -495,6 +464,76 @@ async function bootstrapAuth(engineCore, context, envVars, features) {
|
|
|
495
464
|
}
|
|
496
465
|
}
|
|
497
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Bootstrap the synchronised storage blob encryption and verification methods.
|
|
469
|
+
* @param engineCore The engine core for the node.
|
|
470
|
+
* @param context The context for the node.
|
|
471
|
+
* @param envVars The environment variables for the node.
|
|
472
|
+
* @param features The features that are enabled on the node.
|
|
473
|
+
*/
|
|
474
|
+
async function bootstrapSynchronisedStorage(engineCore, context, envVars, features) {
|
|
475
|
+
if (Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false) {
|
|
476
|
+
// Add the verification method to the identity if it doesn't exist
|
|
477
|
+
await addVerificationMethod(engineCore, context, "synchronised storage", envVars.synchronisedStorageVerificationMethodId);
|
|
478
|
+
// If this is a trusted node we need to add the blob encryption key pair
|
|
479
|
+
if (Is.stringValue(envVars.synchronisedStorageBlobStorageEncryptionKeyId) &&
|
|
480
|
+
Is.stringBase64(envVars.synchronisedStorageBlobStorageKey)) {
|
|
481
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
482
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
483
|
+
const keyName = envVars.synchronisedStorageBlobStorageEncryptionKeyId;
|
|
484
|
+
let existingKey;
|
|
485
|
+
try {
|
|
486
|
+
existingKey = await vaultConnector.getKey(keyName);
|
|
487
|
+
}
|
|
488
|
+
catch { }
|
|
489
|
+
if (Is.empty(existingKey)) {
|
|
490
|
+
engineCore.logInfo(I18n.formatMessage("node.addingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
491
|
+
await vaultConnector.addKey(keyName, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(envVars.synchronisedStorageBlobStorageKey));
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
engineCore.logInfo(I18n.formatMessage("node.existingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Add a verification method if it doesn't exist.
|
|
501
|
+
* @param engineCore The engine core for the node.
|
|
502
|
+
* @param context The context for the node.
|
|
503
|
+
* @param verificationMethodTitle The verification method title.
|
|
504
|
+
* @param verificationMethodId The verification method ID.
|
|
505
|
+
*/
|
|
506
|
+
async function addVerificationMethod(engineCore, context, verificationMethodTitle, verificationMethodId) {
|
|
507
|
+
if (Is.stringValue(context.state.nodeIdentity) &&
|
|
508
|
+
Is.arrayValue(context.config.types.identityConnector) &&
|
|
509
|
+
Is.stringValue(verificationMethodId)) {
|
|
510
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
511
|
+
const identityConnector = IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
512
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
513
|
+
const identityResolverConnector = IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
514
|
+
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
515
|
+
const fullMethodId = `${identityDocument.id}#${verificationMethodId}`;
|
|
516
|
+
let exists = false;
|
|
517
|
+
try {
|
|
518
|
+
DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
519
|
+
exists = true;
|
|
520
|
+
}
|
|
521
|
+
catch { }
|
|
522
|
+
if (!exists) {
|
|
523
|
+
engineCore.logInfo(I18n.formatMessage("node.addingVerificationMethod", {
|
|
524
|
+
title: verificationMethodTitle,
|
|
525
|
+
methodId: fullMethodId
|
|
526
|
+
}));
|
|
527
|
+
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", verificationMethodId);
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
engineCore.logInfo(I18n.formatMessage("node.existingVerificationMethod", {
|
|
531
|
+
title: verificationMethodTitle,
|
|
532
|
+
methodId: fullMethodId
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
498
537
|
|
|
499
538
|
// Copyright 2024 IOTA Stiftung.
|
|
500
539
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -512,7 +551,10 @@ function buildEngineConfiguration(envVars) {
|
|
|
512
551
|
envVars.attestationVerificationMethodId ??= "attestation-assertion";
|
|
513
552
|
envVars.immutableProofVerificationMethodId ??= "immutable-proof-assertion";
|
|
514
553
|
envVars.blobStorageEnableEncryption ??= "false";
|
|
515
|
-
envVars.
|
|
554
|
+
envVars.blobStorageEncryptionKeyId ??= "blob-encryption";
|
|
555
|
+
envVars.synchronisedStorageBlobStorageEncryptionKeyId ??= "synchronised-storage-blob-encryption";
|
|
556
|
+
envVars.synchronisedStorageVerificationMethodId ??= "synchronised-storage-assertion";
|
|
557
|
+
envVars.rightsManagementNegotiationMethodId ??= "policy-negotiation-assertion";
|
|
516
558
|
const coreConfig = {
|
|
517
559
|
debug: Coerce.boolean(envVars.debug) ?? false,
|
|
518
560
|
types: {}
|
|
@@ -523,6 +565,7 @@ function buildEngineConfiguration(envVars) {
|
|
|
523
565
|
configureDlt(coreConfig, envVars);
|
|
524
566
|
configureLogging(coreConfig, envVars);
|
|
525
567
|
configureBackgroundTask(coreConfig, envVars);
|
|
568
|
+
configureTaskScheduler(coreConfig, envVars);
|
|
526
569
|
configureEventBus(coreConfig, envVars);
|
|
527
570
|
configureTelemetry(coreConfig, envVars);
|
|
528
571
|
configureMessaging(coreConfig, envVars);
|
|
@@ -538,9 +581,10 @@ function buildEngineConfiguration(envVars) {
|
|
|
538
581
|
configureAuditableItemGraph(coreConfig);
|
|
539
582
|
configureAuditableItemStream(coreConfig);
|
|
540
583
|
configureDocumentManagement(coreConfig);
|
|
541
|
-
configureFederatedCatalogue(coreConfig, envVars);
|
|
542
584
|
configureRightsManagement(coreConfig, envVars);
|
|
543
|
-
|
|
585
|
+
configureSynchronisedStorage(coreConfig, envVars);
|
|
586
|
+
configureFederatedCatalogue(coreConfig, envVars);
|
|
587
|
+
configureDataSpaceConnector(coreConfig, envVars);
|
|
544
588
|
return coreConfig;
|
|
545
589
|
}
|
|
546
590
|
/**
|
|
@@ -560,14 +604,13 @@ function getIotaConfig(coreConfig) {
|
|
|
560
604
|
function configureEntityStorage(coreConfig, envVars) {
|
|
561
605
|
coreConfig.types ??= {};
|
|
562
606
|
coreConfig.types.entityStorageConnector ??= [];
|
|
563
|
-
|
|
564
|
-
|
|
607
|
+
const entityStorageConnectorTypes = envVars.entityStorageConnectorType?.split(",") ?? [];
|
|
608
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.Memory)) {
|
|
565
609
|
coreConfig.types.entityStorageConnector.push({
|
|
566
610
|
type: EntityStorageConnectorType.Memory
|
|
567
611
|
});
|
|
568
612
|
}
|
|
569
|
-
if (
|
|
570
|
-
envVars.entityStorageConnectorType === EntityStorageConnectorType.File) {
|
|
613
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.File)) {
|
|
571
614
|
coreConfig.types.entityStorageConnector.push({
|
|
572
615
|
type: EntityStorageConnectorType.File,
|
|
573
616
|
options: {
|
|
@@ -576,7 +619,7 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
576
619
|
}
|
|
577
620
|
});
|
|
578
621
|
}
|
|
579
|
-
if (
|
|
622
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.AwsDynamoDb)) {
|
|
580
623
|
coreConfig.types.entityStorageConnector.push({
|
|
581
624
|
type: EntityStorageConnectorType.AwsDynamoDb,
|
|
582
625
|
options: {
|
|
@@ -590,7 +633,7 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
590
633
|
}
|
|
591
634
|
});
|
|
592
635
|
}
|
|
593
|
-
if (
|
|
636
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.AzureCosmosDb)) {
|
|
594
637
|
coreConfig.types.entityStorageConnector.push({
|
|
595
638
|
type: EntityStorageConnectorType.AzureCosmosDb,
|
|
596
639
|
options: {
|
|
@@ -604,7 +647,7 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
604
647
|
}
|
|
605
648
|
});
|
|
606
649
|
}
|
|
607
|
-
if (
|
|
650
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.GcpFirestoreDb)) {
|
|
608
651
|
coreConfig.types.entityStorageConnector.push({
|
|
609
652
|
type: EntityStorageConnectorType.GcpFirestoreDb,
|
|
610
653
|
options: {
|
|
@@ -619,41 +662,27 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
619
662
|
}
|
|
620
663
|
});
|
|
621
664
|
}
|
|
622
|
-
if (
|
|
665
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.ScyllaDb)) {
|
|
623
666
|
coreConfig.types.entityStorageConnector.push({
|
|
624
667
|
type: EntityStorageConnectorType.ScyllaDb,
|
|
625
668
|
options: {
|
|
626
669
|
config: {
|
|
627
|
-
hosts: envVars.scylladbHosts
|
|
670
|
+
hosts: envVars.scylladbHosts?.split(",") ?? [],
|
|
628
671
|
localDataCenter: envVars.scylladbLocalDataCenter ?? "",
|
|
629
|
-
keyspace: envVars.scylladbKeyspace ?? ""
|
|
630
|
-
|
|
631
|
-
tablePrefix: envVars.entityStorageTablePrefix
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
if (Is.stringValue(envVars.mySqlHost)) {
|
|
636
|
-
coreConfig.types.entityStorageConnector.push({
|
|
637
|
-
type: EntityStorageConnectorType.MySqlDb,
|
|
638
|
-
options: {
|
|
639
|
-
config: {
|
|
640
|
-
host: envVars.mySqlHost,
|
|
641
|
-
port: envVars.mySqlPort ?? 3306,
|
|
642
|
-
user: envVars.mySqlUser ?? "",
|
|
643
|
-
password: envVars.mySqlPassword ?? "",
|
|
644
|
-
database: envVars.mySqlDatabase ?? ""
|
|
672
|
+
keyspace: envVars.scylladbKeyspace ?? "",
|
|
673
|
+
port: Coerce.integer(envVars.scylladbPort)
|
|
645
674
|
},
|
|
646
675
|
tablePrefix: envVars.entityStorageTablePrefix
|
|
647
676
|
}
|
|
648
677
|
});
|
|
649
678
|
}
|
|
650
|
-
if (
|
|
679
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.MySqlDb)) {
|
|
651
680
|
coreConfig.types.entityStorageConnector.push({
|
|
652
681
|
type: EntityStorageConnectorType.MySqlDb,
|
|
653
682
|
options: {
|
|
654
683
|
config: {
|
|
655
|
-
host: envVars.mySqlHost,
|
|
656
|
-
port: envVars.mySqlPort
|
|
684
|
+
host: envVars.mySqlHost ?? "",
|
|
685
|
+
port: Coerce.integer(envVars.mySqlPort),
|
|
657
686
|
user: envVars.mySqlUser ?? "",
|
|
658
687
|
password: envVars.mySqlPassword ?? "",
|
|
659
688
|
database: envVars.mySqlDatabase ?? ""
|
|
@@ -662,13 +691,13 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
662
691
|
}
|
|
663
692
|
});
|
|
664
693
|
}
|
|
665
|
-
if (
|
|
694
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.MongoDb)) {
|
|
666
695
|
coreConfig.types.entityStorageConnector.push({
|
|
667
696
|
type: EntityStorageConnectorType.MongoDb,
|
|
668
697
|
options: {
|
|
669
698
|
config: {
|
|
670
|
-
host: envVars.mongoDbHost,
|
|
671
|
-
port: envVars.mongoDbPort,
|
|
699
|
+
host: envVars.mongoDbHost ?? "",
|
|
700
|
+
port: Coerce.integer(envVars.mongoDbPort),
|
|
672
701
|
user: envVars.mongoDbUser ?? "",
|
|
673
702
|
password: envVars.mongoDbPassword ?? "",
|
|
674
703
|
database: envVars.mongoDbDatabase ?? ""
|
|
@@ -677,13 +706,13 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
677
706
|
}
|
|
678
707
|
});
|
|
679
708
|
}
|
|
680
|
-
if (
|
|
709
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.PostgreSql)) {
|
|
681
710
|
coreConfig.types.entityStorageConnector.push({
|
|
682
711
|
type: EntityStorageConnectorType.PostgreSql,
|
|
683
712
|
options: {
|
|
684
713
|
config: {
|
|
685
|
-
host: envVars.postgreSqlHost,
|
|
686
|
-
port: envVars.postgreSqlPort,
|
|
714
|
+
host: envVars.postgreSqlHost ?? "",
|
|
715
|
+
port: Coerce.integer(envVars.postgreSqlPort),
|
|
687
716
|
user: envVars.postgreSqlUser ?? "",
|
|
688
717
|
password: envVars.postgreSqlPassword ?? "",
|
|
689
718
|
database: envVars.postgreSqlDatabase ?? ""
|
|
@@ -692,11 +721,21 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
692
721
|
}
|
|
693
722
|
});
|
|
694
723
|
}
|
|
695
|
-
const
|
|
696
|
-
if (
|
|
724
|
+
const defaultEntityStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
725
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.Synchronised)) {
|
|
726
|
+
// For synchronised storage we use the default connector as the one we wrap for real DB operations
|
|
727
|
+
coreConfig.types.entityStorageConnector.push({
|
|
728
|
+
type: EntityStorageConnectorType.Synchronised,
|
|
729
|
+
options: {
|
|
730
|
+
entityStorageConnectorType: defaultEntityStorageConnectorType
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
if (Is.arrayValue(entityStorageConnectorTypes)) {
|
|
697
735
|
for (const config of coreConfig.types.entityStorageConnector) {
|
|
698
|
-
if (config.type ===
|
|
736
|
+
if (config.type === defaultEntityStorageConnectorType) {
|
|
699
737
|
config.isDefault = true;
|
|
738
|
+
break;
|
|
700
739
|
}
|
|
701
740
|
}
|
|
702
741
|
}
|
|
@@ -708,14 +747,13 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
708
747
|
*/
|
|
709
748
|
function configureBlobStorage(coreConfig, envVars) {
|
|
710
749
|
coreConfig.types.blobStorageConnector ??= [];
|
|
711
|
-
|
|
712
|
-
|
|
750
|
+
const blobStorageConnectorTypes = envVars.blobStorageConnectorType?.split(",") ?? [];
|
|
751
|
+
if (blobStorageConnectorTypes.includes(BlobStorageConnectorType.Memory)) {
|
|
713
752
|
coreConfig.types.blobStorageConnector.push({
|
|
714
753
|
type: BlobStorageConnectorType.Memory
|
|
715
754
|
});
|
|
716
755
|
}
|
|
717
|
-
if (
|
|
718
|
-
envVars.blobStorageConnectorType === BlobStorageConnectorType.File) {
|
|
756
|
+
if (blobStorageConnectorTypes.includes(BlobStorageConnectorType.File)) {
|
|
719
757
|
coreConfig.types.blobStorageConnector.push({
|
|
720
758
|
type: BlobStorageConnectorType.File,
|
|
721
759
|
options: {
|
|
@@ -728,18 +766,18 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
728
766
|
}
|
|
729
767
|
});
|
|
730
768
|
}
|
|
731
|
-
if (
|
|
769
|
+
if (blobStorageConnectorTypes.includes(BlobStorageConnectorType.Ipfs)) {
|
|
732
770
|
coreConfig.types.blobStorageConnector.push({
|
|
733
771
|
type: BlobStorageConnectorType.Ipfs,
|
|
734
772
|
options: {
|
|
735
773
|
config: {
|
|
736
|
-
apiUrl: envVars.ipfsApiUrl,
|
|
774
|
+
apiUrl: envVars.ipfsApiUrl ?? "",
|
|
737
775
|
bearerToken: envVars.ipfsBearerToken
|
|
738
776
|
}
|
|
739
777
|
}
|
|
740
778
|
});
|
|
741
779
|
}
|
|
742
|
-
if (
|
|
780
|
+
if (blobStorageConnectorTypes.includes(BlobStorageConnectorType.AwsS3)) {
|
|
743
781
|
coreConfig.types.blobStorageConnector.push({
|
|
744
782
|
type: BlobStorageConnectorType.AwsS3,
|
|
745
783
|
options: {
|
|
@@ -754,7 +792,7 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
754
792
|
}
|
|
755
793
|
});
|
|
756
794
|
}
|
|
757
|
-
if (
|
|
795
|
+
if (blobStorageConnectorTypes.includes(BlobStorageConnectorType.AzureStorage)) {
|
|
758
796
|
coreConfig.types.blobStorageConnector.push({
|
|
759
797
|
type: BlobStorageConnectorType.AzureStorage,
|
|
760
798
|
options: {
|
|
@@ -768,7 +806,7 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
768
806
|
}
|
|
769
807
|
});
|
|
770
808
|
}
|
|
771
|
-
if (
|
|
809
|
+
if (blobStorageConnectorTypes.includes(BlobStorageConnectorType.GcpStorage)) {
|
|
772
810
|
coreConfig.types.blobStorageConnector.push({
|
|
773
811
|
type: BlobStorageConnectorType.GcpStorage,
|
|
774
812
|
options: {
|
|
@@ -782,12 +820,20 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
782
820
|
}
|
|
783
821
|
});
|
|
784
822
|
}
|
|
785
|
-
|
|
786
|
-
|
|
823
|
+
if (Is.arrayValue(blobStorageConnectorTypes)) {
|
|
824
|
+
const defaultStorageConnectorType = envVars.blobStorageConnectorDefault ?? blobStorageConnectorTypes[0];
|
|
787
825
|
for (const config of coreConfig.types.blobStorageConnector) {
|
|
788
826
|
if (config.type === defaultStorageConnectorType) {
|
|
789
827
|
config.isDefault = true;
|
|
790
828
|
}
|
|
829
|
+
// If this blob storage connector is the one to use for public access
|
|
830
|
+
// then add it as a feature
|
|
831
|
+
if (Is.stringValue(envVars.blobStorageConnectorPublic) &&
|
|
832
|
+
config.type === envVars.blobStorageConnectorPublic) {
|
|
833
|
+
config.features ??= [];
|
|
834
|
+
config.features.push("public");
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
791
837
|
}
|
|
792
838
|
}
|
|
793
839
|
if (coreConfig.types.blobStorageConnector.length > 0) {
|
|
@@ -797,7 +843,7 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
797
843
|
options: {
|
|
798
844
|
config: {
|
|
799
845
|
vaultKeyId: (envVars.blobStorageEnableEncryption ?? false)
|
|
800
|
-
? envVars.
|
|
846
|
+
? envVars.blobStorageEncryptionKeyId
|
|
801
847
|
: undefined
|
|
802
848
|
}
|
|
803
849
|
}
|
|
@@ -1292,51 +1338,168 @@ function configureDocumentManagement(coreConfig, envVars) {
|
|
|
1292
1338
|
}
|
|
1293
1339
|
}
|
|
1294
1340
|
/**
|
|
1295
|
-
* Configures the
|
|
1341
|
+
* Configures the rights management.
|
|
1296
1342
|
* @param coreConfig The core config.
|
|
1297
1343
|
* @param envVars The environment variables.
|
|
1298
1344
|
*/
|
|
1299
|
-
function
|
|
1300
|
-
if (
|
|
1301
|
-
coreConfig.types.
|
|
1302
|
-
coreConfig.types.
|
|
1303
|
-
type:
|
|
1345
|
+
function configureRightsManagement(coreConfig, envVars) {
|
|
1346
|
+
if (Coerce.boolean(envVars.rightsManagementEnabled) ?? false) {
|
|
1347
|
+
coreConfig.types.rightsManagementPapComponent ??= [];
|
|
1348
|
+
coreConfig.types.rightsManagementPapComponent.push({
|
|
1349
|
+
type: RightsManagementPapComponentType.Service
|
|
1350
|
+
});
|
|
1351
|
+
coreConfig.types.rightsManagementPmpComponent ??= [];
|
|
1352
|
+
coreConfig.types.rightsManagementPmpComponent.push({
|
|
1353
|
+
type: RightsManagementPmpComponentType.Service
|
|
1354
|
+
});
|
|
1355
|
+
coreConfig.types.rightsManagementPipComponent ??= [];
|
|
1356
|
+
coreConfig.types.rightsManagementPipComponent.push({
|
|
1357
|
+
type: RightsManagementPipComponentType.Service,
|
|
1358
|
+
options: {
|
|
1359
|
+
informationModulesConfig: Is.arrayValue(envVars.rightsManagementInformationSources)
|
|
1360
|
+
? envVars.rightsManagementInformationSources
|
|
1361
|
+
: undefined
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
coreConfig.types.rightsManagementPxpComponent ??= [];
|
|
1365
|
+
coreConfig.types.rightsManagementPxpComponent.push({
|
|
1366
|
+
type: RightsManagementPxpComponentType.Service,
|
|
1367
|
+
options: {
|
|
1368
|
+
actionModulesConfig: Is.arrayValue(envVars.rightsManagementExecutionActions)
|
|
1369
|
+
? envVars.rightsManagementExecutionActions
|
|
1370
|
+
: undefined
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
coreConfig.types.rightsManagementPdpComponent ??= [];
|
|
1374
|
+
coreConfig.types.rightsManagementPdpComponent.push({
|
|
1375
|
+
type: RightsManagementPdpComponentType.Service
|
|
1376
|
+
});
|
|
1377
|
+
coreConfig.types.rightsManagementPepComponent ??= [];
|
|
1378
|
+
coreConfig.types.rightsManagementPepComponent.push({
|
|
1379
|
+
type: RightsManagementPepComponentType.Service,
|
|
1380
|
+
options: {
|
|
1381
|
+
processorModulesConfig: Is.arrayValue(envVars.rightsManagementEnforcementProcessors)
|
|
1382
|
+
? envVars.rightsManagementEnforcementProcessors
|
|
1383
|
+
: undefined
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
coreConfig.types.rightsManagementPnpComponent ??= [];
|
|
1387
|
+
coreConfig.types.rightsManagementPnpComponent.push({
|
|
1388
|
+
type: RightsManagementPnpComponentType.Service,
|
|
1389
|
+
options: {
|
|
1390
|
+
negotiatorModulesConfig: Is.arrayValue(envVars.rightsManagementNegotiators)
|
|
1391
|
+
? envVars.rightsManagementNegotiators
|
|
1392
|
+
: undefined
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
coreConfig.types.rightsManagementPnapComponent ??= [];
|
|
1396
|
+
coreConfig.types.rightsManagementPnapComponent.push({
|
|
1397
|
+
type: RightsManagementPnapComponentType.Service
|
|
1398
|
+
});
|
|
1399
|
+
coreConfig.types.rightsManagementPnrpComponent ??= [];
|
|
1400
|
+
coreConfig.types.rightsManagementPnrpComponent.push({
|
|
1401
|
+
type: RightsManagementPnrpComponentType.Service,
|
|
1304
1402
|
options: {
|
|
1305
1403
|
config: {
|
|
1306
|
-
|
|
1307
|
-
|
|
1404
|
+
negotiationMethodId: envVars.rightsManagementNegotiationMethodId ?? "",
|
|
1405
|
+
negotiationComponentCreator: async (url) => new PolicyNegotiationPointClient({ endpoint: url })
|
|
1308
1406
|
}
|
|
1309
1407
|
}
|
|
1310
1408
|
});
|
|
1311
1409
|
}
|
|
1312
1410
|
}
|
|
1313
1411
|
/**
|
|
1314
|
-
* Configures the
|
|
1412
|
+
* Configures the task scheduler.
|
|
1315
1413
|
* @param coreConfig The core config.
|
|
1316
1414
|
* @param envVars The environment variables.
|
|
1317
1415
|
*/
|
|
1318
|
-
function
|
|
1319
|
-
if (Coerce.boolean(envVars.
|
|
1320
|
-
coreConfig.types.
|
|
1321
|
-
coreConfig.types.
|
|
1322
|
-
type:
|
|
1416
|
+
function configureTaskScheduler(coreConfig, envVars) {
|
|
1417
|
+
if (Coerce.boolean(envVars.taskSchedulerEnabled) ?? false) {
|
|
1418
|
+
coreConfig.types.taskSchedulerComponent ??= [];
|
|
1419
|
+
coreConfig.types.taskSchedulerComponent.push({
|
|
1420
|
+
type: TaskSchedulerComponentType.Service
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Configures the synchronised storage.
|
|
1426
|
+
* @param coreConfig The core config.
|
|
1427
|
+
* @param envVars The environment variables.
|
|
1428
|
+
*/
|
|
1429
|
+
function configureSynchronisedStorage(coreConfig, envVars) {
|
|
1430
|
+
if (Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1431
|
+
(Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false)) {
|
|
1432
|
+
// Check if the config provides a custom verifiable storage key id
|
|
1433
|
+
let verifiableStorageKeyId = Coerce.string(envVars.synchronisedStorageVerifiableStorageKeyId);
|
|
1434
|
+
if (!Is.stringValue(verifiableStorageKeyId)) {
|
|
1435
|
+
// No custom key so default to the network setting
|
|
1436
|
+
verifiableStorageKeyId = envVars.iotaNetwork;
|
|
1437
|
+
}
|
|
1438
|
+
coreConfig.types.synchronisedStorageComponent ??= [];
|
|
1439
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1440
|
+
type: SynchronisedStorageComponentType.Service,
|
|
1441
|
+
options: {
|
|
1442
|
+
config: {
|
|
1443
|
+
verifiableStorageKeyId: verifiableStorageKeyId ?? "",
|
|
1444
|
+
synchronisedStorageMethodId: envVars.synchronisedStorageVerificationMethodId,
|
|
1445
|
+
blobStorageEncryptionKeyId: envVars.synchronisedStorageBlobStorageEncryptionKeyId,
|
|
1446
|
+
entityUpdateIntervalMinutes: Coerce.number(envVars.synchronisedStorageEntityUpdateIntervalMinutes),
|
|
1447
|
+
consolidationIntervalMinutes: Coerce.number(envVars.synchronisedStorageConsolidationIntervalMinutes),
|
|
1448
|
+
consolidationBatchSize: Coerce.number(envVars.synchronisedStorageConsolidationBatchSize),
|
|
1449
|
+
maxConsolidations: Coerce.number(envVars.synchronisedStorageMaxConsolidations)
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1323
1452
|
});
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1453
|
+
// If there is a trusted url set, we need to add a client
|
|
1454
|
+
// and give it a feature of trusted so that when the synchronised
|
|
1455
|
+
// storage is created it can pickup the correct component
|
|
1456
|
+
if (Is.stringValue(envVars.synchronisedStorageTrustedUrl)) {
|
|
1457
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1458
|
+
type: SynchronisedStorageComponentType.RestClient,
|
|
1459
|
+
options: {
|
|
1460
|
+
endpoint: envVars.synchronisedStorageTrustedUrl
|
|
1461
|
+
},
|
|
1462
|
+
features: ["trusted"]
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Configures the federated catalogue.
|
|
1469
|
+
* @param coreConfig The core config.
|
|
1470
|
+
* @param envVars The environment variables.
|
|
1471
|
+
*/
|
|
1472
|
+
function configureFederatedCatalogue(coreConfig, envVars) {
|
|
1473
|
+
if (Coerce.boolean(envVars.federatedCatalogueEnabled) ?? false) {
|
|
1474
|
+
coreConfig.types.federatedCatalogueComponent ??= [];
|
|
1475
|
+
coreConfig.types.federatedCatalogueComponent.push({
|
|
1476
|
+
type: FederatedCatalogueComponentType.Service,
|
|
1477
|
+
options: {
|
|
1478
|
+
config: {
|
|
1479
|
+
subResourceCacheTtlMs: Coerce.number(envVars.federatedCatalogueCacheTtlMs),
|
|
1480
|
+
clearingHouseApproverList: Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1327
1483
|
});
|
|
1328
1484
|
}
|
|
1329
1485
|
}
|
|
1330
1486
|
/**
|
|
1331
|
-
* Configures the
|
|
1487
|
+
* Configures the data space connector.
|
|
1332
1488
|
* @param coreConfig The core config.
|
|
1333
1489
|
* @param envVars The environment variables.
|
|
1334
1490
|
*/
|
|
1335
|
-
function
|
|
1336
|
-
if (Coerce.boolean(envVars.
|
|
1337
|
-
coreConfig.types.
|
|
1338
|
-
coreConfig.types.
|
|
1339
|
-
type:
|
|
1491
|
+
function configureDataSpaceConnector(coreConfig, envVars) {
|
|
1492
|
+
if (Coerce.boolean(envVars.dataSpaceConnectorEnabled) ?? false) {
|
|
1493
|
+
coreConfig.types.dataSpaceConnectorComponent ??= [];
|
|
1494
|
+
coreConfig.types.dataSpaceConnectorComponent.push({
|
|
1495
|
+
type: DataSpaceConnectorComponentType.Service,
|
|
1496
|
+
options: {
|
|
1497
|
+
config: {
|
|
1498
|
+
dataSpaceConnectorAppDescriptors: Is.arrayValue(envVars.dataSpaceConnectorApps)
|
|
1499
|
+
? envVars.dataSpaceConnectorApps
|
|
1500
|
+
: undefined
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1340
1503
|
});
|
|
1341
1504
|
}
|
|
1342
1505
|
}
|
|
@@ -1379,9 +1542,10 @@ function configureDlt(coreConfig, envVars) {
|
|
|
1379
1542
|
* @param coreEngineConfig The core engine config.
|
|
1380
1543
|
* @param serverInfo The server information.
|
|
1381
1544
|
* @param openApiSpecPath The path to the open api spec.
|
|
1545
|
+
* @param favIconPath The path to the favicon.
|
|
1382
1546
|
* @returns The the config for the core and the server.
|
|
1383
1547
|
*/
|
|
1384
|
-
function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, openApiSpecPath) {
|
|
1548
|
+
function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, openApiSpecPath, favIconPath) {
|
|
1385
1549
|
envVars.authSigningKeyId ??= "auth-signing";
|
|
1386
1550
|
const webServerOptions = {
|
|
1387
1551
|
port: Coerce.number(envVars.port),
|
|
@@ -1408,7 +1572,8 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
|
|
|
1408
1572
|
options: {
|
|
1409
1573
|
config: {
|
|
1410
1574
|
serverInfo,
|
|
1411
|
-
openApiSpecPath
|
|
1575
|
+
openApiSpecPath,
|
|
1576
|
+
favIconPath
|
|
1412
1577
|
}
|
|
1413
1578
|
}
|
|
1414
1579
|
}
|
|
@@ -1525,16 +1690,19 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
|
|
|
1525
1690
|
* @returns The engine server.
|
|
1526
1691
|
*/
|
|
1527
1692
|
async function start(nodeOptions, engineServerConfig, envVars) {
|
|
1528
|
-
envVars.
|
|
1529
|
-
|
|
1530
|
-
|
|
1693
|
+
const entityStorageConnectorType = envVars.entityStorageConnectorType?.split(",") ?? [];
|
|
1694
|
+
const blobStorageConnectorType = envVars.blobStorageConnectorType?.split(",") ?? [];
|
|
1695
|
+
// If the blob storage or entity storage is configured with file connectors
|
|
1696
|
+
// then we need to make sure the storageFileRoot is set
|
|
1697
|
+
if ((entityStorageConnectorType.includes(EntityStorageConnectorType.File) ||
|
|
1698
|
+
blobStorageConnectorType.includes(BlobStorageConnectorType.File) ||
|
|
1531
1699
|
Is.empty(nodeOptions?.stateStorage)) &&
|
|
1532
1700
|
!Is.stringValue(envVars.storageFileRoot)) {
|
|
1533
1701
|
throw new GeneralError("node", "storageFileRootNotSet", {
|
|
1534
1702
|
storageFileRoot: `${nodeOptions?.envPrefix ?? ""}_STORAGE_FILE_ROOT`
|
|
1535
1703
|
});
|
|
1536
1704
|
}
|
|
1537
|
-
// Create the engine instance using file state storage
|
|
1705
|
+
// Create the engine instance using file state storage unless one is configured in options
|
|
1538
1706
|
const engine = new Engine({
|
|
1539
1707
|
config: engineServerConfig,
|
|
1540
1708
|
stateStorage: nodeOptions?.stateStorage ?? new FileStateStorage(envVars.stateFilename ?? ""),
|
|
@@ -1578,7 +1746,7 @@ async function run(nodeOptions) {
|
|
|
1578
1746
|
nodeOptions ??= {};
|
|
1579
1747
|
const serverInfo = {
|
|
1580
1748
|
name: nodeOptions?.serverName ?? "TWIN Node Server",
|
|
1581
|
-
version: nodeOptions?.serverVersion ?? "0.0.2-next.
|
|
1749
|
+
version: nodeOptions?.serverVersion ?? "0.0.2-next.11" // x-release-please-version
|
|
1582
1750
|
};
|
|
1583
1751
|
console.log(`\u001B[4mđŠī¸ ${serverInfo.name} v${serverInfo.version}\u001B[24m\n`);
|
|
1584
1752
|
if (!Is.stringValue(nodeOptions?.executionDirectory)) {
|
|
@@ -1591,15 +1759,24 @@ async function run(nodeOptions) {
|
|
|
1591
1759
|
console.info("Locales Directory:", nodeOptions.localesDirectory);
|
|
1592
1760
|
await initialiseLocales(nodeOptions.localesDirectory);
|
|
1593
1761
|
if (Is.empty(nodeOptions?.openApiSpecFile)) {
|
|
1594
|
-
const specFile = path.resolve(path.join(nodeOptions.executionDirectory, "docs", "open-api", "spec.json"));
|
|
1762
|
+
const specFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "docs", "open-api", "spec.json"));
|
|
1595
1763
|
console.info("Default OpenAPI Spec File:", specFile);
|
|
1596
1764
|
if (await fileExists(specFile)) {
|
|
1597
1765
|
nodeOptions ??= {};
|
|
1598
1766
|
nodeOptions.openApiSpecFile = specFile;
|
|
1599
1767
|
}
|
|
1600
1768
|
}
|
|
1769
|
+
if (Is.empty(nodeOptions?.favIconFile)) {
|
|
1770
|
+
const favIconFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "static", "favicon.png"));
|
|
1771
|
+
console.info("Default Favicon File:", favIconFile);
|
|
1772
|
+
if (await fileExists(favIconFile)) {
|
|
1773
|
+
nodeOptions ??= {};
|
|
1774
|
+
nodeOptions.favIconFile = favIconFile;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1601
1777
|
nodeOptions.envPrefix ??= "TWIN_NODE_";
|
|
1602
1778
|
console.info("Environment Prefix:", nodeOptions.envPrefix);
|
|
1779
|
+
overrideModuleImport(nodeOptions.executionDirectory ?? "");
|
|
1603
1780
|
const { engineServerConfig, nodeEnvVars: envVars } = await buildConfiguration(process.env, nodeOptions, serverInfo);
|
|
1604
1781
|
console.info();
|
|
1605
1782
|
const startResult = await start(nodeOptions, engineServerConfig, envVars);
|
|
@@ -1636,7 +1813,8 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1636
1813
|
}
|
|
1637
1814
|
if (Is.arrayValue(options?.envFilenames)) {
|
|
1638
1815
|
const output = dotenv.config({
|
|
1639
|
-
path: options?.envFilenames
|
|
1816
|
+
path: options?.envFilenames,
|
|
1817
|
+
quiet: true
|
|
1640
1818
|
});
|
|
1641
1819
|
// We don't want to throw an error if the default environment file is not found.
|
|
1642
1820
|
// Only if we have custom environment files.
|
|
@@ -1651,12 +1829,18 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1651
1829
|
// Expand any environment variables that use the @file: syntax
|
|
1652
1830
|
const keys = Object.keys(envVars);
|
|
1653
1831
|
for (const key of keys) {
|
|
1654
|
-
if (Is.stringValue(envVars[key]) &&
|
|
1832
|
+
if (Is.stringValue(envVars[key]) &&
|
|
1833
|
+
(envVars[key].startsWith("@text:") || envVars[key].startsWith("@json:"))) {
|
|
1655
1834
|
const filePath = envVars[key].slice(6).trim();
|
|
1656
1835
|
const embeddedFile = path.resolve(path.join(options.executionDirectory ?? "", filePath));
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1836
|
+
if (envVars[key].startsWith("@text:")) {
|
|
1837
|
+
console.info(`Expanding Environment Variable: ${key} from text file: ${embeddedFile}`);
|
|
1838
|
+
envVars[key] = await loadTextFile(embeddedFile);
|
|
1839
|
+
}
|
|
1840
|
+
else if (envVars[key].startsWith("@json:")) {
|
|
1841
|
+
console.info(`Expanding Environment Variable: ${key} from JSON file: ${embeddedFile}`);
|
|
1842
|
+
envVars[key] = await loadJsonFile(embeddedFile);
|
|
1843
|
+
}
|
|
1660
1844
|
}
|
|
1661
1845
|
}
|
|
1662
1846
|
// Extend the environment variables with any additional custom configuration.
|
|
@@ -1666,7 +1850,7 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1666
1850
|
}
|
|
1667
1851
|
// Build the engine configuration from the environment variables.
|
|
1668
1852
|
const coreConfig = buildEngineConfiguration(envVars);
|
|
1669
|
-
const engineServerConfig = buildEngineServerConfiguration(envVars, coreConfig, serverInfo, options?.openApiSpecFile);
|
|
1853
|
+
const engineServerConfig = buildEngineServerConfiguration(envVars, coreConfig, serverInfo, options?.openApiSpecFile, options?.favIconFile);
|
|
1670
1854
|
// Merge any custom configuration provided in the options.
|
|
1671
1855
|
if (Is.arrayValue(options?.configFilenames)) {
|
|
1672
1856
|
for (const configFile of options.configFilenames) {
|
|
@@ -1687,5 +1871,39 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1687
1871
|
}
|
|
1688
1872
|
return { engineServerConfig, nodeEnvVars: envVars };
|
|
1689
1873
|
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Override module imports to use local files where possible.
|
|
1876
|
+
* @param executionDirectory The execution directory for resolving local module paths.
|
|
1877
|
+
*/
|
|
1878
|
+
function overrideModuleImport(executionDirectory) {
|
|
1879
|
+
ModuleHelper.overrideImport(async (moduleName) => {
|
|
1880
|
+
// If the module path for example when dynamically loading
|
|
1881
|
+
// modules looks like a local file then we try to resolve
|
|
1882
|
+
// using the local file system
|
|
1883
|
+
const isLocal = ModuleHelper.isLocalModule(moduleName);
|
|
1884
|
+
if (isLocal) {
|
|
1885
|
+
// See if we can just resolve the filename locally
|
|
1886
|
+
let localFilename = path.resolve(moduleName);
|
|
1887
|
+
let exists = await fileExists(localFilename);
|
|
1888
|
+
if (!exists) {
|
|
1889
|
+
// Doesn't exist in the current directory, try the execution directory
|
|
1890
|
+
localFilename = path.resolve(executionDirectory, moduleName);
|
|
1891
|
+
exists = await fileExists(localFilename);
|
|
1892
|
+
}
|
|
1893
|
+
if (exists) {
|
|
1894
|
+
// If the module exists then we can load it, otherwise
|
|
1895
|
+
// we fallback to regular handling to see if that can import it
|
|
1896
|
+
return {
|
|
1897
|
+
module: await import(process.platform === "win32" ? `file://${localFilename}` : localFilename),
|
|
1898
|
+
useDefault: false
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
// The filename doesn't look like a local module, so just use default handling
|
|
1903
|
+
return {
|
|
1904
|
+
useDefault: true
|
|
1905
|
+
};
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1690
1908
|
|
|
1691
|
-
export { NodeFeatures, bootstrap,
|
|
1909
|
+
export { NodeFeatures, bootstrap, bootstrapAuth, bootstrapBlobEncryption, bootstrapImmutableProofMethod, bootstrapNodeIdentity, bootstrapNodeUser, bootstrapSynchronisedStorage, buildConfiguration, buildEngineConfiguration, buildEngineServerConfiguration, fileExists, getExecutionDirectory, getFeatures, initialiseLocales, loadJsonFile, loadTextFile, overrideModuleImport, run, start };
|