@tdxvolt/volt-client-grpc 0.15.0 → 0.16.0-beta.1

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/src/constants.js CHANGED
@@ -4,37 +4,37 @@ export const constants = {
4
4
  collectionOperation: {
5
5
  add: "ADD",
6
6
  remove: "REMOVE",
7
- update: "UPDATE",
7
+ update: "UPDATE"
8
8
  },
9
9
  crypto: {
10
10
  subjectDefaults: [
11
11
  {
12
12
  name: "countryName",
13
- value: "GB",
13
+ value: "GB"
14
14
  },
15
15
  {
16
16
  name: "localityName",
17
- value: "Southampton",
17
+ value: "Southampton"
18
18
  },
19
19
  {
20
20
  name: "organizationName",
21
- value: "nquiringminds Ltd",
22
- },
21
+ value: "nquiringminds Ltd"
22
+ }
23
23
  ],
24
- userIdOID: "0.9.2342.19200300.100.1.1",
24
+ userIdOID: "0.9.2342.19200300.100.1.1"
25
25
  },
26
26
  defaultDIDHostName: "cloud.tdxvolt.com",
27
27
  didServiceType: {
28
- voltConfig: "tdx-volt-config",
28
+ voltConfig: "tdx-volt-config"
29
29
  },
30
30
  identityType: {
31
31
  did: "did",
32
- volt: "volt",
32
+ volt: "volt"
33
33
  },
34
34
  onlineStatus: {
35
35
  offline: "offline",
36
36
  online: "online",
37
- unknown: "unknown",
37
+ unknown: "unknown"
38
38
  },
39
39
  publicKeyPrefix: "-----BEGIN PUBLIC KEY-----",
40
40
  privateKeyPrefix: "-----BEGIN RSA PRIVATE KEY-----",
@@ -45,14 +45,14 @@ export const constants = {
45
45
  proxy: "proxy",
46
46
  proxyView: "proxy-view",
47
47
  vc: "vc",
48
- vcView: "vc-view",
48
+ vcView: "vc-view"
49
49
  },
50
50
  voltStatus: {
51
51
  pending: "",
52
52
  online: "online",
53
53
  offline: "offline",
54
54
  deleted: "deleted",
55
- revoked: "revoked",
55
+ revoked: "revoked"
56
56
  },
57
57
  /**
58
58
  * These top-level service types should map to a protobuf definition
@@ -62,14 +62,14 @@ export const constants = {
62
62
  fileAPI: "tdx.volt_api.volt.v1.FileAPI",
63
63
  sqliteServerAPI: "tdx.volt_api.data.v1.SqliteServerAPI",
64
64
  sqliteDatabaseAPI: "tdx.volt_api.data.v1.SqliteDatabaseAPI",
65
- ssiAPI: "tdx.volt_api.ssi.v1.SsiAPI",
65
+ ssiAPI: "tdx.volt_api.volt.v1.SsiAPI",
66
66
  relayAPI: "tdx.volt_api.relay.v1.RelayAPI",
67
67
  voltAPI: "tdx.volt_api.volt.v1.VoltAPI",
68
- wireAPI: "tdx.volt_api.volt.v1.WireAPI",
68
+ wireAPI: "tdx.volt_api.volt.v1.WireAPI"
69
69
  },
70
70
  tunnelInvokeMethod: "/tdx.volt_api.volt.v1.VoltAPI/Invoke",
71
71
  tunnelPingInterval: 10000,
72
72
  tunnelPingTimeout: 2500,
73
73
  tunnelReconnectAfter: 30000,
74
- tunnelTimeoutInterval: 10000 * 2, // This must always be greater than `tunnelPingInterval`.
74
+ tunnelTimeoutInterval: 10000 * 2 // This must always be greater than `tunnelPingInterval`.
75
75
  };
package/src/grpc-call.js CHANGED
@@ -8,7 +8,7 @@ const log = debug("volt-client-grpc:grpc-call");
8
8
  function _prepareInvokeRequest(
9
9
  request,
10
10
  method,
11
- methodType = "METHOD_TYPE_UNARY",
11
+ methodType = "METHOD_TYPE_UNARY"
12
12
  ) {
13
13
  const isServiceRelayed =
14
14
  this._service?.service_description.host_type ===
@@ -21,7 +21,7 @@ function _prepareInvokeRequest(
21
21
  this._voltClient.voltConfig.id,
22
22
  this._service?.service_description.host_public_key ||
23
23
  this._voltClient.credential.voltPublicKey,
24
- true,
24
+ true
25
25
  );
26
26
 
27
27
  let invokeRequest;
@@ -52,42 +52,40 @@ function _prepareInvokeRequest(
52
52
  method_name: this._grpcClient[method].path,
53
53
  method_type: methodType,
54
54
  token: methodToken.token,
55
- request: requestPayload,
56
- },
55
+ request: requestPayload
56
+ }
57
57
  };
58
58
 
59
- let targetFingerprint = [];
59
+ let targetDID = [];
60
60
  let invokePayload;
61
61
  if (this._voltClient.isVoltRelay || isServiceRelayed) {
62
62
  // When sending via a relay we need to encrypt the payload.
63
63
  invokePayload = VoltCredential.aesEncrypt(
64
64
  tunnelMethod.responseSerialize(tunnelResp),
65
65
  methodToken.sharedKey.key,
66
- methodToken.sharedKey.iv,
66
+ methodToken.sharedKey.iv
67
67
  );
68
68
  } else {
69
69
  invokePayload = tunnelMethod.responseSerialize(tunnelResp);
70
70
  }
71
71
 
72
72
  if (this._voltClient.isVoltRelay) {
73
- targetFingerprint.push(this._voltClient.credential.voltFingerprint);
73
+ targetDID.push(this._voltClient.voltConfig.id);
74
74
  }
75
75
 
76
76
  if (isServiceRelayed) {
77
77
  // Add another relay hop if the target service is relayed.
78
78
  log("target service is relayed");
79
- targetFingerprint.push(
80
- this._service.service_description.host_fingerprint,
81
- );
79
+ targetDID.push(this._service.service_description.host_client_id);
82
80
  }
83
81
 
84
- log("target is %j", targetFingerprint);
82
+ log("target is %j", targetDID);
85
83
 
86
84
  invokeRequest = {
87
- target_fingerprint: targetFingerprint,
85
+ target_did: targetDID,
88
86
  token: methodToken.token,
89
87
  payload: invokePayload,
90
- target_service_id: isServiceRelayed ? this._service.id : undefined,
88
+ target_service_id: isServiceRelayed ? this._service.id : undefined
91
89
  };
92
90
 
93
91
  // Replace the target method with a call to Invoke on the relay Volt.
@@ -100,7 +98,7 @@ function _prepareInvokeRequest(
100
98
  meta: methodToken,
101
99
  method: callMethod,
102
100
  methodName: method,
103
- request: invokeRequest,
101
+ request: invokeRequest
104
102
  };
105
103
  }
106
104
 
@@ -120,18 +118,18 @@ function _preparePayloadRequest(request, invokeInfo) {
120
118
 
121
119
  const tunnelResp = {
122
120
  method_payload: {
123
- payload: requestPayload,
124
- },
121
+ payload: requestPayload
122
+ }
125
123
  };
126
124
 
127
125
  const invokePayload = VoltCredential.aesEncrypt(
128
126
  tunnelMethod.responseSerialize(tunnelResp),
129
127
  invokeInfo.meta.sharedKey.key,
130
- invokeInfo.meta.sharedKey.iv,
128
+ invokeInfo.meta.sharedKey.iv
131
129
  );
132
130
 
133
131
  payloadRequest = {
134
- payload: invokePayload,
132
+ payload: invokePayload
135
133
  };
136
134
  } else {
137
135
  payloadRequest = request;
@@ -155,7 +153,7 @@ function _parseResponse(method, meta, response) {
155
153
  decryptedPayload = VoltCredential.aesDecrypt(
156
154
  response.payload,
157
155
  meta.sharedKey.key,
158
- meta.sharedKey.iv,
156
+ meta.sharedKey.iv
159
157
  );
160
158
  } else {
161
159
  decryptedPayload = response.payload;
@@ -166,12 +164,12 @@ function _parseResponse(method, meta, response) {
166
164
  if (responsePayload.payload === "method_payload") {
167
165
  try {
168
166
  invokeResponse.payload = this._grpcClient[method].responseDeserialize(
169
- responsePayload.method_payload.payload,
167
+ responsePayload.method_payload.payload
170
168
  );
171
169
  } catch (err) {
172
170
  log("failure deserialising payload for %s", this._methodName);
173
171
  throw new Error(
174
- "failure deserialising payload - check protobuf definition matches with data being sent",
172
+ "failure deserialising payload - check protobuf definition matches with data being sent"
175
173
  );
176
174
  }
177
175
  } else if (responsePayload.payload === "method_end") {
@@ -185,11 +183,11 @@ function _parseResponse(method, meta, response) {
185
183
  "Tunnel status received: %s, code %d, description: %s",
186
184
  response.status.message,
187
185
  response.status.code,
188
- response.status.description || "n/a",
186
+ response.status.description || "n/a"
189
187
  );
190
188
  invokeResponse = {
191
189
  ended: true,
192
- error: `Error in tunnel: ${response.status.message}`,
190
+ error: `Error in tunnel: ${response.status.message}`
193
191
  };
194
192
  } else {
195
193
  invokeResponse.methodId = response.invoke_id;
@@ -199,11 +197,11 @@ function _parseResponse(method, meta, response) {
199
197
  const decryptedPayload = VoltCredential.aesDecrypt(
200
198
  response,
201
199
  meta.sharedKey.key,
202
- meta.sharedKey.iv,
200
+ meta.sharedKey.iv
203
201
  );
204
202
  invokeResponse = {
205
203
  payload:
206
- this._grpcClient[method].responseDeserializeOriginal(decryptedPayload),
204
+ this._grpcClient[method].responseDeserializeOriginal(decryptedPayload)
207
205
  };
208
206
  } else {
209
207
  invokeResponse = { payload: response };
@@ -233,7 +231,7 @@ export default class GRPCCall extends EventEmitter {
233
231
  this,
234
232
  request,
235
233
  this._methodName,
236
- this._methodType,
234
+ this._methodType
237
235
  );
238
236
 
239
237
  const isServiceRelayed =
@@ -248,7 +246,7 @@ export default class GRPCCall extends EventEmitter {
248
246
  }
249
247
 
250
248
  this._call = callClient[this._initialRequest.method](
251
- this._initialRequest.meta.metadata,
249
+ this._initialRequest.meta.metadata
252
250
  );
253
251
 
254
252
  this._call.on("data", (streamResponse) => {
@@ -257,14 +255,14 @@ export default class GRPCCall extends EventEmitter {
257
255
  this,
258
256
  this._methodName,
259
257
  this._initialRequest.meta,
260
- streamResponse,
258
+ streamResponse
261
259
  );
262
260
  if (parsedResponse.payload) {
263
261
  // Interpret a non-empty status message as an error.
264
262
  if (parsedResponse.payload?.status?.message) {
265
263
  this.emit(
266
264
  "error",
267
- new Error(parsedResponse.payload.status.message),
265
+ new Error(parsedResponse.payload.status.message)
268
266
  );
269
267
  } else {
270
268
  this.emit("data", parsedResponse.payload);
@@ -283,7 +281,7 @@ export default class GRPCCall extends EventEmitter {
283
281
  log(
284
282
  "failure processing %s call response: %s",
285
283
  this._methodName,
286
- err.message,
284
+ err.message
287
285
  );
288
286
  this.emit("error", err);
289
287
  }
@@ -305,7 +303,7 @@ export default class GRPCCall extends EventEmitter {
305
303
  log(
306
304
  "ended call %s, method id: %s",
307
305
  this._methodName,
308
- this._initialRequest.methodId || "n/a - not tunnelling",
306
+ this._initialRequest.methodId || "n/a - not tunnelling"
309
307
  );
310
308
  this.emit("end");
311
309
  });
@@ -338,7 +336,7 @@ export default class GRPCCall extends EventEmitter {
338
336
  const payloadRequest = _preparePayloadRequest.call(
339
337
  this,
340
338
  request,
341
- this._initialRequest,
339
+ this._initialRequest
342
340
  );
343
341
  return this._call.write(payloadRequest);
344
342
  } else {
@@ -346,7 +344,7 @@ export default class GRPCCall extends EventEmitter {
346
344
  this,
347
345
  request,
348
346
  this._methodName,
349
- this._methodType,
347
+ this._methodType
350
348
  );
351
349
 
352
350
  return this._call.write(this._initialRequest.request);
package/src/utils.js CHANGED
@@ -64,11 +64,16 @@ export const createPublicKeyFingerprint = (key) => {
64
64
  return createBase58Hash(removeCarriageReturn(key));
65
65
  };
66
66
 
67
+ export const base64ToBase64Url = (base64) => {
68
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
69
+ };
70
+
67
71
  export const signBase64 = (key, msg) => {
68
72
  const md = forge.md.sha256.create();
69
73
  md.update(msg);
70
74
  const signature = key.sign(md);
71
- return forge.util.encode64(signature);
75
+ const base64Standard = forge.util.encode64(signature);
76
+ return base64ToBase64Url(base64Standard);
72
77
  };
73
78
 
74
79
  export const verifyBase64 = (key, msg, signature) => {
@@ -137,10 +142,25 @@ export const createRSAKey = (passphrase = "") => {
137
142
  export const sha256Base64 = (msg) => {
138
143
  const md = forge.md.sha256.create();
139
144
  md.update(msg);
140
- return forge.util.encode64(md.digest().bytes());
145
+ const base64Standard = forge.util.encode64(md.digest().bytes());
146
+ return base64ToBase64Url(base64Standard);
141
147
  };
142
148
 
143
149
  export const decryptPrivateKey = (pem, passphrase) => {
144
150
  const decryptedKey = pki.decryptRsaPrivateKey(pem, passphrase);
145
151
  return forgePrivateKeyToPem(decryptedKey);
146
152
  };
153
+
154
+ export const jwsSignature = (signature) => {
155
+ //
156
+ // Use an unattached payload JWS signature for the DID document proof.
157
+ // This is a JWS with an empty payload and a detached payload digest.
158
+ //
159
+ // (https://tools.ietf.org/html/rfc7797#appendix-F) as used in the VC spec
160
+ // (https://www.w3.org/TR/vc-data-model/).
161
+ //
162
+ const jwsHeader = base64ToBase64Url(
163
+ forge.util.encode64('{"alg":"RS256","b64": false}')
164
+ );
165
+ return jwsHeader + ".." + signature;
166
+ };
@@ -16,6 +16,7 @@ import * as voltUtils from "./utils.js";
16
16
  import { constants } from "./constants.js";
17
17
  import GRPCCall from "./grpc-call.js";
18
18
  import RpcInvocation from "./rpc-invocation.js";
19
+ import { v4 as generateId } from "uuid";
19
20
 
20
21
  const { pki } = forge;
21
22
  const { filter, pick } = lodash;
@@ -31,6 +32,55 @@ const voltServices = [
31
32
  constants.serviceType.wireAPI
32
33
  ];
33
34
 
35
+ function issueAuthenticate(authenticateRequest, ttl) {
36
+ // eslint-disable-next-line no-use-before-define
37
+ return unaryCallInternal
38
+ .call(this, "Authenticate", authenticateRequest)
39
+ .then((authenticateResponse) => {
40
+ log("got authenticate response %j", authenticateResponse);
41
+ if (authenticateResponse.status?.code) {
42
+ log("error in bind response: %s", authenticateResponse.status.message);
43
+ return Promise.reject(new Error(authenticateResponse.status.message));
44
+ } else {
45
+ switch (authenticateResponse.decision) {
46
+ case "POLICY_DECISION_PERMIT": {
47
+ //
48
+ // The request status is permit => cache the bind response info.
49
+ //
50
+
51
+ // This is the certificate assigned to us by the volt.
52
+ this._credential.cache.cert = authenticateResponse.cert;
53
+
54
+ // This is the signing CA used by the volt.
55
+ this._credential.cache.ca = authenticateResponse.chain;
56
+
57
+ // Identity resource id is assigned by the volt.
58
+ this._credential.cache.client_id = authenticateResponse.session_id;
59
+ this._credential.saveCache();
60
+ break;
61
+ }
62
+ case "POLICY_DECISION_DENY": {
63
+ log(">>>>>>>>>>>>>>> access request is DENIED <<<<<<<<<<<<<<<<<<<");
64
+ break;
65
+ }
66
+ case "POLICY_DECISION_PROMPT":
67
+ case "POLICY_DECISION_PENDING": {
68
+ log("access pending approval - waiting...");
69
+ break;
70
+ }
71
+ default:
72
+ log(
73
+ "ignoring unknown bind descision %s",
74
+ authenticateResponse.status
75
+ );
76
+ break;
77
+ }
78
+ }
79
+
80
+ return authenticateResponse.decision;
81
+ });
82
+ }
83
+
34
84
  function issueBind(bindRequest, ttl) {
35
85
  // eslint-disable-next-line no-use-before-define
36
86
  return unaryCallInternal
@@ -86,7 +136,8 @@ function findDIDDocumentService(document, serviceType) {
86
136
  return matches;
87
137
  }
88
138
 
89
- export async function bindInternal() {
139
+
140
+ export async function authenticateInternal() {
90
141
  try {
91
142
  // Check if we need to resolve a Relay volt.
92
143
  if (
@@ -100,6 +151,7 @@ export async function bindInternal() {
100
151
  this,
101
152
  `${relayURL}/discovery`
102
153
  );
154
+
103
155
  if (this._voltConfig.relay.ca_pem) {
104
156
  log(
105
157
  "Auto-fetched Relay CA %s, remote address %s",
@@ -133,19 +185,107 @@ export async function bindInternal() {
133
185
  this._credential.saveCache();
134
186
 
135
187
  // Form the request message.
136
- const bindRequest = {
188
+ const authenticateRequest = {
137
189
  binding_name: this._config.client_name,
138
190
  public_key: publicKeyPem,
139
191
  host: this._credential.cache.bindIp,
140
- x509_credential: []
141
192
  };
142
193
 
143
- if (this._credential.cache.cert) {
144
- bindRequest.x509_credential = [
145
- this._credential.cache.cert + this._credential.cache.ca
146
- ];
194
+ if (this._voltConfig.challenge_code) {
195
+ authenticateRequest.challenge = voltUtils.signBase64(
196
+ keyPair.privateKey,
197
+ this._voltConfig.challenge_code
198
+ );
199
+ } else {
200
+ log(
201
+ "****no Volt challenge code available**** => not sending challenge signature"
202
+ );
203
+ }
204
+
205
+ log("authenticate TTL is ", this._config.bindRequestTTL);
206
+
207
+ // This will block while the request is pending.
208
+ const pollInterval = 10000;
209
+ let decision = "";
210
+ do {
211
+ if (decision) {
212
+ // Wait a while before retrying.
213
+ // eslint-disable-next-line no-await-in-loop
214
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
215
+ }
216
+ // eslint-disable-next-line no-await-in-loop
217
+ decision = await issueAuthenticate.call(
218
+ this,
219
+ authenticateRequest,
220
+ this._voltConfig.bindRequestTTL
221
+ );
222
+ log("authenticate decision: %s", decision);
223
+ } while (
224
+ decision === "POLICY_DECISION_PROMPT" ||
225
+ decision === "POLICY_DECISION_PENDING"
226
+ );
227
+
228
+ return decision === "POLICY_DECISION_PERMIT";
229
+ } catch (err) {
230
+ log("failure binding to Volt [%s]", err.message);
231
+ throw err;
232
+ }
233
+ }
234
+
235
+ export async function bindInternal() {
236
+ try {
237
+ // Check if we need to resolve a Relay volt.
238
+ if (
239
+ typeof this._voltConfig?.relay === "string" ||
240
+ this._voltConfig?.relay?.http_address
241
+ ) {
242
+ const relayURL =
243
+ this._voltConfig.relay?.http_address || this._voltConfig.relay;
244
+ log("attempting to retrieve Relay information from %s", relayURL);
245
+ this._voltConfig.relay = await fetchVoltConfig.call(
246
+ this,
247
+ `${relayURL}/discovery`
248
+ );
249
+ if (this._voltConfig.relay.ca_pem) {
250
+ log(
251
+ "Auto-fetched Relay CA %s, remote address %s",
252
+ this._voltConfig.relay.ca_pem,
253
+ this._voltConfig.relay.address
254
+ );
255
+ } else {
256
+ throw new Error("Unable to fetch Relay CA - cannot securely connect.");
257
+ }
147
258
  }
148
259
 
260
+ if (!this._credential.cache.ca) {
261
+ this._credential.cache.ca = this._voltConfig.ca_pem;
262
+ }
263
+
264
+ if (!this._credential.cache.ca) {
265
+ throw new Error(
266
+ "No certificate authority found in configuration for target Volt - check configuration"
267
+ );
268
+ }
269
+
270
+ const keyPair = this._credential.getKey();
271
+ const publicKeyPem = voltUtils.removeCarriageReturn(
272
+ pki.publicKeyToPem(keyPair.publicKey)
273
+ );
274
+
275
+ if (!this._credential.cache.bindIp) {
276
+ // Default the IP address if none is specified. This is used to set the SAN in the
277
+ // certificate issued by the Volt.
278
+ this._credential.cache.bindIp = ip.address();
279
+ }
280
+
281
+ this._credential.saveCache();
282
+
283
+ // Form the request message.
284
+ const bindRequest = {
285
+ binding_name: this._config.client_name,
286
+ host: this._credential.cache.bindIp
287
+ };
288
+
149
289
  if (this._voltConfig.challenge_code) {
150
290
  bindRequest.challenge = voltUtils.signBase64(
151
291
  keyPair.privateKey,
@@ -157,14 +297,65 @@ export async function bindInternal() {
157
297
  );
158
298
  }
159
299
 
160
- // For remote connections, add our cloud-issued certificate as an additional credential.
161
- if (this._voltConfig?.relay?.ca_pem && this._credential.cache.cloud_cert) {
162
- bindRequest.x509_credential = bindRequest.x509_credential.concat(
163
- this._credential.cache.cloud_cert + this._voltConfig.relay.ca_pem
164
- );
300
+ if (!this._credential.cache.client_id) {
301
+ // If we don't have a client id, then we need to create a DID.
302
+ this._credential.cache.client_id = `did:volt:${generateId()}`;
165
303
  }
166
304
 
167
- log("sending %d x509 credentials", bindRequest.x509_credential.length);
305
+ const did = this._credential.cache.client_id;
306
+
307
+ // Order of properties in the DID document is important,
308
+ // they must be in alphabetical order throughout the document.
309
+ // This is because the DID document is signed and the signature
310
+ // is over the entire document.
311
+ const didDocument = {
312
+ "@context": "https://www.w3.org/ns/did/v1",
313
+ authentication: [`${did}#key-1`],
314
+ description: this._config.client_name,
315
+ id: did,
316
+ verificationMethod: [
317
+ {
318
+ controller: did,
319
+ id: `${did}#key-1`,
320
+ publicKeyPem: publicKeyPem,
321
+ type: "RsaSignature2018"
322
+ }
323
+ ]
324
+ };
325
+
326
+ const signDocument = JSON.stringify(didDocument, null, 2);
327
+ log("signing did document");
328
+ log(signDocument);
329
+
330
+ // Sign the DID document.
331
+ const documentDigest = voltUtils.signBase64(
332
+ keyPair.privateKey,
333
+ signDocument
334
+ );
335
+
336
+ const proofSignature = voltUtils.jwsSignature(documentDigest);
337
+
338
+ // Create the DID document proof.
339
+ didDocument.proof = {
340
+ created: new Date().toISOString(),
341
+ jws: proofSignature,
342
+ proofPurpose: "assertionMethod",
343
+ type: "RsaSignature2018",
344
+ verificationMethod: `${did}#key-1`
345
+ };
346
+
347
+ // Set the DID document with proof in the bind request.
348
+ bindRequest.did_document = JSON.stringify(didDocument, null, 2);
349
+
350
+ // Now sign the entire DID document, including the proof and set it in the
351
+ // bind request. This is required by the Volt to verify that we have the private key for the DID.
352
+ bindRequest.did_document_signature = voltUtils.signBase64(
353
+ keyPair.privateKey,
354
+ bindRequest.did_document
355
+ );
356
+
357
+ log("did document is %s", bindRequest.did_document);
358
+ log("did signature is %s", bindRequest.did_document_signature);
168
359
  log("bind TTL is ", this._config.bindRequestTTL);
169
360
 
170
361
  // This will block while the request is pending.
@@ -304,9 +495,6 @@ export function getVoltAPIClientInternal() {
304
495
 
305
496
  export function getAPIClientInternal(service) {
306
497
  if (!this._cachedService[service.id]) {
307
- const isRelayedService =
308
- service.service_description.host_type === "SERVICE_HOST_TYPE_RELAYED";
309
-
310
498
  const serviceAddress = this.isRemote
311
499
  ? this._voltConfig.relay.address
312
500
  : service.service_description.host_address;
@@ -354,9 +542,8 @@ export function getServiceClient(service) {
354
542
  }
355
543
 
356
544
  export function unaryCallInternal(method, request, service) {
357
- const grpcClient = getServiceClient.call(this, service);
358
-
359
545
  return new Promise((resolve, reject) => {
546
+ const grpcClient = getServiceClient.call(this, service);
360
547
  const call = new GRPCCall(this, method, "METHOD_TYPE_UNARY", service);
361
548
 
362
549
  let response = null;
@@ -5,6 +5,7 @@ import path from "path";
5
5
  import { getServiceDescriptors } from "./proto-utils.js";
6
6
  import { createClient as createGrpcClient } from "./grpc-utils.js";
7
7
  import {
8
+ authenticateInternal,
8
9
  bindInternal,
9
10
  connectInternal,
10
11
  fetchVoltConfig,
@@ -166,14 +167,14 @@ export class VoltClient extends EventEmitter {
166
167
  if (this._credential._cryptoCache.cert) {
167
168
  isBound = true;
168
169
  } else {
169
- // No certificate present in the cache yet => we need to bind.
170
+ // No certificate present in the cache yet => we need to authenticate.
170
171
  try {
171
- isBound = await bindInternal.call(this);
172
+ isBound = await authenticateInternal.call(this);
172
173
  } catch (bindErr) {
173
174
  if (bindErr.message === "invalid arguments") {
174
175
  // This is usually the result of a missing challenge signature or credential.
175
176
  log(
176
- "failure binding to Volt - did you supply a challenge code or verified credential?"
177
+ "failure authenticating on Volt - did you supply a challenge code or verified credential?"
177
178
  );
178
179
  throw bindErr;
179
180
  }
@@ -182,7 +183,7 @@ export class VoltClient extends EventEmitter {
182
183
 
183
184
  if (!isBound) {
184
185
  // This is usually because of request being denied.
185
- throw new Error("Volt binding failed");
186
+ throw new Error("Volt authentication failed");
186
187
  }
187
188
 
188
189
  return Promise.resolve();
@@ -408,6 +409,10 @@ export class VoltClient extends EventEmitter {
408
409
  * VoltAPI - volt management
409
410
  */
410
411
 
412
+ Authenticate(request) {
413
+ return unaryCallInternal.call(this, "Authenticate", request);
414
+ }
415
+
411
416
  Bind(request) {
412
417
  return unaryCallInternal.call(this, "Bind", request);
413
418
  }
@@ -569,6 +574,13 @@ export class VoltClient extends EventEmitter {
569
574
  });
570
575
  }
571
576
 
577
+ /**
578
+ * SqliteServerAPI
579
+ */
580
+ CreateDatabase(request) {
581
+ return unaryCallInternal.call(this, "CreateDatabase", request);
582
+ }
583
+
572
584
  /**
573
585
  * SqliteDatabaseAPI
574
586
  */