@tdxvolt/volt-client-grpc 0.18.9 → 0.19.0

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.18.9",
6
+ "version": "0.19.0",
7
7
  "description": "tdx Volt library for nodejs clients",
8
8
  "type": "module",
9
9
  "exports": {
@@ -37,8 +37,7 @@
37
37
  "author": "toby.ealden@gmail.com",
38
38
  "license": "ISC",
39
39
  "dependencies": {
40
- "@tdxvolt/volt-client-web": "^0.18.1",
41
- "bent": "^7.3.12",
40
+ "@tdxvolt/volt-client-web": "^0.19.0",
42
41
  "builtin-modules": "^3.3.0",
43
42
  "debug": "^4.1.1",
44
43
  "ip": "^1.1.5",
package/src/constants.js CHANGED
@@ -1,4 +1,4 @@
1
- export const constants = {
1
+ const _constants = {
2
2
  authTokenName: "volt-token",
3
3
  defaultDIDHostName: "coreid.com",
4
4
  didServiceType: {
@@ -19,5 +19,14 @@ export const constants = {
19
19
  terminalAPI: "tdx.volt_api.volt.v1.TerminalAPI",
20
20
  voltAPI: "tdx.volt_api.volt.v1.VoltAPI",
21
21
  wireAPI: "tdx.volt_api.volt.v1.WireAPI"
22
- }
22
+ },
23
+ voltDIDPrefix: "did:volt:"
23
24
  };
25
+
26
+ _constants.defaultRegistryList = [
27
+ _constants.defaultDIDHostName,
28
+ "https://tdxvolt.com",
29
+ "https://tdxid.com"
30
+ ];
31
+
32
+ export const constants = _constants;
package/src/index.js CHANGED
@@ -1,7 +1,8 @@
1
- export {constants} from "./constants.js";
1
+ export { SyncProvider } from "@tdxvolt/volt-client-web";
2
+ export { v4 as generateId } from "uuid";
3
+ export { constants } from "./constants.js";
2
4
  export * as grpcUtils from "./grpc-utils.js";
5
+ export * as protoUtils from "./proto-utils.js";
3
6
  export * as utils from "./utils.js";
4
- export {VoltClient} from "./volt-client.js";
7
+ export { VoltClient } from "./volt-client.js";
5
8
  export * from "./volt-credential.js";
6
- export * as protoUtils from "./proto-utils.js";
7
- export {v4 as generateId} from "uuid";
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
  import { voltUtils as voltClientUtils } from "@tdxvolt/volt-client-web/js";
3
- import bent from "bent";
4
3
  import debug from "debug";
5
4
  import ip from "ip";
6
5
  import lodash from "lodash";
@@ -106,30 +105,6 @@ export async function authenticateInternal(
106
105
  ownDID = false
107
106
  ) {
108
107
  try {
109
- // Check if we need to resolve a Relay volt.
110
- if (
111
- typeof this._voltConfig?.relay === "string" ||
112
- this._voltConfig?.relay?.http_address
113
- ) {
114
- const relayURL =
115
- this._voltConfig.relay?.http_address || this._voltConfig.relay;
116
- log("attempting to retrieve Relay information from %s", relayURL);
117
- this._voltConfig.relay = await fetchVoltConfig.call(
118
- this,
119
- `${relayURL}/discover`
120
- );
121
-
122
- if (this._voltConfig.relay.ca_pem) {
123
- log(
124
- "Auto-fetched Relay CA %s, remote address %s",
125
- this._voltConfig.relay.ca_pem,
126
- this._voltConfig.relay.address
127
- );
128
- } else {
129
- throw new Error("Unable to fetch Relay CA - cannot securely connect.");
130
- }
131
- }
132
-
133
108
  if (!this._credential.cache.ca) {
134
109
  this._credential.cache.ca = this._voltConfig.ca_pem;
135
110
  }
@@ -159,10 +134,10 @@ export async function authenticateInternal(
159
134
  host: this._credential.cache.bindIp
160
135
  };
161
136
 
162
- if (this._voltConfig.challenge_code) {
137
+ if (this._credential.cache.challenge_code) {
163
138
  authenticateRequest.challenge = signBase64(
164
139
  this._credential.cache.key,
165
- this._voltConfig.challenge_code
140
+ this._credential.cache.challenge_code
166
141
  );
167
142
  } else {
168
143
  log(
@@ -459,11 +434,18 @@ export function streamingCallInternal(methodType, method, request, service) {
459
434
 
460
435
  export async function fetchVoltConfig(discovery_url) {
461
436
  try {
462
- const getJSON = bent("json");
463
- const fullVoltConfig = await getJSON(discovery_url);
437
+ const response = await fetch(discovery_url);
438
+
439
+ if (!response.ok) {
440
+ log("fetchVoltConfig - error [%s]", response.status);
441
+ return null;
442
+ }
443
+
444
+ const fullVoltConfig = await response.json();
464
445
 
465
446
  if (!fullVoltConfig || typeof fullVoltConfig !== "object") {
466
- throw new Error(`invalid config resolved from: ${discovery_url}`);
447
+ log("fetchVoltConfig - invalid config resolved from: %s", discovery_url);
448
+ return null;
467
449
  }
468
450
 
469
451
  const voltConfig = pick(
@@ -475,16 +457,15 @@ export async function fetchVoltConfig(discovery_url) {
475
457
  "address",
476
458
  "http_address",
477
459
  "ca_pem",
478
- "challenge_code",
479
- "cloud",
480
460
  "relay"
481
461
  );
482
462
 
463
+ log("fetchVoltConfig from %s", discovery_url);
464
+
483
465
  return voltConfig;
484
466
  } catch (err) {
485
- throw new Error(
486
- `Failure loading config from ${discovery_url}: ${err.message}`
487
- );
467
+ log("fetchVoltConfig - %s error [%s]", discovery_url, err.message);
468
+ return null;
488
469
  }
489
470
  }
490
471
 
@@ -495,8 +476,13 @@ async function fetchVoltConfigFromDIDInternal(volt_did, registryUrl) {
495
476
  throw new Error(`Invalid Volt DID: ${volt_did}`);
496
477
  }
497
478
 
498
- const getJSON = bent("json");
499
- const didDocument = await getJSON(didResolution);
479
+ const response = await fetch(didResolution);
480
+
481
+ if (!response.ok) {
482
+ throw new Error(`HTTP error, status: ${response.status}`);
483
+ }
484
+
485
+ const didDocument = await response.json();
500
486
  const voltConfigServices = findDIDDocumentService(
501
487
  didDocument,
502
488
  constants.didServiceType.voltConfig
@@ -512,7 +498,12 @@ async function fetchVoltConfigFromDIDInternal(volt_did, registryUrl) {
512
498
  log("resolved DID from %s", didResolution);
513
499
 
514
500
  const serviceEndpoint = voltConfigServices[0].serviceEndpoint;
515
- return await fetchVoltConfig.call(this, serviceEndpoint);
501
+ const voltConfig = await fetchVoltConfig.call(this, serviceEndpoint);
502
+ if (!voltConfig) {
503
+ throw new Error(`failed to resolve Volt config from ${serviceEndpoint}`);
504
+ }
505
+
506
+ return voltConfig;
516
507
  } catch (err) {
517
508
  throw new Error(
518
509
  `Failure fetching Volt config from DID document ${volt_did}: ${err.message}`
@@ -520,21 +511,128 @@ async function fetchVoltConfigFromDIDInternal(volt_did, registryUrl) {
520
511
  }
521
512
  }
522
513
 
523
- export async function fetchVoltConfigFromDID(
524
- volt_did,
525
- registryList = [constants.defaultDIDHostName]
526
- ) {
514
+ export async function fetchVoltConfigFromDID(volt_did, registryListIn) {
515
+ let registryList;
516
+ if (!registryListIn) {
517
+ // If no registry list is provided, use the default registry list.
518
+ registryList = constants.defaultRegistryList;
519
+ } else {
520
+ // If a registry list is provided, merge it with the default registry list.
521
+ registryList = Array.from(
522
+ new Set(registryListIn.concat(constants.defaultRegistryList))
523
+ );
524
+ }
525
+
526
+ let voltConfig;
527
527
  for (let registryUrl of registryList) {
528
528
  try {
529
- return await fetchVoltConfigFromDIDInternal.call(
529
+ voltConfig = await fetchVoltConfigFromDIDInternal.call(
530
530
  this,
531
531
  volt_did,
532
532
  registryUrl
533
533
  );
534
+
535
+ // If we've found a config, break out of the loop.
536
+ break;
537
+ } catch (err) {
538
+ log("unable to fetch Volt config from %s: %s", registryUrl, err.message);
539
+ }
540
+ }
541
+
542
+ if (!voltConfig) {
543
+ log("failed to fetch Volt config for %s from any registry", volt_did);
544
+ }
545
+
546
+ return voltConfig;
547
+ }
548
+
549
+ export async function resolveVoltConfig(cfg, didRegistryList) {
550
+ // Start by resolving the relay if one is specified.
551
+ let relay = cfg?.relay?.http_address || cfg?.relay;
552
+ if (relay) {
553
+ relay = await resolveVoltConfig.call(this, relay, didRegistryList);
554
+ }
555
+
556
+ let voltDID = "";
557
+ let voltHttpAddress = "";
558
+
559
+ if (typeof cfg === "string") {
560
+ // If the configuration is a string, determine if it is a DID or HTTP address.
561
+ if (cfg.indexOf("did:") === 0) {
562
+ voltDID = cfg;
563
+ } else {
564
+ voltHttpAddress = cfg;
565
+ }
566
+ cfg = {};
567
+ } else if (typeof cfg === "object") {
568
+ voltDID = cfg.id;
569
+ voltHttpAddress = cfg.http_address;
570
+ } else {
571
+ throw new Error("configuration is missing the 'volt' object");
572
+ }
573
+
574
+ let resolvedConfig;
575
+
576
+ if (relay) {
577
+ if (!voltDID) {
578
+ throw new Error("relay specified but no Volt DID provided");
579
+ }
580
+
581
+ // Build the sub-domain URL for the Volt discovery service by inserting the volt DID into the relay URL.
582
+ const relayURL = relay.http_address;
583
+ const relayParts = relayURL.split("://");
584
+ const subdomain = voltDID.substring(constants.voltDIDPrefix.length);
585
+ voltHttpAddress = `${relayParts[0]}://${subdomain}.${relayParts[1]}`;
586
+ } else if (voltDID) {
587
+ resolvedConfig = await fetchVoltConfigFromDID.call(
588
+ this,
589
+ voltDID,
590
+ didRegistryList
591
+ );
592
+ }
593
+
594
+ if (!resolvedConfig && voltHttpAddress) {
595
+ try {
596
+ resolvedConfig = await fetchVoltConfig.call(
597
+ this,
598
+ `${voltHttpAddress}/discover`
599
+ );
600
+
601
+ // Update the http address in the resolved config to the one we used to
602
+ // fetch it, since we may have used the relay rather than local address.
603
+ resolvedConfig.http_address = voltHttpAddress;
534
604
  } catch (err) {
535
- log("Failed to fetch Volt config from %s: %s", registryUrl, err.message);
605
+ log(
606
+ "failed to resolve Volt configuration %s [%s]",
607
+ voltHttpAddress,
608
+ err.message
609
+ );
610
+ }
611
+ }
612
+
613
+ if (!resolvedConfig) {
614
+ if (cfg.address && cfg.ca_pem) {
615
+ log(
616
+ "unable to resolve %s / %s, using existing configuration",
617
+ voltDID,
618
+ voltHttpAddress
619
+ );
620
+ } else {
621
+ throw new Error(
622
+ `failed to resolve Volt ${voltDID} / ${voltHttpAddress} and no existing configuration`
623
+ );
536
624
  }
625
+ } else {
626
+ log("resolved config from: %s", voltDID || voltHttpAddress);
627
+ log(JSON.stringify(resolvedConfig, null, 2));
628
+
629
+ cfg = {
630
+ ...cfg,
631
+ ...resolvedConfig
632
+ };
537
633
  }
538
634
 
539
- throw new Error(`Failed to fetch Volt config from any of these registries: ${registryList.join(", ")}`);
635
+ cfg.relay = relay;
636
+
637
+ return cfg;
540
638
  }
@@ -6,9 +6,8 @@ import GRPCCall from "./grpc-call.js";
6
6
  import {
7
7
  authenticateInternal,
8
8
  connectInternal,
9
- fetchVoltConfig,
10
- fetchVoltConfigFromDID,
11
9
  getVoltAPIClientInternal,
10
+ resolveVoltConfig,
12
11
  streamingCallInternal,
13
12
  unaryCallInternal
14
13
  } from "./volt-client-internal.js";
@@ -74,19 +73,22 @@ export class VoltClient extends EventEmitter {
74
73
 
75
74
  /**
76
75
  * Initialises a binding and connection to the volt.
77
- * @param {string} [config] - the location of the Volt configuration file, or a configuration object
78
- * @param {object} [extras] - additional configuration properties
76
+ * @param {string | object} [config] - the location of the Volt configuration
77
+ * file, or a configuration object
78
+ * @param {object} [options] - initialisation options
79
+ * @param {string[]} [options.didRegistryList] - list of DID registry URLs to use for
80
+ * resolving DIDs
81
+ * @param {object} [options.extraConfig] - additional configuration to be merged with the
82
+ * configuration from the config file.
83
+ * @param {boolean} [options.ownDID] - whether the Volt should manage the DID - set to
84
+ * true if you want to manage the DID yourself
79
85
  */
80
- async initialise(
81
- config,
82
- extras = {},
83
- ownDID = false,
84
- didRegistryList = undefined
85
- ) {
86
+ async initialise(config, options = {}) {
87
+ const { ownDID, didRegistryList, extraConfig } = options;
86
88
  try {
87
89
  if (!config) {
88
90
  throw new Error(
89
- "configPath argument is required to be a non-empty string"
91
+ "config argument is required to be a non-empty string or JSON object"
90
92
  );
91
93
  }
92
94
 
@@ -119,7 +121,8 @@ export class VoltClient extends EventEmitter {
119
121
  throw new Error("config argument is required to be an object");
120
122
  }
121
123
 
122
- this._config = { ...extras, ...configJSON };
124
+ // Merge any extra configuration supplied, preferring that loaded from file.
125
+ this._config = { ...extraConfig, ...configJSON };
123
126
 
124
127
  if (
125
128
  !this._config.client_name ||
@@ -128,52 +131,13 @@ export class VoltClient extends EventEmitter {
128
131
  throw new Error("client_name property required in config");
129
132
  }
130
133
 
131
- let voltDID = "";
132
- let voltHttpAddress = "";
133
- if (typeof this._config.volt === "string") {
134
- if (this._config.volt.indexOf("did:") === 0) {
135
- voltDID = this._config.volt;
136
- } else {
137
- voltHttpAddress = this._config.volt;
138
- }
139
- delete this._config.volt;
140
- } else if (typeof this._config.volt !== "object") {
141
- throw new Error("configuration is missing the 'volt' object");
142
- } else {
143
- voltDID = this._config.volt.did;
144
- voltHttpAddress = this._config.volt.http_address;
145
- }
146
-
147
- if (voltDID) {
148
- const didConfig = await fetchVoltConfigFromDID.call(
149
- this,
150
- voltDID,
151
- didRegistryList
152
- );
153
-
154
- // Remove the relay list from the DID config, as it would overwrite the relay in the supplied config.
155
- delete didConfig.relay;
156
-
157
- this._config = { ...this._config, volt: didConfig };
158
- log("resolved config from DID: %s", JSON.stringify(didConfig, null, 2));
159
- } else if (voltHttpAddress && !this._config?.volt?.id) {
160
- const discoConfig = await fetchVoltConfig.call(
161
- this,
162
- `${voltHttpAddress}/discover`
163
- );
164
- this._config = {
165
- ...this._config,
166
- volt: { ...this._config.volt, ...discoConfig }
167
- };
168
- }
134
+ this._config.volt = await resolveVoltConfig.call(
135
+ this,
136
+ this._config.volt,
137
+ didRegistryList
138
+ );
169
139
 
170
140
  this._voltConfig = this._config.volt;
171
- if (voltDID) {
172
- this._voltConfig.did = voltDID;
173
- }
174
- if (voltHttpAddress) {
175
- this._voltConfig.http_address = voltHttpAddress;
176
- }
177
141
 
178
142
  if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
179
143
  throw new Error("'id' property is missing in Volt configuration");
@@ -228,13 +192,14 @@ export class VoltClient extends EventEmitter {
228
192
 
229
193
  return Promise.resolve(this._config);
230
194
  } catch (err) {
231
- log("Failure starting Volt client [%s]", err.message);
195
+ log("failure starting Volt client [%s]", err.message);
232
196
  throw err;
233
197
  }
234
198
  }
235
199
 
236
- initialiseAndConnect(configPath, helloPayload = undefined, extras = {}) {
237
- return this.initialise(configPath, extras)
200
+ initialiseAndConnect(configPath, options) {
201
+ const helloPayload = options?.helloPayload;
202
+ return this.initialise(configPath, options)
238
203
  .then(() => {
239
204
  return connectInternal.call(this, helloPayload);
240
205
  })
@@ -1,8 +1,9 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
+ import { voltUtils as voltClientUtils } from "@tdxvolt/volt-client-web/js";
3
+ import crypto from "crypto";
2
4
  import debug from "debug";
3
5
  import fs from "fs";
4
6
  import { constants } from "./constants.js";
5
- import { voltUtils as voltClientUtils } from "@tdxvolt/volt-client-web/js";
6
7
 
7
8
  const {
8
9
  createSigningKey,
@@ -54,6 +55,11 @@ export class VoltCredential {
54
55
 
55
56
  // Extract Volt public key.
56
57
  this._voltPublicKey = this._voltConfig.public_key;
58
+ if (!this._voltPublicKey) {
59
+ // Extract the public key from the Volt CA cert.
60
+ const voltCA = new crypto.X509Certificate(this._voltConfig.ca_pem);
61
+ this._voltPublicKey = voltCA.publicKey.export({ type: 'spki', format: 'pem' });
62
+ }
57
63
 
58
64
  const voltKey = keyFromPem(this._voltPublicKey).publicKey;
59
65
  this._voltFingerprint = fingerprintFromPem(voltKey);
@@ -2747,6 +2747,7 @@ message InvokeResponse {
2747
2747
  // The invocation id to match the originating request.
2748
2748
  uint64 invoke_id = 1;
2749
2749
 
2750
+ // Key exchange details for the response payload encryption. Only necessary for the first response.
2750
2751
  InvokeRequestKeyExchange key_exchange = 2;
2751
2752
 
2752
2753
  // The initialisation vector for the response payload encryption. This will be a random 16 byte value, that is different for each response.