@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/cjs/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var apiAuthEntityStorageService = require('@twin.org/api-auth-entity-storage-service');
|
|
4
4
|
var core = require('@twin.org/core');
|
|
5
5
|
var crypto = require('@twin.org/crypto');
|
|
6
|
+
var engineServerTypes = require('@twin.org/engine-server-types');
|
|
6
7
|
var engineTypes = require('@twin.org/engine-types');
|
|
7
8
|
var entityStorageModels = require('@twin.org/entity-storage-models');
|
|
8
9
|
var identityModels = require('@twin.org/identity-models');
|
|
@@ -11,7 +12,6 @@ var walletModels = require('@twin.org/wallet-models');
|
|
|
11
12
|
var promises = require('node:fs/promises');
|
|
12
13
|
var path = require('node:path');
|
|
13
14
|
var engineServer = require('@twin.org/engine-server');
|
|
14
|
-
var engineServerTypes = require('@twin.org/engine-server-types');
|
|
15
15
|
var dotenv = require('dotenv');
|
|
16
16
|
var engine = require('@twin.org/engine');
|
|
17
17
|
var engineCore = require('@twin.org/engine-core');
|
|
@@ -50,7 +50,11 @@ const NodeFeatures = {
|
|
|
50
50
|
/**
|
|
51
51
|
* NodeUser - generates a user for the node if not provided in config.
|
|
52
52
|
*/
|
|
53
|
-
NodeUser: "node-user"
|
|
53
|
+
NodeUser: "node-user",
|
|
54
|
+
/**
|
|
55
|
+
* NodeWallet - generates a wallet for the node and funds it when there is a faucet available.
|
|
56
|
+
*/
|
|
57
|
+
NodeWallet: "node-wallet"
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -137,8 +141,9 @@ async function bootstrap(engineCore, context, envVars) {
|
|
|
137
141
|
await bootstrapNodeUser(engineCore, context, envVars, features);
|
|
138
142
|
await bootstrapAuth(engineCore, context, envVars);
|
|
139
143
|
await bootstrapBlobEncryption(engineCore, context, envVars);
|
|
140
|
-
await
|
|
141
|
-
await
|
|
144
|
+
await addVerificationMethod(engineCore, context, "attestation", envVars.attestationVerificationMethodId);
|
|
145
|
+
await addVerificationMethod(engineCore, context, "immutable proof", envVars.immutableProofVerificationMethodId);
|
|
146
|
+
await bootstrapSynchronisedStorage(engineCore, context, envVars);
|
|
142
147
|
}
|
|
143
148
|
/**
|
|
144
149
|
* Bootstrap the node creating any necessary resources.
|
|
@@ -153,23 +158,21 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
|
|
|
153
158
|
// But we have a chicken and egg problem in that we can't create the identity
|
|
154
159
|
// to store the mnemonic in the vault without an identity. We use a temporary identity
|
|
155
160
|
// and then replace it with the new identity later in the process.
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}));
|
|
172
|
-
}
|
|
161
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
162
|
+
const vaultConnector = vaultModels.VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
163
|
+
const workingIdentity = envVars.identity ??
|
|
164
|
+
context.state.nodeIdentity ??
|
|
165
|
+
`bootstrap-temp-${core.Converter.bytesToHex(core.RandomHelper.generate(16))}`;
|
|
166
|
+
await bootstrapMnemonic(engineCore, envVars, features, vaultConnector, workingIdentity);
|
|
167
|
+
const addresses = await bootstrapWallet(engineCore, envVars, features, workingIdentity);
|
|
168
|
+
const finalIdentity = await bootstrapIdentity(engineCore, envVars, features, workingIdentity);
|
|
169
|
+
await finaliseWallet(engineCore, envVars, features, finalIdentity, addresses);
|
|
170
|
+
await finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity);
|
|
171
|
+
context.state.nodeIdentity = finalIdentity;
|
|
172
|
+
context.stateDirty = true;
|
|
173
|
+
engineCore.logInfo(core.I18n.formatMessage("node.nodeIdentity", {
|
|
174
|
+
identity: context.state.nodeIdentity
|
|
175
|
+
}));
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
/**
|
|
@@ -181,12 +184,13 @@ async function bootstrapNodeIdentity(engineCore, context, envVars, features) {
|
|
|
181
184
|
* @returns The addresses for the wallet.
|
|
182
185
|
*/
|
|
183
186
|
async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
184
|
-
const
|
|
187
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
185
188
|
// Now create an identity for the node controlled by the address we just funded
|
|
186
|
-
const identityConnector = identityModels.IdentityConnectorFactory.get(
|
|
189
|
+
const identityConnector = identityModels.IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
187
190
|
let identityDocument;
|
|
188
191
|
try {
|
|
189
|
-
const
|
|
192
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
193
|
+
const identityResolverConnector = identityModels.IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
190
194
|
identityDocument = await identityResolverConnector.resolveDocument(nodeIdentity);
|
|
191
195
|
engineCore.logInfo(core.I18n.formatMessage("node.existingNodeIdentity", { identity: nodeIdentity }));
|
|
192
196
|
}
|
|
@@ -196,7 +200,7 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
|
196
200
|
identityDocument = await identityConnector.createDocument(nodeIdentity);
|
|
197
201
|
engineCore.logInfo(core.I18n.formatMessage("node.createdNodeIdentity", { identity: identityDocument.id }));
|
|
198
202
|
}
|
|
199
|
-
if (
|
|
203
|
+
if (defaultIdentityConnectorType.startsWith(engineTypes.IdentityConnectorType.Iota)) {
|
|
200
204
|
const didUrn = core.Urn.fromValidString(identityDocument.id);
|
|
201
205
|
const didParts = didUrn.parts();
|
|
202
206
|
const objectId = didParts[3];
|
|
@@ -215,23 +219,26 @@ async function bootstrapIdentity(engineCore, envVars, features, nodeIdentity) {
|
|
|
215
219
|
* @returns The addresses for the wallet.
|
|
216
220
|
*/
|
|
217
221
|
async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
if (features.includes(NodeFeatures.NodeWallet)) {
|
|
223
|
+
const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
|
|
224
|
+
const walletConnector = walletModels.WalletConnectorFactory.get(defaultWalletConnectorType);
|
|
225
|
+
const addresses = await walletConnector.getAddresses(nodeIdentity, 0, 0, 5);
|
|
226
|
+
const balance = await walletConnector.getBalance(nodeIdentity, addresses[0]);
|
|
227
|
+
if (balance === 0n) {
|
|
228
|
+
let address0 = addresses[0];
|
|
229
|
+
if (defaultWalletConnectorType.startsWith(engineTypes.WalletConnectorType.Iota)) {
|
|
230
|
+
address0 = `${envVars.iotaExplorerEndpoint}address/${address0}?network=${envVars.iotaNetwork}`;
|
|
231
|
+
}
|
|
232
|
+
engineCore.logInfo(core.I18n.formatMessage("node.fundingWallet", { address: address0 }));
|
|
233
|
+
// Add some funds to the wallet from the faucet
|
|
234
|
+
await walletConnector.ensureBalance(nodeIdentity, addresses[0], 1000000000n);
|
|
226
235
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
else {
|
|
232
|
-
engineCore.logInfo(core.I18n.formatMessage("node.fundedWallet"));
|
|
236
|
+
else {
|
|
237
|
+
engineCore.logInfo(core.I18n.formatMessage("node.fundedWallet"));
|
|
238
|
+
}
|
|
239
|
+
return addresses;
|
|
233
240
|
}
|
|
234
|
-
return
|
|
241
|
+
return [];
|
|
235
242
|
}
|
|
236
243
|
/**
|
|
237
244
|
* Bootstrap the identity for the node.
|
|
@@ -242,15 +249,17 @@ async function bootstrapWallet(engineCore, envVars, features, nodeIdentity) {
|
|
|
242
249
|
* @param addresses The addresses for the wallet.
|
|
243
250
|
*/
|
|
244
251
|
async function finaliseWallet(engineCore, envVars, features, finalIdentity, addresses) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
addr
|
|
253
|
-
|
|
252
|
+
if (features.includes(NodeFeatures.NodeWallet)) {
|
|
253
|
+
const defaultWalletConnectorType = engineCore.getRegisteredInstanceType("walletConnector");
|
|
254
|
+
// If we are using entity storage for wallet the identity associated with the
|
|
255
|
+
// address will be wrong, so fix it
|
|
256
|
+
if (defaultWalletConnectorType.startsWith(engineTypes.WalletConnectorType.EntityStorage)) {
|
|
257
|
+
const walletAddress = entityStorageModels.EntityStorageConnectorFactory.get(core.StringHelper.kebabCase("WalletAddress"));
|
|
258
|
+
const addr = await walletAddress.get(addresses[0]);
|
|
259
|
+
if (!core.Is.empty(addr)) {
|
|
260
|
+
addr.identity = finalIdentity;
|
|
261
|
+
await walletAddress.set(addr);
|
|
262
|
+
}
|
|
254
263
|
}
|
|
255
264
|
}
|
|
256
265
|
}
|
|
@@ -312,8 +321,8 @@ async function finaliseMnemonic(vaultConnector, workingIdentity, finalIdentity)
|
|
|
312
321
|
*/
|
|
313
322
|
async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
314
323
|
if (features.includes(NodeFeatures.NodeUser)) {
|
|
315
|
-
const
|
|
316
|
-
if (
|
|
324
|
+
const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceType("authenticationComponent");
|
|
325
|
+
if (defaultAuthenticationComponentType.startsWith(engineServerTypes.AuthenticationComponentType.EntityStorage) &&
|
|
317
326
|
core.Is.stringValue(context.state.nodeIdentity)) {
|
|
318
327
|
const authUserEntityStorage = entityStorageModels.EntityStorageConnectorFactory.get(core.StringHelper.kebabCase("AuthenticationUser"));
|
|
319
328
|
const email = envVars.username ?? DEFAULT_NODE_USERNAME;
|
|
@@ -356,7 +365,8 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
|
356
365
|
}
|
|
357
366
|
}
|
|
358
367
|
// We have create a node user, now we need to create a profile for the user
|
|
359
|
-
const
|
|
368
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
369
|
+
const identityProfileConnector = identityModels.IdentityProfileConnectorFactory.get(defaultIdentityConnectorType);
|
|
360
370
|
if (identityProfileConnector) {
|
|
361
371
|
let userProfile;
|
|
362
372
|
try {
|
|
@@ -386,43 +396,6 @@ async function bootstrapNodeUser(engineCore, context, envVars, features) {
|
|
|
386
396
|
}
|
|
387
397
|
}
|
|
388
398
|
}
|
|
389
|
-
/**
|
|
390
|
-
* Bootstrap the attestation verification methods.
|
|
391
|
-
* @param engineCore The engine core for the node.
|
|
392
|
-
* @param context The context for the node.
|
|
393
|
-
* @param envVars The environment variables for the node.
|
|
394
|
-
* @param features The features that are enabled on the node.
|
|
395
|
-
*/
|
|
396
|
-
async function bootstrapAttestationMethod(engineCore, context, envVars, features) {
|
|
397
|
-
if (core.Is.stringValue(context.state.nodeIdentity) &&
|
|
398
|
-
core.Is.arrayValue(context.config.types.identityConnector) &&
|
|
399
|
-
core.Is.stringValue(envVars.attestationVerificationMethodId)) {
|
|
400
|
-
const engineDefaultTypes = engineCore.getDefaultTypes();
|
|
401
|
-
const identityConnector = identityModels.IdentityConnectorFactory.get(engineDefaultTypes.identityConnector);
|
|
402
|
-
const identityResolverConnector = identityModels.IdentityResolverConnectorFactory.get(engineDefaultTypes.identityResolverConnector);
|
|
403
|
-
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
404
|
-
const fullMethodId = `${identityDocument.id}#${envVars.attestationVerificationMethodId}`;
|
|
405
|
-
let createVm = true;
|
|
406
|
-
try {
|
|
407
|
-
identityModels.DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
408
|
-
createVm = false;
|
|
409
|
-
}
|
|
410
|
-
catch { }
|
|
411
|
-
if (createVm) {
|
|
412
|
-
// Add attestation verification method to DID, the correct node context is now in place
|
|
413
|
-
// so the keys for the verification method will be stored correctly
|
|
414
|
-
engineCore.logInfo(core.I18n.formatMessage("node.addingAttestation", {
|
|
415
|
-
methodId: fullMethodId
|
|
416
|
-
}));
|
|
417
|
-
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", envVars.attestationVerificationMethodId);
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
engineCore.logInfo(core.I18n.formatMessage("node.existingAttestation", {
|
|
421
|
-
methodId: fullMethodId
|
|
422
|
-
}));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
399
|
/**
|
|
427
400
|
* Bootstrap the immutable proof verification methods.
|
|
428
401
|
* @param engineCore The engine core for the node.
|
|
@@ -430,36 +403,7 @@ async function bootstrapAttestationMethod(engineCore, context, envVars, features
|
|
|
430
403
|
* @param envVars The environment variables for the node.
|
|
431
404
|
* @param features The features that are enabled on the node.
|
|
432
405
|
*/
|
|
433
|
-
async function bootstrapImmutableProofMethod(engineCore, context, envVars, features) {
|
|
434
|
-
const engineDefaultTypes = engineCore.getDefaultTypes();
|
|
435
|
-
if (core.Is.stringValue(context.state.nodeIdentity) &&
|
|
436
|
-
core.Is.arrayValue(context.config.types.identityConnector) &&
|
|
437
|
-
core.Is.stringValue(envVars.immutableProofVerificationMethodId)) {
|
|
438
|
-
const identityConnector = identityModels.IdentityConnectorFactory.get(engineDefaultTypes.identityConnector);
|
|
439
|
-
const identityResolverConnector = identityModels.IdentityResolverConnectorFactory.get(engineDefaultTypes.identityResolverConnector);
|
|
440
|
-
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
441
|
-
const fullMethodId = `${identityDocument.id}#${envVars.immutableProofVerificationMethodId}`;
|
|
442
|
-
let createVm = true;
|
|
443
|
-
try {
|
|
444
|
-
identityModels.DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
445
|
-
createVm = false;
|
|
446
|
-
}
|
|
447
|
-
catch { }
|
|
448
|
-
if (createVm) {
|
|
449
|
-
// Add AIG verification method to DID, the correct node context is now in place
|
|
450
|
-
// so the keys for the verification method will be stored correctly
|
|
451
|
-
engineCore.logInfo(core.I18n.formatMessage("node.addingImmutableProof", {
|
|
452
|
-
methodId: fullMethodId
|
|
453
|
-
}));
|
|
454
|
-
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", envVars.immutableProofVerificationMethodId);
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
engineCore.logInfo(core.I18n.formatMessage("node.existingImmutableProof", {
|
|
458
|
-
methodId: fullMethodId
|
|
459
|
-
}));
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
406
|
+
async function bootstrapImmutableProofMethod(engineCore, context, envVars, features) { }
|
|
463
407
|
/**
|
|
464
408
|
* Bootstrap the keys for blob encryption.
|
|
465
409
|
* @param engineCore The engine core for the node.
|
|
@@ -470,18 +414,28 @@ async function bootstrapImmutableProofMethod(engineCore, context, envVars, featu
|
|
|
470
414
|
async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
|
|
471
415
|
if ((core.Coerce.boolean(envVars.blobStorageEnableEncryption) ?? false) &&
|
|
472
416
|
core.Is.stringValue(context.state.nodeIdentity)) {
|
|
473
|
-
const engineDefaultTypes = engineCore.getDefaultTypes();
|
|
474
417
|
// Create a new key for encrypting blobs
|
|
475
|
-
const
|
|
476
|
-
const
|
|
418
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
419
|
+
const vaultConnector = vaultModels.VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
420
|
+
const keyName = `${context.state.nodeIdentity}/${envVars.blobStorageEncryptionKeyId}`;
|
|
477
421
|
let existingKey;
|
|
478
422
|
try {
|
|
479
423
|
existingKey = await vaultConnector.getKey(keyName);
|
|
480
424
|
}
|
|
481
425
|
catch { }
|
|
482
426
|
if (core.Is.empty(existingKey)) {
|
|
483
|
-
|
|
484
|
-
|
|
427
|
+
if (core.Is.stringBase64(envVars.blobStorageSymmetricEncryptionKey)) {
|
|
428
|
+
engineCore.logInfo(core.I18n.formatMessage("node.addingBlobEncryptionKey", { keyName }));
|
|
429
|
+
await vaultConnector.addKey(keyName, vaultModels.VaultKeyType.ChaCha20Poly1305, core.Converter.base64ToBytes(envVars.blobStorageSymmetricEncryptionKey));
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
engineCore.logInfo(core.I18n.formatMessage("node.creatingBlobEncryptionKey", { keyName }));
|
|
433
|
+
const key = await vaultConnector.createKey(keyName, vaultModels.VaultKeyType.ChaCha20Poly1305);
|
|
434
|
+
engineCore.logInfo(core.I18n.formatMessage("node.createdBlobEncryptionKey", {
|
|
435
|
+
keyName,
|
|
436
|
+
keyValue: core.Converter.bytesToBase64(key)
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
485
439
|
}
|
|
486
440
|
else {
|
|
487
441
|
engineCore.logInfo(core.I18n.formatMessage("node.existingBlobEncryptionKey", { keyName }));
|
|
@@ -496,11 +450,13 @@ async function bootstrapBlobEncryption(engineCore, context, envVars, features) {
|
|
|
496
450
|
* @param features The features that are enabled on the node.
|
|
497
451
|
*/
|
|
498
452
|
async function bootstrapAuth(engineCore, context, envVars, features) {
|
|
499
|
-
const
|
|
500
|
-
if (
|
|
453
|
+
const defaultAuthenticationComponentType = engineCore.getRegisteredInstanceTypeOptional("authenticationComponent");
|
|
454
|
+
if (core.Is.stringValue(defaultAuthenticationComponentType) &&
|
|
455
|
+
defaultAuthenticationComponentType.startsWith(engineServerTypes.AuthenticationComponentType.EntityStorage) &&
|
|
501
456
|
core.Is.stringValue(context.state.nodeIdentity)) {
|
|
502
457
|
// Create a new JWT signing key and a user login for the node
|
|
503
|
-
const
|
|
458
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
459
|
+
const vaultConnector = vaultModels.VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
504
460
|
const keyName = `${context.state.nodeIdentity}/${envVars.authSigningKeyId}`;
|
|
505
461
|
let existingKey;
|
|
506
462
|
try {
|
|
@@ -516,6 +472,77 @@ async function bootstrapAuth(engineCore, context, envVars, features) {
|
|
|
516
472
|
}
|
|
517
473
|
}
|
|
518
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Bootstrap the synchronised storage blob encryption and verification methods.
|
|
477
|
+
* @param engineCore The engine core for the node.
|
|
478
|
+
* @param context The context for the node.
|
|
479
|
+
* @param envVars The environment variables for the node.
|
|
480
|
+
* @param features The features that are enabled on the node.
|
|
481
|
+
*/
|
|
482
|
+
async function bootstrapSynchronisedStorage(engineCore, context, envVars, features) {
|
|
483
|
+
if (core.Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false) {
|
|
484
|
+
// Add the verification method to the identity if it doesn't exist
|
|
485
|
+
await addVerificationMethod(engineCore, context, "synchronised storage", envVars.synchronisedStorageVerificationMethodId);
|
|
486
|
+
// If this is a trusted node we need to add the blob encryption key pair
|
|
487
|
+
if (core.Is.stringValue(envVars.synchronisedStorageBlobStorageEncryptionKeyId) &&
|
|
488
|
+
core.Is.stringBase64(envVars.synchronisedStorageBlobStoragePrivateKey) &&
|
|
489
|
+
core.Is.stringBase64(envVars.synchronisedStorageBlobStoragePublicKey)) {
|
|
490
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
491
|
+
const vaultConnector = vaultModels.VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
492
|
+
const keyName = envVars.synchronisedStorageBlobStorageEncryptionKeyId;
|
|
493
|
+
let existingKey;
|
|
494
|
+
try {
|
|
495
|
+
existingKey = await vaultConnector.getKey(keyName);
|
|
496
|
+
}
|
|
497
|
+
catch { }
|
|
498
|
+
if (core.Is.empty(existingKey)) {
|
|
499
|
+
engineCore.logInfo(core.I18n.formatMessage("node.addingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
500
|
+
await vaultConnector.addKey(keyName, vaultModels.VaultKeyType.Rsa2048, core.Converter.base64ToBytes(envVars.synchronisedStorageBlobStoragePrivateKey), core.Converter.base64ToBytes(envVars.synchronisedStorageBlobStoragePublicKey));
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
engineCore.logInfo(core.I18n.formatMessage("node.existingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Add a verification method if it doesn't exist.
|
|
510
|
+
* @param engineCore The engine core for the node.
|
|
511
|
+
* @param context The context for the node.
|
|
512
|
+
* @param verificationMethodTitle The verification method title.
|
|
513
|
+
* @param verificationMethodId The verification method ID.
|
|
514
|
+
*/
|
|
515
|
+
async function addVerificationMethod(engineCore, context, verificationMethodTitle, verificationMethodId) {
|
|
516
|
+
if (core.Is.stringValue(context.state.nodeIdentity) &&
|
|
517
|
+
core.Is.arrayValue(context.config.types.identityConnector) &&
|
|
518
|
+
core.Is.stringValue(verificationMethodId)) {
|
|
519
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
520
|
+
const identityConnector = identityModels.IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
521
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
522
|
+
const identityResolverConnector = identityModels.IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
523
|
+
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
524
|
+
const fullMethodId = `${identityDocument.id}#${verificationMethodId}`;
|
|
525
|
+
let exists = false;
|
|
526
|
+
try {
|
|
527
|
+
identityModels.DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
528
|
+
exists = true;
|
|
529
|
+
}
|
|
530
|
+
catch { }
|
|
531
|
+
if (!exists) {
|
|
532
|
+
engineCore.logInfo(core.I18n.formatMessage("node.addingVerificationMethod", {
|
|
533
|
+
title: verificationMethodTitle,
|
|
534
|
+
methodId: fullMethodId
|
|
535
|
+
}));
|
|
536
|
+
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", verificationMethodId);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
engineCore.logInfo(core.I18n.formatMessage("node.existingVerificationMethod", {
|
|
540
|
+
title: verificationMethodTitle,
|
|
541
|
+
methodId: fullMethodId
|
|
542
|
+
}));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
519
546
|
|
|
520
547
|
// Copyright 2024 IOTA Stiftung.
|
|
521
548
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -533,7 +560,9 @@ function buildEngineConfiguration(envVars) {
|
|
|
533
560
|
envVars.attestationVerificationMethodId ??= "attestation-assertion";
|
|
534
561
|
envVars.immutableProofVerificationMethodId ??= "immutable-proof-assertion";
|
|
535
562
|
envVars.blobStorageEnableEncryption ??= "false";
|
|
536
|
-
envVars.
|
|
563
|
+
envVars.blobStorageEncryptionKeyId ??= "blob-encryption";
|
|
564
|
+
envVars.synchronisedStorageBlobStorageEncryptionKeyId ??= "synchronised-storage-blob-encryption";
|
|
565
|
+
envVars.synchronisedStorageVerificationMethodId ??= "synchronised-storage-assertion";
|
|
537
566
|
const coreConfig = {
|
|
538
567
|
debug: core.Coerce.boolean(envVars.debug) ?? false,
|
|
539
568
|
types: {}
|
|
@@ -559,9 +588,10 @@ function buildEngineConfiguration(envVars) {
|
|
|
559
588
|
configureAuditableItemGraph(coreConfig);
|
|
560
589
|
configureAuditableItemStream(coreConfig);
|
|
561
590
|
configureDocumentManagement(coreConfig);
|
|
562
|
-
configureFederatedCatalogue(coreConfig, envVars);
|
|
563
591
|
configureRightsManagement(coreConfig, envVars);
|
|
564
592
|
configureTaskScheduler(coreConfig, envVars);
|
|
593
|
+
configureSynchronisedStorage(coreConfig, envVars);
|
|
594
|
+
configureFederatedCatalogue(coreConfig, envVars);
|
|
565
595
|
return coreConfig;
|
|
566
596
|
}
|
|
567
597
|
/**
|
|
@@ -683,7 +713,7 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
683
713
|
}
|
|
684
714
|
});
|
|
685
715
|
}
|
|
686
|
-
if (entityStorageConnectorTypes) {
|
|
716
|
+
if (entityStorageConnectorTypes.includes(engineTypes.EntityStorageConnectorType.PostgreSql)) {
|
|
687
717
|
coreConfig.types.entityStorageConnector.push({
|
|
688
718
|
type: engineTypes.EntityStorageConnectorType.PostgreSql,
|
|
689
719
|
options: {
|
|
@@ -698,10 +728,19 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
698
728
|
}
|
|
699
729
|
});
|
|
700
730
|
}
|
|
731
|
+
const defaultEntityStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
732
|
+
if (entityStorageConnectorTypes.includes(engineTypes.EntityStorageConnectorType.Synchronised)) {
|
|
733
|
+
// For synchronised storage we use the default connector as the one we wrap for real DB operations
|
|
734
|
+
coreConfig.types.entityStorageConnector.push({
|
|
735
|
+
type: engineTypes.EntityStorageConnectorType.Synchronised,
|
|
736
|
+
options: {
|
|
737
|
+
entityStorageConnectorType: defaultEntityStorageConnectorType
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
701
741
|
if (core.Is.arrayValue(entityStorageConnectorTypes)) {
|
|
702
|
-
const defaultStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
703
742
|
for (const config of coreConfig.types.entityStorageConnector) {
|
|
704
|
-
if (config.type ===
|
|
743
|
+
if (config.type === defaultEntityStorageConnectorType) {
|
|
705
744
|
config.isDefault = true;
|
|
706
745
|
break;
|
|
707
746
|
}
|
|
@@ -793,6 +832,13 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
793
832
|
for (const config of coreConfig.types.blobStorageConnector) {
|
|
794
833
|
if (config.type === defaultStorageConnectorType) {
|
|
795
834
|
config.isDefault = true;
|
|
835
|
+
}
|
|
836
|
+
// If this blob storage connector is the one to use for public access
|
|
837
|
+
// then add it as a feature
|
|
838
|
+
if (core.Is.stringValue(envVars.blobStorageConnectorPublic) &&
|
|
839
|
+
config.type === envVars.blobStorageConnectorPublic) {
|
|
840
|
+
config.features ??= [];
|
|
841
|
+
config.features.push("public");
|
|
796
842
|
break;
|
|
797
843
|
}
|
|
798
844
|
}
|
|
@@ -804,7 +850,7 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
804
850
|
options: {
|
|
805
851
|
config: {
|
|
806
852
|
vaultKeyId: (envVars.blobStorageEnableEncryption ?? false)
|
|
807
|
-
? envVars.
|
|
853
|
+
? envVars.blobStorageEncryptionKeyId
|
|
808
854
|
: undefined
|
|
809
855
|
}
|
|
810
856
|
}
|
|
@@ -1298,25 +1344,6 @@ function configureDocumentManagement(coreConfig, envVars) {
|
|
|
1298
1344
|
});
|
|
1299
1345
|
}
|
|
1300
1346
|
}
|
|
1301
|
-
/**
|
|
1302
|
-
* Configures the federated catalogue.
|
|
1303
|
-
* @param coreConfig The core config.
|
|
1304
|
-
* @param envVars The environment variables.
|
|
1305
|
-
*/
|
|
1306
|
-
function configureFederatedCatalogue(coreConfig, envVars) {
|
|
1307
|
-
if (core.Is.arrayValue(coreConfig.types.identityResolverComponent)) {
|
|
1308
|
-
coreConfig.types.federatedCatalogueComponent ??= [];
|
|
1309
|
-
coreConfig.types.federatedCatalogueComponent.push({
|
|
1310
|
-
type: engineTypes.FederatedCatalogueComponentType.Service,
|
|
1311
|
-
options: {
|
|
1312
|
-
config: {
|
|
1313
|
-
subResourceCacheTtlMs: core.Coerce.number(envVars.federatedCatalogueCacheTtlMs),
|
|
1314
|
-
clearingHouseApproverList: core.Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
1347
|
/**
|
|
1321
1348
|
* Configures the rights management.
|
|
1322
1349
|
* @param coreConfig The core config.
|
|
@@ -1340,13 +1367,76 @@ function configureRightsManagement(coreConfig, envVars) {
|
|
|
1340
1367
|
* @param envVars The environment variables.
|
|
1341
1368
|
*/
|
|
1342
1369
|
function configureTaskScheduler(coreConfig, envVars) {
|
|
1343
|
-
if (core.Coerce.boolean(envVars.taskSchedulerEnabled) ??
|
|
1370
|
+
if (core.Coerce.boolean(envVars.taskSchedulerEnabled) ?? false) {
|
|
1344
1371
|
coreConfig.types.taskSchedulerComponent ??= [];
|
|
1345
1372
|
coreConfig.types.taskSchedulerComponent.push({
|
|
1346
1373
|
type: engineTypes.TaskSchedulerComponentType.Service
|
|
1347
1374
|
});
|
|
1348
1375
|
}
|
|
1349
1376
|
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Configures the synchronised storage.
|
|
1379
|
+
* @param coreConfig The core config.
|
|
1380
|
+
* @param envVars The environment variables.
|
|
1381
|
+
*/
|
|
1382
|
+
function configureSynchronisedStorage(coreConfig, envVars) {
|
|
1383
|
+
if (core.Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1384
|
+
(core.Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false)) {
|
|
1385
|
+
// Check if the config provides a custom verifiable storage key id
|
|
1386
|
+
let verifiableStorageKeyId = core.Coerce.string(envVars.synchronisedStorageVerifiableStorageKeyId);
|
|
1387
|
+
if (!core.Is.stringValue(verifiableStorageKeyId)) {
|
|
1388
|
+
// No custom key so default to the network setting
|
|
1389
|
+
verifiableStorageKeyId = envVars.iotaNetwork;
|
|
1390
|
+
}
|
|
1391
|
+
coreConfig.types.synchronisedStorageComponent ??= [];
|
|
1392
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1393
|
+
type: engineTypes.SynchronisedStorageComponentType.Service,
|
|
1394
|
+
options: {
|
|
1395
|
+
config: {
|
|
1396
|
+
verifiableStorageKeyId: verifiableStorageKeyId ?? "",
|
|
1397
|
+
synchronisedStorageMethodId: envVars.synchronisedStorageVerificationMethodId,
|
|
1398
|
+
blobStorageEncryptionKeyId: envVars.synchronisedStorageBlobStorageEncryptionKeyId,
|
|
1399
|
+
entityUpdateIntervalMinutes: core.Coerce.number(envVars.synchronisedStorageEntityUpdateIntervalMinutes),
|
|
1400
|
+
consolidationIntervalMinutes: core.Coerce.number(envVars.synchronisedStorageConsolidationIntervalMinutes),
|
|
1401
|
+
consolidationBatchSize: core.Coerce.number(envVars.synchronisedStorageConsolidationBatchSize),
|
|
1402
|
+
maxConsolidations: core.Coerce.number(envVars.synchronisedStorageMaxConsolidations)
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
// If there is a trusted url set, we need to add a client
|
|
1407
|
+
// and give it a feature of trusted so that when the synchronised
|
|
1408
|
+
// storage is created it can pickup the correct component
|
|
1409
|
+
if (core.Is.stringValue(envVars.synchronisedStorageTrustedUrl)) {
|
|
1410
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1411
|
+
type: engineTypes.SynchronisedStorageComponentType.RestClient,
|
|
1412
|
+
options: {
|
|
1413
|
+
endpoint: envVars.synchronisedStorageTrustedUrl
|
|
1414
|
+
},
|
|
1415
|
+
features: ["trusted"]
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Configures the federated catalogue.
|
|
1422
|
+
* @param coreConfig The core config.
|
|
1423
|
+
* @param envVars The environment variables.
|
|
1424
|
+
*/
|
|
1425
|
+
function configureFederatedCatalogue(coreConfig, envVars) {
|
|
1426
|
+
if (core.Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1427
|
+
(core.Coerce.boolean(envVars.federatedCatalogueEnabled) ?? false)) {
|
|
1428
|
+
coreConfig.types.federatedCatalogueComponent ??= [];
|
|
1429
|
+
coreConfig.types.federatedCatalogueComponent.push({
|
|
1430
|
+
type: engineTypes.FederatedCatalogueComponentType.Service,
|
|
1431
|
+
options: {
|
|
1432
|
+
config: {
|
|
1433
|
+
subResourceCacheTtlMs: core.Coerce.number(envVars.federatedCatalogueCacheTtlMs),
|
|
1434
|
+
clearingHouseApproverList: core.Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1350
1440
|
/**
|
|
1351
1441
|
* Configures the DLT.
|
|
1352
1442
|
* @param coreConfig The core config.
|
|
@@ -1532,9 +1622,10 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
|
|
|
1532
1622
|
* @returns The engine server.
|
|
1533
1623
|
*/
|
|
1534
1624
|
async function start(nodeOptions, engineServerConfig, envVars) {
|
|
1535
|
-
envVars.storageFileRoot ??= "";
|
|
1536
1625
|
const entityStorageConnectorType = envVars.entityStorageConnectorType?.split(",") ?? [];
|
|
1537
1626
|
const blobStorageConnectorType = envVars.blobStorageConnectorType?.split(",") ?? [];
|
|
1627
|
+
// If the blob storage or entity storage is configured with file connectors
|
|
1628
|
+
// then we need to make sure the storageFileRoot is set
|
|
1538
1629
|
if ((entityStorageConnectorType.includes(engineTypes.EntityStorageConnectorType.File) ||
|
|
1539
1630
|
blobStorageConnectorType.includes(engineTypes.BlobStorageConnectorType.File) ||
|
|
1540
1631
|
core.Is.empty(nodeOptions?.stateStorage)) &&
|
|
@@ -1543,7 +1634,7 @@ async function start(nodeOptions, engineServerConfig, envVars) {
|
|
|
1543
1634
|
storageFileRoot: `${nodeOptions?.envPrefix ?? ""}_STORAGE_FILE_ROOT`
|
|
1544
1635
|
});
|
|
1545
1636
|
}
|
|
1546
|
-
// Create the engine instance using file state storage
|
|
1637
|
+
// Create the engine instance using file state storage unless one is configured in options
|
|
1547
1638
|
const engine$1 = new engine.Engine({
|
|
1548
1639
|
config: engineServerConfig,
|
|
1549
1640
|
stateStorage: nodeOptions?.stateStorage ?? new engineCore.FileStateStorage(envVars.stateFilename ?? ""),
|
|
@@ -1587,7 +1678,7 @@ async function run(nodeOptions) {
|
|
|
1587
1678
|
nodeOptions ??= {};
|
|
1588
1679
|
const serverInfo = {
|
|
1589
1680
|
name: nodeOptions?.serverName ?? "TWIN Node Server",
|
|
1590
|
-
version: nodeOptions?.serverVersion ?? "0.0.2-next.
|
|
1681
|
+
version: nodeOptions?.serverVersion ?? "0.0.2-next.4" // x-release-please-version
|
|
1591
1682
|
};
|
|
1592
1683
|
console.log(`\u001B[4mđŠī¸ ${serverInfo.name} v${serverInfo.version}\u001B[24m\n`);
|
|
1593
1684
|
if (!core.Is.stringValue(nodeOptions?.executionDirectory)) {
|
|
@@ -1645,7 +1736,8 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1645
1736
|
}
|
|
1646
1737
|
if (core.Is.arrayValue(options?.envFilenames)) {
|
|
1647
1738
|
const output = dotenv__namespace.config({
|
|
1648
|
-
path: options?.envFilenames
|
|
1739
|
+
path: options?.envFilenames,
|
|
1740
|
+
quiet: true
|
|
1649
1741
|
});
|
|
1650
1742
|
// We don't want to throw an error if the default environment file is not found.
|
|
1651
1743
|
// Only if we have custom environment files.
|
|
@@ -1699,12 +1791,12 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1699
1791
|
|
|
1700
1792
|
exports.NodeFeatures = NodeFeatures;
|
|
1701
1793
|
exports.bootstrap = bootstrap;
|
|
1702
|
-
exports.bootstrapAttestationMethod = bootstrapAttestationMethod;
|
|
1703
1794
|
exports.bootstrapAuth = bootstrapAuth;
|
|
1704
1795
|
exports.bootstrapBlobEncryption = bootstrapBlobEncryption;
|
|
1705
1796
|
exports.bootstrapImmutableProofMethod = bootstrapImmutableProofMethod;
|
|
1706
1797
|
exports.bootstrapNodeIdentity = bootstrapNodeIdentity;
|
|
1707
1798
|
exports.bootstrapNodeUser = bootstrapNodeUser;
|
|
1799
|
+
exports.bootstrapSynchronisedStorage = bootstrapSynchronisedStorage;
|
|
1708
1800
|
exports.buildConfiguration = buildConfiguration;
|
|
1709
1801
|
exports.buildEngineConfiguration = buildEngineConfiguration;
|
|
1710
1802
|
exports.buildEngineServerConfiguration = buildEngineServerConfiguration;
|