@twin.org/node-core 0.0.2-next.3 â 0.0.2-next.5
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 +263 -162
- package/dist/esm/index.mjs +264 -163
- package/dist/types/bootstrap.d.ts +9 -9
- package/dist/types/builders/engineServerEnvBuilder.d.ts +2 -1
- package/dist/types/models/IEngineEnvironmentVariables.d.ts +63 -9
- package/dist/types/models/INodeOptions.d.ts +4 -0
- package/dist/types/models/nodeFeatures.d.ts +4 -0
- package/docs/changelog.md +14 -0
- package/docs/reference/functions/{bootstrapAttestationMethod.md â bootstrapSynchronisedStorage.md} +3 -3
- package/docs/reference/functions/buildEngineServerConfiguration.md +7 -1
- package/docs/reference/index.md +1 -1
- package/docs/reference/interfaces/IEngineEnvironmentVariables.md +133 -15
- package/docs/reference/interfaces/INodeEnvironmentVariables.md +183 -21
- package/docs/reference/interfaces/INodeOptions.md +8 -0
- package/docs/reference/variables/NodeFeatures.md +6 -0
- package/locales/en.json +6 -4
- package/package.json +2 -2
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,76 @@ 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.synchronisedStorageBlobStorageKey)) {
|
|
468
|
+
const defaultVaultConnectorType = engineCore.getRegisteredInstanceType("vaultConnector");
|
|
469
|
+
const vaultConnector = VaultConnectorFactory.get(defaultVaultConnectorType);
|
|
470
|
+
const keyName = envVars.synchronisedStorageBlobStorageEncryptionKeyId;
|
|
471
|
+
let existingKey;
|
|
472
|
+
try {
|
|
473
|
+
existingKey = await vaultConnector.getKey(keyName);
|
|
474
|
+
}
|
|
475
|
+
catch { }
|
|
476
|
+
if (Is.empty(existingKey)) {
|
|
477
|
+
engineCore.logInfo(I18n.formatMessage("node.addingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
478
|
+
await vaultConnector.addKey(keyName, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(envVars.synchronisedStorageBlobStorageKey));
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
engineCore.logInfo(I18n.formatMessage("node.existingSynchronisedStorageBlobEncryptionKey", { keyName }));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Add a verification method if it doesn't exist.
|
|
488
|
+
* @param engineCore The engine core for the node.
|
|
489
|
+
* @param context The context for the node.
|
|
490
|
+
* @param verificationMethodTitle The verification method title.
|
|
491
|
+
* @param verificationMethodId The verification method ID.
|
|
492
|
+
*/
|
|
493
|
+
async function addVerificationMethod(engineCore, context, verificationMethodTitle, verificationMethodId) {
|
|
494
|
+
if (Is.stringValue(context.state.nodeIdentity) &&
|
|
495
|
+
Is.arrayValue(context.config.types.identityConnector) &&
|
|
496
|
+
Is.stringValue(verificationMethodId)) {
|
|
497
|
+
const defaultIdentityConnectorType = engineCore.getRegisteredInstanceType("identityConnector");
|
|
498
|
+
const identityConnector = IdentityConnectorFactory.get(defaultIdentityConnectorType);
|
|
499
|
+
const defaultIdentityResolverConnectorType = engineCore.getRegisteredInstanceType("identityResolverConnector");
|
|
500
|
+
const identityResolverConnector = IdentityResolverConnectorFactory.get(defaultIdentityResolverConnectorType);
|
|
501
|
+
const identityDocument = await identityResolverConnector.resolveDocument(context.state.nodeIdentity);
|
|
502
|
+
const fullMethodId = `${identityDocument.id}#${verificationMethodId}`;
|
|
503
|
+
let exists = false;
|
|
504
|
+
try {
|
|
505
|
+
DocumentHelper.getVerificationMethod(identityDocument, fullMethodId, "assertionMethod");
|
|
506
|
+
exists = true;
|
|
507
|
+
}
|
|
508
|
+
catch { }
|
|
509
|
+
if (!exists) {
|
|
510
|
+
engineCore.logInfo(I18n.formatMessage("node.addingVerificationMethod", {
|
|
511
|
+
title: verificationMethodTitle,
|
|
512
|
+
methodId: fullMethodId
|
|
513
|
+
}));
|
|
514
|
+
await identityConnector.addVerificationMethod(context.state.nodeIdentity, context.state.nodeIdentity, "assertionMethod", verificationMethodId);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
engineCore.logInfo(I18n.formatMessage("node.existingVerificationMethod", {
|
|
518
|
+
title: verificationMethodTitle,
|
|
519
|
+
methodId: fullMethodId
|
|
520
|
+
}));
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
498
524
|
|
|
499
525
|
// Copyright 2024 IOTA Stiftung.
|
|
500
526
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -512,7 +538,9 @@ function buildEngineConfiguration(envVars) {
|
|
|
512
538
|
envVars.attestationVerificationMethodId ??= "attestation-assertion";
|
|
513
539
|
envVars.immutableProofVerificationMethodId ??= "immutable-proof-assertion";
|
|
514
540
|
envVars.blobStorageEnableEncryption ??= "false";
|
|
515
|
-
envVars.
|
|
541
|
+
envVars.blobStorageEncryptionKeyId ??= "blob-encryption";
|
|
542
|
+
envVars.synchronisedStorageBlobStorageEncryptionKeyId ??= "synchronised-storage-blob-encryption";
|
|
543
|
+
envVars.synchronisedStorageVerificationMethodId ??= "synchronised-storage-assertion";
|
|
516
544
|
const coreConfig = {
|
|
517
545
|
debug: Coerce.boolean(envVars.debug) ?? false,
|
|
518
546
|
types: {}
|
|
@@ -538,9 +566,10 @@ function buildEngineConfiguration(envVars) {
|
|
|
538
566
|
configureAuditableItemGraph(coreConfig);
|
|
539
567
|
configureAuditableItemStream(coreConfig);
|
|
540
568
|
configureDocumentManagement(coreConfig);
|
|
541
|
-
configureFederatedCatalogue(coreConfig, envVars);
|
|
542
569
|
configureRightsManagement(coreConfig, envVars);
|
|
543
570
|
configureTaskScheduler(coreConfig, envVars);
|
|
571
|
+
configureSynchronisedStorage(coreConfig, envVars);
|
|
572
|
+
configureFederatedCatalogue(coreConfig, envVars);
|
|
544
573
|
return coreConfig;
|
|
545
574
|
}
|
|
546
575
|
/**
|
|
@@ -662,7 +691,7 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
662
691
|
}
|
|
663
692
|
});
|
|
664
693
|
}
|
|
665
|
-
if (entityStorageConnectorTypes) {
|
|
694
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.PostgreSql)) {
|
|
666
695
|
coreConfig.types.entityStorageConnector.push({
|
|
667
696
|
type: EntityStorageConnectorType.PostgreSql,
|
|
668
697
|
options: {
|
|
@@ -677,10 +706,19 @@ function configureEntityStorage(coreConfig, envVars) {
|
|
|
677
706
|
}
|
|
678
707
|
});
|
|
679
708
|
}
|
|
709
|
+
const defaultEntityStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
710
|
+
if (entityStorageConnectorTypes.includes(EntityStorageConnectorType.Synchronised)) {
|
|
711
|
+
// For synchronised storage we use the default connector as the one we wrap for real DB operations
|
|
712
|
+
coreConfig.types.entityStorageConnector.push({
|
|
713
|
+
type: EntityStorageConnectorType.Synchronised,
|
|
714
|
+
options: {
|
|
715
|
+
entityStorageConnectorType: defaultEntityStorageConnectorType
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
680
719
|
if (Is.arrayValue(entityStorageConnectorTypes)) {
|
|
681
|
-
const defaultStorageConnectorType = envVars.entityStorageConnectorDefault ?? entityStorageConnectorTypes[0];
|
|
682
720
|
for (const config of coreConfig.types.entityStorageConnector) {
|
|
683
|
-
if (config.type ===
|
|
721
|
+
if (config.type === defaultEntityStorageConnectorType) {
|
|
684
722
|
config.isDefault = true;
|
|
685
723
|
break;
|
|
686
724
|
}
|
|
@@ -772,6 +810,13 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
772
810
|
for (const config of coreConfig.types.blobStorageConnector) {
|
|
773
811
|
if (config.type === defaultStorageConnectorType) {
|
|
774
812
|
config.isDefault = true;
|
|
813
|
+
}
|
|
814
|
+
// If this blob storage connector is the one to use for public access
|
|
815
|
+
// then add it as a feature
|
|
816
|
+
if (Is.stringValue(envVars.blobStorageConnectorPublic) &&
|
|
817
|
+
config.type === envVars.blobStorageConnectorPublic) {
|
|
818
|
+
config.features ??= [];
|
|
819
|
+
config.features.push("public");
|
|
775
820
|
break;
|
|
776
821
|
}
|
|
777
822
|
}
|
|
@@ -783,7 +828,7 @@ function configureBlobStorage(coreConfig, envVars) {
|
|
|
783
828
|
options: {
|
|
784
829
|
config: {
|
|
785
830
|
vaultKeyId: (envVars.blobStorageEnableEncryption ?? false)
|
|
786
|
-
? envVars.
|
|
831
|
+
? envVars.blobStorageEncryptionKeyId
|
|
787
832
|
: undefined
|
|
788
833
|
}
|
|
789
834
|
}
|
|
@@ -1277,25 +1322,6 @@ function configureDocumentManagement(coreConfig, envVars) {
|
|
|
1277
1322
|
});
|
|
1278
1323
|
}
|
|
1279
1324
|
}
|
|
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
1325
|
/**
|
|
1300
1326
|
* Configures the rights management.
|
|
1301
1327
|
* @param coreConfig The core config.
|
|
@@ -1319,13 +1345,76 @@ function configureRightsManagement(coreConfig, envVars) {
|
|
|
1319
1345
|
* @param envVars The environment variables.
|
|
1320
1346
|
*/
|
|
1321
1347
|
function configureTaskScheduler(coreConfig, envVars) {
|
|
1322
|
-
if (Coerce.boolean(envVars.taskSchedulerEnabled) ??
|
|
1348
|
+
if (Coerce.boolean(envVars.taskSchedulerEnabled) ?? false) {
|
|
1323
1349
|
coreConfig.types.taskSchedulerComponent ??= [];
|
|
1324
1350
|
coreConfig.types.taskSchedulerComponent.push({
|
|
1325
1351
|
type: TaskSchedulerComponentType.Service
|
|
1326
1352
|
});
|
|
1327
1353
|
}
|
|
1328
1354
|
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Configures the synchronised storage.
|
|
1357
|
+
* @param coreConfig The core config.
|
|
1358
|
+
* @param envVars The environment variables.
|
|
1359
|
+
*/
|
|
1360
|
+
function configureSynchronisedStorage(coreConfig, envVars) {
|
|
1361
|
+
if (Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1362
|
+
(Coerce.boolean(envVars.synchronisedStorageEnabled) ?? false)) {
|
|
1363
|
+
// Check if the config provides a custom verifiable storage key id
|
|
1364
|
+
let verifiableStorageKeyId = Coerce.string(envVars.synchronisedStorageVerifiableStorageKeyId);
|
|
1365
|
+
if (!Is.stringValue(verifiableStorageKeyId)) {
|
|
1366
|
+
// No custom key so default to the network setting
|
|
1367
|
+
verifiableStorageKeyId = envVars.iotaNetwork;
|
|
1368
|
+
}
|
|
1369
|
+
coreConfig.types.synchronisedStorageComponent ??= [];
|
|
1370
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1371
|
+
type: SynchronisedStorageComponentType.Service,
|
|
1372
|
+
options: {
|
|
1373
|
+
config: {
|
|
1374
|
+
verifiableStorageKeyId: verifiableStorageKeyId ?? "",
|
|
1375
|
+
synchronisedStorageMethodId: envVars.synchronisedStorageVerificationMethodId,
|
|
1376
|
+
blobStorageEncryptionKeyId: envVars.synchronisedStorageBlobStorageEncryptionKeyId,
|
|
1377
|
+
entityUpdateIntervalMinutes: Coerce.number(envVars.synchronisedStorageEntityUpdateIntervalMinutes),
|
|
1378
|
+
consolidationIntervalMinutes: Coerce.number(envVars.synchronisedStorageConsolidationIntervalMinutes),
|
|
1379
|
+
consolidationBatchSize: Coerce.number(envVars.synchronisedStorageConsolidationBatchSize),
|
|
1380
|
+
maxConsolidations: Coerce.number(envVars.synchronisedStorageMaxConsolidations)
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
// If there is a trusted url set, we need to add a client
|
|
1385
|
+
// and give it a feature of trusted so that when the synchronised
|
|
1386
|
+
// storage is created it can pickup the correct component
|
|
1387
|
+
if (Is.stringValue(envVars.synchronisedStorageTrustedUrl)) {
|
|
1388
|
+
coreConfig.types.synchronisedStorageComponent.push({
|
|
1389
|
+
type: SynchronisedStorageComponentType.RestClient,
|
|
1390
|
+
options: {
|
|
1391
|
+
endpoint: envVars.synchronisedStorageTrustedUrl
|
|
1392
|
+
},
|
|
1393
|
+
features: ["trusted"]
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Configures the federated catalogue.
|
|
1400
|
+
* @param coreConfig The core config.
|
|
1401
|
+
* @param envVars The environment variables.
|
|
1402
|
+
*/
|
|
1403
|
+
function configureFederatedCatalogue(coreConfig, envVars) {
|
|
1404
|
+
if (Is.arrayValue(coreConfig.types.identityResolverComponent) &&
|
|
1405
|
+
(Coerce.boolean(envVars.federatedCatalogueEnabled) ?? false)) {
|
|
1406
|
+
coreConfig.types.federatedCatalogueComponent ??= [];
|
|
1407
|
+
coreConfig.types.federatedCatalogueComponent.push({
|
|
1408
|
+
type: FederatedCatalogueComponentType.Service,
|
|
1409
|
+
options: {
|
|
1410
|
+
config: {
|
|
1411
|
+
subResourceCacheTtlMs: Coerce.number(envVars.federatedCatalogueCacheTtlMs),
|
|
1412
|
+
clearingHouseApproverList: Coerce.object(envVars.federatedCatalogueClearingHouseApproverList) ?? []
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1329
1418
|
/**
|
|
1330
1419
|
* Configures the DLT.
|
|
1331
1420
|
* @param coreConfig The core config.
|
|
@@ -1365,9 +1454,10 @@ function configureDlt(coreConfig, envVars) {
|
|
|
1365
1454
|
* @param coreEngineConfig The core engine config.
|
|
1366
1455
|
* @param serverInfo The server information.
|
|
1367
1456
|
* @param openApiSpecPath The path to the open api spec.
|
|
1457
|
+
* @param favIconPath The path to the favicon.
|
|
1368
1458
|
* @returns The the config for the core and the server.
|
|
1369
1459
|
*/
|
|
1370
|
-
function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, openApiSpecPath) {
|
|
1460
|
+
function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, openApiSpecPath, favIconPath) {
|
|
1371
1461
|
envVars.authSigningKeyId ??= "auth-signing";
|
|
1372
1462
|
const webServerOptions = {
|
|
1373
1463
|
port: Coerce.number(envVars.port),
|
|
@@ -1394,7 +1484,8 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
|
|
|
1394
1484
|
options: {
|
|
1395
1485
|
config: {
|
|
1396
1486
|
serverInfo,
|
|
1397
|
-
openApiSpecPath
|
|
1487
|
+
openApiSpecPath,
|
|
1488
|
+
favIconPath
|
|
1398
1489
|
}
|
|
1399
1490
|
}
|
|
1400
1491
|
}
|
|
@@ -1511,9 +1602,10 @@ function buildEngineServerConfiguration(envVars, coreEngineConfig, serverInfo, o
|
|
|
1511
1602
|
* @returns The engine server.
|
|
1512
1603
|
*/
|
|
1513
1604
|
async function start(nodeOptions, engineServerConfig, envVars) {
|
|
1514
|
-
envVars.storageFileRoot ??= "";
|
|
1515
1605
|
const entityStorageConnectorType = envVars.entityStorageConnectorType?.split(",") ?? [];
|
|
1516
1606
|
const blobStorageConnectorType = envVars.blobStorageConnectorType?.split(",") ?? [];
|
|
1607
|
+
// If the blob storage or entity storage is configured with file connectors
|
|
1608
|
+
// then we need to make sure the storageFileRoot is set
|
|
1517
1609
|
if ((entityStorageConnectorType.includes(EntityStorageConnectorType.File) ||
|
|
1518
1610
|
blobStorageConnectorType.includes(BlobStorageConnectorType.File) ||
|
|
1519
1611
|
Is.empty(nodeOptions?.stateStorage)) &&
|
|
@@ -1522,7 +1614,7 @@ async function start(nodeOptions, engineServerConfig, envVars) {
|
|
|
1522
1614
|
storageFileRoot: `${nodeOptions?.envPrefix ?? ""}_STORAGE_FILE_ROOT`
|
|
1523
1615
|
});
|
|
1524
1616
|
}
|
|
1525
|
-
// Create the engine instance using file state storage
|
|
1617
|
+
// Create the engine instance using file state storage unless one is configured in options
|
|
1526
1618
|
const engine = new Engine({
|
|
1527
1619
|
config: engineServerConfig,
|
|
1528
1620
|
stateStorage: nodeOptions?.stateStorage ?? new FileStateStorage(envVars.stateFilename ?? ""),
|
|
@@ -1566,7 +1658,7 @@ async function run(nodeOptions) {
|
|
|
1566
1658
|
nodeOptions ??= {};
|
|
1567
1659
|
const serverInfo = {
|
|
1568
1660
|
name: nodeOptions?.serverName ?? "TWIN Node Server",
|
|
1569
|
-
version: nodeOptions?.serverVersion ?? "0.0.2-next.
|
|
1661
|
+
version: nodeOptions?.serverVersion ?? "0.0.2-next.5" // x-release-please-version
|
|
1570
1662
|
};
|
|
1571
1663
|
console.log(`\u001B[4mđŠī¸ ${serverInfo.name} v${serverInfo.version}\u001B[24m\n`);
|
|
1572
1664
|
if (!Is.stringValue(nodeOptions?.executionDirectory)) {
|
|
@@ -1579,13 +1671,21 @@ async function run(nodeOptions) {
|
|
|
1579
1671
|
console.info("Locales Directory:", nodeOptions.localesDirectory);
|
|
1580
1672
|
await initialiseLocales(nodeOptions.localesDirectory);
|
|
1581
1673
|
if (Is.empty(nodeOptions?.openApiSpecFile)) {
|
|
1582
|
-
const specFile = path.resolve(path.join(nodeOptions.executionDirectory, "docs", "open-api", "spec.json"));
|
|
1674
|
+
const specFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "docs", "open-api", "spec.json"));
|
|
1583
1675
|
console.info("Default OpenAPI Spec File:", specFile);
|
|
1584
1676
|
if (await fileExists(specFile)) {
|
|
1585
1677
|
nodeOptions ??= {};
|
|
1586
1678
|
nodeOptions.openApiSpecFile = specFile;
|
|
1587
1679
|
}
|
|
1588
1680
|
}
|
|
1681
|
+
if (Is.empty(nodeOptions?.favIconFile)) {
|
|
1682
|
+
const favIconFile = path.resolve(path.join(nodeOptions.executionDirectory ?? "", "static", "favicon.png"));
|
|
1683
|
+
console.info("Default Favicon File:", favIconFile);
|
|
1684
|
+
if (await fileExists(favIconFile)) {
|
|
1685
|
+
nodeOptions ??= {};
|
|
1686
|
+
nodeOptions.favIconFile = favIconFile;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1589
1689
|
nodeOptions.envPrefix ??= "TWIN_NODE_";
|
|
1590
1690
|
console.info("Environment Prefix:", nodeOptions.envPrefix);
|
|
1591
1691
|
const { engineServerConfig, nodeEnvVars: envVars } = await buildConfiguration(process.env, nodeOptions, serverInfo);
|
|
@@ -1624,7 +1724,8 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1624
1724
|
}
|
|
1625
1725
|
if (Is.arrayValue(options?.envFilenames)) {
|
|
1626
1726
|
const output = dotenv.config({
|
|
1627
|
-
path: options?.envFilenames
|
|
1727
|
+
path: options?.envFilenames,
|
|
1728
|
+
quiet: true
|
|
1628
1729
|
});
|
|
1629
1730
|
// We don't want to throw an error if the default environment file is not found.
|
|
1630
1731
|
// Only if we have custom environment files.
|
|
@@ -1654,7 +1755,7 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1654
1755
|
}
|
|
1655
1756
|
// Build the engine configuration from the environment variables.
|
|
1656
1757
|
const coreConfig = buildEngineConfiguration(envVars);
|
|
1657
|
-
const engineServerConfig = buildEngineServerConfiguration(envVars, coreConfig, serverInfo, options?.openApiSpecFile);
|
|
1758
|
+
const engineServerConfig = buildEngineServerConfiguration(envVars, coreConfig, serverInfo, options?.openApiSpecFile, options?.favIconFile);
|
|
1658
1759
|
// Merge any custom configuration provided in the options.
|
|
1659
1760
|
if (Is.arrayValue(options?.configFilenames)) {
|
|
1660
1761
|
for (const configFile of options.configFilenames) {
|
|
@@ -1676,4 +1777,4 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
1676
1777
|
return { engineServerConfig, nodeEnvVars: envVars };
|
|
1677
1778
|
}
|
|
1678
1779
|
|
|
1679
|
-
export { NodeFeatures, bootstrap,
|
|
1780
|
+
export { NodeFeatures, bootstrap, bootstrapAuth, bootstrapBlobEncryption, bootstrapImmutableProofMethod, bootstrapNodeIdentity, bootstrapNodeUser, bootstrapSynchronisedStorage, buildConfiguration, buildEngineConfiguration, buildEngineServerConfiguration, fileExists, getExecutionDirectory, getFeatures, initialiseLocales, loadJsonFile, run, start };
|