@twin.org/node-core 0.0.2-next.3 â 0.0.2-next.4
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 +250 -158
- package/dist/esm/index.mjs +251 -159
- package/dist/types/bootstrap.d.ts +9 -9
- package/dist/types/models/IEngineEnvironmentVariables.d.ts +68 -9
- package/dist/types/models/nodeFeatures.d.ts +4 -0
- package/docs/changelog.md +7 -0
- package/docs/reference/functions/{bootstrapAttestationMethod.md â bootstrapSynchronisedStorage.md} +3 -3
- package/docs/reference/index.md +1 -1
- package/docs/reference/interfaces/IEngineEnvironmentVariables.md +142 -15
- package/docs/reference/interfaces/INodeEnvironmentVariables.md +196 -21
- package/docs/reference/variables/NodeFeatures.md +6 -0
- package/locales/en.json +6 -4
- package/package.json +1 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { PasswordHelper } from '@twin.org/api-auth-entity-storage-service';
|
|
2
2
|
import { I18n, Is, Converter, RandomHelper, StringHelper, Coerce, 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, 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, RightsManagementComponentType, TaskSchedulerComponentType, SynchronisedStorageComponentType, FederatedCatalogueComponentType } 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';
|
|
@@ -9,7 +10,6 @@ import { WalletConnectorFactory } from '@twin.org/wallet-models';
|
|
|
9
10
|
import { readFile, stat } from 'node:fs/promises';
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
import { addDefaultRestPaths, addDefaultSocketPaths, EngineServer } from '@twin.org/engine-server';
|
|
12
|
-
import { InformationComponentType, RestRouteProcessorType, SocketRouteProcessorType, AuthenticationAdminComponentType, AuthenticationComponentType } from '@twin.org/engine-server-types';
|
|
13
13
|
import * as dotenv from 'dotenv';
|
|
14
14
|
import { Engine } from '@twin.org/engine';
|
|
15
15
|
import { FileStateStorage } from '@twin.org/engine-core';
|
|
@@ -29,7 +29,11 @@ const NodeFeatures = {
|
|
|
29
29
|
/**
|
|
30
30
|
* NodeUser - generates a user for the node if not provided in config.
|
|
31
31
|
*/
|
|
32
|
-
NodeUser: "node-user"
|
|
32
|
+
NodeUser: "node-user",
|
|
33
|
+
/**
|
|
34
|
+
* NodeWallet - generates a wallet for the node and funds it when there is a faucet available.
|
|
35
|
+
*/
|
|
36
|
+
NodeWallet: "node-wallet"
|
|
33
37
|
};
|
|
34
38
|
|
|
35
39
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -116,8 +120,9 @@ async function bootstrap(engineCore, context, envVars) {
|
|
|
116
120
|
await bootstrapNodeUser(engineCore, context, envVars, features);
|
|
117
121
|
await bootstrapAuth(engineCore, context, envVars);
|
|
118
122
|
await bootstrapBlobEncryption(engineCore, context, envVars);
|
|
119
|
-
await
|
|
120
|
-
await
|
|
123
|
+
await addVerificationMethod(engineCore, context, "attestation", envVars.attestationVerificationMethodId);
|
|
124
|
+
await addVerificationMethod(engineCore, context, "immutable proof", envVars.immutableProofVerificationMethodId);
|
|
125
|
+
await bootstrapSynchronisedStorage(engineCore, context, envVars);
|
|
121
126
|
}
|
|
122
127
|
/**
|
|
123
128
|
* Bootstrap the node creating any necessary resources.
|
|
@@ -132,23 +137,21 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
|
|
|
132
137
|
// But we have a chicken and egg problem in that we can't create the identity
|
|
133
138
|
// to store the mnemonic in the vault without an identity. We use a temporary identity
|
|
134
139
|
// 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
|
-
}
|
|
140
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
141
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
142
|
+
const workingIdentity = envVars.identity ??
|
|
143
|
+
context.state.nodeIdentity ??
|
|
144
|
+
`bootstrap-temp-${Converter.bytesToHex(RandomHelper.generate(16))}`;
|
|
145
|
+
await bootstrapMnemonic(engineCore, envVars, features, vaultConnector, workingIdentity);
|
|
146
|
+
const addresses = await bootstrapWallet(engineCore, envVars, features, workingIdentity);
|
|
147
|
+
const finalIdentity = await bootstrapIdentity(engineCore, envVars, features, workingIdentity);
|
|
148
|
+
await finaliseWallet(engineCore, envVars, features, finalIdentity, addresses);
|
|
149
|
+
await finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity);
|
|
150
|
+
context.state.nodeIdentity = finalIdentity;
|
|
151
|
+
context.stateDirty = true;
|
|
152
|
+
engineCore.logInfo(I18n.formatMessage("node.nodeIdentity", {
|
|
153
|
+
identity: context.state.nodeIdentity
|
|
154
|
+
}));
|
|
152
155
|
}
|
|
153
156
|
}
|
|
154
157
|
/**
|
|
@@ -160,12 +163,13 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
|
|
|
160
163
|
* @returns The addresses for the wallet.
|
|
161
164
|
*/
|
|
162
165
|
async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
163
|
-
const
|
|
166
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
164
167
|
// Now create an identity for the node controlled by the address we just funded
|
|
165
|
-
const identityConnector = IdentityConnectorFactory.get(
|
|
168
|
+
const identityConnector = IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
166
169
|
let identityDocument;
|
|
167
170
|
try {
|
|
168
|
-
const
|
|
171
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
172
|
+
const identityResolverConnector = IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
169
173
|
identityDocument = await identityResolverConnector.resolveDocument(nodeIdentity);
|
|
170
174
|
engineCore.logInfo(I18n.formatMessage("node.existingNodeIdentity", { identity: nodeIdentity }));
|
|
171
175
|
}
|
|
@@ -175,7 +179,7 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
|
175
179
|
identityDocument = await identityConnector.createDocument(nodeIdentity);
|
|
176
180
|
engineCore.logInfo(I18n.formatMessage("node.createdNodeIdentity", { identity: identityDocument.id }));
|
|
177
181
|
}
|
|
178
|
-
if (
|
|
182
|
+
if (defaultIdentityConnectorType.startsWith(IdentityConnectorType.Iota)) {
|
|
179
183
|
const didUrn = Urn.fromValidString(identityDocument.id);
|
|
180
184
|
const didParts = didUrn.parts();
|
|
181
185
|
const objectId = didParts[3];
|
|
@@ -194,23 +198,26 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
|
194
198
|
* @returns The addresses for the wallet.
|
|
195
199
|
*/
|
|
196
200
|
async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
if (features.includes(NodeFeatures.NodeWallet)) {
|
|
202
|
+
const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
|
|
203
|
+
const walletConnector = WalletConnectorFactory.get(defaultWalletConnectorType);
|
|
204
|
+
const addresses = await walletConnector.getAddresses(nodeIdentity, 0, 0, 5);
|
|
205
|
+
const balance = await walletConnector.getBalance(nodeIdentity, addresses[0]);
|
|
206
|
+
if (balance === 0n) {
|
|
207
|
+
let address0 = addresses[0];
|
|
208
|
+
if (defaultWalletConnectorType.startsWith(WalletConnectorType.Iota)) {
|
|
209
|
+
address0 = `${envVars.iotaExplorerEndpoint}address/${address0}?network=${envVars.iotaNetwork}`;
|
|
210
|
+
}
|
|
211
|
+
engineCore.logInfo(I18n.formatMessage("node.fundingWallet", { address: address0 }));
|
|
212
|
+
// Add some funds to the wallet from the faucet
|
|
213
|
+
await walletConnector.ensureBalance(nodeIdentity, addresses[0], 1000000000n);
|
|
205
214
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
else {
|
|
211
|
-
engineCore.logInfo(I18n.formatMessage("node.fundedWallet"));
|
|
215
|
+
else {
|
|
216
|
+
engineCore.logInfo(I18n.formatMessage("node.fundedWallet"));
|
|
217
|
+
}
|
|
218
|
+
return addresses;
|
|
212
219
|
}
|
|
213
|
-
return
|
|
220
|
+
return [];
|
|
214
221
|
}
|
|
215
222
|
/**
|
|
216
223
|
* Bootstrap the identity for the node.
|
|
@@ -221,15 +228,17 @@ async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
|
|
|
221
228
|
* @param addresses The addresses for the wallet.
|
|
222
229
|
*/
|
|
223
230
|
async function finaliseWallet(engineCore, envVars, features, finalIdentity, addresses) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
addr
|
|
232
|
-
|
|
231
|
+
if (features.includes(NodeFeatures.NodeWallet)) {
|
|
232
|
+
const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
|
|
233
|
+
// If we are using entity storage for wallet the identity associated with the
|
|
234
|
+
// address will be wrong, so fix it
|
|
235
|
+
if (defaultWalletConnectorType.startsWith(WalletConnectorType.EntityStorage)) {
|
|
236
|
+
const walletAddress = EntityStorageConnectorFactory.get(StringHelper.kebabCase("WalletAddress"));
|
|
237
|
+
const addr = await walletAddress.get(addresses[0]);
|
|
238
|
+
if (!Is.empty(addr)) {
|
|
239
|
+
addr.identity = finalIdentity;
|
|
240
|
+
await walletAddress.set(addr);
|
|
241
|
+
}
|
|
233
242
|
}
|
|
234
243
|
}
|
|
235
244
|
}
|
|
@@ -291,8 +300,8 @@ async function finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity)
|
|
|
291
300
|
*/
|
|
292
301
|
async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
293
302
|
if (features.includes(NodeFeatures.NodeUser)) {
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
303
|
+
const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceType("authenticationComponent");
|
|
304
|
+
if (defaultAuthenticationComponentType.startsWith(AuthenticationComponentType.EntityStorage) &&
|
|
296
305
|
Is.stringValue(context.state.nodeIdentity)) {
|
|
297
306
|
const authUserEntityStorage = EntityStorageConnectorFactory.get(StringHelper.kebabCase("AuthenticationUser"));
|
|
298
307
|
const email = envVars.username ?? DEFAULT_NODE_USERNAME;
|
|
@@ -335,7 +344,8 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
|
335
344
|
}
|
|
336
345
|
}
|
|
337
346
|
// We have create a node user, now we need to create a profile for the user
|
|
338
|
-
const
|
|
347
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
348
|
+
const identityProfileConnector = IdentityProfileConnectorFactory.get(defaultIdentityConnectorType);
|
|
339
349
|
if (identityProfileConnector) {
|
|
340
350
|
let userProfile;
|
|
341
351
|
try {
|
|
@@ -365,43 +375,6 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
|
365
375
|
}
|
|
366
376
|
}
|
|
367
377
|
}
|
|
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
378
|
/**
|
|
406
379
|
* Bootstrap the immutable proof verification methods.
|
|
407
380
|
* @param engineCore The engine core for the node.
|
|
@@ -409,36 +382,7 @@ async function bootstrapAttestationMethod(engineCore, context, envVars, features
|
|
|
409
382
|
* @param envVars The environment variables for the node.
|
|
410
383
|
* @param features The features that are enabled on the node.
|
|
411
384
|
*/
|
|
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
|
-
}
|
|
385
|
+
async function bootstrapImmutableProofMethod(engineCore, context, envVars, features) { }
|
|
442
386
|
/**
|
|
443
387
|
* Bootstrap the keys for blob encryption.
|
|
444
388
|
* @param engineCore The engine core for the node.
|
|
@@ -449,18 +393,28 @@ async function bootstrapImmutableProofMethod(engineCore, context, envVars, featu
|
|
|
449
393
|
async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
|
|
450
394
|
if ((Coerce.boolean(envVars.blobStorageEnableEncryption) ?? false) &&
|
|
451
395
|
Is.stringValue(context.state.nodeIdentity)) {
|
|
452
|
-
const engineDefaultTypes = engineCore.getDefaultTypes();
|
|
453
396
|
// Create a new key for encrypting blobs
|
|
454
|
-
const
|
|
455
|
-
const
|
|
397
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
398
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
399
|
+
const keyName = `${context.state.nodeIdentity}/${envVars.blobStorageEncryptionKeyId}`;
|
|
456
400
|
let existingKey;
|
|
457
401
|
try {
|
|
458
402
|
existingKey = await vaultConnector.getKey(keyName);
|
|
459
403
|
}
|
|
460
404
|
catch { }
|
|
461
405
|
if (Is.empty(existingKey)) {
|
|
462
|
-
|
|
463
|
-
|
|
406
|
+
if (Is.stringBase64(envVars.blobStorageSymmetricEncryptionKey)) {
|
|
407
|
+
engineCore.logInfo(I18n.formatMessage("node.addingBlobEncryptionKey", { keyName }));
|
|
408
|
+
await vaultConnector.addKey(keyName, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(envVars.blobStorageSymmetricEncryptionKey));
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
engineCore.logInfo(I18n.formatMessage("node.creatingBlobEncryptionKey", { keyName }));
|
|
412
|
+
const key = await vaultConnector.createKey(keyName, VaultKeyType.ChaCha20Poly1305);
|
|
413
|
+
engineCore.logInfo(I18n.formatMessage("node.createdBlobEncryptionKey", {
|
|
414
|
+
keyName,
|
|
415
|
+
keyValue: Converter.bytesToBase64(key)
|
|
416
|
+
}));
|
|
417
|
+
}
|
|
464
418
|
}
|
|
465
419
|
else {
|
|
466
420
|
engineCore.logInfo(I18n.formatMessage("node.existingBlobEncryptionKey", { keyName }));
|
|
@@ -475,11 +429,13 @@ async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
|
|
|
475
429
|
* @param features The features that are enabled on the node.
|
|
476
430
|
*/
|
|
477
431
|
async function bootstrapAuth(engineCore, context, envVars, features) {
|
|
478
|
-
const
|
|
479
|
-
if (
|
|
432
|
+
const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceTypeOptional("authenticationComponent");
|
|
433
|
+
if (Is.stringValue(defaultAuthenticationComponentType) &&
|
|
434
|
+
defaultAuthenticationComponentType.startsWith(AuthenticationComponentType.EntityStorage) &&
|
|
480
435
|
Is.stringValue(context.state.nodeIdentity)) {
|
|
481
436
|
// Create a new JWT signing key and a user login for the node
|
|
482
|
-
const
|
|
437
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
438
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
483
439
|
const keyName = `${context.state.nodeIdentity}/${envVars.authSigningKeyId}`;
|
|
484
440
|
let existingKey;
|
|
485
441
|
try {
|
|
@@ -495,6 +451,77 @@ async function bootstrapAuth(engineCore, context, envVars, features) {
|
|
|
495
451
|
}
|
|
496
452
|
}
|
|
497
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Bootstrap the synchronised storage blob encryption and verification methods.
|
|
456
|
+
* @param engineCore The engine core for the node.
|
|
457
|
+
* @param context The context for the node.
|
|
458
|
+
* @param envVars The environment variables for the node.
|
|
459
|
+
* @param features The features that are enabled on the node.
|
|
460
|
+
*/
|
|
461
|
+
async function bootstrapSynchronisedStorage(engineCore, context, envVars, features) {
|
|
462
|
+
if (Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false) {
|
|
463
|
+
// Add the verification method to the identity if it doesn't exist
|
|
464
|
+
await addVerificationMethod(engineCore, context, "synchronised storage", envVars.synchronisedStorageVerificationMethodId);
|
|
465
|
+
// If this is a trusted node we need to add the blob encryption key pair
|
|
466
|
+
if (Is.stringValue(envVars.synchronisedStorageBlobStorageEncryptionKeyId) &&
|
|
467
|
+
Is.stringBase64(envVars.synchronisedStorageBlobStoragePrivateKey) &&
|
|
468
|
+
Is.stringBase64(envVars.synchronisedStorageBlobStoragePublicKey)) {
|
|
469
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
470
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
471
|
+
const keyName = envVars.synchronisedStorageBlobStorageEncryptionKeyId;
|
|
472
|
+
let existingKey;
|
|
473
|
+
try {
|
|
474
|
+
existingKey = await vaultConnector.getKey(keyName);
|
|
475
|
+
}
|
|
476
|
+
catch { }
|
|
477
|
+
if (Is.empty(existingKey)) {
|
|
478
|
+
engineCore.logInfo(I18n.formatMessage("node.addingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
479
|
+
await vaultConnector.addKey(keyName, VaultKeyType.Rsa2048, Converter.base64ToBytes(envVars.synchronisedStorageBlobStoragePrivateKey), Converter.base64ToBytes(envVars.synchronisedStorageBlobStoragePublicKey));
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
engineCore.logInfo(I18n.formatMessage("node.existingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Add a verification method if it doesn't exist.
|
|
489
|
+
* @param engineCore The engine core for the node.
|
|
490
|
+
* @param context The context for the node.
|
|
491
|
+
* @param verificationMethodTitle The verification method title.
|
|
492
|
+
* @param verificationMethodId The verification method ID.
|
|
493
|
+
*/
|
|
494
|
+
async function addVerificationMethod(engineCore, context, verificationMethodTitle, verificationMethodId) {
|
|
495
|
+
if (Is.stringValue(context.state.nodeIdentity) &&
|
|
496
|
+
Is.arrayValue(context.config.types.identityConnector) &&
|
|
497
|
+
Is.stringValue(verificationMethodId)) {
|
|
498
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
499
|
+
const identityConnector = IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
500
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
501
|
+
const identityResolverConnector = IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
502
|
+
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
503
|
+
const fullMethodId = `${identityDocument.id}#${verificationMethodId}`;
|
|
504
|
+
let exists = false;
|
|
505
|
+
try {
|
|
506
|
+
DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
507
|
+
exists = true;
|
|
508
|
+
}
|
|
509
|
+
catch { }
|
|
510
|
+
if (!exists) {
|
|
511
|
+
engineCore.logInfo(I18n.formatMessage("node.addingVerificationMethod", {
|
|
512
|
+
title: verificationMethodTitle,
|
|
513
|
+
methodId: fullMethodId
|
|
514
|
+
}));
|
|
515
|
+
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", verificationMethodId);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
engineCore.logInfo(I18n.formatMessage("node.existingVerificationMethod", {
|
|
519
|
+
title: verificationMethodTitle,
|
|
520
|
+
methodId: fullMethodId
|
|
521
|
+
}));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
498
525
|
|
|
499
526
|
// Copyright 2024 IOTA Stiftung.
|
|
500
527
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -512,7 +539,9 @@ function buildEngineConfiguration(envVars) {
|
|
|
512
539
|
envVars.attestationVerificationMethodId ??= "attestation-assertion";
|
|
513
540
|
envVars.immutableProofVerificationMethodId ??= "immutable-proof-assertion";
|
|
514
541
|
envVars.blobStorageEnableEncryption ??= "false";
|
|
515
|
-
envVars.
|
|
542
|
+
envVars.blobStorageEncryptionKeyId ??= "blob-encryption";
|
|
543
|
+
envVars.synchronisedStorageBlobStorageEncryptionKeyId ??= "synchronised-storage-blob-encryption";
|
|
544
|
+
envVars.synchronisedStorageVerificationMethodId ??= "synchronised-storage-assertion";
|
|
516
545
|
const coreConfig = {
|
|
517
546
|
debug: Coerce.boolean(envVars.debug) ?? false,
|
|
518
547
|
types: {}
|
|
@@ -538,9 +567,10 @@ function buildEngineConfiguration(envVars) {
|
|
|
538
567
|
configureAuditableItemGraph(coreConfig);
|
|
539
568
|
configureAuditableItemStream(coreConfig);
|
|
540
569
|
configureDocumentManagement(coreConfig);
|
|
541
|
-
configureFederatedCatalogue(coreConfig, envVars);
|
|
542
570
|
configureRightsManagement(coreConfig, envVars);
|
|
543
571
|
configureTaskScheduler(coreConfig, envVars);
|
|
572
|
+
configureSynchronisedStorage(coreConfig, envVars);
|
|
573
|
+
configureFederatedCatalogue(coreConfig, envVars);
|
|
544
574
|
return coreConfig;
|
|
545
575
|
}
|
|
546
576
|
/**
|
|
@@ -662,7 +692,7 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
662
692
|
}
|
|
663
693
|
});
|
|
664
694
|
}
|
|
665
|
-
if (entityStorageConnectorTypes) {
|
|
695
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.PostgreSql)) {
|
|
666
696
|
coreConfig.types.entityStorageConnector.push({
|
|
667
697
|
type: EntityStorageConnectorType.PostgreSql,
|
|
668
698
|
options: {
|
|
@@ -677,10 +707,19 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
677
707
|
}
|
|
678
708
|
});
|
|
679
709
|
}
|
|
710
|
+
const defaultEntityStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
711
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.Synchronised)) {
|
|
712
|
+
// For synchronised storage we use the default connector as the one we wrap for real DB operations
|
|
713
|
+
coreConfig.types.entityStorageConnector.push({
|
|
714
|
+
type: EntityStorageConnectorType.Synchronised,
|
|
715
|
+
options: {
|
|
716
|
+
entityStorageConnectorType: defaultEntityStorageConnectorType
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
}
|
|
680
720
|
if (Is.arrayValue(entityStorageConnectorTypes)) {
|
|
681
|
-
const defaultStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
682
721
|
for (const config of coreConfig.types.entityStorageConnector) {
|
|
683
|
-
if (config.type ===
|
|
722
|
+
if (config.type === defaultEntityStorageConnectorType) {
|
|
684
723
|
config.isDefault = true;
|
|
685
724
|
break;
|
|
686
725
|
}
|
|
@@ -772,6 +811,13 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
772
811
|
for (const config of coreConfig.types.blobStorageConnector) {
|
|
773
812
|
if (config.type === defaultStorageConnectorType) {
|
|
774
813
|
config.isDefault = true;
|
|
814
|
+
}
|
|
815
|
+
// If this blob storage connector is the one to use for public access
|
|
816
|
+
// then add it as a feature
|
|
817
|
+
if (Is.stringValue(envVars.blobStorageConnectorPublic) &&
|
|
818
|
+
config.type === envVars.blobStorageConnectorPublic) {
|
|
819
|
+
config.features ??= [];
|
|
820
|
+
config.features.push("public");
|
|
775
821
|
break;
|
|
776
822
|
}
|
|
777
823
|
}
|
|
@@ -783,7 +829,7 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
783
829
|
options: {
|
|
784
830
|
config: {
|
|
785
831
|
vaultKeyId: (envVars.blobStorageEnableEncryption ?? false)
|
|
786
|
-
? envVars.
|
|
832
|
+
? envVars.blobStorageEncryptionKeyId
|
|
787
833
|
: undefined
|
|
788
834
|
}
|
|
789
835
|
}
|
|
@@ -1277,25 +1323,6 @@ function configureDocumentManagement(coreConfig, envVars) {
|
|
|
1277
1323
|
});
|
|
1278
1324
|
}
|
|
1279
1325
|
}
|
|
1280
|
-
/**
|
|
1281
|
-
* Configures the federated catalogue.
|
|
1282
|
-
* @param coreConfig The core config.
|
|
1283
|
-
* @param envVars The environment variables.
|
|
1284
|
-
*/
|
|
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,
|
|
1290
|
-
options: {
|
|
1291
|
-
config: {
|
|
1292
|
-
subResourceCacheTtlMs: Coerce.number(envVars.federatedCatalogueCacheTtlMs),
|
|
1293
|
-
clearingHouseApproverList: Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
1326
|
/**
|
|
1300
1327
|
* Configures the rights management.
|
|
1301
1328
|
* @param coreConfig The core config.
|
|
@@ -1319,13 +1346,76 @@ function configureRightsManagement(coreConfig, envVars) {
|
|
|
1319
1346
|
* @param envVars The environment variables.
|
|
1320
1347
|
*/
|
|
1321
1348
|
function configureTaskScheduler(coreConfig, envVars) {
|
|
1322
|
-
if (Coerce.boolean(envVars.taskSchedulerEnabled) ??
|
|
1349
|
+
if (Coerce.boolean(envVars.taskSchedulerEnabled) ?? false) {
|
|
1323
1350
|
coreConfig.types.taskSchedulerComponent ??= [];
|
|
1324
1351
|
coreConfig.types.taskSchedulerComponent.push({
|
|
1325
1352
|
type: TaskSchedulerComponentType.Service
|
|
1326
1353
|
});
|
|
1327
1354
|
}
|
|
1328
1355
|
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Configures the synchronised storage.
|
|
1358
|
+
* @param coreConfig The core config.
|
|
1359
|
+
* @param envVars The environment variables.
|
|
1360
|
+
*/
|
|
1361
|
+
function configureSynchronisedStorage(coreConfig, envVars) {
|
|
1362
|
+
if (Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1363
|
+
(Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false)) {
|
|
1364
|
+
// Check if the config provides a custom verifiable storage key id
|
|
1365
|
+
let verifiableStorageKeyId = Coerce.string(envVars.synchronisedStorageVerifiableStorageKeyId);
|
|
1366
|
+
if (!Is.stringValue(verifiableStorageKeyId)) {
|
|
1367
|
+
// No custom key so default to the network setting
|
|
1368
|
+
verifiableStorageKeyId = envVars.iotaNetwork;
|
|
1369
|
+
}
|
|
1370
|
+
coreConfig.types.synchronisedStorageComponent ??= [];
|
|
1371
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1372
|
+
type: SynchronisedStorageComponentType.Service,
|
|
1373
|
+
options: {
|
|
1374
|
+
config: {
|
|
1375
|
+
verifiableStorageKeyId: verifiableStorageKeyId ?? "",
|
|
1376
|
+
synchronisedStorageMethodId: envVars.synchronisedStorageVerificationMethodId,
|
|
1377
|
+
blobStorageEncryptionKeyId: envVars.synchronisedStorageBlobStorageEncryptionKeyId,
|
|
1378
|
+
entityUpdateIntervalMinutes: Coerce.number(envVars.synchronisedStorageEntityUpdateIntervalMinutes),
|
|
1379
|
+
consolidationIntervalMinutes: Coerce.number(envVars.synchronisedStorageConsolidationIntervalMinutes),
|
|
1380
|
+
consolidationBatchSize: Coerce.number(envVars.synchronisedStorageConsolidationBatchSize),
|
|
1381
|
+
maxConsolidations: Coerce.number(envVars.synchronisedStorageMaxConsolidations)
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
// If there is a trusted url set, we need to add a client
|
|
1386
|
+
// and give it a feature of trusted so that when the synchronised
|
|
1387
|
+
// storage is created it can pickup the correct component
|
|
1388
|
+
if (Is.stringValue(envVars.synchronisedStorageTrustedUrl)) {
|
|
1389
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1390
|
+
type: SynchronisedStorageComponentType.RestClient,
|
|
1391
|
+
options: {
|
|
1392
|
+
endpoint: envVars.synchronisedStorageTrustedUrl
|
|
1393
|
+
},
|
|
1394
|
+
features: ["trusted"]
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Configures the federated catalogue.
|
|
1401
|
+
* @param coreConfig The core config.
|
|
1402
|
+
* @param envVars The environment variables.
|
|
1403
|
+
*/
|
|
1404
|
+
function configureFederatedCatalogue(coreConfig, envVars) {
|
|
1405
|
+
if (Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1406
|
+
(Coerce.boolean(envVars.federatedCatalogueEnabled) ?? false)) {
|
|
1407
|
+
coreConfig.types.federatedCatalogueComponent ??= [];
|
|
1408
|
+
coreConfig.types.federatedCatalogueComponent.push({
|
|
1409
|
+
type: FederatedCatalogueComponentType.Service,
|
|
1410
|
+
options: {
|
|
1411
|
+
config: {
|
|
1412
|
+
subResourceCacheTtlMs: Coerce.number(envVars.federatedCatalogueCacheTtlMs),
|
|
1413
|
+
clearingHouseApproverList: Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1329
1419
|
/**
|
|
1330
1420
|
* Configures the DLT.
|
|
1331
1421
|
* @param coreConfig The core config.
|
|
@@ -1511,9 +1601,10 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
|
|
|
1511
1601
|
* @returns The engine server.
|
|
1512
1602
|
*/
|
|
1513
1603
|
async function start(nodeOptions, engineServerConfig, envVars) {
|
|
1514
|
-
envVars.storageFileRoot ??= "";
|
|
1515
1604
|
const entityStorageConnectorType = envVars.entityStorageConnectorType?.split(",") ?? [];
|
|
1516
1605
|
const blobStorageConnectorType = envVars.blobStorageConnectorType?.split(",") ?? [];
|
|
1606
|
+
// If the blob storage or entity storage is configured with file connectors
|
|
1607
|
+
// then we need to make sure the storageFileRoot is set
|
|
1517
1608
|
if ((entityStorageConnectorType.includes(EntityStorageConnectorType.File) ||
|
|
1518
1609
|
blobStorageConnectorType.includes(BlobStorageConnectorType.File) ||
|
|
1519
1610
|
Is.empty(nodeOptions?.stateStorage)) &&
|
|
@@ -1522,7 +1613,7 @@ async function start(nodeOptions, engineServerConfig, envVars) {
|
|
|
1522
1613
|
storageFileRoot: `${nodeOptions?.envPrefix ?? ""}_STORAGE_FILE_ROOT`
|
|
1523
1614
|
});
|
|
1524
1615
|
}
|
|
1525
|
-
// Create the engine instance using file state storage
|
|
1616
|
+
// Create the engine instance using file state storage unless one is configured in options
|
|
1526
1617
|
const engine = new Engine({
|
|
1527
1618
|
config: engineServerConfig,
|
|
1528
1619
|
stateStorage: nodeOptions?.stateStorage ?? new FileStateStorage(envVars.stateFilename ?? ""),
|
|
@@ -1566,7 +1657,7 @@ async function run(nodeOptions) {
|
|
|
1566
1657
|
nodeOptions ??= {};
|
|
1567
1658
|
const serverInfo = {
|
|
1568
1659
|
name: nodeOptions?.serverName ?? "TWIN Node Server",
|
|
1569
|
-
version: nodeOptions?.serverVersion ?? "0.0.2-next.
|
|
1660
|
+
version: nodeOptions?.serverVersion ?? "0.0.2-next.4" // x-release-please-version
|
|
1570
1661
|
};
|
|
1571
1662
|
console.log(`\u001B[4mđŠī¸ ${serverInfo.name} v${serverInfo.version}\u001B[24m\n`);
|
|
1572
1663
|
if (!Is.stringValue(nodeOptions?.executionDirectory)) {
|
|
@@ -1624,7 +1715,8 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1624
1715
|
}
|
|
1625
1716
|
if (Is.arrayValue(options?.envFilenames)) {
|
|
1626
1717
|
const output = dotenv.config({
|
|
1627
|
-
path: options?.envFilenames
|
|
1718
|
+
path: options?.envFilenames,
|
|
1719
|
+
quiet: true
|
|
1628
1720
|
});
|
|
1629
1721
|
// We don't want to throw an error if the default environment file is not found.
|
|
1630
1722
|
// Only if we have custom environment files.
|
|
@@ -1676,4 +1768,4 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1676
1768
|
return { engineServerConfig, nodeEnvVars: envVars };
|
|
1677
1769
|
}
|
|
1678
1770
|
|
|
1679
|
-
export { NodeFeatures, bootstrap,
|
|
1771
|
+
export { NodeFeatures, bootstrap, bootstrapAuth, bootstrapBlobEncryption, bootstrapImmutableProofMethod, bootstrapNodeIdentity, bootstrapNodeUser, bootstrapSynchronisedStorage, buildConfiguration, buildEngineConfiguration, buildEngineServerConfiguration, fileExists, getExecutionDirectory, getFeatures, initialiseLocales, loadJsonFile, run, start };
|