@lightsparkdev/core 0.2.1 → 0.2.3
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/CHANGELOG.md +16 -0
- package/README.md +4 -0
- package/dist/index.cjs +91 -114
- package/dist/index.d.ts +30 -17
- package/dist/index.js +90 -104
- package/package.json +15 -1
- package/src/crypto/NodeKeyCache.ts +18 -15
- package/src/crypto/crypto.ts +72 -78
- package/src/requester/Requester.ts +9 -13
- package/src/utils/base64.ts +51 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @lightsparkdev/core
|
|
2
2
|
|
|
3
|
+
## 0.2.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8bf7fe0: Patch release for core and wallet sdks.
|
|
8
|
+
|
|
9
|
+
- Expose a function for raw subscriptions from the wallet client.
|
|
10
|
+
- Fix type exports.
|
|
11
|
+
- Fix a parsing bug in the wallet dashboard query
|
|
12
|
+
|
|
13
|
+
## 0.2.2
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Refactor internals to allow for a custom react native crypto implementation to be injected into the LightsparkClient.
|
|
18
|
+
|
|
3
19
|
## 0.2.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# The Lightspark JS+TS Core package
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
This is the Lightspark Typescript Core package, which can be used either from a node or browser environment. The package contains core objects and utilities that are used by all Lightspark Typescript SDKs and may be useful for your project.
|
|
4
6
|
|
|
5
7
|
## Getting started
|
|
@@ -9,3 +11,5 @@ To use the package, you'll need to install it from npm:
|
|
|
9
11
|
```bash
|
|
10
12
|
$ npm install @lightsparkdev/core
|
|
11
13
|
```
|
|
14
|
+
|
|
15
|
+
TODO
|
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
DefaultCrypto: () => DefaultCrypto,
|
|
33
34
|
LightsparkAuthException: () => LightsparkAuthException_default,
|
|
34
35
|
LightsparkException: () => LightsparkException_default,
|
|
35
36
|
LightsparkSigningException: () => LightsparkSigningException_default,
|
|
@@ -41,19 +42,9 @@ __export(src_exports, {
|
|
|
41
42
|
b64decode: () => b64decode,
|
|
42
43
|
b64encode: () => b64encode,
|
|
43
44
|
convertCurrencyAmount: () => convertCurrencyAmount,
|
|
44
|
-
decode: () => decode,
|
|
45
|
-
decrypt: () => decrypt,
|
|
46
|
-
decryptSecretWithNodePassword: () => decryptSecretWithNodePassword,
|
|
47
|
-
encrypt: () => encrypt,
|
|
48
|
-
encryptWithNodeKey: () => encryptWithNodeKey,
|
|
49
|
-
generateSigningKeyPair: () => generateSigningKeyPair,
|
|
50
|
-
getCrypto: () => getCrypto,
|
|
51
|
-
getNonce: () => getNonce,
|
|
52
45
|
isBrowser: () => isBrowser,
|
|
53
46
|
isNode: () => isNode,
|
|
54
47
|
isType: () => isType,
|
|
55
|
-
loadNodeEncryptionKey: () => loadNodeEncryptionKey,
|
|
56
|
-
serializeSigningKey: () => serializeSigningKey,
|
|
57
48
|
urlsafe_b64decode: () => urlsafe_b64decode
|
|
58
49
|
});
|
|
59
50
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -94,14 +85,44 @@ var StubAuthProvider = class {
|
|
|
94
85
|
};
|
|
95
86
|
|
|
96
87
|
// src/utils/base64.ts
|
|
88
|
+
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
89
|
+
var Base64 = {
|
|
90
|
+
btoa: (input = "") => {
|
|
91
|
+
let str = input;
|
|
92
|
+
let output = "";
|
|
93
|
+
for (let block = 0, charCode, i = 0, map = chars; str.charAt(i | 0) || (map = "=", i % 1); output += map.charAt(63 & block >> 8 - i % 1 * 8)) {
|
|
94
|
+
charCode = str.charCodeAt(i += 3 / 4);
|
|
95
|
+
if (charCode > 255) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
block = block << 8 | charCode;
|
|
101
|
+
}
|
|
102
|
+
return output;
|
|
103
|
+
},
|
|
104
|
+
atob: (input = "") => {
|
|
105
|
+
let str = input.replace(/=+$/, "");
|
|
106
|
+
let output = "";
|
|
107
|
+
if (str.length % 4 == 1) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"'atob' failed: The string to be decoded is not correctly encoded."
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
for (let bc = 0, bs = 0, buffer, i = 0; buffer = str.charAt(i++); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0) {
|
|
113
|
+
buffer = chars.indexOf(buffer);
|
|
114
|
+
}
|
|
115
|
+
return output;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
97
118
|
var b64decode = (encoded) => {
|
|
98
|
-
return Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
|
|
119
|
+
return Uint8Array.from(Base64.atob(encoded), (c) => c.charCodeAt(0));
|
|
99
120
|
};
|
|
100
121
|
var urlsafe_b64decode = (encoded) => {
|
|
101
122
|
return b64decode(encoded.replace(/_/g, "/").replace(/-/g, "+"));
|
|
102
123
|
};
|
|
103
124
|
var b64encode = (data) => {
|
|
104
|
-
return btoa(
|
|
125
|
+
return Base64.btoa(
|
|
105
126
|
String.fromCharCode.apply(null, Array.from(new Uint8Array(data)))
|
|
106
127
|
);
|
|
107
128
|
};
|
|
@@ -115,8 +136,7 @@ var LightsparkSigningException = class extends LightsparkException_default {
|
|
|
115
136
|
var LightsparkSigningException_default = LightsparkSigningException;
|
|
116
137
|
|
|
117
138
|
// src/crypto/crypto.ts
|
|
118
|
-
var
|
|
119
|
-
function getCrypto() {
|
|
139
|
+
var getCrypto = () => {
|
|
120
140
|
let cryptoImplPromise;
|
|
121
141
|
if (typeof crypto !== "undefined") {
|
|
122
142
|
cryptoImplPromise = Promise.resolve(crypto);
|
|
@@ -126,34 +146,26 @@ function getCrypto() {
|
|
|
126
146
|
});
|
|
127
147
|
}
|
|
128
148
|
return cryptoImplPromise;
|
|
129
|
-
}
|
|
130
|
-
var getRandomValues = async (arr) => {
|
|
131
|
-
if (typeof crypto !== "undefined") {
|
|
132
|
-
return crypto.getRandomValues(arr);
|
|
133
|
-
} else {
|
|
134
|
-
const cryptoImpl2 = await getCrypto();
|
|
135
|
-
return cryptoImpl2.getRandomValues(arr);
|
|
136
|
-
}
|
|
137
149
|
};
|
|
138
150
|
var getRandomValues32 = async (arr) => {
|
|
139
151
|
if (typeof crypto !== "undefined") {
|
|
140
152
|
return crypto.getRandomValues(arr);
|
|
141
153
|
} else {
|
|
142
|
-
const
|
|
143
|
-
return
|
|
154
|
+
const cryptoImpl = await getCrypto();
|
|
155
|
+
return cryptoImpl.getRandomValues(arr);
|
|
144
156
|
}
|
|
145
157
|
};
|
|
146
158
|
var deriveKey = async (password, salt, iterations, algorithm, bit_len) => {
|
|
147
159
|
const enc = new TextEncoder();
|
|
148
|
-
const
|
|
149
|
-
const password_key = await
|
|
160
|
+
const cryptoImpl = await getCrypto();
|
|
161
|
+
const password_key = await cryptoImpl.subtle.importKey(
|
|
150
162
|
"raw",
|
|
151
163
|
enc.encode(password),
|
|
152
164
|
"PBKDF2",
|
|
153
165
|
false,
|
|
154
166
|
["deriveBits", "deriveKey"]
|
|
155
167
|
);
|
|
156
|
-
const derived = await
|
|
168
|
+
const derived = await cryptoImpl.subtle.deriveBits(
|
|
157
169
|
{
|
|
158
170
|
name: "PBKDF2",
|
|
159
171
|
salt,
|
|
@@ -163,7 +175,7 @@ var deriveKey = async (password, salt, iterations, algorithm, bit_len) => {
|
|
|
163
175
|
password_key,
|
|
164
176
|
bit_len
|
|
165
177
|
);
|
|
166
|
-
const key = await
|
|
178
|
+
const key = await cryptoImpl.subtle.importKey(
|
|
167
179
|
"raw",
|
|
168
180
|
derived.slice(0, 32),
|
|
169
181
|
{ name: algorithm, length: 256 },
|
|
@@ -173,25 +185,6 @@ var deriveKey = async (password, salt, iterations, algorithm, bit_len) => {
|
|
|
173
185
|
const iv = derived.slice(32);
|
|
174
186
|
return [key, iv];
|
|
175
187
|
};
|
|
176
|
-
var encrypt = async (plaintext, password, salt) => {
|
|
177
|
-
if (!salt) {
|
|
178
|
-
salt = new Uint8Array(16);
|
|
179
|
-
getRandomValues(salt);
|
|
180
|
-
}
|
|
181
|
-
const [key, iv] = await deriveKey(password, salt, ITERATIONS, "AES-GCM", 352);
|
|
182
|
-
const cryptoImpl2 = await getCrypto();
|
|
183
|
-
const encrypted = new Uint8Array(
|
|
184
|
-
await cryptoImpl2.subtle.encrypt({ name: "AES-GCM", iv }, key, plaintext)
|
|
185
|
-
);
|
|
186
|
-
const output = new Uint8Array(salt.byteLength + encrypted.byteLength);
|
|
187
|
-
output.set(salt);
|
|
188
|
-
output.set(encrypted, salt.byteLength);
|
|
189
|
-
const header = {
|
|
190
|
-
v: 4,
|
|
191
|
-
i: ITERATIONS
|
|
192
|
-
};
|
|
193
|
-
return [JSON.stringify(header), b64encode(output)];
|
|
194
|
-
};
|
|
195
188
|
var decrypt = async (header_json, ciphertext, password) => {
|
|
196
189
|
var decoded = b64decode(ciphertext);
|
|
197
190
|
var header;
|
|
@@ -210,7 +203,7 @@ var decrypt = async (header_json, ciphertext, password) => {
|
|
|
210
203
|
"Unknown version ".concat(header.v)
|
|
211
204
|
);
|
|
212
205
|
}
|
|
213
|
-
const
|
|
206
|
+
const cryptoImpl = await getCrypto();
|
|
214
207
|
const algorithm = header.v < 2 ? "AES-CBC" : "AES-GCM";
|
|
215
208
|
const bit_len = header.v < 4 ? 384 : 352;
|
|
216
209
|
const salt_len = header.v < 4 ? 8 : 16;
|
|
@@ -225,7 +218,7 @@ var decrypt = async (header_json, ciphertext, password) => {
|
|
|
225
218
|
algorithm,
|
|
226
219
|
256
|
|
227
220
|
);
|
|
228
|
-
return await
|
|
221
|
+
return await cryptoImpl.subtle.decrypt(
|
|
229
222
|
{ name: algorithm, iv: nonce.buffer },
|
|
230
223
|
key,
|
|
231
224
|
cipherText
|
|
@@ -240,7 +233,7 @@ var decrypt = async (header_json, ciphertext, password) => {
|
|
|
240
233
|
algorithm,
|
|
241
234
|
bit_len
|
|
242
235
|
);
|
|
243
|
-
return await
|
|
236
|
+
return await cryptoImpl.subtle.decrypt(
|
|
244
237
|
{ name: algorithm, iv },
|
|
245
238
|
key,
|
|
246
239
|
encrypted
|
|
@@ -256,13 +249,9 @@ async function decryptSecretWithNodePassword(cipher, encryptedSecret, nodePasswo
|
|
|
256
249
|
}
|
|
257
250
|
return decryptedValue;
|
|
258
251
|
}
|
|
259
|
-
function decode(arrBuff) {
|
|
260
|
-
const dec = new TextDecoder();
|
|
261
|
-
return dec.decode(arrBuff);
|
|
262
|
-
}
|
|
263
252
|
var generateSigningKeyPair = async () => {
|
|
264
|
-
const
|
|
265
|
-
return await
|
|
253
|
+
const cryptoImpl = await getCrypto();
|
|
254
|
+
return await cryptoImpl.subtle.generateKey(
|
|
266
255
|
/*algorithm:*/
|
|
267
256
|
{
|
|
268
257
|
name: "RSA-PSS",
|
|
@@ -277,74 +266,72 @@ var generateSigningKeyPair = async () => {
|
|
|
277
266
|
);
|
|
278
267
|
};
|
|
279
268
|
var serializeSigningKey = async (key, format) => {
|
|
280
|
-
const
|
|
281
|
-
return await
|
|
269
|
+
const cryptoImpl = await getCrypto();
|
|
270
|
+
return await cryptoImpl.subtle.exportKey(
|
|
282
271
|
/*format*/
|
|
283
272
|
format,
|
|
284
273
|
/*key*/
|
|
285
274
|
key
|
|
286
275
|
);
|
|
287
276
|
};
|
|
288
|
-
var
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
277
|
+
var getNonce = async () => {
|
|
278
|
+
const cryptoImpl = await getCrypto();
|
|
279
|
+
const nonceSt = await getRandomValues32(new Uint32Array(1));
|
|
280
|
+
return Number(nonceSt);
|
|
281
|
+
};
|
|
282
|
+
var sign = async (key, data) => {
|
|
283
|
+
const cryptoImpl = await getCrypto();
|
|
284
|
+
return await cryptoImpl.subtle.sign(
|
|
293
285
|
{
|
|
294
|
-
name: "RSA-
|
|
286
|
+
name: "RSA-PSS",
|
|
287
|
+
saltLength: 32
|
|
295
288
|
},
|
|
296
|
-
/*key*/
|
|
297
289
|
key,
|
|
298
|
-
|
|
299
|
-
encoded
|
|
290
|
+
data
|
|
300
291
|
);
|
|
301
|
-
return b64encode(encrypted);
|
|
302
292
|
};
|
|
303
|
-
var
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
return await cryptoImpl2.subtle.importKey(
|
|
293
|
+
var importPrivateSigningKey = async (keyData, format) => {
|
|
294
|
+
const cryptoImpl = await getCrypto();
|
|
295
|
+
return await cryptoImpl.subtle.importKey(
|
|
307
296
|
/*format*/
|
|
308
|
-
|
|
297
|
+
format,
|
|
309
298
|
/*keyData*/
|
|
310
|
-
|
|
311
|
-
/*algorithm
|
|
299
|
+
keyData,
|
|
300
|
+
/*algorithm*/
|
|
312
301
|
{
|
|
313
|
-
name: "RSA-
|
|
302
|
+
name: "RSA-PSS",
|
|
314
303
|
hash: "SHA-256"
|
|
315
304
|
},
|
|
316
305
|
/*extractable*/
|
|
317
306
|
true,
|
|
318
307
|
/*keyUsages*/
|
|
319
|
-
["
|
|
308
|
+
["sign"]
|
|
320
309
|
);
|
|
321
310
|
};
|
|
322
|
-
var
|
|
323
|
-
|
|
324
|
-
|
|
311
|
+
var DefaultCrypto = {
|
|
312
|
+
decryptSecretWithNodePassword,
|
|
313
|
+
generateSigningKeyPair,
|
|
314
|
+
serializeSigningKey,
|
|
315
|
+
getNonce,
|
|
316
|
+
sign,
|
|
317
|
+
importPrivateSigningKey
|
|
325
318
|
};
|
|
326
319
|
|
|
327
320
|
// src/crypto/NodeKeyCache.ts
|
|
328
321
|
var import_auto_bind = __toESM(require("auto-bind"), 1);
|
|
329
322
|
var NodeKeyCache = class {
|
|
330
|
-
|
|
331
|
-
|
|
323
|
+
constructor(cryptoImpl = DefaultCrypto) {
|
|
324
|
+
this.cryptoImpl = cryptoImpl;
|
|
332
325
|
this.idToKey = /* @__PURE__ */ new Map();
|
|
333
326
|
(0, import_auto_bind.default)(this);
|
|
334
327
|
}
|
|
335
|
-
|
|
336
|
-
|
|
328
|
+
idToKey;
|
|
329
|
+
async loadKey(id, rawKey, format = "pkcs8") {
|
|
330
|
+
const decoded = b64decode(this.stripPemTags(rawKey));
|
|
337
331
|
try {
|
|
338
|
-
const
|
|
339
|
-
const key = await cryptoImpl2.subtle.importKey(
|
|
340
|
-
"pkcs8",
|
|
332
|
+
const key = await this.cryptoImpl.importPrivateSigningKey(
|
|
341
333
|
decoded,
|
|
342
|
-
|
|
343
|
-
name: "RSA-PSS",
|
|
344
|
-
hash: "SHA-256"
|
|
345
|
-
},
|
|
346
|
-
true,
|
|
347
|
-
["sign"]
|
|
334
|
+
format
|
|
348
335
|
);
|
|
349
336
|
this.idToKey.set(id, key);
|
|
350
337
|
return key;
|
|
@@ -359,6 +346,9 @@ var NodeKeyCache = class {
|
|
|
359
346
|
hasKey(id) {
|
|
360
347
|
return this.idToKey.has(id);
|
|
361
348
|
}
|
|
349
|
+
stripPemTags(pem) {
|
|
350
|
+
return pem.replace(/-----BEGIN (.*)-----/, "").replace(/-----END (.*)----/, "");
|
|
351
|
+
}
|
|
362
352
|
};
|
|
363
353
|
var NodeKeyCache_default = NodeKeyCache;
|
|
364
354
|
|
|
@@ -380,12 +370,13 @@ var LIGHTSPARK_BETA_HEADER_KEY = "X-Lightspark-Beta";
|
|
|
380
370
|
var LIGHTSPARK_BETA_HEADER_VALUE = "z2h0BBYxTA83cjW7fi8QwWtBPCzkQKiemcuhKY08LOo";
|
|
381
371
|
import_dayjs.default.extend(import_utc.default);
|
|
382
372
|
var Requester = class {
|
|
383
|
-
constructor(nodeKeyCache, schemaEndpoint, sdkUserAgent, authProvider = new StubAuthProvider(), baseUrl = DEFAULT_BASE_URL) {
|
|
373
|
+
constructor(nodeKeyCache, schemaEndpoint, sdkUserAgent, authProvider = new StubAuthProvider(), baseUrl = DEFAULT_BASE_URL, cryptoImpl = DefaultCrypto) {
|
|
384
374
|
this.nodeKeyCache = nodeKeyCache;
|
|
385
375
|
this.schemaEndpoint = schemaEndpoint;
|
|
386
376
|
this.sdkUserAgent = sdkUserAgent;
|
|
387
377
|
this.authProvider = authProvider;
|
|
388
378
|
this.baseUrl = baseUrl;
|
|
379
|
+
this.cryptoImpl = cryptoImpl;
|
|
389
380
|
let websocketImpl;
|
|
390
381
|
if (typeof WebSocket === "undefined" && typeof window === "undefined") {
|
|
391
382
|
websocketImpl = import_ws.default;
|
|
@@ -511,7 +502,7 @@ var Requester = class {
|
|
|
511
502
|
const query = queryPayload.query;
|
|
512
503
|
const variables = queryPayload.variables;
|
|
513
504
|
const operationName = queryPayload.operationName;
|
|
514
|
-
const nonce = await getNonce();
|
|
505
|
+
const nonce = await this.cryptoImpl.getNonce();
|
|
515
506
|
const expiration = import_dayjs.default.utc().add(1, "hour").format();
|
|
516
507
|
const payload = {
|
|
517
508
|
query,
|
|
@@ -526,16 +517,11 @@ var Requester = class {
|
|
|
526
517
|
"Missing node of encrypted_signing_private_key"
|
|
527
518
|
);
|
|
528
519
|
}
|
|
520
|
+
if (typeof TextEncoder === "undefined") {
|
|
521
|
+
const TextEncoder2 = (await import("text-encoding")).TextEncoder;
|
|
522
|
+
}
|
|
529
523
|
const encodedPayload = new TextEncoder().encode(JSON.stringify(payload));
|
|
530
|
-
const
|
|
531
|
-
const signedPayload = await cryptoImpl2.subtle.sign(
|
|
532
|
-
{
|
|
533
|
-
name: "RSA-PSS",
|
|
534
|
-
saltLength: 32
|
|
535
|
-
},
|
|
536
|
-
key,
|
|
537
|
-
encodedPayload
|
|
538
|
-
);
|
|
524
|
+
const signedPayload = await this.cryptoImpl.sign(key, encodedPayload);
|
|
539
525
|
const encodedSignedPayload = b64encode(signedPayload);
|
|
540
526
|
headers["X-Lightspark-Signing"] = JSON.stringify({
|
|
541
527
|
v: "1",
|
|
@@ -635,6 +621,7 @@ var isType = (typename) => (node) => {
|
|
|
635
621
|
};
|
|
636
622
|
// Annotate the CommonJS export names for ESM import in node:
|
|
637
623
|
0 && (module.exports = {
|
|
624
|
+
DefaultCrypto,
|
|
638
625
|
LightsparkAuthException,
|
|
639
626
|
LightsparkException,
|
|
640
627
|
LightsparkSigningException,
|
|
@@ -646,18 +633,8 @@ var isType = (typename) => (node) => {
|
|
|
646
633
|
b64decode,
|
|
647
634
|
b64encode,
|
|
648
635
|
convertCurrencyAmount,
|
|
649
|
-
decode,
|
|
650
|
-
decrypt,
|
|
651
|
-
decryptSecretWithNodePassword,
|
|
652
|
-
encrypt,
|
|
653
|
-
encryptWithNodeKey,
|
|
654
|
-
generateSigningKeyPair,
|
|
655
|
-
getCrypto,
|
|
656
|
-
getNonce,
|
|
657
636
|
isBrowser,
|
|
658
637
|
isNode,
|
|
659
638
|
isType,
|
|
660
|
-
loadNodeEncryptionKey,
|
|
661
|
-
serializeSigningKey,
|
|
662
639
|
urlsafe_b64decode
|
|
663
640
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -27,26 +27,38 @@ declare class LightsparkSigningException extends LightsparkException {
|
|
|
27
27
|
constructor(message: string, extraInfo?: any);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
type CryptoInterface = {
|
|
31
|
+
decryptSecretWithNodePassword: (cipher: string, encryptedSecret: string, nodePassword: string) => Promise<ArrayBuffer | null>;
|
|
32
|
+
generateSigningKeyPair: () => Promise<{
|
|
33
|
+
publicKey: CryptoKey | string;
|
|
34
|
+
privateKey: CryptoKey | string;
|
|
35
|
+
}>;
|
|
36
|
+
serializeSigningKey: (key: CryptoKey | string, format: "pkcs8" | "spki") => Promise<ArrayBuffer>;
|
|
37
|
+
getNonce: () => Promise<number>;
|
|
38
|
+
sign: (key: CryptoKey | Uint8Array, data: Uint8Array) => Promise<ArrayBuffer>;
|
|
39
|
+
importPrivateSigningKey: (keyData: Uint8Array, format: "pkcs8" | "spki") => Promise<CryptoKey | Uint8Array>;
|
|
40
|
+
};
|
|
36
41
|
declare function decryptSecretWithNodePassword(cipher: string, encryptedSecret: string, nodePassword: string): Promise<ArrayBuffer | null>;
|
|
37
|
-
declare
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
declare const DefaultCrypto: {
|
|
43
|
+
decryptSecretWithNodePassword: typeof decryptSecretWithNodePassword;
|
|
44
|
+
generateSigningKeyPair: () => Promise<CryptoKeyPair | {
|
|
45
|
+
publicKey: string;
|
|
46
|
+
privateKey: string;
|
|
47
|
+
}>;
|
|
48
|
+
serializeSigningKey: (key: CryptoKey | string, format: "pkcs8" | "spki") => Promise<ArrayBuffer>;
|
|
49
|
+
getNonce: () => Promise<number>;
|
|
50
|
+
sign: (key: CryptoKey | Uint8Array, data: Uint8Array) => Promise<ArrayBuffer>;
|
|
51
|
+
importPrivateSigningKey: (keyData: Uint8Array, format: "pkcs8" | "spki") => Promise<CryptoKey | Uint8Array>;
|
|
52
|
+
};
|
|
43
53
|
|
|
44
54
|
declare class NodeKeyCache {
|
|
55
|
+
private readonly cryptoImpl;
|
|
45
56
|
private idToKey;
|
|
46
|
-
constructor();
|
|
47
|
-
loadKey(id: string, rawKey: string): Promise<CryptoKey | null>;
|
|
48
|
-
getKey(id: string): CryptoKey | undefined;
|
|
57
|
+
constructor(cryptoImpl?: CryptoInterface);
|
|
58
|
+
loadKey(id: string, rawKey: string, format?: "pkcs8" | "spki"): Promise<CryptoKey | Uint8Array | null>;
|
|
59
|
+
getKey(id: string): CryptoKey | Uint8Array | undefined;
|
|
49
60
|
hasKey(id: string): boolean;
|
|
61
|
+
private stripPemTags;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
type Query<T> = {
|
|
@@ -68,8 +80,9 @@ declare class Requester {
|
|
|
68
80
|
private readonly sdkUserAgent;
|
|
69
81
|
private readonly authProvider;
|
|
70
82
|
private readonly baseUrl;
|
|
83
|
+
private readonly cryptoImpl;
|
|
71
84
|
private readonly wsClient;
|
|
72
|
-
constructor(nodeKeyCache: NodeKeyCache, schemaEndpoint: string, sdkUserAgent: string, authProvider?: AuthProvider, baseUrl?: string);
|
|
85
|
+
constructor(nodeKeyCache: NodeKeyCache, schemaEndpoint: string, sdkUserAgent: string, authProvider?: AuthProvider, baseUrl?: string, cryptoImpl?: CryptoInterface);
|
|
73
86
|
executeQuery<T>(query: Query<T>): Promise<T | null>;
|
|
74
87
|
subscribe(queryPayload: string, variables?: {
|
|
75
88
|
[key: string]: any;
|
|
@@ -150,4 +163,4 @@ declare const isType: <T extends string>(typename: T) => <N extends {
|
|
|
150
163
|
__typename: T;
|
|
151
164
|
}>;
|
|
152
165
|
|
|
153
|
-
export { AuthProvider, ById, ExpandRecursively, LightsparkAuthException, LightsparkException, LightsparkSigningException, Maybe, NodeKeyCache, OmitTypename, Query, Requester, ServerEnvironment, StubAuthProvider, apiDomainForEnvironment, b64decode, b64encode, convertCurrencyAmount,
|
|
166
|
+
export { AuthProvider, ById, CryptoInterface, DefaultCrypto, ExpandRecursively, LightsparkAuthException, LightsparkException, LightsparkSigningException, Maybe, NodeKeyCache, OmitTypename, Query, Requester, ServerEnvironment, StubAuthProvider, apiDomainForEnvironment, b64decode, b64encode, convertCurrencyAmount, isBrowser, isNode, isType, urlsafe_b64decode };
|
package/dist/index.js
CHANGED
|
@@ -34,14 +34,44 @@ var StubAuthProvider = class {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
// src/utils/base64.ts
|
|
37
|
+
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
38
|
+
var Base64 = {
|
|
39
|
+
btoa: (input = "") => {
|
|
40
|
+
let str = input;
|
|
41
|
+
let output = "";
|
|
42
|
+
for (let block = 0, charCode, i = 0, map = chars; str.charAt(i | 0) || (map = "=", i % 1); output += map.charAt(63 & block >> 8 - i % 1 * 8)) {
|
|
43
|
+
charCode = str.charCodeAt(i += 3 / 4);
|
|
44
|
+
if (charCode > 255) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
block = block << 8 | charCode;
|
|
50
|
+
}
|
|
51
|
+
return output;
|
|
52
|
+
},
|
|
53
|
+
atob: (input = "") => {
|
|
54
|
+
let str = input.replace(/=+$/, "");
|
|
55
|
+
let output = "";
|
|
56
|
+
if (str.length % 4 == 1) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"'atob' failed: The string to be decoded is not correctly encoded."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
for (let bc = 0, bs = 0, buffer, i = 0; buffer = str.charAt(i++); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0) {
|
|
62
|
+
buffer = chars.indexOf(buffer);
|
|
63
|
+
}
|
|
64
|
+
return output;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
37
67
|
var b64decode = (encoded) => {
|
|
38
|
-
return Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
|
|
68
|
+
return Uint8Array.from(Base64.atob(encoded), (c) => c.charCodeAt(0));
|
|
39
69
|
};
|
|
40
70
|
var urlsafe_b64decode = (encoded) => {
|
|
41
71
|
return b64decode(encoded.replace(/_/g, "/").replace(/-/g, "+"));
|
|
42
72
|
};
|
|
43
73
|
var b64encode = (data) => {
|
|
44
|
-
return btoa(
|
|
74
|
+
return Base64.btoa(
|
|
45
75
|
String.fromCharCode.apply(null, Array.from(new Uint8Array(data)))
|
|
46
76
|
);
|
|
47
77
|
};
|
|
@@ -55,8 +85,7 @@ var LightsparkSigningException = class extends LightsparkException_default {
|
|
|
55
85
|
var LightsparkSigningException_default = LightsparkSigningException;
|
|
56
86
|
|
|
57
87
|
// src/crypto/crypto.ts
|
|
58
|
-
var
|
|
59
|
-
function getCrypto() {
|
|
88
|
+
var getCrypto = () => {
|
|
60
89
|
let cryptoImplPromise;
|
|
61
90
|
if (typeof crypto !== "undefined") {
|
|
62
91
|
cryptoImplPromise = Promise.resolve(crypto);
|
|
@@ -66,34 +95,26 @@ function getCrypto() {
|
|
|
66
95
|
});
|
|
67
96
|
}
|
|
68
97
|
return cryptoImplPromise;
|
|
69
|
-
}
|
|
70
|
-
var getRandomValues = async (arr) => {
|
|
71
|
-
if (typeof crypto !== "undefined") {
|
|
72
|
-
return crypto.getRandomValues(arr);
|
|
73
|
-
} else {
|
|
74
|
-
const cryptoImpl2 = await getCrypto();
|
|
75
|
-
return cryptoImpl2.getRandomValues(arr);
|
|
76
|
-
}
|
|
77
98
|
};
|
|
78
99
|
var getRandomValues32 = async (arr) => {
|
|
79
100
|
if (typeof crypto !== "undefined") {
|
|
80
101
|
return crypto.getRandomValues(arr);
|
|
81
102
|
} else {
|
|
82
|
-
const
|
|
83
|
-
return
|
|
103
|
+
const cryptoImpl = await getCrypto();
|
|
104
|
+
return cryptoImpl.getRandomValues(arr);
|
|
84
105
|
}
|
|
85
106
|
};
|
|
86
107
|
var deriveKey = async (password, salt, iterations, algorithm, bit_len) => {
|
|
87
108
|
const enc = new TextEncoder();
|
|
88
|
-
const
|
|
89
|
-
const password_key = await
|
|
109
|
+
const cryptoImpl = await getCrypto();
|
|
110
|
+
const password_key = await cryptoImpl.subtle.importKey(
|
|
90
111
|
"raw",
|
|
91
112
|
enc.encode(password),
|
|
92
113
|
"PBKDF2",
|
|
93
114
|
false,
|
|
94
115
|
["deriveBits", "deriveKey"]
|
|
95
116
|
);
|
|
96
|
-
const derived = await
|
|
117
|
+
const derived = await cryptoImpl.subtle.deriveBits(
|
|
97
118
|
{
|
|
98
119
|
name: "PBKDF2",
|
|
99
120
|
salt,
|
|
@@ -103,7 +124,7 @@ var deriveKey = async (password, salt, iterations, algorithm, bit_len) => {
|
|
|
103
124
|
password_key,
|
|
104
125
|
bit_len
|
|
105
126
|
);
|
|
106
|
-
const key = await
|
|
127
|
+
const key = await cryptoImpl.subtle.importKey(
|
|
107
128
|
"raw",
|
|
108
129
|
derived.slice(0, 32),
|
|
109
130
|
{ name: algorithm, length: 256 },
|
|
@@ -113,25 +134,6 @@ var deriveKey = async (password, salt, iterations, algorithm, bit_len) => {
|
|
|
113
134
|
const iv = derived.slice(32);
|
|
114
135
|
return [key, iv];
|
|
115
136
|
};
|
|
116
|
-
var encrypt = async (plaintext, password, salt) => {
|
|
117
|
-
if (!salt) {
|
|
118
|
-
salt = new Uint8Array(16);
|
|
119
|
-
getRandomValues(salt);
|
|
120
|
-
}
|
|
121
|
-
const [key, iv] = await deriveKey(password, salt, ITERATIONS, "AES-GCM", 352);
|
|
122
|
-
const cryptoImpl2 = await getCrypto();
|
|
123
|
-
const encrypted = new Uint8Array(
|
|
124
|
-
await cryptoImpl2.subtle.encrypt({ name: "AES-GCM", iv }, key, plaintext)
|
|
125
|
-
);
|
|
126
|
-
const output = new Uint8Array(salt.byteLength + encrypted.byteLength);
|
|
127
|
-
output.set(salt);
|
|
128
|
-
output.set(encrypted, salt.byteLength);
|
|
129
|
-
const header = {
|
|
130
|
-
v: 4,
|
|
131
|
-
i: ITERATIONS
|
|
132
|
-
};
|
|
133
|
-
return [JSON.stringify(header), b64encode(output)];
|
|
134
|
-
};
|
|
135
137
|
var decrypt = async (header_json, ciphertext, password) => {
|
|
136
138
|
var decoded = b64decode(ciphertext);
|
|
137
139
|
var header;
|
|
@@ -150,7 +152,7 @@ var decrypt = async (header_json, ciphertext, password) => {
|
|
|
150
152
|
"Unknown version ".concat(header.v)
|
|
151
153
|
);
|
|
152
154
|
}
|
|
153
|
-
const
|
|
155
|
+
const cryptoImpl = await getCrypto();
|
|
154
156
|
const algorithm = header.v < 2 ? "AES-CBC" : "AES-GCM";
|
|
155
157
|
const bit_len = header.v < 4 ? 384 : 352;
|
|
156
158
|
const salt_len = header.v < 4 ? 8 : 16;
|
|
@@ -165,7 +167,7 @@ var decrypt = async (header_json, ciphertext, password) => {
|
|
|
165
167
|
algorithm,
|
|
166
168
|
256
|
|
167
169
|
);
|
|
168
|
-
return await
|
|
170
|
+
return await cryptoImpl.subtle.decrypt(
|
|
169
171
|
{ name: algorithm, iv: nonce.buffer },
|
|
170
172
|
key,
|
|
171
173
|
cipherText
|
|
@@ -180,7 +182,7 @@ var decrypt = async (header_json, ciphertext, password) => {
|
|
|
180
182
|
algorithm,
|
|
181
183
|
bit_len
|
|
182
184
|
);
|
|
183
|
-
return await
|
|
185
|
+
return await cryptoImpl.subtle.decrypt(
|
|
184
186
|
{ name: algorithm, iv },
|
|
185
187
|
key,
|
|
186
188
|
encrypted
|
|
@@ -196,13 +198,9 @@ async function decryptSecretWithNodePassword(cipher, encryptedSecret, nodePasswo
|
|
|
196
198
|
}
|
|
197
199
|
return decryptedValue;
|
|
198
200
|
}
|
|
199
|
-
function decode(arrBuff) {
|
|
200
|
-
const dec = new TextDecoder();
|
|
201
|
-
return dec.decode(arrBuff);
|
|
202
|
-
}
|
|
203
201
|
var generateSigningKeyPair = async () => {
|
|
204
|
-
const
|
|
205
|
-
return await
|
|
202
|
+
const cryptoImpl = await getCrypto();
|
|
203
|
+
return await cryptoImpl.subtle.generateKey(
|
|
206
204
|
/*algorithm:*/
|
|
207
205
|
{
|
|
208
206
|
name: "RSA-PSS",
|
|
@@ -217,74 +215,72 @@ var generateSigningKeyPair = async () => {
|
|
|
217
215
|
);
|
|
218
216
|
};
|
|
219
217
|
var serializeSigningKey = async (key, format) => {
|
|
220
|
-
const
|
|
221
|
-
return await
|
|
218
|
+
const cryptoImpl = await getCrypto();
|
|
219
|
+
return await cryptoImpl.subtle.exportKey(
|
|
222
220
|
/*format*/
|
|
223
221
|
format,
|
|
224
222
|
/*key*/
|
|
225
223
|
key
|
|
226
224
|
);
|
|
227
225
|
};
|
|
228
|
-
var
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
var getNonce = async () => {
|
|
227
|
+
const cryptoImpl = await getCrypto();
|
|
228
|
+
const nonceSt = await getRandomValues32(new Uint32Array(1));
|
|
229
|
+
return Number(nonceSt);
|
|
230
|
+
};
|
|
231
|
+
var sign = async (key, data) => {
|
|
232
|
+
const cryptoImpl = await getCrypto();
|
|
233
|
+
return await cryptoImpl.subtle.sign(
|
|
233
234
|
{
|
|
234
|
-
name: "RSA-
|
|
235
|
+
name: "RSA-PSS",
|
|
236
|
+
saltLength: 32
|
|
235
237
|
},
|
|
236
|
-
/*key*/
|
|
237
238
|
key,
|
|
238
|
-
|
|
239
|
-
encoded
|
|
239
|
+
data
|
|
240
240
|
);
|
|
241
|
-
return b64encode(encrypted);
|
|
242
241
|
};
|
|
243
|
-
var
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
return await cryptoImpl2.subtle.importKey(
|
|
242
|
+
var importPrivateSigningKey = async (keyData, format) => {
|
|
243
|
+
const cryptoImpl = await getCrypto();
|
|
244
|
+
return await cryptoImpl.subtle.importKey(
|
|
247
245
|
/*format*/
|
|
248
|
-
|
|
246
|
+
format,
|
|
249
247
|
/*keyData*/
|
|
250
|
-
|
|
251
|
-
/*algorithm
|
|
248
|
+
keyData,
|
|
249
|
+
/*algorithm*/
|
|
252
250
|
{
|
|
253
|
-
name: "RSA-
|
|
251
|
+
name: "RSA-PSS",
|
|
254
252
|
hash: "SHA-256"
|
|
255
253
|
},
|
|
256
254
|
/*extractable*/
|
|
257
255
|
true,
|
|
258
256
|
/*keyUsages*/
|
|
259
|
-
["
|
|
257
|
+
["sign"]
|
|
260
258
|
);
|
|
261
259
|
};
|
|
262
|
-
var
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
var DefaultCrypto = {
|
|
261
|
+
decryptSecretWithNodePassword,
|
|
262
|
+
generateSigningKeyPair,
|
|
263
|
+
serializeSigningKey,
|
|
264
|
+
getNonce,
|
|
265
|
+
sign,
|
|
266
|
+
importPrivateSigningKey
|
|
265
267
|
};
|
|
266
268
|
|
|
267
269
|
// src/crypto/NodeKeyCache.ts
|
|
268
270
|
import autoBind from "auto-bind";
|
|
269
271
|
var NodeKeyCache = class {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
constructor(cryptoImpl = DefaultCrypto) {
|
|
273
|
+
this.cryptoImpl = cryptoImpl;
|
|
272
274
|
this.idToKey = /* @__PURE__ */ new Map();
|
|
273
275
|
autoBind(this);
|
|
274
276
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
+
idToKey;
|
|
278
|
+
async loadKey(id, rawKey, format = "pkcs8") {
|
|
279
|
+
const decoded = b64decode(this.stripPemTags(rawKey));
|
|
277
280
|
try {
|
|
278
|
-
const
|
|
279
|
-
const key = await cryptoImpl2.subtle.importKey(
|
|
280
|
-
"pkcs8",
|
|
281
|
+
const key = await this.cryptoImpl.importPrivateSigningKey(
|
|
281
282
|
decoded,
|
|
282
|
-
|
|
283
|
-
name: "RSA-PSS",
|
|
284
|
-
hash: "SHA-256"
|
|
285
|
-
},
|
|
286
|
-
true,
|
|
287
|
-
["sign"]
|
|
283
|
+
format
|
|
288
284
|
);
|
|
289
285
|
this.idToKey.set(id, key);
|
|
290
286
|
return key;
|
|
@@ -299,6 +295,9 @@ var NodeKeyCache = class {
|
|
|
299
295
|
hasKey(id) {
|
|
300
296
|
return this.idToKey.has(id);
|
|
301
297
|
}
|
|
298
|
+
stripPemTags(pem) {
|
|
299
|
+
return pem.replace(/-----BEGIN (.*)-----/, "").replace(/-----END (.*)----/, "");
|
|
300
|
+
}
|
|
302
301
|
};
|
|
303
302
|
var NodeKeyCache_default = NodeKeyCache;
|
|
304
303
|
|
|
@@ -320,12 +319,13 @@ var LIGHTSPARK_BETA_HEADER_KEY = "X-Lightspark-Beta";
|
|
|
320
319
|
var LIGHTSPARK_BETA_HEADER_VALUE = "z2h0BBYxTA83cjW7fi8QwWtBPCzkQKiemcuhKY08LOo";
|
|
321
320
|
dayjs.extend(utc);
|
|
322
321
|
var Requester = class {
|
|
323
|
-
constructor(nodeKeyCache, schemaEndpoint, sdkUserAgent, authProvider = new StubAuthProvider(), baseUrl = DEFAULT_BASE_URL) {
|
|
322
|
+
constructor(nodeKeyCache, schemaEndpoint, sdkUserAgent, authProvider = new StubAuthProvider(), baseUrl = DEFAULT_BASE_URL, cryptoImpl = DefaultCrypto) {
|
|
324
323
|
this.nodeKeyCache = nodeKeyCache;
|
|
325
324
|
this.schemaEndpoint = schemaEndpoint;
|
|
326
325
|
this.sdkUserAgent = sdkUserAgent;
|
|
327
326
|
this.authProvider = authProvider;
|
|
328
327
|
this.baseUrl = baseUrl;
|
|
328
|
+
this.cryptoImpl = cryptoImpl;
|
|
329
329
|
let websocketImpl;
|
|
330
330
|
if (typeof WebSocket === "undefined" && typeof window === "undefined") {
|
|
331
331
|
websocketImpl = NodeWebSocket;
|
|
@@ -451,7 +451,7 @@ var Requester = class {
|
|
|
451
451
|
const query = queryPayload.query;
|
|
452
452
|
const variables = queryPayload.variables;
|
|
453
453
|
const operationName = queryPayload.operationName;
|
|
454
|
-
const nonce = await getNonce();
|
|
454
|
+
const nonce = await this.cryptoImpl.getNonce();
|
|
455
455
|
const expiration = dayjs.utc().add(1, "hour").format();
|
|
456
456
|
const payload = {
|
|
457
457
|
query,
|
|
@@ -466,16 +466,11 @@ var Requester = class {
|
|
|
466
466
|
"Missing node of encrypted_signing_private_key"
|
|
467
467
|
);
|
|
468
468
|
}
|
|
469
|
+
if (typeof TextEncoder === "undefined") {
|
|
470
|
+
const TextEncoder2 = (await import("text-encoding")).TextEncoder;
|
|
471
|
+
}
|
|
469
472
|
const encodedPayload = new TextEncoder().encode(JSON.stringify(payload));
|
|
470
|
-
const
|
|
471
|
-
const signedPayload = await cryptoImpl2.subtle.sign(
|
|
472
|
-
{
|
|
473
|
-
name: "RSA-PSS",
|
|
474
|
-
saltLength: 32
|
|
475
|
-
},
|
|
476
|
-
key,
|
|
477
|
-
encodedPayload
|
|
478
|
-
);
|
|
473
|
+
const signedPayload = await this.cryptoImpl.sign(key, encodedPayload);
|
|
479
474
|
const encodedSignedPayload = b64encode(signedPayload);
|
|
480
475
|
headers["X-Lightspark-Signing"] = JSON.stringify({
|
|
481
476
|
v: "1",
|
|
@@ -574,6 +569,7 @@ var isType = (typename) => (node) => {
|
|
|
574
569
|
return node?.__typename === typename;
|
|
575
570
|
};
|
|
576
571
|
export {
|
|
572
|
+
DefaultCrypto,
|
|
577
573
|
LightsparkAuthException_default as LightsparkAuthException,
|
|
578
574
|
LightsparkException_default as LightsparkException,
|
|
579
575
|
LightsparkSigningException_default as LightsparkSigningException,
|
|
@@ -585,18 +581,8 @@ export {
|
|
|
585
581
|
b64decode,
|
|
586
582
|
b64encode,
|
|
587
583
|
convertCurrencyAmount,
|
|
588
|
-
decode,
|
|
589
|
-
decrypt,
|
|
590
|
-
decryptSecretWithNodePassword,
|
|
591
|
-
encrypt,
|
|
592
|
-
encryptWithNodeKey,
|
|
593
|
-
generateSigningKeyPair,
|
|
594
|
-
getCrypto,
|
|
595
|
-
getNonce,
|
|
596
584
|
isBrowser,
|
|
597
585
|
isNode,
|
|
598
586
|
isType,
|
|
599
|
-
loadNodeEncryptionKey,
|
|
600
|
-
serializeSigningKey,
|
|
601
587
|
urlsafe_b64decode
|
|
602
588
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightsparkdev/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Lightspark JS SDK",
|
|
5
5
|
"author": "Lightspark Inc.",
|
|
6
6
|
"keywords": [
|
|
@@ -22,6 +22,19 @@
|
|
|
22
22
|
"main": "./dist/index.js",
|
|
23
23
|
"module": "./dist/index.js",
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"require": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.cjs"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
25
38
|
"engines": {
|
|
26
39
|
"node": ">=14.16"
|
|
27
40
|
},
|
|
@@ -52,6 +65,7 @@
|
|
|
52
65
|
"dayjs": "^1.11.7",
|
|
53
66
|
"graphql": "^16.6.0",
|
|
54
67
|
"graphql-ws": "^5.11.3",
|
|
68
|
+
"text-encoding": "^0.7.0",
|
|
55
69
|
"ws": "^8.12.1",
|
|
56
70
|
"zen-observable-ts": "^1.1.0"
|
|
57
71
|
},
|
|
@@ -3,28 +3,25 @@
|
|
|
3
3
|
import autoBind from "auto-bind";
|
|
4
4
|
|
|
5
5
|
import { b64decode } from "../utils/base64.js";
|
|
6
|
-
import {
|
|
6
|
+
import { CryptoInterface, DefaultCrypto } from "./crypto.js";
|
|
7
7
|
|
|
8
8
|
class NodeKeyCache {
|
|
9
|
-
private idToKey: Map<string, CryptoKey>;
|
|
10
|
-
constructor() {
|
|
9
|
+
private idToKey: Map<string, CryptoKey | Uint8Array>;
|
|
10
|
+
constructor(private readonly cryptoImpl: CryptoInterface = DefaultCrypto) {
|
|
11
11
|
this.idToKey = new Map();
|
|
12
12
|
autoBind(this);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
public async loadKey(
|
|
16
|
-
|
|
15
|
+
public async loadKey(
|
|
16
|
+
id: string,
|
|
17
|
+
rawKey: string,
|
|
18
|
+
format: "pkcs8" | "spki" = "pkcs8"
|
|
19
|
+
): Promise<CryptoKey | Uint8Array | null> {
|
|
20
|
+
const decoded = b64decode(this.stripPemTags(rawKey));
|
|
17
21
|
try {
|
|
18
|
-
const
|
|
19
|
-
const key = await cryptoImpl.subtle.importKey(
|
|
20
|
-
"pkcs8",
|
|
22
|
+
const key = await this.cryptoImpl.importPrivateSigningKey(
|
|
21
23
|
decoded,
|
|
22
|
-
|
|
23
|
-
name: "RSA-PSS",
|
|
24
|
-
hash: "SHA-256",
|
|
25
|
-
},
|
|
26
|
-
true,
|
|
27
|
-
["sign"]
|
|
24
|
+
format
|
|
28
25
|
);
|
|
29
26
|
this.idToKey.set(id, key);
|
|
30
27
|
return key;
|
|
@@ -34,13 +31,19 @@ class NodeKeyCache {
|
|
|
34
31
|
return null;
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
public getKey(id: string): CryptoKey | undefined {
|
|
34
|
+
public getKey(id: string): CryptoKey | Uint8Array | undefined {
|
|
38
35
|
return this.idToKey.get(id);
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
public hasKey(id: string): boolean {
|
|
42
39
|
return this.idToKey.has(id);
|
|
43
40
|
}
|
|
41
|
+
|
|
42
|
+
private stripPemTags(pem: string): string {
|
|
43
|
+
return pem
|
|
44
|
+
.replace(/-----BEGIN (.*)-----/, "")
|
|
45
|
+
.replace(/-----END (.*)----/, "");
|
|
46
|
+
}
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export default NodeKeyCache;
|
package/src/crypto/crypto.ts
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved
|
|
2
2
|
import LightsparkException from "../LightsparkException.js";
|
|
3
3
|
|
|
4
|
-
import { b64decode
|
|
4
|
+
import { b64decode } from "../utils/base64.js";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
export type CryptoInterface = {
|
|
7
|
+
decryptSecretWithNodePassword: (
|
|
8
|
+
cipher: string,
|
|
9
|
+
encryptedSecret: string,
|
|
10
|
+
nodePassword: string
|
|
11
|
+
) => Promise<ArrayBuffer | null>;
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
generateSigningKeyPair: () => Promise<{
|
|
14
|
+
publicKey: CryptoKey | string;
|
|
15
|
+
privateKey: CryptoKey | string;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
serializeSigningKey: (
|
|
19
|
+
key: CryptoKey | string,
|
|
20
|
+
format: "pkcs8" | "spki"
|
|
21
|
+
) => Promise<ArrayBuffer>;
|
|
22
|
+
|
|
23
|
+
getNonce: () => Promise<number>;
|
|
24
|
+
|
|
25
|
+
sign: (key: CryptoKey | Uint8Array, data: Uint8Array) => Promise<ArrayBuffer>;
|
|
26
|
+
|
|
27
|
+
importPrivateSigningKey: (
|
|
28
|
+
keyData: Uint8Array,
|
|
29
|
+
format: "pkcs8" | "spki"
|
|
30
|
+
) => Promise<CryptoKey | Uint8Array>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getCrypto = () => {
|
|
9
34
|
let cryptoImplPromise: Promise<typeof crypto>;
|
|
10
35
|
if (typeof crypto !== "undefined") {
|
|
11
36
|
cryptoImplPromise = Promise.resolve(crypto);
|
|
@@ -15,15 +40,6 @@ export function getCrypto() {
|
|
|
15
40
|
});
|
|
16
41
|
}
|
|
17
42
|
return cryptoImplPromise;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const getRandomValues = async (arr: Uint8Array): Promise<Uint8Array> => {
|
|
21
|
-
if (typeof crypto !== "undefined") {
|
|
22
|
-
return crypto.getRandomValues(arr);
|
|
23
|
-
} else {
|
|
24
|
-
const cryptoImpl = await getCrypto();
|
|
25
|
-
return cryptoImpl.getRandomValues(arr);
|
|
26
|
-
}
|
|
27
43
|
};
|
|
28
44
|
|
|
29
45
|
const getRandomValues32 = async (arr: Uint32Array): Promise<Uint32Array> => {
|
|
@@ -77,36 +93,7 @@ const deriveKey = async (
|
|
|
77
93
|
return [key, iv];
|
|
78
94
|
};
|
|
79
95
|
|
|
80
|
-
|
|
81
|
-
plaintext: ArrayBuffer,
|
|
82
|
-
password: string,
|
|
83
|
-
salt?: Uint8Array
|
|
84
|
-
): Promise<[string, string]> => {
|
|
85
|
-
if (!salt) {
|
|
86
|
-
salt = new Uint8Array(16);
|
|
87
|
-
getRandomValues(salt);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const [key, iv] = await deriveKey(password, salt, ITERATIONS, "AES-GCM", 352);
|
|
91
|
-
const cryptoImpl = await getCrypto();
|
|
92
|
-
|
|
93
|
-
const encrypted = new Uint8Array(
|
|
94
|
-
await cryptoImpl.subtle.encrypt({ name: "AES-GCM", iv }, key, plaintext)
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const output = new Uint8Array(salt.byteLength + encrypted.byteLength);
|
|
98
|
-
output.set(salt);
|
|
99
|
-
output.set(encrypted, salt.byteLength);
|
|
100
|
-
|
|
101
|
-
const header = {
|
|
102
|
-
v: 4,
|
|
103
|
-
i: ITERATIONS,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return [JSON.stringify(header), b64encode(output)];
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
export const decrypt = async (
|
|
96
|
+
const decrypt = async (
|
|
110
97
|
header_json: string,
|
|
111
98
|
ciphertext: string,
|
|
112
99
|
password: string
|
|
@@ -172,7 +159,7 @@ export const decrypt = async (
|
|
|
172
159
|
}
|
|
173
160
|
};
|
|
174
161
|
|
|
175
|
-
|
|
162
|
+
async function decryptSecretWithNodePassword(
|
|
176
163
|
cipher: string,
|
|
177
164
|
encryptedSecret: string,
|
|
178
165
|
nodePassword: string
|
|
@@ -188,12 +175,9 @@ export async function decryptSecretWithNodePassword(
|
|
|
188
175
|
return decryptedValue;
|
|
189
176
|
}
|
|
190
177
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export const generateSigningKeyPair = async (): Promise<CryptoKeyPair> => {
|
|
178
|
+
const generateSigningKeyPair = async (): Promise<
|
|
179
|
+
CryptoKeyPair | { publicKey: string; privateKey: string }
|
|
180
|
+
> => {
|
|
197
181
|
const cryptoImpl = await getCrypto();
|
|
198
182
|
return await cryptoImpl.subtle.generateKey(
|
|
199
183
|
/*algorithm:*/ {
|
|
@@ -207,52 +191,62 @@ export const generateSigningKeyPair = async (): Promise<CryptoKeyPair> => {
|
|
|
207
191
|
);
|
|
208
192
|
};
|
|
209
193
|
|
|
210
|
-
|
|
211
|
-
key: CryptoKey,
|
|
194
|
+
const serializeSigningKey = async (
|
|
195
|
+
key: CryptoKey | string,
|
|
212
196
|
format: "pkcs8" | "spki"
|
|
213
197
|
): Promise<ArrayBuffer> => {
|
|
214
198
|
const cryptoImpl = await getCrypto();
|
|
215
|
-
return await cryptoImpl.subtle.exportKey(
|
|
199
|
+
return await cryptoImpl.subtle.exportKey(
|
|
200
|
+
/*format*/ format,
|
|
201
|
+
/*key*/ key as CryptoKey
|
|
202
|
+
);
|
|
216
203
|
};
|
|
217
204
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
205
|
+
const getNonce = async () => {
|
|
206
|
+
const cryptoImpl = await getCrypto();
|
|
207
|
+
const nonceSt = await getRandomValues32(new Uint32Array(1));
|
|
208
|
+
return Number(nonceSt);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const sign = async (
|
|
212
|
+
key: CryptoKey | Uint8Array,
|
|
213
|
+
data: Uint8Array
|
|
214
|
+
): Promise<ArrayBuffer> => {
|
|
215
|
+
const cryptoImpl = await getCrypto();
|
|
216
|
+
return await cryptoImpl.subtle.sign(
|
|
217
|
+
{
|
|
218
|
+
name: "RSA-PSS",
|
|
219
|
+
saltLength: 32,
|
|
228
220
|
},
|
|
229
|
-
|
|
230
|
-
|
|
221
|
+
key as CryptoKey,
|
|
222
|
+
data
|
|
231
223
|
);
|
|
232
|
-
// @ts-ignore
|
|
233
|
-
return b64encode(encrypted);
|
|
234
224
|
};
|
|
235
225
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
226
|
+
const importPrivateSigningKey = async (
|
|
227
|
+
keyData: Uint8Array,
|
|
228
|
+
format: "pkcs8" | "spki"
|
|
229
|
+
): Promise<CryptoKey | Uint8Array> => {
|
|
240
230
|
const cryptoImpl = await getCrypto();
|
|
241
231
|
return await cryptoImpl.subtle.importKey(
|
|
242
|
-
/*format*/
|
|
243
|
-
/*keyData*/
|
|
244
|
-
/*algorithm
|
|
245
|
-
name: "RSA-
|
|
232
|
+
/*format*/ format,
|
|
233
|
+
/*keyData*/ keyData,
|
|
234
|
+
/*algorithm*/ {
|
|
235
|
+
name: "RSA-PSS",
|
|
246
236
|
hash: "SHA-256",
|
|
247
237
|
},
|
|
248
238
|
/*extractable*/ true,
|
|
249
|
-
/*keyUsages*/ ["
|
|
239
|
+
/*keyUsages*/ ["sign"]
|
|
250
240
|
);
|
|
251
241
|
};
|
|
252
242
|
|
|
253
|
-
export const
|
|
254
|
-
|
|
255
|
-
|
|
243
|
+
export const DefaultCrypto = {
|
|
244
|
+
decryptSecretWithNodePassword,
|
|
245
|
+
generateSigningKeyPair,
|
|
246
|
+
serializeSigningKey,
|
|
247
|
+
getNonce,
|
|
248
|
+
sign,
|
|
249
|
+
importPrivateSigningKey,
|
|
256
250
|
};
|
|
257
251
|
|
|
258
252
|
export { default as LightsparkSigningException } from "./LightsparkSigningException.js";
|
|
@@ -12,8 +12,8 @@ import Query from "./Query.js";
|
|
|
12
12
|
import AuthProvider from "../auth/AuthProvider.js";
|
|
13
13
|
import StubAuthProvider from "../auth/StubAuthProvider.js";
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
CryptoInterface,
|
|
16
|
+
DefaultCrypto,
|
|
17
17
|
LightsparkSigningException,
|
|
18
18
|
} from "../crypto/crypto.js";
|
|
19
19
|
import NodeKeyCache from "../crypto/NodeKeyCache.js";
|
|
@@ -34,7 +34,8 @@ class Requester {
|
|
|
34
34
|
private readonly schemaEndpoint: string,
|
|
35
35
|
private readonly sdkUserAgent: string,
|
|
36
36
|
private readonly authProvider: AuthProvider = new StubAuthProvider(),
|
|
37
|
-
private readonly baseUrl: string = DEFAULT_BASE_URL
|
|
37
|
+
private readonly baseUrl: string = DEFAULT_BASE_URL,
|
|
38
|
+
private readonly cryptoImpl: CryptoInterface = DefaultCrypto
|
|
38
39
|
) {
|
|
39
40
|
let websocketImpl;
|
|
40
41
|
if (typeof WebSocket === "undefined" && typeof window === "undefined") {
|
|
@@ -183,7 +184,7 @@ class Requester {
|
|
|
183
184
|
const variables = queryPayload.variables;
|
|
184
185
|
const operationName = queryPayload.operationName;
|
|
185
186
|
|
|
186
|
-
const nonce = await getNonce();
|
|
187
|
+
const nonce = await this.cryptoImpl.getNonce();
|
|
187
188
|
const expiration = dayjs.utc().add(1, "hour").format();
|
|
188
189
|
|
|
189
190
|
const payload = {
|
|
@@ -201,16 +202,11 @@ class Requester {
|
|
|
201
202
|
);
|
|
202
203
|
}
|
|
203
204
|
|
|
205
|
+
if (typeof TextEncoder === "undefined") {
|
|
206
|
+
const TextEncoder = (await import("text-encoding")).TextEncoder;
|
|
207
|
+
}
|
|
204
208
|
const encodedPayload = new TextEncoder().encode(JSON.stringify(payload));
|
|
205
|
-
const
|
|
206
|
-
const signedPayload = await cryptoImpl.subtle.sign(
|
|
207
|
-
{
|
|
208
|
-
name: "RSA-PSS",
|
|
209
|
-
saltLength: 32,
|
|
210
|
-
},
|
|
211
|
-
key,
|
|
212
|
-
encodedPayload
|
|
213
|
-
);
|
|
209
|
+
const signedPayload = await this.cryptoImpl.sign(key, encodedPayload);
|
|
214
210
|
const encodedSignedPayload = b64encode(signedPayload);
|
|
215
211
|
|
|
216
212
|
headers["X-Lightspark-Signing"] = JSON.stringify({
|
package/src/utils/base64.ts
CHANGED
|
@@ -1,7 +1,56 @@
|
|
|
1
1
|
// Copyright ©, 2023, Lightspark Group, Inc. - All Rights Reserved
|
|
2
2
|
|
|
3
|
+
const chars =
|
|
4
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
5
|
+
const Base64 = {
|
|
6
|
+
btoa: (input: string = "") => {
|
|
7
|
+
let str = input;
|
|
8
|
+
let output = "";
|
|
9
|
+
|
|
10
|
+
for (
|
|
11
|
+
let block = 0, charCode, i = 0, map = chars;
|
|
12
|
+
str.charAt(i | 0) || ((map = "="), i % 1);
|
|
13
|
+
output += map.charAt(63 & (block >> (8 - (i % 1) * 8)))
|
|
14
|
+
) {
|
|
15
|
+
charCode = str.charCodeAt((i += 3 / 4));
|
|
16
|
+
|
|
17
|
+
if (charCode > 0xff) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
block = (block << 8) | charCode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return output;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
atob: (input: string = "") => {
|
|
30
|
+
let str = input.replace(/=+$/, "");
|
|
31
|
+
let output = "";
|
|
32
|
+
|
|
33
|
+
if (str.length % 4 == 1) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"'atob' failed: The string to be decoded is not correctly encoded."
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
for (
|
|
39
|
+
let bc = 0, bs = 0, buffer, i = 0;
|
|
40
|
+
(buffer = str.charAt(i++));
|
|
41
|
+
~buffer && ((bs = bc % 4 ? bs * 64 + buffer : buffer), bc++ % 4)
|
|
42
|
+
? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))
|
|
43
|
+
: 0
|
|
44
|
+
) {
|
|
45
|
+
buffer = chars.indexOf(buffer);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return output;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
3
52
|
export const b64decode = (encoded: string): Uint8Array => {
|
|
4
|
-
return Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
|
|
53
|
+
return Uint8Array.from(Base64.atob(encoded), (c) => c.charCodeAt(0));
|
|
5
54
|
};
|
|
6
55
|
|
|
7
56
|
export const urlsafe_b64decode = (encoded: string): Uint8Array => {
|
|
@@ -9,7 +58,7 @@ export const urlsafe_b64decode = (encoded: string): Uint8Array => {
|
|
|
9
58
|
};
|
|
10
59
|
|
|
11
60
|
export const b64encode = (data: ArrayBuffer): string => {
|
|
12
|
-
return btoa(
|
|
61
|
+
return Base64.btoa(
|
|
13
62
|
String.fromCharCode.apply(null, Array.from(new Uint8Array(data)))
|
|
14
63
|
);
|
|
15
64
|
};
|