@tdxvolt/volt-client-grpc 0.1.2 → 0.11.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/lib/index.cjs +1544 -1483
- package/package.json +1 -1
- package/protobuf/tdx/volt_api/data/v1/sqlite.proto +13 -13
- package/protobuf/tdx/volt_api/volt/v1/volt.proto +3 -0
- package/protobuf/tdx/volt_api/volt/v1/wire_api.proto +1 -1
- package/src/constants.js +72 -72
- package/src/grpc-call.js +115 -92
- package/src/grpc-utils.js +206 -206
- package/src/proto-utils.js +61 -61
- package/src/utils.js +64 -64
- package/src/volt-client-internal.js +315 -273
- package/src/volt-client.js +541 -540
- package/src/volt-connection.js +47 -52
- package/src/volt-credential.js +221 -221
package/lib/index.cjs
CHANGED
|
@@ -34,192 +34,192 @@ var bent__default = /*#__PURE__*/_interopDefaultLegacy(bent);
|
|
|
34
34
|
|
|
35
35
|
// eslint-disable-next-line import/prefer-default-export
|
|
36
36
|
const constants = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
37
|
+
authTokenName: "tdx-token",
|
|
38
|
+
collectionOperation: {
|
|
39
|
+
add: "ADD",
|
|
40
|
+
remove: "REMOVE",
|
|
41
|
+
update: "UPDATE",
|
|
42
|
+
},
|
|
43
|
+
crypto: {
|
|
44
|
+
subjectDefaults: [
|
|
45
|
+
{
|
|
46
|
+
name: "countryName",
|
|
47
|
+
value: "GB",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "localityName",
|
|
51
|
+
value: "Southampton",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "organizationName",
|
|
55
|
+
value: "nquiringminds Ltd",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
userIdOID: "0.9.2342.19200300.100.1.1",
|
|
59
|
+
},
|
|
60
|
+
defaultDIDHostName: "cloud.tdxvolt.com",
|
|
61
|
+
didServiceType: {
|
|
62
|
+
voltConfig: "tdx-volt-config",
|
|
63
|
+
},
|
|
64
|
+
identityType: {
|
|
65
|
+
did: "did",
|
|
66
|
+
volt: "volt",
|
|
67
|
+
},
|
|
68
|
+
onlineStatus: {
|
|
69
|
+
offline: "offline",
|
|
70
|
+
online: "online",
|
|
71
|
+
unknown: "unknown",
|
|
72
|
+
},
|
|
73
|
+
publicKeyPrefix: "-----BEGIN PUBLIC KEY-----",
|
|
74
|
+
privateKeyPrefix: "-----BEGIN RSA PRIVATE KEY-----",
|
|
75
|
+
privateEncryptedKeyPrefix: "-----BEGIN ENCRYPTED PRIVATE KEY-----",
|
|
76
|
+
resource: {
|
|
77
|
+
identity: "ident",
|
|
78
|
+
identityView: "ident-view",
|
|
79
|
+
proxy: "proxy",
|
|
80
|
+
proxyView: "proxy-view",
|
|
81
|
+
vc: "vc",
|
|
82
|
+
vcView: "vc-view",
|
|
83
|
+
},
|
|
84
|
+
voltStatus: {
|
|
85
|
+
pending: "",
|
|
86
|
+
online: "online",
|
|
87
|
+
offline: "offline",
|
|
88
|
+
deleted: "deleted",
|
|
89
|
+
revoked: "revoked",
|
|
90
|
+
},
|
|
91
|
+
/**
|
|
92
|
+
* These top-level service types should map to a protobuf definition
|
|
93
|
+
* in @tdxvolt/volt-proto.
|
|
94
|
+
*/
|
|
95
|
+
serviceType: {
|
|
96
|
+
fileAPI: "tdx.volt_api.volt.v1.FileAPI",
|
|
97
|
+
sqliteServerAPI: "tdx.volt_api.data.v1.SqliteServerAPI",
|
|
98
|
+
sqliteDatabaseAPI: "tdx.volt_api.data.v1.SqliteDatabaseAPI",
|
|
99
|
+
ssiAPI: "tdx.volt_api.ssi.v1.SsiAPI",
|
|
100
|
+
relayAPI: "tdx.volt_api.relay.v1.RelayAPI",
|
|
101
|
+
voltAPI: "tdx.volt_api.volt.v1.VoltAPI",
|
|
102
|
+
wireAPI: "tdx.volt_api.volt.v1.WireAPI",
|
|
103
|
+
},
|
|
104
|
+
tunnelInvokeMethod: "/tdx.volt_api.volt.v1.VoltAPI/Invoke",
|
|
105
|
+
tunnelPingInterval: 10000,
|
|
106
|
+
tunnelPingTimeout: 2500,
|
|
107
|
+
tunnelReconnectAfter: 30000,
|
|
108
|
+
tunnelTimeoutInterval: 10000 * 2, // This must always be greater than `tunnelPingInterval`.
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
const { pki: pki$2 } = forge__default["default"];
|
|
112
112
|
|
|
113
113
|
const createSecureContextOptions = (cryptoOptions) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
const tlsOptions = {
|
|
115
|
+
cert: Buffer.from(cryptoOptions.cert),
|
|
116
|
+
ca: Buffer.from(cryptoOptions.ca),
|
|
117
|
+
key: Buffer.from(cryptoOptions.key),
|
|
118
|
+
};
|
|
119
|
+
return tlsOptions;
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
const getServiceAddress = (mdnsService) => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
address = `${serviceDetails.host}:${serviceDetails.port}`;
|
|
137
|
-
}
|
|
123
|
+
let address;
|
|
124
|
+
|
|
125
|
+
if (mdnsService.txtRecord?.ip) {
|
|
126
|
+
// Use the advertised IP address if present.
|
|
127
|
+
address = `${mdnsService.txtRecord.ip}:${mdnsService.port}`;
|
|
128
|
+
} else {
|
|
129
|
+
// Find a valid ipv4 address.
|
|
130
|
+
const ipv4 = lodash__default["default"].find(mdnsService.addresses, (addy) => ip__default["default"].isV4Format(addy));
|
|
131
|
+
const serviceDetails = {
|
|
132
|
+
host: ipv4,
|
|
133
|
+
port: mdnsService.port,
|
|
134
|
+
};
|
|
138
135
|
|
|
139
|
-
|
|
136
|
+
address = `${serviceDetails.host}:${serviceDetails.port}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return address;
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
const privateKeyToPem = (keyPair) => {
|
|
143
|
-
|
|
143
|
+
return pki$2.privateKeyToPem(keyPair.privateKey);
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
const publicKeyToPem = (keyPair) => {
|
|
147
|
-
|
|
147
|
+
return pki$2.publicKeyToPem(keyPair.publicKey);
|
|
148
148
|
};
|
|
149
149
|
|
|
150
150
|
const deserialisePEM = (pem) => {
|
|
151
|
-
|
|
151
|
+
return pem.replace(/\\r\\n/g, "\r\n");
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
const removeCarriageReturn = (inp) => {
|
|
155
|
-
|
|
155
|
+
return inp.replace(/\r\n/g, "\n");
|
|
156
156
|
};
|
|
157
157
|
|
|
158
158
|
const createBase58Hash = (inp) => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
const md = forge__default["default"].md.sha256.create();
|
|
160
|
+
md.update(inp);
|
|
161
|
+
const hashBuf = Buffer.from(md.digest().getBytes(), "binary");
|
|
162
|
+
return bs58__default["default"].encode(hashBuf);
|
|
163
163
|
};
|
|
164
164
|
|
|
165
165
|
const createPublicKeyFingerprint = (key) => {
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
// Remove carriage returns before creating hash to bring into line with openSSL / volt format.
|
|
167
|
+
return createBase58Hash(removeCarriageReturn(key));
|
|
168
168
|
};
|
|
169
169
|
|
|
170
170
|
const signBase64 = (key, msg) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
const md = forge__default["default"].md.sha256.create();
|
|
172
|
+
md.update(msg);
|
|
173
|
+
const signature = key.sign(md);
|
|
174
|
+
return forge__default["default"].util.encode64(signature);
|
|
175
175
|
};
|
|
176
176
|
|
|
177
177
|
const verifyBase64 = (key, msg, signature) => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
const md = forge__default["default"].md.sha256.create();
|
|
179
|
+
md.update(msg);
|
|
180
|
+
return key.verify(md.digest().bytes(), forge__default["default"].util.decode64(signature));
|
|
181
181
|
};
|
|
182
182
|
|
|
183
183
|
const getDIDResolutionURL = (didIn) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
184
|
+
const did = didIn.toLowerCase().split(":");
|
|
185
|
+
if (did.length < 3) {
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
if (did[0] !== "did") {
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
if (did[1] !== "tdx") {
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
197
|
+
let hostName;
|
|
198
|
+
let didGUID;
|
|
199
|
+
if (did.length === 3) {
|
|
200
|
+
hostName = constants.defaultDIDHostName;
|
|
201
|
+
didGUID = did[2];
|
|
202
|
+
} else {
|
|
203
|
+
hostName = did[2];
|
|
204
|
+
didGUID = did[3];
|
|
205
|
+
}
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
return `https://${hostName}/api/identity/${didGUID}`;
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
var utils = /*#__PURE__*/Object.freeze({
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
211
|
+
__proto__: null,
|
|
212
|
+
createSecureContextOptions: createSecureContextOptions,
|
|
213
|
+
getServiceAddress: getServiceAddress,
|
|
214
|
+
privateKeyToPem: privateKeyToPem,
|
|
215
|
+
publicKeyToPem: publicKeyToPem,
|
|
216
|
+
deserialisePEM: deserialisePEM,
|
|
217
|
+
removeCarriageReturn: removeCarriageReturn,
|
|
218
|
+
createBase58Hash: createBase58Hash,
|
|
219
|
+
createPublicKeyFingerprint: createPublicKeyFingerprint,
|
|
220
|
+
signBase64: signBase64,
|
|
221
|
+
verifyBase64: verifyBase64,
|
|
222
|
+
getDIDResolutionURL: getDIDResolutionURL
|
|
223
223
|
});
|
|
224
224
|
|
|
225
225
|
/* eslint-disable no-underscore-dangle */
|
|
@@ -227,77 +227,77 @@ var utils = /*#__PURE__*/Object.freeze({
|
|
|
227
227
|
const { forEach, isEmpty } = lodash__default["default"];
|
|
228
228
|
|
|
229
229
|
const serialiseNOP = (arg) => {
|
|
230
|
-
|
|
230
|
+
return arg;
|
|
231
231
|
};
|
|
232
232
|
|
|
233
233
|
function createClient(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
234
|
+
grpc,
|
|
235
|
+
serviceDescriptors,
|
|
236
|
+
address,
|
|
237
|
+
credentials,
|
|
238
|
+
noSerialise,
|
|
239
|
+
voltId,
|
|
240
|
+
serviceId,
|
|
241
241
|
) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
242
|
+
let grpcCredentials;
|
|
243
|
+
if (credentials) {
|
|
244
|
+
const ca = credentials.config?.relay?.ca_pem || credentials.cache.ca;
|
|
245
|
+
const cert = credentials.cache.cloud_cert || credentials.cache.cert;
|
|
246
|
+
const { key } = credentials.cache;
|
|
247
|
+
|
|
248
|
+
if (key && cert) {
|
|
249
|
+
const cryyptoOptions = { ca, cert, key };
|
|
250
|
+
const tlsOptions = createSecureContextOptions(cryyptoOptions);
|
|
251
|
+
grpcCredentials = grpc.credentials.createSsl(
|
|
252
|
+
tlsOptions.ca,
|
|
253
|
+
tlsOptions.key,
|
|
254
|
+
tlsOptions.cert,
|
|
255
|
+
);
|
|
256
|
+
} else {
|
|
257
|
+
// Cache has no private key, this implies we are connecting without supplying a client certificate.
|
|
258
|
+
// Simply connect using the CA certificate.
|
|
259
|
+
grpcCredentials = grpc.credentials.createSsl(Buffer.from(ca));
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
grpcCredentials = grpc.credentials.createInsecure();
|
|
263
|
+
}
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
265
|
+
// Force all methods to appear bi-directional, since we manually handle the call flow (see grpc-call.js).
|
|
266
|
+
forEach(serviceDescriptors, (method) => {
|
|
267
|
+
method.requestStream = true;
|
|
268
|
+
method.responseStream = true;
|
|
269
|
+
});
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
271
|
+
if (noSerialise || voltId) {
|
|
272
|
+
if (voltId && !serviceId) {
|
|
273
|
+
// We must have a service identifier id when tunnelling in order to be able to route the rpc.
|
|
274
|
+
throw new Error("createClient - no serviceId given");
|
|
275
|
+
}
|
|
276
|
+
forEach(serviceDescriptors, (method) => {
|
|
277
|
+
if (voltId) {
|
|
278
|
+
// Prepend the resource id to the method path if tunnelling.
|
|
279
|
+
if (voltId === serviceId) {
|
|
280
|
+
method.path = `/${voltId}${method.path}`;
|
|
281
|
+
} else {
|
|
282
|
+
method.path = `/${voltId}/${serviceId}${method.path}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (noSerialise) {
|
|
286
|
+
// Save original serialisation methods so we can encrypt/decrypt in the tunnel.
|
|
287
|
+
method.requestSerializeOriginal = method.requestSerialize;
|
|
288
|
+
method.responseDeserializeOriginal = method.responseDeserialize;
|
|
289
|
+
|
|
290
|
+
// Replace default serialisation with NOP, since when tunnelling we encrypt the payload
|
|
291
|
+
// before sending it to the channel, so we want it to be treated as a pure byte array (pass-through).
|
|
292
|
+
method.requestSerialize = serialiseNOP;
|
|
293
|
+
method.responseDeserialize = serialiseNOP;
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
const SubClass = grpc.makeGenericClientConstructor(serviceDescriptors);
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
return new SubClass(address, grpcCredentials);
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
/**
|
|
@@ -305,158 +305,158 @@ function createClient(
|
|
|
305
305
|
* @param {*} api
|
|
306
306
|
*/
|
|
307
307
|
const grpcWrap = (api) => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
308
|
+
const grpcRoutes = {};
|
|
309
|
+
|
|
310
|
+
// Don't bother trying to wrap class constructors.
|
|
311
|
+
const ignoreMethods = ["constructor"];
|
|
312
|
+
|
|
313
|
+
// Support class instances and PoJos
|
|
314
|
+
const isClass = api.constructor !== Object;
|
|
315
|
+
const iterate = Object.getOwnPropertyNames(
|
|
316
|
+
isClass ? api.constructor.prototype : api,
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Wrap each method on the api.
|
|
320
|
+
forEach(iterate, (name) => {
|
|
321
|
+
if (!ignoreMethods.includes(name)) {
|
|
322
|
+
const method = api[name];
|
|
323
|
+
grpcRoutes[name] = (call, callback) => {
|
|
324
|
+
// Execute the method in the context of the api.
|
|
325
|
+
const resultPromise = method.call(api, call.request, call);
|
|
326
|
+
|
|
327
|
+
// Enforce promise results.
|
|
328
|
+
if (!(resultPromise?.then && resultPromise?.catch)) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`implementation methods must return a Promise [${name}]`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
resultPromise
|
|
335
|
+
.then((response) => {
|
|
336
|
+
if (callback) {
|
|
337
|
+
callback(null, response);
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
.catch((err) => {
|
|
341
|
+
if (callback) {
|
|
342
|
+
callback(err);
|
|
343
|
+
} else {
|
|
344
|
+
// TODO - verify this => how to send error to stream clients?
|
|
345
|
+
// Destroy stream on error.
|
|
346
|
+
call.destroy(err);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
352
|
|
|
353
|
-
|
|
353
|
+
return grpcRoutes;
|
|
354
354
|
};
|
|
355
355
|
|
|
356
356
|
class GrpcServer {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (tlsOptions) {
|
|
371
|
-
const caList = [];
|
|
372
|
-
const certChains = [];
|
|
373
|
-
[].concat(tlsOptions).forEach((tlsOption) => {
|
|
374
|
-
caList.push(tlsOption.ca);
|
|
375
|
-
certChains.push({
|
|
376
|
-
private_key: tlsOption.key,
|
|
377
|
-
cert_chain: tlsOption.cert,
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// Create credentials from the volt CA and our own pub/priv key.
|
|
382
|
-
this._credentials = grpc.ServerCredentials.createSsl(
|
|
383
|
-
// The root CAs we trust when verifying client certificates.
|
|
384
|
-
Buffer.concat(caList),
|
|
385
|
-
// The certificate chain(s) we present to the client.
|
|
386
|
-
certChains,
|
|
387
|
-
// Flag indicating if we insist on a client certificate.
|
|
388
|
-
requireClientCert,
|
|
389
|
-
);
|
|
390
|
-
} else {
|
|
391
|
-
this._credentials = grpc.ServerCredentials.createInsecure();
|
|
392
|
-
}
|
|
357
|
+
constructor(options) {
|
|
358
|
+
const { grpc, serviceDescriptors, routes, tlsOptions, requireClientCert } =
|
|
359
|
+
options;
|
|
360
|
+
this._grpc = grpc;
|
|
361
|
+
this._server = new this._grpc.Server({
|
|
362
|
+
"grpc.keepalive_time_ms": 500,
|
|
363
|
+
"grpc.keepalive_timeout_ms": 5000,
|
|
364
|
+
"grpc.http2.max_pings_without_data": 0,
|
|
365
|
+
"grpc.keepalive_permit_without_calls": 1,
|
|
366
|
+
"grpc.http2.min_ping_interval_without_data_ms": 250,
|
|
367
|
+
"grpc.http2.max_ping_strikes": 0,
|
|
368
|
+
});
|
|
393
369
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
370
|
+
if (tlsOptions) {
|
|
371
|
+
const caList = [];
|
|
372
|
+
const certChains = [];
|
|
373
|
+
[].concat(tlsOptions).forEach((tlsOption) => {
|
|
374
|
+
caList.push(tlsOption.ca);
|
|
375
|
+
certChains.push({
|
|
376
|
+
private_key: tlsOption.key,
|
|
377
|
+
cert_chain: tlsOption.cert,
|
|
378
|
+
});
|
|
379
|
+
});
|
|
400
380
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
381
|
+
// Create credentials from the volt CA and our own pub/priv key.
|
|
382
|
+
this._credentials = grpc.ServerCredentials.createSsl(
|
|
383
|
+
// The root CAs we trust when verifying client certificates.
|
|
384
|
+
Buffer.concat(caList),
|
|
385
|
+
// The certificate chain(s) we present to the client.
|
|
386
|
+
certChains,
|
|
387
|
+
// Flag indicating if we insist on a client certificate.
|
|
388
|
+
requireClientCert,
|
|
389
|
+
);
|
|
390
|
+
} else {
|
|
391
|
+
this._credentials = grpc.ServerCredentials.createInsecure();
|
|
392
|
+
}
|
|
410
393
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
} else {
|
|
418
|
-
this._port = port;
|
|
419
|
-
this._server.start();
|
|
420
|
-
resolve(port);
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
}
|
|
394
|
+
if (serviceDescriptors && !isEmpty(routes)) {
|
|
395
|
+
// Enforce decoupling of the service implementation.
|
|
396
|
+
const router = grpcWrap(routes);
|
|
397
|
+
this._server.addService(serviceDescriptors, router);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
425
400
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
401
|
+
close(force = false) {
|
|
402
|
+
return new Promise((resolve) => {
|
|
403
|
+
if (force) {
|
|
404
|
+
resolve(this._server.forceShutdown());
|
|
405
|
+
} else {
|
|
406
|
+
this._server.tryShutdown(resolve);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
429
410
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
411
|
+
listen(address) {
|
|
412
|
+
return new Promise((resolve, reject) => {
|
|
413
|
+
// If no port is specified, grpc will return the port number assigned.
|
|
414
|
+
this._server.bindAsync(address, this._credentials, (err, port) => {
|
|
415
|
+
if (err) {
|
|
416
|
+
reject(err);
|
|
417
|
+
} else {
|
|
418
|
+
this._port = port;
|
|
419
|
+
this._server.start();
|
|
420
|
+
resolve(port);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
get server() {
|
|
427
|
+
return this._server;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
get port() {
|
|
431
|
+
return this._port;
|
|
432
|
+
}
|
|
433
433
|
}
|
|
434
434
|
|
|
435
435
|
function createServer(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
436
|
+
grpc,
|
|
437
|
+
serviceDescriptors,
|
|
438
|
+
routes,
|
|
439
|
+
cryptoCache,
|
|
440
|
+
requireClientCert,
|
|
441
441
|
) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
442
|
+
let tlsOptions;
|
|
443
|
+
if (cryptoCache) {
|
|
444
|
+
tlsOptions = createSecureContextOptions(cryptoCache);
|
|
445
|
+
}
|
|
446
|
+
const serverArgs = {
|
|
447
|
+
grpc,
|
|
448
|
+
serviceDescriptors,
|
|
449
|
+
routes,
|
|
450
|
+
tlsOptions,
|
|
451
|
+
requireClientCert,
|
|
452
|
+
};
|
|
453
|
+
return new GrpcServer(serverArgs);
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
var grpcUtils = /*#__PURE__*/Object.freeze({
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
457
|
+
__proto__: null,
|
|
458
|
+
createClient: createClient,
|
|
459
|
+
createServer: createServer
|
|
460
460
|
});
|
|
461
461
|
|
|
462
462
|
/* eslint-disable no-underscore-dangle */
|
|
@@ -467,227 +467,227 @@ const aesAlgorithm = "aes-256-cbc";
|
|
|
467
467
|
const rs256Algorithm = "RS256";
|
|
468
468
|
|
|
469
469
|
class VoltCredential {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
470
|
+
constructor(config, configPath) {
|
|
471
|
+
this._config = config;
|
|
472
|
+
this._voltConfig = config.volt;
|
|
473
|
+
this._configPath = configPath;
|
|
474
|
+
this._persistCache = !!configPath;
|
|
475
|
+
|
|
476
|
+
if (this._config.credential) {
|
|
477
|
+
this._cryptoCache = this._config.credential;
|
|
478
|
+
} else {
|
|
479
|
+
this._cryptoCache = {};
|
|
480
|
+
}
|
|
481
481
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
// Need to unencrypt the private key.
|
|
491
|
-
const decrypted = pki$1.decryptRsaPrivateKey(
|
|
492
|
-
this._cryptoCache.key,
|
|
493
|
-
config.p,
|
|
494
|
-
);
|
|
495
|
-
if (!decrypted) {
|
|
496
|
-
throw new Error("failed to decrypt key - check passphrase");
|
|
497
|
-
}
|
|
498
|
-
this._cryptoCache.key = pki$1.privateKeyToPem(decrypted);
|
|
499
|
-
}
|
|
482
|
+
// Determine if we're dealing with an encrypted key.
|
|
483
|
+
if (
|
|
484
|
+
this._cryptoCache.key?.startsWith(constants.privateEncryptedKeyPrefix)
|
|
485
|
+
) {
|
|
486
|
+
if (!config.p) {
|
|
487
|
+
throw new Error("encrypted key but no passphrase given");
|
|
488
|
+
}
|
|
500
489
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
490
|
+
// Need to unencrypt the private key.
|
|
491
|
+
const decrypted = pki$1.decryptRsaPrivateKey(
|
|
492
|
+
this._cryptoCache.key,
|
|
493
|
+
config.p,
|
|
494
|
+
);
|
|
495
|
+
if (!decrypted) {
|
|
496
|
+
throw new Error("failed to decrypt key - check passphrase");
|
|
497
|
+
}
|
|
498
|
+
this._cryptoCache.key = pki$1.privateKeyToPem(decrypted);
|
|
499
|
+
}
|
|
506
500
|
|
|
507
|
-
|
|
508
|
-
|
|
501
|
+
if (this._voltConfig.ca_pem) {
|
|
502
|
+
if (!this._cryptoCache.ca) {
|
|
503
|
+
// Copy the volt signing CA to the crypto cache.
|
|
504
|
+
this._cryptoCache.ca = this._voltConfig.ca_pem;
|
|
505
|
+
}
|
|
509
506
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
this._voltFingerprint = createPublicKeyFingerprint(
|
|
513
|
-
this._voltPublicKey,
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
507
|
+
// Pre-cache the target Volt public key by extracting it from the signing certificate.
|
|
508
|
+
const voltCert = forge__default["default"].pki.certificateFromPem(this._voltConfig.ca_pem);
|
|
517
509
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
510
|
+
// Extract Volt public key (used for encrypting Relay payloads).
|
|
511
|
+
this._voltPublicKey = forge__default["default"].pki.publicKeyToPem(voltCert.publicKey);
|
|
512
|
+
this._voltFingerprint = createPublicKeyFingerprint(
|
|
513
|
+
this._voltPublicKey,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
521
517
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
518
|
+
get config() {
|
|
519
|
+
return this._config;
|
|
520
|
+
}
|
|
525
521
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
522
|
+
get cache() {
|
|
523
|
+
return this._cryptoCache;
|
|
524
|
+
}
|
|
529
525
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
return { privateKey, publicKey };
|
|
534
|
-
}
|
|
526
|
+
get voltFingerprint() {
|
|
527
|
+
return this._voltFingerprint;
|
|
528
|
+
}
|
|
535
529
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
// Already have key data in the cache in pem format => create fully-formed pki instances.
|
|
542
|
-
log$4("createKey - key already exists");
|
|
543
|
-
return Promise.resolve(this.getKey());
|
|
544
|
-
} else {
|
|
545
|
-
log$4("creating key");
|
|
546
|
-
return new Promise((resolve, reject) => {
|
|
547
|
-
pki$1.rsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
|
|
548
|
-
if (err) {
|
|
549
|
-
log$4("failure creating key [%s]", err.message);
|
|
550
|
-
reject(err);
|
|
551
|
-
} else {
|
|
552
|
-
log$4("successfully created key");
|
|
553
|
-
const pem = pki$1.privateKeyToPem(keypair.privateKey);
|
|
554
|
-
this._cryptoCache.key = pem;
|
|
555
|
-
resolve(keypair);
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
}
|
|
530
|
+
getKey() {
|
|
531
|
+
const privateKey = pki$1.privateKeyFromPem(this._cryptoCache.key);
|
|
532
|
+
const publicKey = pki$1.rsa.setPublicKey(privateKey.n, privateKey.e);
|
|
533
|
+
return { privateKey, publicKey };
|
|
534
|
+
}
|
|
561
535
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
536
|
+
/**
|
|
537
|
+
* Retrieves and re-hydrates the pub/priv key data from the crypto cache, or creates one if not found.
|
|
538
|
+
*/
|
|
539
|
+
createKey() {
|
|
540
|
+
if (this._cryptoCache.key) {
|
|
541
|
+
// Already have key data in the cache in pem format => create fully-formed pki instances.
|
|
542
|
+
log$4("createKey - key already exists");
|
|
543
|
+
return Promise.resolve(this.getKey());
|
|
544
|
+
} else {
|
|
545
|
+
log$4("creating key");
|
|
546
|
+
return new Promise((resolve, reject) => {
|
|
547
|
+
pki$1.rsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
|
|
548
|
+
if (err) {
|
|
549
|
+
log$4("failure creating key [%s]", err.message);
|
|
550
|
+
reject(err);
|
|
551
|
+
} else {
|
|
552
|
+
log$4("successfully created key");
|
|
553
|
+
const pem = pki$1.privateKeyToPem(keypair.privateKey);
|
|
554
|
+
this._cryptoCache.key = pem;
|
|
555
|
+
resolve(keypair);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
578
561
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Create cryptocache from scratch.
|
|
564
|
+
*/
|
|
565
|
+
initialise() {
|
|
566
|
+
return this.createKey()
|
|
567
|
+
.then((/* keypair */) => {
|
|
568
|
+
return this.saveCache();
|
|
569
|
+
})
|
|
570
|
+
.then(() => {
|
|
571
|
+
return this._cryptoCache;
|
|
572
|
+
})
|
|
573
|
+
.catch((err) => {
|
|
574
|
+
log$4("failure initialising crypto [%s]", err.message);
|
|
575
|
+
return Promise.reject(err);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
582
578
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
* @param {*} tunnelling flag indicating if the token is required for a tunnelled connection
|
|
587
|
-
* @param {*} ttl time to live in seconds (default to 1 minute)
|
|
588
|
-
*/
|
|
589
|
-
getIdentityToken(audience, tunnelling = false, ttl = 60) {
|
|
590
|
-
if (!this._cryptoCache.key) {
|
|
591
|
-
throw new Error("crypto cache invalid");
|
|
592
|
-
}
|
|
579
|
+
get isBound() {
|
|
580
|
+
return !!this._cryptoCache.cert;
|
|
581
|
+
}
|
|
593
582
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
iat: Math.floor(Date.now() / 1000) - ttl,
|
|
605
|
-
exp: Math.floor(Date.now() / 1000) + ttl,
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
let sharedKey;
|
|
609
|
-
if (tunnelling) {
|
|
610
|
-
// Initialise the Relay encryption key.
|
|
611
|
-
sharedKey = VoltCredential.aesCreateKey();
|
|
612
|
-
|
|
613
|
-
// Encrypt the key details using the target Volt public key and include this in the JWT payload.
|
|
614
|
-
payload.sk = VoltCredential.rsaEncrypt(
|
|
615
|
-
this._voltPublicKey,
|
|
616
|
-
sharedKey.key,
|
|
617
|
-
);
|
|
618
|
-
payload.iv = VoltCredential.rsaEncrypt(this._voltPublicKey, sharedKey.iv);
|
|
619
|
-
}
|
|
583
|
+
/**
|
|
584
|
+
* Signs the identity resource id using the private key.
|
|
585
|
+
* @param {*} audience the token audience (usually the target volt)
|
|
586
|
+
* @param {*} tunnelling flag indicating if the token is required for a tunnelled connection
|
|
587
|
+
* @param {*} ttl time to live in seconds (default to 1 minute)
|
|
588
|
+
*/
|
|
589
|
+
getIdentityToken(audience, tunnelling = false, ttl = 60) {
|
|
590
|
+
if (!this._cryptoCache.key) {
|
|
591
|
+
throw new Error("crypto cache invalid");
|
|
592
|
+
}
|
|
620
593
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
keyid: base58Key,
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
// Return the token along with any encryption key details.
|
|
628
|
-
return {
|
|
629
|
-
token,
|
|
630
|
-
sharedKey,
|
|
631
|
-
};
|
|
632
|
-
}
|
|
594
|
+
const keyPair = this.getKey();
|
|
595
|
+
const publicKeyPem = pki$1.publicKeyToPem(keyPair.publicKey);
|
|
596
|
+
const base58Key = createPublicKeyFingerprint(publicKeyPem);
|
|
633
597
|
|
|
634
|
-
|
|
635
|
-
* Creates the metadata required for a grpc call, including the Volt authentication token
|
|
636
|
-
* and optionally tunnel encryption parameters.
|
|
637
|
-
* @param {*} grpc
|
|
638
|
-
* @param {*} audience
|
|
639
|
-
* @param {*} tunnelling
|
|
640
|
-
* @param {*} ttl
|
|
641
|
-
* @returns
|
|
642
|
-
*/
|
|
643
|
-
getIdentityMetadata(grpc, audience, tunnelling = false, ttl = 60) {
|
|
644
|
-
const identityToken = this.getIdentityToken(
|
|
645
|
-
audience || this._voltConfig.id,
|
|
646
|
-
tunnelling,
|
|
647
|
-
ttl,
|
|
648
|
-
);
|
|
649
|
-
const metadata = new grpc.Metadata();
|
|
650
|
-
metadata.add(constants.authTokenName, identityToken.token);
|
|
651
|
-
return { ...identityToken, metadata };
|
|
652
|
-
}
|
|
598
|
+
log$4("base58 key is %s", base58Key);
|
|
653
599
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
privateKey,
|
|
662
|
-
this._config.p,
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
fs__default["default"].writeFileSync(this._configPath, JSON.stringify(clone, null, 2));
|
|
666
|
-
}
|
|
667
|
-
}
|
|
600
|
+
// Allow TTL either side of the current time.
|
|
601
|
+
// @todo - fix with server time sync on volt connection.
|
|
602
|
+
const payload = {
|
|
603
|
+
aud: audience,
|
|
604
|
+
iat: Math.floor(Date.now() / 1000) - ttl,
|
|
605
|
+
exp: Math.floor(Date.now() / 1000) + ttl,
|
|
606
|
+
};
|
|
668
607
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
return { key, iv };
|
|
674
|
-
}
|
|
608
|
+
let sharedKey;
|
|
609
|
+
if (tunnelling) {
|
|
610
|
+
// Initialise the Relay encryption key.
|
|
611
|
+
sharedKey = VoltCredential.aesCreateKey();
|
|
675
612
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
613
|
+
// Encrypt the key details using the target Volt public key and include this in the JWT payload.
|
|
614
|
+
payload.sk = VoltCredential.rsaEncrypt(
|
|
615
|
+
this._voltPublicKey,
|
|
616
|
+
sharedKey.key,
|
|
617
|
+
);
|
|
618
|
+
payload.iv = VoltCredential.rsaEncrypt(this._voltPublicKey, sharedKey.iv);
|
|
619
|
+
}
|
|
681
620
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
621
|
+
// Sign synchronously.
|
|
622
|
+
const token = jwt__default["default"].sign(payload, this._cryptoCache.key, {
|
|
623
|
+
algorithm: rs256Algorithm,
|
|
624
|
+
keyid: base58Key,
|
|
625
|
+
});
|
|
687
626
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
627
|
+
// Return the token along with any encryption key details.
|
|
628
|
+
return {
|
|
629
|
+
token,
|
|
630
|
+
sharedKey,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Creates the metadata required for a grpc call, including the Volt authentication token
|
|
636
|
+
* and optionally tunnel encryption parameters.
|
|
637
|
+
* @param {*} grpc
|
|
638
|
+
* @param {*} audience
|
|
639
|
+
* @param {*} tunnelling
|
|
640
|
+
* @param {*} ttl
|
|
641
|
+
* @returns
|
|
642
|
+
*/
|
|
643
|
+
getIdentityMetadata(grpc, audience, tunnelling = false, ttl = 60) {
|
|
644
|
+
const identityToken = this.getIdentityToken(
|
|
645
|
+
audience || this._voltConfig.id,
|
|
646
|
+
tunnelling,
|
|
647
|
+
ttl,
|
|
648
|
+
);
|
|
649
|
+
const metadata = new grpc.Metadata();
|
|
650
|
+
metadata.add(constants.authTokenName, identityToken.token);
|
|
651
|
+
return { ...identityToken, metadata };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
saveCache() {
|
|
655
|
+
if (this._persistCache) {
|
|
656
|
+
const clone = { ...this._config, credential: { ...this._cryptoCache } };
|
|
657
|
+
if (this._config.p) {
|
|
658
|
+
// A passphrase option exists => encrypt the key before writing the cache file.
|
|
659
|
+
const privateKey = pki$1.privateKeyFromPem(this._cryptoCache.key);
|
|
660
|
+
clone.credential.key = pki$1.encryptRsaPrivateKey(
|
|
661
|
+
privateKey,
|
|
662
|
+
this._config.p,
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
fs__default["default"].writeFileSync(this._configPath, JSON.stringify(clone, null, 2));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
static aesCreateKey() {
|
|
670
|
+
// Generate random key for the AES-256 algorithm (32 bytes = 256 bits).
|
|
671
|
+
const key = crypto__default["default"].randomBytes(32);
|
|
672
|
+
const iv = crypto__default["default"].randomBytes(16);
|
|
673
|
+
return { key, iv };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static aesEncrypt(buffer, key, iv) {
|
|
677
|
+
const cipher = crypto__default["default"].createCipheriv(aesAlgorithm, key, iv);
|
|
678
|
+
const crypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
|
|
679
|
+
return crypted;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
static aesDecrypt(buffer, key, iv) {
|
|
683
|
+
const cipher = crypto__default["default"].createDecipheriv(aesAlgorithm, key, iv);
|
|
684
|
+
const decrypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
|
|
685
|
+
return decrypted;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
static rsaEncrypt(key, buffer) {
|
|
689
|
+
return crypto__default["default"].publicEncrypt(key, buffer).toString("base64");
|
|
690
|
+
}
|
|
691
691
|
}
|
|
692
692
|
|
|
693
693
|
const { get: getProp, snakeCase } = lodash__default["default"];
|
|
@@ -696,104 +696,108 @@ const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefin
|
|
|
696
696
|
const defaultProtoPath = path.join(__dirname$1, "../protobuf");
|
|
697
697
|
|
|
698
698
|
const defaultLoaderOptions = {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
699
|
+
keepCase: true,
|
|
700
|
+
longs: String,
|
|
701
|
+
enums: String,
|
|
702
|
+
defaults: true,
|
|
703
|
+
oneofs: true,
|
|
704
|
+
includeDirs: [defaultProtoPath],
|
|
705
705
|
};
|
|
706
706
|
|
|
707
707
|
const getProtoDescriptors = (grpc, protoPath, opts) => {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
708
|
+
const definition = protoLoader__default["default"].loadSync(
|
|
709
|
+
protoPath,
|
|
710
|
+
opts || defaultLoaderOptions,
|
|
711
|
+
);
|
|
712
|
+
const descriptors = grpc.loadPackageDefinition(definition);
|
|
713
|
+
return descriptors;
|
|
714
714
|
};
|
|
715
715
|
|
|
716
716
|
const getProtoService = (descriptors, servicePath) => {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
717
|
+
const serviceDef = getProp(descriptors, servicePath);
|
|
718
|
+
if (serviceDef?.service) {
|
|
719
|
+
return serviceDef.service;
|
|
720
|
+
} else {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
723
|
};
|
|
724
724
|
|
|
725
725
|
function getServiceDescriptorsFromPath(
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
726
|
+
grpc,
|
|
727
|
+
protoPath,
|
|
728
|
+
servicePackageName,
|
|
729
|
+
opts,
|
|
730
730
|
) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
731
|
+
let serviceDescriptors;
|
|
732
|
+
const descriptors = getProtoDescriptors(grpc, protoPath, opts);
|
|
733
|
+
if (descriptors) {
|
|
734
|
+
serviceDescriptors = getProtoService(descriptors, servicePackageName);
|
|
735
|
+
}
|
|
736
736
|
|
|
737
|
-
|
|
737
|
+
return serviceDescriptors;
|
|
738
738
|
}
|
|
739
739
|
|
|
740
740
|
function getServiceDescriptors(grpc, servicePackageName, opts) {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
741
|
+
// Extract the package components of the service proto package. The package name should be of the
|
|
742
|
+
// fully qualified proto path, e.g. tdx.volt_api.webcam.v1.WebcamControlAPI.
|
|
743
|
+
const protoServiceComponents = servicePackageName.split(".");
|
|
744
|
+
|
|
745
|
+
// The service name is the last component of the path.
|
|
746
|
+
const protoServiceName = protoServiceComponents.pop();
|
|
747
|
+
|
|
748
|
+
// The actual file name should match the snake case of the service name.
|
|
749
|
+
const protoFileName = `${snakeCase(protoServiceName).toLowerCase()}.proto`;
|
|
750
|
+
|
|
751
|
+
// The path to the proto file should match each component of the package name.
|
|
752
|
+
const protoPath = path.join(
|
|
753
|
+
defaultProtoPath,
|
|
754
|
+
...protoServiceComponents,
|
|
755
|
+
protoFileName,
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
let serviceDescriptors;
|
|
759
|
+
const descriptors = getProtoDescriptors(grpc, protoPath, opts);
|
|
760
|
+
if (descriptors) {
|
|
761
|
+
serviceDescriptors = getProtoService(descriptors, servicePackageName);
|
|
762
|
+
}
|
|
763
763
|
|
|
764
|
-
|
|
764
|
+
return serviceDescriptors;
|
|
765
765
|
}
|
|
766
766
|
|
|
767
767
|
function getProtoDescriptorMethods(protoDescriptor) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
768
|
+
const methods = [];
|
|
769
|
+
Object.keys(protoDescriptor).forEach((methodName) => {
|
|
770
|
+
const method = protoDescriptor[methodName];
|
|
771
|
+
methods.push({
|
|
772
|
+
path: method.path,
|
|
773
|
+
client_streaming: method.requestStream,
|
|
774
|
+
server_streaming: method.responseStream,
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
return methods;
|
|
778
778
|
}
|
|
779
779
|
|
|
780
780
|
var protoUtils = /*#__PURE__*/Object.freeze({
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
781
|
+
__proto__: null,
|
|
782
|
+
getServiceDescriptors: getServiceDescriptors,
|
|
783
|
+
getProtoDescriptors: getProtoDescriptors,
|
|
784
|
+
getProtoDescriptorMethods: getProtoDescriptorMethods,
|
|
785
|
+
getServiceDescriptorsFromPath: getServiceDescriptorsFromPath
|
|
786
786
|
});
|
|
787
787
|
|
|
788
788
|
/* eslint-disable no-underscore-dangle */
|
|
789
789
|
|
|
790
790
|
const log$3 = debug__default["default"]("volt-client-grpc:grpc-call");
|
|
791
791
|
|
|
792
|
-
function _prepareInvokeRequest(
|
|
792
|
+
function _prepareInvokeRequest(
|
|
793
|
+
request,
|
|
794
|
+
method,
|
|
795
|
+
methodType = "METHOD_TYPE_UNARY",
|
|
796
|
+
) {
|
|
793
797
|
const meta = this._voltClient.credential.getIdentityMetadata(
|
|
794
798
|
this._voltClient.grpc,
|
|
795
799
|
this._voltClient.voltConfig.id,
|
|
796
|
-
this._voltClient.isRemote
|
|
800
|
+
this._voltClient.isRemote,
|
|
797
801
|
);
|
|
798
802
|
let invokeRequest;
|
|
799
803
|
let callMethod = method;
|
|
@@ -822,7 +826,7 @@ function _prepareInvokeRequest(request, method, methodType = "METHOD_TYPE_UNARY"
|
|
|
822
826
|
const invokePayload = VoltCredential.aesEncrypt(
|
|
823
827
|
tunnelMethod.responseSerialize(tunnelResp),
|
|
824
828
|
meta.sharedKey.key,
|
|
825
|
-
meta.sharedKey.iv
|
|
829
|
+
meta.sharedKey.iv,
|
|
826
830
|
);
|
|
827
831
|
|
|
828
832
|
log$3("target volt is %s", this._voltClient.credential.voltFingerprint);
|
|
@@ -834,8 +838,13 @@ function _prepareInvokeRequest(request, method, methodType = "METHOD_TYPE_UNARY"
|
|
|
834
838
|
|
|
835
839
|
callMethod = "Invoke";
|
|
836
840
|
} else if (this._voltClient.isRemote && meta.sharedKey.key) {
|
|
837
|
-
const requestPayload =
|
|
838
|
-
|
|
841
|
+
const requestPayload =
|
|
842
|
+
this._grpcClient[method].requestSerializeOriginal(request);
|
|
843
|
+
invokeRequest = VoltCredential.aesEncrypt(
|
|
844
|
+
requestPayload,
|
|
845
|
+
meta.sharedKey.key,
|
|
846
|
+
meta.sharedKey.iv,
|
|
847
|
+
);
|
|
839
848
|
} else {
|
|
840
849
|
invokeRequest = request;
|
|
841
850
|
}
|
|
@@ -852,7 +861,8 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
852
861
|
let payloadRequest;
|
|
853
862
|
|
|
854
863
|
if (this._voltClient.isVoltRelay) {
|
|
855
|
-
const requestPayload =
|
|
864
|
+
const requestPayload =
|
|
865
|
+
this._grpcClient[invokeInfo.methodName].requestSerialize(request);
|
|
856
866
|
const tunnelMethod = this._grpcClient.Tunnel;
|
|
857
867
|
|
|
858
868
|
const tunnelResp = {
|
|
@@ -864,18 +874,19 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
864
874
|
const invokePayload = VoltCredential.aesEncrypt(
|
|
865
875
|
tunnelMethod.responseSerialize(tunnelResp),
|
|
866
876
|
invokeInfo.meta.sharedKey.key,
|
|
867
|
-
invokeInfo.meta.sharedKey.iv
|
|
877
|
+
invokeInfo.meta.sharedKey.iv,
|
|
868
878
|
);
|
|
869
879
|
|
|
870
880
|
payloadRequest = {
|
|
871
881
|
payload: invokePayload,
|
|
872
882
|
};
|
|
873
883
|
} else if (this._voltClient.isRemote && invokeInfo.meta.sharedKey.key) {
|
|
874
|
-
const requestPayload =
|
|
884
|
+
const requestPayload =
|
|
885
|
+
this._grpcClient[invokeInfo.methodName].requestSerializeOriginal(request);
|
|
875
886
|
payloadRequest = VoltCredential.aesEncrypt(
|
|
876
887
|
requestPayload,
|
|
877
888
|
invokeInfo.meta.sharedKey.key,
|
|
878
|
-
invokeInfo.meta.sharedKey.iv
|
|
889
|
+
invokeInfo.meta.sharedKey.iv,
|
|
879
890
|
);
|
|
880
891
|
} else {
|
|
881
892
|
payloadRequest = request;
|
|
@@ -889,11 +900,17 @@ function _parseResponse(method, meta, response) {
|
|
|
889
900
|
|
|
890
901
|
if (this._voltClient.isVoltRelay) {
|
|
891
902
|
if (response.payload) {
|
|
892
|
-
const decryptedPayload = VoltCredential.aesDecrypt(
|
|
903
|
+
const decryptedPayload = VoltCredential.aesDecrypt(
|
|
904
|
+
response.payload,
|
|
905
|
+
meta.sharedKey.key,
|
|
906
|
+
meta.sharedKey.iv,
|
|
907
|
+
);
|
|
893
908
|
const tunnelMethod = this._grpcClient.Tunnel;
|
|
894
909
|
const responsePayload = tunnelMethod.requestDeserialize(decryptedPayload);
|
|
895
910
|
if (responsePayload.payload === "method_payload") {
|
|
896
|
-
invokeResponse.payload = this._grpcClient[method].responseDeserialize(
|
|
911
|
+
invokeResponse.payload = this._grpcClient[method].responseDeserialize(
|
|
912
|
+
responsePayload.method_payload.payload,
|
|
913
|
+
);
|
|
897
914
|
} else if (responsePayload.payload === "method_end") {
|
|
898
915
|
invokeResponse = responsePayload.method_end;
|
|
899
916
|
} else {
|
|
@@ -905,18 +922,28 @@ function _parseResponse(method, meta, response) {
|
|
|
905
922
|
"Tunnel status received: %s, code %d, description: %s",
|
|
906
923
|
response.status.message,
|
|
907
924
|
response.status.code,
|
|
908
|
-
response.status.description || "n/a"
|
|
925
|
+
response.status.description || "n/a",
|
|
909
926
|
);
|
|
910
|
-
invokeResponse = {
|
|
927
|
+
invokeResponse = {
|
|
928
|
+
ended: true,
|
|
929
|
+
error: `Error in tunnel: ${response.status.message}`,
|
|
930
|
+
};
|
|
911
931
|
} else {
|
|
912
932
|
invokeResponse.methodId = response.invoke_id;
|
|
913
933
|
log$3("started method %d for %s", invokeResponse.methodId, method);
|
|
914
934
|
}
|
|
915
935
|
} else if (this._voltClient.isRemote && meta.sharedKey.key) {
|
|
916
|
-
const decryptedPayload = VoltCredential.aesDecrypt(
|
|
917
|
-
|
|
936
|
+
const decryptedPayload = VoltCredential.aesDecrypt(
|
|
937
|
+
response,
|
|
938
|
+
meta.sharedKey.key,
|
|
939
|
+
meta.sharedKey.iv,
|
|
940
|
+
);
|
|
941
|
+
invokeResponse = {
|
|
942
|
+
payload:
|
|
943
|
+
this._grpcClient[method].responseDeserializeOriginal(decryptedPayload),
|
|
944
|
+
};
|
|
918
945
|
} else {
|
|
919
|
-
invokeResponse = {payload: response};
|
|
946
|
+
invokeResponse = { payload: response };
|
|
920
947
|
}
|
|
921
948
|
|
|
922
949
|
return invokeResponse;
|
|
@@ -929,92 +956,79 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
929
956
|
this._methodType = methodType;
|
|
930
957
|
this._call = null;
|
|
931
958
|
this._voltClient = voltClient;
|
|
932
|
-
this._lastResponse = null;
|
|
933
|
-
this._promise = null;
|
|
934
|
-
this._reject = null;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
reject(err) {
|
|
938
|
-
if (this._reject) {
|
|
939
|
-
this._reject(err);
|
|
940
|
-
}
|
|
941
959
|
}
|
|
942
960
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
start(grpcClient, request, responseCB) {
|
|
948
|
-
this._promise = new Promise((resolve, reject) => {
|
|
949
|
-
this._grpcClient = grpcClient;
|
|
950
|
-
this._reject = reject;
|
|
961
|
+
start(grpcClient, request) {
|
|
962
|
+
this._grpcClient = grpcClient;
|
|
951
963
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (responseCB) {
|
|
959
|
-
responseCB(err, response);
|
|
960
|
-
}
|
|
961
|
-
} else {
|
|
962
|
-
this.end();
|
|
963
|
-
resolve(this._lastResponse);
|
|
964
|
-
}
|
|
965
|
-
};
|
|
966
|
-
|
|
967
|
-
this._initialRequest = _prepareInvokeRequest.call(this, request, this._methodName, this._methodType);
|
|
964
|
+
this._initialRequest = _prepareInvokeRequest.call(
|
|
965
|
+
this,
|
|
966
|
+
request,
|
|
967
|
+
this._methodName,
|
|
968
|
+
this._methodType,
|
|
969
|
+
);
|
|
968
970
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
971
|
+
if (!this._grpcClient[this._initialRequest.method]) {
|
|
972
|
+
return reject(
|
|
973
|
+
new Error(`method not found: '${this._initialRequest.method}'`),
|
|
974
|
+
);
|
|
975
|
+
}
|
|
972
976
|
|
|
973
|
-
|
|
977
|
+
this._call = this._grpcClient[this._initialRequest.method](
|
|
978
|
+
this._initialRequest.meta.metadata,
|
|
979
|
+
);
|
|
974
980
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
981
|
+
this._call.on("data", (streamResponse) => {
|
|
982
|
+
try {
|
|
983
|
+
const parsedResponse = _parseResponse.call(
|
|
984
|
+
this,
|
|
985
|
+
this._methodName,
|
|
986
|
+
this._initialRequest.meta,
|
|
987
|
+
streamResponse,
|
|
988
|
+
);
|
|
989
|
+
if (parsedResponse.payload) {
|
|
990
|
+
this.emit("data", parsedResponse.payload);
|
|
991
|
+
} else if (parsedResponse.ended) {
|
|
992
|
+
if (parsedResponse.error) {
|
|
993
|
+
this.emit("error", new Error(parsedResponse.error));
|
|
986
994
|
} else {
|
|
987
|
-
|
|
995
|
+
// Not sure this is necessary - we should receive "end" from grpc soon...
|
|
996
|
+
this.emit("end");
|
|
988
997
|
}
|
|
989
|
-
}
|
|
990
|
-
log$3("
|
|
991
|
-
this.emit("error", err);
|
|
998
|
+
} else {
|
|
999
|
+
log$3("method id is %s", parsedResponse.methodId);
|
|
992
1000
|
}
|
|
993
|
-
})
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
});
|
|
1001
|
+
} catch (err) {
|
|
1002
|
+
log$3("failure processing connect response: %s", err.message);
|
|
1003
|
+
this.emit("error", err);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
999
1006
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
});
|
|
1007
|
+
this._call.on("error", (err) => {
|
|
1008
|
+
log$3("ERROR - intercepted stream error %s", err.message);
|
|
1009
|
+
this.emit("error", err);
|
|
1010
|
+
});
|
|
1005
1011
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
});
|
|
1012
|
+
this._call.on("finish", () => {
|
|
1013
|
+
// The read side has ended (i.e. we called end()).
|
|
1014
|
+
log$3("finished call %s", this._methodName);
|
|
1015
|
+
this.emit("finish");
|
|
1016
|
+
});
|
|
1012
1017
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1018
|
+
this._call.on("end", () => {
|
|
1019
|
+
// The remote peer ended the stream.
|
|
1020
|
+
log$3(
|
|
1021
|
+
"ended call %s, method id: %s",
|
|
1022
|
+
this._methodName,
|
|
1023
|
+
this._initialRequest.methodId || "n/a - not tunnelling",
|
|
1024
|
+
);
|
|
1025
|
+
this.emit("end");
|
|
1016
1026
|
});
|
|
1017
1027
|
|
|
1028
|
+
if (this._initialRequest.request) {
|
|
1029
|
+
this._call.write(this._initialRequest.request);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1018
1032
|
return this;
|
|
1019
1033
|
}
|
|
1020
1034
|
|
|
@@ -1035,10 +1049,19 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1035
1049
|
log$3("ERROR - call not initialised");
|
|
1036
1050
|
throw new Error("call not initialised");
|
|
1037
1051
|
} else if (this._initialRequest) {
|
|
1038
|
-
const payloadRequest = _preparePayloadRequest.call(
|
|
1052
|
+
const payloadRequest = _preparePayloadRequest.call(
|
|
1053
|
+
this,
|
|
1054
|
+
request,
|
|
1055
|
+
this._initialRequest,
|
|
1056
|
+
);
|
|
1039
1057
|
return this._call.write(payloadRequest);
|
|
1040
1058
|
} else {
|
|
1041
|
-
this._initialRequest = _prepareInvokeRequest.call(
|
|
1059
|
+
this._initialRequest = _prepareInvokeRequest.call(
|
|
1060
|
+
this,
|
|
1061
|
+
request,
|
|
1062
|
+
this._methodName,
|
|
1063
|
+
this._methodType,
|
|
1064
|
+
);
|
|
1042
1065
|
|
|
1043
1066
|
return this._call.write(this._initialRequest.request);
|
|
1044
1067
|
}
|
|
@@ -1093,58 +1116,53 @@ function _doConnect() {
|
|
|
1093
1116
|
const helloPayload = {
|
|
1094
1117
|
hello: {},
|
|
1095
1118
|
};
|
|
1119
|
+
|
|
1120
|
+
this.call.on("error", (err) => {
|
|
1121
|
+
log$2("error on connection stream [%s]", err.message);
|
|
1122
|
+
_cleanUp.call(this);
|
|
1123
|
+
this.emit("error", err);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
this.call.on("data", (response) => {
|
|
1127
|
+
switch (response.payload) {
|
|
1128
|
+
case "ping": {
|
|
1129
|
+
log$2("pinged");
|
|
1130
|
+
|
|
1131
|
+
// Received ping response from server => clear timeout.
|
|
1132
|
+
if (this._pingTimeoutTimer) {
|
|
1133
|
+
clearTimeout(this._pingTimeoutTimer);
|
|
1134
|
+
}
|
|
1096
1135
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
}
|
|
1128
|
-
default:
|
|
1129
|
-
log$2("unimplemented connect response payload: %s", response.payload);
|
|
1130
|
-
break;
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
});
|
|
1134
|
-
|
|
1135
|
-
this.call
|
|
1136
|
-
.wait()
|
|
1137
|
-
.then(() => {
|
|
1138
|
-
if (this._session) {
|
|
1139
|
-
this.emit("connected", false);
|
|
1140
|
-
}
|
|
1141
|
-
})
|
|
1142
|
-
.catch((err) => {
|
|
1143
|
-
log$2("unexpected error on connect call [%s]", err.message);
|
|
1144
|
-
})
|
|
1145
|
-
.finally(() => {
|
|
1146
|
-
_cleanUp.call(this);
|
|
1147
|
-
});
|
|
1136
|
+
// Schedule the next ping.
|
|
1137
|
+
_startPingTimer.call(this);
|
|
1138
|
+
break;
|
|
1139
|
+
}
|
|
1140
|
+
case "hello": {
|
|
1141
|
+
if (!this._session) {
|
|
1142
|
+
this._session = true;
|
|
1143
|
+
this.emit("connected", this._session);
|
|
1144
|
+
}
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
case "evt": {
|
|
1148
|
+
log$2("received event %s", response.evt.event_type);
|
|
1149
|
+
this.emit("evt", response.evt);
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
default:
|
|
1153
|
+
log$2("unimplemented connect response payload: %s", response.payload);
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
this.call.on("end", () => {
|
|
1159
|
+
if (this._session) {
|
|
1160
|
+
this.emit("connected", false);
|
|
1161
|
+
}
|
|
1162
|
+
_cleanUp.call(this);
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
this.call.start(grpcClient, helloPayload);
|
|
1148
1166
|
|
|
1149
1167
|
_startPingTimer.call(this);
|
|
1150
1168
|
}
|
|
@@ -1215,847 +1233,890 @@ const { filter, pick } = lodash__default["default"];
|
|
|
1215
1233
|
|
|
1216
1234
|
const log$1 = debug__default["default"]("volt-client-grpc:volt-client-internal");
|
|
1217
1235
|
const voltServices = [
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1236
|
+
constants.serviceType.voltAPI,
|
|
1237
|
+
constants.serviceType.fileAPI,
|
|
1238
|
+
constants.serviceType.sqliteDatabaseAPI,
|
|
1239
|
+
constants.serviceType.sqliteServerAPI,
|
|
1240
|
+
constants.serviceType.ssiAPI,
|
|
1241
|
+
constants.serviceType.relayAPI,
|
|
1242
|
+
constants.serviceType.wireAPI,
|
|
1225
1243
|
];
|
|
1226
1244
|
|
|
1227
1245
|
function issueBind(bindRequest, ttl) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1246
|
+
// eslint-disable-next-line no-use-before-define
|
|
1247
|
+
return unaryCall.call(this, "Bind", bindRequest).then((bindResponse) => {
|
|
1248
|
+
log$1("got binding request response %j", bindResponse);
|
|
1249
|
+
if (bindResponse.status?.code) {
|
|
1250
|
+
log$1("error in bind response: %s", bindResponse.status.message);
|
|
1251
|
+
return Promise.reject(new Error(bindResponse.status.message));
|
|
1252
|
+
} else {
|
|
1253
|
+
switch (bindResponse.decision) {
|
|
1254
|
+
case "POLICY_DECISION_PERMIT": {
|
|
1255
|
+
//
|
|
1256
|
+
// The request status is permit => cache the bind response info.
|
|
1257
|
+
//
|
|
1258
|
+
|
|
1259
|
+
// This is the certificate assigned to us by the volt.
|
|
1260
|
+
this._credential.cache.cert = bindResponse.cert;
|
|
1261
|
+
|
|
1262
|
+
// This is the signing CA used by the volt (don't ov)
|
|
1263
|
+
this._credential.cache.ca = bindResponse.chain;
|
|
1264
|
+
|
|
1265
|
+
// Resource id is assigned by the volt.
|
|
1266
|
+
this._credential.cache.client_id = bindResponse.identity_id;
|
|
1267
|
+
this._credential.saveCache();
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
case "POLICY_DECISION_DENY": {
|
|
1271
|
+
log$1(">>>>>>>>>>>>>>> access request is DENIED <<<<<<<<<<<<<<<<<<<");
|
|
1272
|
+
break;
|
|
1273
|
+
}
|
|
1274
|
+
case "POLICY_DECISION_PROMPT":
|
|
1275
|
+
case "POLICY_DECISION_PENDING": {
|
|
1276
|
+
log$1("access pending approval - waiting...");
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1279
|
+
default:
|
|
1280
|
+
log$1("ignoring unknown bind descision %s", bindResponse.status);
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1266
1284
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1285
|
+
return bindResponse.decision;
|
|
1286
|
+
});
|
|
1269
1287
|
}
|
|
1270
1288
|
|
|
1271
1289
|
function findDIDDocumentService(document, serviceType) {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1290
|
+
if (!document.service) {
|
|
1291
|
+
return [];
|
|
1292
|
+
}
|
|
1275
1293
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1294
|
+
const matches = filter(document.service, (svc) => svc.type === serviceType);
|
|
1295
|
+
return matches;
|
|
1278
1296
|
}
|
|
1279
1297
|
|
|
1280
1298
|
async function bindInternal() {
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1299
|
+
try {
|
|
1300
|
+
// Check if we need to resolve a Relay volt.
|
|
1301
|
+
if (
|
|
1302
|
+
typeof this._voltConfig?.relay === "string" ||
|
|
1303
|
+
this._voltConfig?.relay?.http_address
|
|
1304
|
+
) {
|
|
1305
|
+
const relayURL =
|
|
1306
|
+
this._voltConfig.relay?.http_address || this._voltConfig.relay;
|
|
1307
|
+
log$1("attempting to retrieve Relay information from %s", relayURL);
|
|
1308
|
+
this._voltConfig.relay = await fetchVoltConfig.call(
|
|
1309
|
+
this,
|
|
1310
|
+
`${relayURL}/discovery`,
|
|
1311
|
+
);
|
|
1312
|
+
if (this._voltConfig.relay.ca_pem) {
|
|
1313
|
+
log$1(
|
|
1314
|
+
"Auto-fetched Relay CA %s, remote address %s",
|
|
1315
|
+
this._voltConfig.relay.ca_pem,
|
|
1316
|
+
this._voltConfig.relay.address,
|
|
1317
|
+
);
|
|
1318
|
+
} else {
|
|
1319
|
+
throw new Error("Unable to fetch Relay CA - cannot securely connect.");
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1302
1322
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1323
|
+
if (!this._credential.cache.ca) {
|
|
1324
|
+
this._credential.cache.ca = this._voltConfig.ca_pem;
|
|
1325
|
+
}
|
|
1306
1326
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1327
|
+
if (!this._credential.cache.ca) {
|
|
1328
|
+
throw new Error(
|
|
1329
|
+
"No certificate authority found in configuration for target Volt - check configuration",
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1312
1332
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1333
|
+
const keyPair = this._credential.getKey();
|
|
1334
|
+
const publicKeyPem = pki.publicKeyToPem(keyPair.publicKey);
|
|
1315
1335
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1336
|
+
if (!this._credential.cache.bindIp) {
|
|
1337
|
+
// Default the IP address if none is specified. This is used to set the SAN in the
|
|
1338
|
+
// certificate issued by the Volt.
|
|
1339
|
+
this._credential.cache.bindIp = ip__default["default"].address();
|
|
1340
|
+
}
|
|
1321
1341
|
|
|
1322
|
-
|
|
1342
|
+
this._credential.saveCache();
|
|
1323
1343
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1344
|
+
// Form the request message.
|
|
1345
|
+
const bindRequest = {
|
|
1346
|
+
binding_name: this._config.client_name,
|
|
1347
|
+
public_key: publicKeyPem,
|
|
1348
|
+
host: this._credential.cache.bindIp,
|
|
1349
|
+
x509_credential: [],
|
|
1350
|
+
};
|
|
1331
1351
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1352
|
+
if (this._credential.cache.cert) {
|
|
1353
|
+
bindRequest.x509_credential = [
|
|
1354
|
+
this._credential.cache.cert + this._credential.cache.ca,
|
|
1355
|
+
];
|
|
1356
|
+
}
|
|
1337
1357
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1358
|
+
if (this._voltConfig.challenge_code) {
|
|
1359
|
+
bindRequest.challenge = signBase64(
|
|
1360
|
+
keyPair.privateKey,
|
|
1361
|
+
this._voltConfig.challenge_code,
|
|
1362
|
+
);
|
|
1363
|
+
} else {
|
|
1364
|
+
log$1(
|
|
1365
|
+
"****no Volt challenge code available**** => not sending challenge signature",
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1348
1368
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1369
|
+
// For remote connections, add our cloud-issued certificate as an additional credential.
|
|
1370
|
+
if (this._config?.relay?.ca_pem && this._credential.cache.cloud_cert) {
|
|
1371
|
+
bindRequest.x509_credential = bindRequest.x509_credential.concat(
|
|
1372
|
+
this._credential.cache.cloud_cert + this._config.relay.ca_pem,
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1355
1375
|
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1376
|
+
log$1("sending %d x509 credentials", bindRequest.x509_credential.length);
|
|
1377
|
+
log$1("bind TTL is ", this._config.bindRequestTTL);
|
|
1378
|
+
|
|
1379
|
+
// This will block while the request is pending.
|
|
1380
|
+
const pollInterval = 10000;
|
|
1381
|
+
let decision = "";
|
|
1382
|
+
do {
|
|
1383
|
+
if (decision) {
|
|
1384
|
+
// Wait a while before retrying.
|
|
1385
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1386
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1387
|
+
}
|
|
1388
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1389
|
+
decision = await issueBind.call(
|
|
1390
|
+
this,
|
|
1391
|
+
bindRequest,
|
|
1392
|
+
this._voltConfig.bindRequestTTL,
|
|
1393
|
+
);
|
|
1394
|
+
log$1("binding decision: %s", decision);
|
|
1395
|
+
} while (
|
|
1396
|
+
decision === "POLICY_DECISION_PROMPT" ||
|
|
1397
|
+
decision === "POLICY_DECISION_PENDING"
|
|
1398
|
+
);
|
|
1379
1399
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1400
|
+
return decision === "POLICY_DECISION_PERMIT";
|
|
1401
|
+
} catch (err) {
|
|
1402
|
+
log$1("failure binding to Volt [%s]", err.message);
|
|
1403
|
+
throw err;
|
|
1404
|
+
}
|
|
1385
1405
|
}
|
|
1386
1406
|
|
|
1387
1407
|
function connectInternal() {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1408
|
+
try {
|
|
1409
|
+
if (this._voltConnection) {
|
|
1410
|
+
log$1("connectInternal - Volt connection already exists");
|
|
1411
|
+
return Promise.resolve();
|
|
1412
|
+
}
|
|
1393
1413
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1414
|
+
const onDisconnected = () => {
|
|
1415
|
+
this._connected = false;
|
|
1416
|
+
this._session = null;
|
|
1417
|
+
|
|
1418
|
+
if (this._cachedClient) {
|
|
1419
|
+
this._grpc.closeClient(this._cachedClient);
|
|
1420
|
+
this._cachedClient = null;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (this._cachedRemoteClient) {
|
|
1424
|
+
this._grpc.closeClient(this._cachedRemoteClient);
|
|
1425
|
+
this._cachedRemoteClient = null;
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
this._voltConnection = new VoltConnection(this, this._grpc);
|
|
1430
|
+
|
|
1431
|
+
this._voltConnection.on("connected", (session) => {
|
|
1432
|
+
this._session = session;
|
|
1433
|
+
this._connected = !!session;
|
|
1434
|
+
if (!this._connected) {
|
|
1435
|
+
onDisconnected();
|
|
1436
|
+
}
|
|
1437
|
+
this.emit("connected", !!session);
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
this._voltConnection.on("error", (err) => {
|
|
1441
|
+
// Should auto-reconnect.
|
|
1442
|
+
log$1("Volt connection error [%s]", err.message);
|
|
1443
|
+
if (this._connected) {
|
|
1444
|
+
onDisconnected();
|
|
1445
|
+
this.emit("connected", false);
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
this._voltConnection.on("evt", (evt) => {
|
|
1450
|
+
this.emit("evt", evt);
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
return this._voltConnection.connect();
|
|
1454
|
+
} catch (err) {
|
|
1455
|
+
log$1("connect - error [%s]", err.message);
|
|
1456
|
+
throw err;
|
|
1457
|
+
}
|
|
1438
1458
|
}
|
|
1439
1459
|
|
|
1440
1460
|
function getVoltAPIClientInternal() {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1461
|
+
if (!this._cachedClient) {
|
|
1462
|
+
const serviceAddress = this.isRemote
|
|
1463
|
+
? this._config.relay.address
|
|
1464
|
+
: this._voltConfig.address;
|
|
1465
|
+
log$1("creating cached VoltAPI service client on %s", serviceAddress);
|
|
1466
|
+
let serviceDescriptors = {};
|
|
1467
|
+
voltServices.forEach((pkg) => {
|
|
1468
|
+
const packageDescriptors = getServiceDescriptors(this._grpc, pkg);
|
|
1469
|
+
serviceDescriptors = { ...serviceDescriptors, ...packageDescriptors };
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
this._cachedClient = createClient(
|
|
1473
|
+
this._grpc,
|
|
1474
|
+
serviceDescriptors,
|
|
1475
|
+
serviceAddress,
|
|
1476
|
+
this._credential,
|
|
1477
|
+
this.isRemote && !this.isVoltRelay,
|
|
1478
|
+
this._config?.relay?.cloud ? this._voltConfig.id : "",
|
|
1479
|
+
this._config?.relay?.cloud ? this._voltConfig.id : "",
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1462
1482
|
|
|
1463
|
-
|
|
1483
|
+
return this._cachedClient;
|
|
1464
1484
|
}
|
|
1465
1485
|
|
|
1466
1486
|
function unaryCall(method, request) {
|
|
1467
|
-
|
|
1487
|
+
return new Promise((resolve, reject) => {
|
|
1488
|
+
const grpcClient = this.getVoltAPIClient();
|
|
1489
|
+
|
|
1490
|
+
const call = new GRPCCall(this, method, "METHOD_TYPE_UNARY");
|
|
1491
|
+
|
|
1492
|
+
let response = null;
|
|
1493
|
+
|
|
1494
|
+
call.on("data", (payload) => {
|
|
1495
|
+
// Cache the response and wait for `end`.
|
|
1496
|
+
response = payload;
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
call.on("error", reject);
|
|
1500
|
+
|
|
1501
|
+
call.on("end", () => {
|
|
1502
|
+
if (!response) {
|
|
1503
|
+
return reject(new Error("no response received"));
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// Convert non-OK status to a JS error.
|
|
1507
|
+
if (response.status?.message) {
|
|
1508
|
+
reject(new Error(response.status.message));
|
|
1509
|
+
} else {
|
|
1510
|
+
resolve(response);
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1468
1513
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
return call.wait();
|
|
1514
|
+
call.start(grpcClient, request);
|
|
1515
|
+
});
|
|
1472
1516
|
}
|
|
1473
1517
|
|
|
1474
|
-
async function fetchVoltConfig(
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1518
|
+
async function fetchVoltConfig(discovery_url) {
|
|
1519
|
+
try {
|
|
1520
|
+
const getJSON = bent__default["default"]("json");
|
|
1521
|
+
const fullVoltConfig = await getJSON(discovery_url);
|
|
1522
|
+
|
|
1523
|
+
if (!fullVoltConfig || typeof fullVoltConfig !== "object") {
|
|
1524
|
+
throw new Error(`invalid config resolved from: ${discovery_url}`);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const voltConfig = pick(
|
|
1528
|
+
fullVoltConfig,
|
|
1529
|
+
"id",
|
|
1530
|
+
"display_name",
|
|
1531
|
+
"public_key",
|
|
1532
|
+
"fingerprint",
|
|
1533
|
+
"address",
|
|
1534
|
+
"ca_pem",
|
|
1535
|
+
"challenge_code",
|
|
1536
|
+
"cloud",
|
|
1537
|
+
"relay",
|
|
1538
|
+
);
|
|
1539
|
+
|
|
1540
|
+
return voltConfig;
|
|
1541
|
+
} catch (err) {
|
|
1542
|
+
throw new Error(
|
|
1543
|
+
`Failure loading config from ${discovery_url}: ${err.message}`,
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1493
1546
|
}
|
|
1494
1547
|
|
|
1495
1548
|
async function fetchVoltConfigFromDID(volt_did) {
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1549
|
+
try {
|
|
1550
|
+
const didResolution = getDIDResolutionURL(volt_did);
|
|
1551
|
+
if (!didResolution) {
|
|
1552
|
+
throw new Error(`Invalid Volt DID: ${volt_did}`);
|
|
1553
|
+
}
|
|
1500
1554
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1555
|
+
const getJSON = bent__default["default"]("json");
|
|
1556
|
+
const didDocument = await getJSON(didResolution);
|
|
1557
|
+
const voltConfigServices = findDIDDocumentService(
|
|
1558
|
+
didDocument,
|
|
1559
|
+
constants.didServiceType.voltConfig,
|
|
1560
|
+
);
|
|
1561
|
+
if (voltConfigServices.length !== 1) {
|
|
1562
|
+
log$1(
|
|
1563
|
+
"Unexpected number of Volt configuration endpoints found in DID document for %s, services found: %d",
|
|
1564
|
+
volt_did,
|
|
1565
|
+
voltConfigServices.length,
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
const serviceEndpoint = voltConfigServices[0].serviceEndpoint;
|
|
1570
|
+
return await fetchVoltConfig.call(this, serviceEndpoint);
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
throw new Error(
|
|
1573
|
+
`Failure fetching Volt config from DID document ${volt_did}: ${err.message}`,
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1516
1576
|
}
|
|
1517
1577
|
|
|
1518
1578
|
const { readFile } = fs__default["default"].promises;
|
|
1519
1579
|
const log = debug__default["default"]("volt-client-grpc:volt-client");
|
|
1520
1580
|
|
|
1521
1581
|
class VoltClient extends EventEmitter__default["default"] {
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1582
|
+
/**
|
|
1583
|
+
* @param {object} grpc - an instance of the grpc module (see https://www.npmjs.com/package/grpc)
|
|
1584
|
+
*/
|
|
1585
|
+
constructor(grpc) {
|
|
1586
|
+
super();
|
|
1527
1587
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1588
|
+
if (!grpc) {
|
|
1589
|
+
throw new Error("invalid arguments, grpc is required");
|
|
1590
|
+
}
|
|
1591
|
+
this._grpc = grpc;
|
|
1592
|
+
this._cachedClient = null;
|
|
1593
|
+
this._cachedRemoteClient = null;
|
|
1594
|
+
this._session = null;
|
|
1595
|
+
this._config = null;
|
|
1596
|
+
this._voltConfig = null;
|
|
1597
|
+
this._voltConnection = null;
|
|
1598
|
+
}
|
|
1539
1599
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1600
|
+
get config() {
|
|
1601
|
+
return this._config;
|
|
1602
|
+
}
|
|
1543
1603
|
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1604
|
+
get credential() {
|
|
1605
|
+
return this._credential;
|
|
1606
|
+
}
|
|
1547
1607
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1608
|
+
get grpc() {
|
|
1609
|
+
return this._grpc;
|
|
1610
|
+
}
|
|
1551
1611
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1612
|
+
get voltConfig() {
|
|
1613
|
+
return this._voltConfig;
|
|
1614
|
+
}
|
|
1555
1615
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
let configPath;
|
|
1569
|
-
let configJSON;
|
|
1570
|
-
if (typeof config === "string") {
|
|
1571
|
-
configPath = path__default["default"].resolve(config);
|
|
1572
|
-
log("Attempt to load config from file %s", configPath);
|
|
1573
|
-
try {
|
|
1574
|
-
const configContents = await readFile(
|
|
1575
|
-
new URL(configPath, (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href))),
|
|
1576
|
-
);
|
|
1577
|
-
configJSON = JSON.parse(configContents);
|
|
1578
|
-
} catch (err) {
|
|
1579
|
-
log("failure loading config file %s [%s]", configPath, err.message);
|
|
1580
|
-
throw new Error(
|
|
1581
|
-
`failed to load configuration - check JSON format in ${configPath}`,
|
|
1582
|
-
);
|
|
1583
|
-
}
|
|
1584
|
-
} else if (typeof config === "object") {
|
|
1585
|
-
configJSON = config;
|
|
1586
|
-
} else {
|
|
1587
|
-
throw new Error("config must be either a file path or a config object");
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
if (!configJSON || typeof configJSON !== "object") {
|
|
1591
|
-
throw new Error("config argument is required to be an object");
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
if (
|
|
1595
|
-
!configJSON.client_name ||
|
|
1596
|
-
typeof configJSON.client_name !== "string"
|
|
1597
|
-
) {
|
|
1598
|
-
throw new Error("client_name property required in config");
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
this._config = { ...configJSON, ...extras };
|
|
1602
|
-
|
|
1603
|
-
let voltDID = "";
|
|
1604
|
-
let voltDiscovery = "";
|
|
1605
|
-
if (typeof this._config.volt === "string") {
|
|
1606
|
-
if (this._config.volt.indexOf("did:") === 0) {
|
|
1607
|
-
voltDID = this._config.volt;
|
|
1608
|
-
} else {
|
|
1609
|
-
voltDiscovery = this._config.volt;
|
|
1610
|
-
}
|
|
1611
|
-
} else if (typeof this._config.volt !== "object") {
|
|
1612
|
-
throw new Error("configuration is missing the 'volt' object");
|
|
1613
|
-
} else {
|
|
1614
|
-
voltDID = this._config.volt.did;
|
|
1615
|
-
voltDiscovery = this._config.volt.discovery_url;
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
if (voltDID) {
|
|
1619
|
-
const didConfig = await fetchVoltConfigFromDID.call(this, voltDID);
|
|
1620
|
-
this._config = { ...this._config, ...didConfig };
|
|
1621
|
-
log("resolved config from DID: %s", JSON.stringify(didConfig, null, 2));
|
|
1622
|
-
} else if (voltDiscovery) {
|
|
1623
|
-
const discoConfig = await fetchVoltConfig.call(this, voltDiscovery);
|
|
1624
|
-
this._config = { ...this._config, ...discoConfig };
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
this._voltConfig = this._config.volt;
|
|
1628
|
-
this._voltConfig.did = voltDID;
|
|
1629
|
-
this._voltConfig.discovery_url = voltDiscovery;
|
|
1630
|
-
|
|
1631
|
-
if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
|
|
1632
|
-
throw new Error("'id' property is missing in Volt configuration");
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
this._credential = new VoltCredential(this._config, configPath);
|
|
1636
|
-
|
|
1637
|
-
await this._credential.initialise();
|
|
1638
|
-
|
|
1639
|
-
if (!this.isRemote) {
|
|
1640
|
-
// If we're not connecting via a Relay, we must have the address and CA.
|
|
1641
|
-
if (
|
|
1642
|
-
!this._voltConfig.address ||
|
|
1643
|
-
typeof this._voltConfig.address !== "string"
|
|
1644
|
-
) {
|
|
1645
|
-
throw new Error("'address' property missing in Volt configuration");
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
if (
|
|
1649
|
-
!this._voltConfig.ca_pem ||
|
|
1650
|
-
typeof this._voltConfig.ca_pem !== "string"
|
|
1651
|
-
) {
|
|
1652
|
-
throw new Error("'ca_pPem' property missing in Volt configuration");
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
let isBound;
|
|
1657
|
-
if (this._credential._cryptoCache.cert) {
|
|
1658
|
-
isBound = true;
|
|
1659
|
-
} else {
|
|
1660
|
-
// No certificate present in the cache yet => we need to bind.
|
|
1661
|
-
try {
|
|
1662
|
-
isBound = await bindInternal.call(this);
|
|
1663
|
-
} catch (bindErr) {
|
|
1664
|
-
if (bindErr.message === "invalid arguments") {
|
|
1665
|
-
// This is usually the result of a missing challenge signature or credential.
|
|
1666
|
-
log(
|
|
1667
|
-
"failure binding to Volt - did you supply a challenge code or verified credential?",
|
|
1668
|
-
);
|
|
1669
|
-
throw bindErr;
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
if (!isBound) {
|
|
1675
|
-
// This is usually because of request being denied.
|
|
1676
|
-
throw new Error("Volt binding failed");
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
return Promise.resolve();
|
|
1680
|
-
} catch (err) {
|
|
1681
|
-
log("Failure starting Volt client [%s]", err.message);
|
|
1682
|
-
throw err;
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Initialises a binding and connection to the volt.
|
|
1618
|
+
* @param {string} [config] - the location of the Volt configuration file, or a configuration object
|
|
1619
|
+
*/
|
|
1620
|
+
async initialise(config, extras = {}) {
|
|
1621
|
+
try {
|
|
1622
|
+
if (!config) {
|
|
1623
|
+
throw new Error(
|
|
1624
|
+
"configPath argument is required to be a non-empty string",
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1685
1627
|
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1628
|
+
let configPath;
|
|
1629
|
+
let configJSON;
|
|
1630
|
+
if (typeof config === "string") {
|
|
1631
|
+
configPath = path__default["default"].resolve(config);
|
|
1632
|
+
log("Attempt to load config from file %s", configPath);
|
|
1633
|
+
try {
|
|
1634
|
+
const configContents = await readFile(
|
|
1635
|
+
new URL(configPath, (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href))),
|
|
1636
|
+
);
|
|
1637
|
+
configJSON = JSON.parse(configContents);
|
|
1638
|
+
} catch (err) {
|
|
1639
|
+
log("failure loading config file %s [%s]", configPath, err.message);
|
|
1640
|
+
throw new Error(
|
|
1641
|
+
`failed to load configuration - check JSON format in ${configPath}`,
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
} else if (typeof config === "object") {
|
|
1645
|
+
configJSON = config;
|
|
1646
|
+
} else {
|
|
1647
|
+
throw new Error("config must be either a file path or a config object");
|
|
1648
|
+
}
|
|
1696
1649
|
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
* Automatically handles remote connections.
|
|
1701
|
-
*
|
|
1702
|
-
* n.b. for this javascript client Protobuf definitions should conform to the recommended
|
|
1703
|
-
* structure, which is that a package proto file resides in a folder path that matches the
|
|
1704
|
-
* package name, and services contained in the package are named in in Pascal Case and using
|
|
1705
|
-
* the suffix 'API'. For example the WebcamControlAPI in package tdx.volt_api.webcam.v1 should reside
|
|
1706
|
-
* in a protobuf file at tdx/api/webcam/v1/webcam_control_api.proto
|
|
1707
|
-
*
|
|
1708
|
-
* @param {object} service a service description.
|
|
1709
|
-
* @param {object} descriptors the service descriptors, must be specified unless the service
|
|
1710
|
-
* packages are well-known Volt APIs.
|
|
1711
|
-
*/
|
|
1712
|
-
createServiceClient(service, descriptors) {
|
|
1713
|
-
const pkg = service.service_description.service_api;
|
|
1714
|
-
|
|
1715
|
-
let serviceDescriptors;
|
|
1716
|
-
if (descriptors) {
|
|
1717
|
-
serviceDescriptors = descriptors;
|
|
1718
|
-
} else {
|
|
1719
|
-
serviceDescriptors = {};
|
|
1720
|
-
pkg.forEach((svc) => {
|
|
1721
|
-
log("adding service %s to service client", svc);
|
|
1722
|
-
const packageDescriptors = getServiceDescriptors(this._grpc, svc);
|
|
1723
|
-
serviceDescriptors = { ...serviceDescriptors, ...packageDescriptors };
|
|
1724
|
-
});
|
|
1725
|
-
}
|
|
1650
|
+
if (!configJSON || typeof configJSON !== "object") {
|
|
1651
|
+
throw new Error("config argument is required to be an object");
|
|
1652
|
+
}
|
|
1726
1653
|
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
// In remote mode => create a client using the discovered Relay address **and** passing the service reourceId
|
|
1735
|
-
return createClient(
|
|
1736
|
-
this._grpc,
|
|
1737
|
-
serviceDescriptors,
|
|
1738
|
-
this._config.relay.address,
|
|
1739
|
-
this._credential,
|
|
1740
|
-
false,
|
|
1741
|
-
service.volt_id,
|
|
1742
|
-
service.id,
|
|
1743
|
-
);
|
|
1744
|
-
} else {
|
|
1745
|
-
return createClient(
|
|
1746
|
-
this._grpc,
|
|
1747
|
-
serviceDescriptors,
|
|
1748
|
-
service.service_description.address,
|
|
1749
|
-
this._credential,
|
|
1750
|
-
);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1654
|
+
if (
|
|
1655
|
+
!configJSON.client_name ||
|
|
1656
|
+
typeof configJSON.client_name !== "string"
|
|
1657
|
+
) {
|
|
1658
|
+
throw new Error("client_name property required in config");
|
|
1659
|
+
}
|
|
1753
1660
|
|
|
1754
|
-
|
|
1755
|
-
return !!(this._config?.relay && this._voltConfig.id);
|
|
1756
|
-
}
|
|
1661
|
+
this._config = { ...configJSON, ...extras };
|
|
1757
1662
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1663
|
+
let voltDID = "";
|
|
1664
|
+
let voltHttpAddress = "";
|
|
1665
|
+
if (typeof this._config.volt === "string") {
|
|
1666
|
+
if (this._config.volt.indexOf("did:") === 0) {
|
|
1667
|
+
voltDID = this._config.volt;
|
|
1668
|
+
} else {
|
|
1669
|
+
voltHttpAddress = this._config.volt;
|
|
1670
|
+
}
|
|
1671
|
+
} else if (typeof this._config.volt !== "object") {
|
|
1672
|
+
throw new Error("configuration is missing the 'volt' object");
|
|
1673
|
+
} else {
|
|
1674
|
+
voltDID = this._config.volt.did;
|
|
1675
|
+
voltHttpAddress = this._config.volt.http_address;
|
|
1676
|
+
}
|
|
1761
1677
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1678
|
+
if (voltDID) {
|
|
1679
|
+
const didConfig = await fetchVoltConfigFromDID.call(this, voltDID);
|
|
1680
|
+
this._config = { ...this._config, volt: didConfig };
|
|
1681
|
+
log("resolved config from DID: %s", JSON.stringify(didConfig, null, 2));
|
|
1682
|
+
} else if (voltHttpAddress) {
|
|
1683
|
+
const discoConfig = await fetchVoltConfig.call(
|
|
1684
|
+
this,
|
|
1685
|
+
`${voltHttpAddress}/discovery`,
|
|
1686
|
+
);
|
|
1687
|
+
this._config = {
|
|
1688
|
+
...this._config,
|
|
1689
|
+
volt: { ...this._config.volt, ...discoConfig },
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1765
1692
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
this._voltConnection = null;
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1693
|
+
this._voltConfig = this._config.volt;
|
|
1694
|
+
this._voltConfig.did = voltDID;
|
|
1695
|
+
this._voltConfig.http_address = voltHttpAddress;
|
|
1772
1696
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1697
|
+
if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
|
|
1698
|
+
throw new Error("'id' property is missing in Volt configuration");
|
|
1699
|
+
}
|
|
1776
1700
|
|
|
1777
|
-
|
|
1778
|
-
* Request resource access.
|
|
1779
|
-
* @param {*} targetResourceId
|
|
1780
|
-
* @param {*} accessType - see vault.proto for enum values.
|
|
1781
|
-
*/
|
|
1782
|
-
async RequestAccessBlocking(
|
|
1783
|
-
targetResourceId,
|
|
1784
|
-
accessType = "VOLT_ACCESS_READ",
|
|
1785
|
-
) {
|
|
1786
|
-
try {
|
|
1787
|
-
const accessRequest = {
|
|
1788
|
-
access: accessType,
|
|
1789
|
-
resource_id: targetResourceId,
|
|
1790
|
-
};
|
|
1791
|
-
|
|
1792
|
-
// This will block while the request is pending.
|
|
1793
|
-
// TODO - implement streamed rpc on volt? Not sure there is a need really...
|
|
1794
|
-
const pollInterval = 10000;
|
|
1795
|
-
let decision = "";
|
|
1796
|
-
do {
|
|
1797
|
-
if (decision) {
|
|
1798
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1799
|
-
}
|
|
1800
|
-
const accessResponse = await this.RequestAccess(accessRequest);
|
|
1801
|
-
decision = accessResponse.decision;
|
|
1802
|
-
log("access decision: %s", decision);
|
|
1803
|
-
} while (
|
|
1804
|
-
decision === "POLICY_DECISION_PROMPT" ||
|
|
1805
|
-
decision === "POLICY_DECISION_PENDING"
|
|
1806
|
-
);
|
|
1807
|
-
|
|
1808
|
-
log("requestResourceAccess result is %j", decision);
|
|
1809
|
-
return decision === "POLICY_DECISION_PERMIT";
|
|
1810
|
-
} catch (err) {
|
|
1811
|
-
log("failure requesting resource access [%s]", err.message);
|
|
1812
|
-
return Promise.reject(err);
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1701
|
+
this._credential = new VoltCredential(this._config, configPath);
|
|
1815
1702
|
|
|
1816
|
-
|
|
1817
|
-
* VoltAPI - resource management
|
|
1818
|
-
*/
|
|
1703
|
+
await this._credential.initialise();
|
|
1819
1704
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1705
|
+
if (!this.isRemote) {
|
|
1706
|
+
// If we're not connecting via a Relay, we must have the address and CA.
|
|
1707
|
+
if (
|
|
1708
|
+
!this._voltConfig.address ||
|
|
1709
|
+
typeof this._voltConfig.address !== "string"
|
|
1710
|
+
) {
|
|
1711
|
+
throw new Error("'address' property missing in Volt configuration");
|
|
1712
|
+
}
|
|
1823
1713
|
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1714
|
+
if (
|
|
1715
|
+
!this._voltConfig.ca_pem ||
|
|
1716
|
+
typeof this._voltConfig.ca_pem !== "string"
|
|
1717
|
+
) {
|
|
1718
|
+
throw new Error("'ca_pPem' property missing in Volt configuration");
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1827
1721
|
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1722
|
+
let isBound;
|
|
1723
|
+
if (this._credential._cryptoCache.cert) {
|
|
1724
|
+
isBound = true;
|
|
1725
|
+
} else {
|
|
1726
|
+
// No certificate present in the cache yet => we need to bind.
|
|
1727
|
+
try {
|
|
1728
|
+
isBound = await bindInternal.call(this);
|
|
1729
|
+
} catch (bindErr) {
|
|
1730
|
+
if (bindErr.message === "invalid arguments") {
|
|
1731
|
+
// This is usually the result of a missing challenge signature or credential.
|
|
1732
|
+
log(
|
|
1733
|
+
"failure binding to Volt - did you supply a challenge code or verified credential?",
|
|
1734
|
+
);
|
|
1735
|
+
throw bindErr;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1831
1739
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1740
|
+
if (!isBound) {
|
|
1741
|
+
// This is usually because of request being denied.
|
|
1742
|
+
throw new Error("Volt binding failed");
|
|
1743
|
+
}
|
|
1835
1744
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1745
|
+
return Promise.resolve();
|
|
1746
|
+
} catch (err) {
|
|
1747
|
+
log("Failure starting Volt client [%s]", err.message);
|
|
1748
|
+
throw err;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1839
1751
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1752
|
+
initialiseAndConnect(configPath, extras = {}) {
|
|
1753
|
+
return this.initialise(configPath, extras)
|
|
1754
|
+
.then(() => {
|
|
1755
|
+
return connectInternal.call(this);
|
|
1756
|
+
})
|
|
1757
|
+
.catch((err) => {
|
|
1758
|
+
log("initialiseAndConnect failure: %s", err.message);
|
|
1759
|
+
return Promise.reject(err);
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1843
1762
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1763
|
+
/**
|
|
1764
|
+
* Creates a GRPC client for the given Volt service description.
|
|
1765
|
+
*
|
|
1766
|
+
* Automatically handles remote connections.
|
|
1767
|
+
*
|
|
1768
|
+
* n.b. for this javascript client Protobuf definitions should conform to the recommended
|
|
1769
|
+
* structure, which is that a package proto file resides in a folder path that matches the
|
|
1770
|
+
* package name, and services contained in the package are named in in Pascal Case and using
|
|
1771
|
+
* the suffix 'API'. For example the WebcamControlAPI in package tdx.volt_api.webcam.v1 should reside
|
|
1772
|
+
* in a protobuf file at tdx/api/webcam/v1/webcam_control_api.proto
|
|
1773
|
+
*
|
|
1774
|
+
* @param {object} service a service description.
|
|
1775
|
+
* @param {object} descriptors the service descriptors, must be specified unless the service
|
|
1776
|
+
* packages are well-known Volt APIs.
|
|
1777
|
+
*/
|
|
1778
|
+
createServiceClient(service, descriptors) {
|
|
1779
|
+
const pkg = service.service_description.service_api;
|
|
1780
|
+
|
|
1781
|
+
let serviceDescriptors;
|
|
1782
|
+
if (descriptors) {
|
|
1783
|
+
serviceDescriptors = descriptors;
|
|
1784
|
+
} else {
|
|
1785
|
+
serviceDescriptors = {};
|
|
1786
|
+
pkg.forEach((svc) => {
|
|
1787
|
+
log("adding service %s to service client", svc);
|
|
1788
|
+
const packageDescriptors = getServiceDescriptors(this._grpc, svc);
|
|
1789
|
+
serviceDescriptors = { ...serviceDescriptors, ...packageDescriptors };
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1847
1792
|
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1793
|
+
if (this.isRemote) {
|
|
1794
|
+
if (!this._config?.relay?.address) {
|
|
1795
|
+
throw new Error(
|
|
1796
|
+
"Remote connection enabled but no Relay address - have you called start()?",
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1851
1799
|
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1800
|
+
// In remote mode => create a client using the discovered Relay address **and** passing the service reourceId
|
|
1801
|
+
return createClient(
|
|
1802
|
+
this._grpc,
|
|
1803
|
+
serviceDescriptors,
|
|
1804
|
+
this._config.relay.address,
|
|
1805
|
+
this._credential,
|
|
1806
|
+
false,
|
|
1807
|
+
service.volt_id,
|
|
1808
|
+
service.id,
|
|
1809
|
+
);
|
|
1810
|
+
} else {
|
|
1811
|
+
return createClient(
|
|
1812
|
+
this._grpc,
|
|
1813
|
+
serviceDescriptors,
|
|
1814
|
+
service.service_description.address,
|
|
1815
|
+
this._credential,
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1855
1819
|
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1820
|
+
get isRemote() {
|
|
1821
|
+
return !!(this._config?.relay && this._voltConfig.id);
|
|
1822
|
+
}
|
|
1859
1823
|
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1824
|
+
get isVoltRelay() {
|
|
1825
|
+
return this.isRemote && !this._config?.relay.cloud;
|
|
1826
|
+
}
|
|
1863
1827
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1828
|
+
connect() {
|
|
1829
|
+
return connectInternal.call(this);
|
|
1830
|
+
}
|
|
1867
1831
|
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1832
|
+
disconnect() {
|
|
1833
|
+
if (this._voltConnection) {
|
|
1834
|
+
this._voltConnection.disconnect();
|
|
1835
|
+
this._voltConnection = null;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1871
1838
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1839
|
+
getVoltAPIClient() {
|
|
1840
|
+
return getVoltAPIClientInternal.call(this);
|
|
1841
|
+
}
|
|
1875
1842
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1843
|
+
/**
|
|
1844
|
+
* Request resource access.
|
|
1845
|
+
* @param {*} targetResourceId
|
|
1846
|
+
* @param {*} accessType - see volt.proto for values.
|
|
1847
|
+
*/
|
|
1848
|
+
async RequestAccessBlocking(
|
|
1849
|
+
targetResourceId,
|
|
1850
|
+
accessType = "VOLT_ACCESS_READ",
|
|
1851
|
+
) {
|
|
1852
|
+
try {
|
|
1853
|
+
const accessRequest = {
|
|
1854
|
+
access: accessType,
|
|
1855
|
+
resource_id: targetResourceId,
|
|
1856
|
+
};
|
|
1879
1857
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1858
|
+
// This will block while the request is pending.
|
|
1859
|
+
// TODO - implement streamed rpc on volt? Not sure there is a need really...
|
|
1860
|
+
const pollInterval = 10000;
|
|
1861
|
+
let decision = "";
|
|
1862
|
+
do {
|
|
1863
|
+
if (decision) {
|
|
1864
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1865
|
+
}
|
|
1866
|
+
const accessResponse = await this.RequestAccess(accessRequest);
|
|
1867
|
+
decision = accessResponse.decision;
|
|
1868
|
+
log("access decision: %s", decision);
|
|
1869
|
+
} while (
|
|
1870
|
+
decision === "POLICY_DECISION_PROMPT" ||
|
|
1871
|
+
decision === "POLICY_DECISION_PENDING"
|
|
1872
|
+
);
|
|
1883
1873
|
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1874
|
+
log("requestResourceAccess result is %j", decision);
|
|
1875
|
+
return decision === "POLICY_DECISION_PERMIT";
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
log("failure requesting resource access [%s]", err.message);
|
|
1878
|
+
return Promise.reject(err);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1887
1881
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1882
|
+
/**
|
|
1883
|
+
* VoltAPI - resource management
|
|
1884
|
+
*/
|
|
1891
1885
|
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1886
|
+
CanAccessResource(request) {
|
|
1887
|
+
return unaryCall.call(this, "CanAccessResource", request);
|
|
1888
|
+
}
|
|
1895
1889
|
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1890
|
+
DeleteResource(request) {
|
|
1891
|
+
return unaryCall.call(this, "DeleteResource", request);
|
|
1892
|
+
}
|
|
1899
1893
|
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1894
|
+
DiscoverServices(request) {
|
|
1895
|
+
return unaryCall.call(this, "DiscoverServices", request);
|
|
1896
|
+
}
|
|
1903
1897
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1898
|
+
GetResource(request) {
|
|
1899
|
+
return unaryCall.call(this, "GetResource", request);
|
|
1900
|
+
}
|
|
1907
1901
|
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1902
|
+
GetResources(request) {
|
|
1903
|
+
return unaryCall.call(this, "GetResources", request);
|
|
1904
|
+
}
|
|
1911
1905
|
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1906
|
+
GetResourceAncestors(request) {
|
|
1907
|
+
return unaryCall.call(this, "GetResourceAncestors", request);
|
|
1908
|
+
}
|
|
1915
1909
|
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1910
|
+
GetResourceDescendants(request) {
|
|
1911
|
+
return unaryCall.call(this, "GetResourceDescendants", request);
|
|
1912
|
+
}
|
|
1919
1913
|
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1914
|
+
RequestAccess(request) {
|
|
1915
|
+
return unaryCall.call(this, "RequestAccess", request);
|
|
1916
|
+
}
|
|
1923
1917
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1918
|
+
SaveResource(request) {
|
|
1919
|
+
return unaryCall.call(this, "SaveResource", request);
|
|
1920
|
+
}
|
|
1927
1921
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1922
|
+
SaveResourceAttribute(request) {
|
|
1923
|
+
return unaryCall.call(this, "SaveResourceAttribute", request);
|
|
1924
|
+
}
|
|
1931
1925
|
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1926
|
+
SetServiceStatus(request) {
|
|
1927
|
+
return unaryCall.call(this, "SetServiceStatus", request);
|
|
1928
|
+
}
|
|
1935
1929
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1930
|
+
/**
|
|
1931
|
+
* VoltAPI - volt management
|
|
1932
|
+
*/
|
|
1939
1933
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
GetFileDescendants(request) {
|
|
1944
|
-
return unaryCall.call(this, "GetFileDescendants", request);
|
|
1945
|
-
}
|
|
1934
|
+
Bind(request) {
|
|
1935
|
+
return unaryCall.call(this, "Bind", request);
|
|
1936
|
+
}
|
|
1946
1937
|
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
* @returns stream
|
|
1951
|
-
*/
|
|
1952
|
-
DownloadFile(request) {
|
|
1953
|
-
const grpcClient = this.getVoltAPIClient();
|
|
1954
|
-
|
|
1955
|
-
return new Promise((resolve, reject) => {
|
|
1956
|
-
const meta = this._credential.getIdentityMetadata(
|
|
1957
|
-
this._grpc,
|
|
1958
|
-
this._voltConfig.id,
|
|
1959
|
-
this.isRemote,
|
|
1960
|
-
);
|
|
1961
|
-
resolve(grpcClient.DownloadFile(request, meta.metadata));
|
|
1962
|
-
}).catch((err) => {
|
|
1963
|
-
log("error during %s [%s]", method, err.message);
|
|
1964
|
-
return Promise.reject(err);
|
|
1965
|
-
});
|
|
1966
|
-
}
|
|
1938
|
+
DeleteAccess(request) {
|
|
1939
|
+
return unaryCall.call(this, "DeleteAccess", request);
|
|
1940
|
+
}
|
|
1967
1941
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1942
|
+
DeleteVolt(request) {
|
|
1943
|
+
return unaryCall.call(this, "DeleteVolt", request);
|
|
1944
|
+
}
|
|
1970
1945
|
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1946
|
+
GetAccess(request) {
|
|
1947
|
+
return unaryCall.call(this, "GetAccess", request);
|
|
1948
|
+
}
|
|
1974
1949
|
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
* @returns file buffer, base64 encoded
|
|
1979
|
-
*/
|
|
1980
|
-
DownloadFileSync(request) {
|
|
1981
|
-
const grpcClient = this.getVoltAPIClient();
|
|
1950
|
+
GetBindings(request) {
|
|
1951
|
+
return unaryCall.call(this, "GetBindings", request);
|
|
1952
|
+
}
|
|
1982
1953
|
|
|
1983
|
-
|
|
1954
|
+
GetIdentities(request) {
|
|
1955
|
+
return unaryCall.call(this, "GetIdentities", request);
|
|
1956
|
+
}
|
|
1984
1957
|
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
"METHOD_TYPE_SERVER_STREAM",
|
|
1989
|
-
);
|
|
1990
|
-
call.start(grpcClient, request, (err, response) => {
|
|
1991
|
-
if (err) {
|
|
1992
|
-
return call.reject(err);
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
switch (response.payload) {
|
|
1996
|
-
case "block": {
|
|
1997
|
-
blocks.push(response.block);
|
|
1998
|
-
break;
|
|
1999
|
-
}
|
|
2000
|
-
case "status": {
|
|
2001
|
-
if (response.status.message) {
|
|
2002
|
-
return call.reject(new Error(response.status.message));
|
|
2003
|
-
}
|
|
2004
|
-
break;
|
|
2005
|
-
}
|
|
2006
|
-
default:
|
|
2007
|
-
log("unrecognised response payload %s", response.payload);
|
|
2008
|
-
break;
|
|
2009
|
-
}
|
|
2010
|
-
});
|
|
2011
|
-
|
|
2012
|
-
return call
|
|
2013
|
-
.wait()
|
|
2014
|
-
.then(() => {
|
|
2015
|
-
log("Download finished");
|
|
2016
|
-
return { buffer: Buffer.concat(blocks).toString("base64") };
|
|
2017
|
-
})
|
|
2018
|
-
.catch((err) => {
|
|
2019
|
-
log("error during DownloadFile [%s]", err.message);
|
|
2020
|
-
return Promise.reject(err);
|
|
2021
|
-
});
|
|
2022
|
-
}
|
|
1958
|
+
GetIdentity(request) {
|
|
1959
|
+
return unaryCall.call(this, "GetIdentity", request);
|
|
1960
|
+
}
|
|
2023
1961
|
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
SqlExecute(request, callback) {
|
|
2028
|
-
const grpcClient = this.getVoltAPIClient();
|
|
1962
|
+
GetIdentityToken(request) {
|
|
1963
|
+
return unaryCall.call(this, "GetIdentityToken", request);
|
|
1964
|
+
}
|
|
2029
1965
|
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
1966
|
+
GetPolicy(request) {
|
|
1967
|
+
return unaryCall.call(this, "GetPolicy", request);
|
|
1968
|
+
}
|
|
2033
1969
|
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
PublishWire(request, callback) {
|
|
2038
|
-
const grpcClient = this.getVoltAPIClient();
|
|
1970
|
+
GetSettings(request) {
|
|
1971
|
+
return unaryCall.call(this, "GetSettings", request);
|
|
1972
|
+
}
|
|
2039
1973
|
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
1974
|
+
SaveAccess(request) {
|
|
1975
|
+
return unaryCall.call(this, "SaveAccess", request);
|
|
1976
|
+
}
|
|
2043
1977
|
|
|
2044
|
-
|
|
2045
|
-
|
|
1978
|
+
SaveCloudConnection(request) {
|
|
1979
|
+
return unaryCall.call(this, "SaveCloudConnection", request);
|
|
1980
|
+
}
|
|
2046
1981
|
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
1982
|
+
SaveIdentity(request) {
|
|
1983
|
+
return unaryCall.call(this, "SaveIdentity", request);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
SaveSettings(request) {
|
|
1987
|
+
return unaryCall.call(this, "SaveSettings", request);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
SetAccessRequestDecision(request) {
|
|
1991
|
+
return unaryCall.call(this, "SetAccessRequestDecision", request);
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
SetBindingDecision(request) {
|
|
1995
|
+
return unaryCall.call(this, "SetBindingDecision", request);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
Shutdown(request) {
|
|
1999
|
+
return unaryCall.call(this, "Shutdown", request);
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
SignVerify(request) {
|
|
2003
|
+
return unaryCall.call(this, "SignVerify", request);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* FileAPI
|
|
2008
|
+
*/
|
|
2009
|
+
GetFileDescendants(request) {
|
|
2010
|
+
return unaryCall.call(this, "GetFileDescendants", request);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
/**
|
|
2014
|
+
* Asynchronous file download
|
|
2015
|
+
* @param {*} request
|
|
2016
|
+
* @returns stream
|
|
2017
|
+
*/
|
|
2018
|
+
DownloadFile(request) {
|
|
2019
|
+
const grpcClient = this.getVoltAPIClient();
|
|
2020
|
+
|
|
2021
|
+
const downloadCall = new GRPCCall(
|
|
2022
|
+
this,
|
|
2023
|
+
"DownloadFile",
|
|
2024
|
+
"METHOD_TYPE_SERVER_STREAM",
|
|
2025
|
+
);
|
|
2026
|
+
return downloadCall.start(grpcClient, request);
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
UploadFile(request) {
|
|
2030
|
+
const grpcClient = this.getVoltAPIClient();
|
|
2031
|
+
|
|
2032
|
+
const uploadCall = new GRPCCall(this, "UploadFile", "METHOD_TYPE_BIDI");
|
|
2033
|
+
return uploadCall.start(grpcClient, request);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
/**
|
|
2037
|
+
* Synchronous file download
|
|
2038
|
+
* @param {*} request
|
|
2039
|
+
* @returns file buffer, base64 encoded
|
|
2040
|
+
*/
|
|
2041
|
+
DownloadFileSync(request) {
|
|
2042
|
+
return new Promise((resolve, reject) => {
|
|
2043
|
+
const grpcClient = this.getVoltAPIClient();
|
|
2044
|
+
|
|
2045
|
+
const blocks = [];
|
|
2046
|
+
|
|
2047
|
+
const call = new GRPCCall(
|
|
2048
|
+
this,
|
|
2049
|
+
"DownloadFile",
|
|
2050
|
+
"METHOD_TYPE_SERVER_STREAM",
|
|
2051
|
+
);
|
|
2052
|
+
|
|
2053
|
+
call.on("data", (response) => {
|
|
2054
|
+
switch (response.payload) {
|
|
2055
|
+
case "block": {
|
|
2056
|
+
blocks.push(response.block);
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2059
|
+
case "status": {
|
|
2060
|
+
if (response.status.message) {
|
|
2061
|
+
return call.reject(new Error(response.status.message));
|
|
2062
|
+
}
|
|
2063
|
+
break;
|
|
2064
|
+
}
|
|
2065
|
+
default:
|
|
2066
|
+
log("unrecognised response payload %s", response.payload);
|
|
2067
|
+
break;
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
|
|
2071
|
+
call.on("error", (err) => {
|
|
2072
|
+
log("error during DownloadFile [%s]", err.message);
|
|
2073
|
+
reject(err);
|
|
2074
|
+
});
|
|
2075
|
+
|
|
2076
|
+
call.on("end", () => {
|
|
2077
|
+
log("Download finished");
|
|
2078
|
+
resolve({ buffer: Buffer.concat(blocks).toString("base64") });
|
|
2079
|
+
});
|
|
2080
|
+
|
|
2081
|
+
call.start(grpcClient, request);
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
/**
|
|
2086
|
+
* SqliteDatabaseAPI
|
|
2087
|
+
*/
|
|
2088
|
+
SqlExecute(request) {
|
|
2089
|
+
const grpcClient = this.getVoltAPIClient();
|
|
2090
|
+
|
|
2091
|
+
const subscribeCall = new GRPCCall(this, "Execute", "METHOD_TYPE_BIDI");
|
|
2092
|
+
return subscribeCall.start(grpcClient, request);
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
/**
|
|
2096
|
+
* WireAPI
|
|
2097
|
+
*/
|
|
2098
|
+
PublishWire(request) {
|
|
2099
|
+
const grpcClient = this.getVoltAPIClient();
|
|
2100
|
+
|
|
2101
|
+
const publishCall = new GRPCCall(this, "PublishWire", "METHOD_TYPE_BIDI");
|
|
2102
|
+
return publishCall.start(grpcClient, request);
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
SubscribeWire(request) {
|
|
2106
|
+
const grpcClient = this.getVoltAPIClient();
|
|
2107
|
+
|
|
2108
|
+
const subscribeCall = new GRPCCall(
|
|
2109
|
+
this,
|
|
2110
|
+
"SubscribeWire",
|
|
2111
|
+
"METHOD_TYPE_BIDI",
|
|
2112
|
+
);
|
|
2113
|
+
return subscribeCall.start(grpcClient, request);
|
|
2114
|
+
}
|
|
2054
2115
|
}
|
|
2055
2116
|
|
|
2056
2117
|
Object.defineProperty(exports, 'generateId', {
|
|
2057
|
-
|
|
2058
|
-
|
|
2118
|
+
enumerable: true,
|
|
2119
|
+
get: function () { return uuid.v4; }
|
|
2059
2120
|
});
|
|
2060
2121
|
exports.VoltClient = VoltClient;
|
|
2061
2122
|
exports.VoltCredential = VoltCredential;
|