@wishknish/knishio-client-js 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.cjs.js +29 -29
- package/dist/client.cjs.js.map +1 -1
- package/dist/client.es.mjs +1377 -891
- package/dist/client.es.mjs.map +1 -1
- package/dist/client.iife.js +29 -29
- package/dist/client.iife.js.map +1 -1
- package/package.json +2 -2
- package/src/KnishIOClient.js +70 -6
- package/src/Molecule.js +1 -1
- package/src/exception/SecretStorageException.js +95 -0
- package/src/exception/index.js +2 -0
- package/src/index.js +15 -0
- package/src/libraries/secureMemory.js +125 -0
- package/src/storage/MemorySecretStorageProvider.js +170 -0
- package/src/storage/WebCryptoSecretStorageProvider.js +416 -0
- package/src/storage/index.js +79 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wishknish/knishio-client-js",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"productName": "Knish.IO Javascript SDK Client",
|
|
6
6
|
"description": "JavaScript implementation of the Knish.IO SDK to consume Knish.IO GraphQL APIs.",
|
|
@@ -96,5 +96,5 @@
|
|
|
96
96
|
"/src",
|
|
97
97
|
"/dist"
|
|
98
98
|
],
|
|
99
|
-
"packageManager": "yarn@4.
|
|
99
|
+
"packageManager": "yarn@4.15.0"
|
|
100
100
|
}
|
package/src/KnishIOClient.js
CHANGED
|
@@ -46,6 +46,7 @@ Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
|
|
|
46
46
|
License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
|
|
47
47
|
*/
|
|
48
48
|
import Dot from './libraries/Dot.js'
|
|
49
|
+
import MemorySecretStorageProvider from './storage/MemorySecretStorageProvider.js'
|
|
49
50
|
import Decimal from './libraries/Decimal.js'
|
|
50
51
|
import {
|
|
51
52
|
generateBatchId,
|
|
@@ -127,7 +128,8 @@ export default class KnishIOClient {
|
|
|
127
128
|
socket = null,
|
|
128
129
|
serverSdkVersion = 3,
|
|
129
130
|
logging = false,
|
|
130
|
-
defaultRequestPolicy = null
|
|
131
|
+
defaultRequestPolicy = null,
|
|
132
|
+
secretStorage = null
|
|
131
133
|
}) {
|
|
132
134
|
this.initialize({
|
|
133
135
|
uri,
|
|
@@ -136,7 +138,8 @@ export default class KnishIOClient {
|
|
|
136
138
|
client,
|
|
137
139
|
serverSdkVersion,
|
|
138
140
|
logging,
|
|
139
|
-
defaultRequestPolicy
|
|
141
|
+
defaultRequestPolicy,
|
|
142
|
+
secretStorage
|
|
140
143
|
})
|
|
141
144
|
}
|
|
142
145
|
|
|
@@ -157,10 +160,12 @@ export default class KnishIOClient {
|
|
|
157
160
|
client = null,
|
|
158
161
|
serverSdkVersion = 3,
|
|
159
162
|
logging = false,
|
|
160
|
-
defaultRequestPolicy = null
|
|
163
|
+
defaultRequestPolicy = null,
|
|
164
|
+
secretStorage = null
|
|
161
165
|
}) {
|
|
162
166
|
this.reset()
|
|
163
167
|
|
|
168
|
+
this.$__secretStorage = secretStorage
|
|
164
169
|
this.$__logging = logging
|
|
165
170
|
// Client-level urql request policy applied to reads that omit a per-call
|
|
166
171
|
// policy. A long-lived server/sync client set to 'network-only' never serves
|
|
@@ -278,6 +283,7 @@ export default class KnishIOClient {
|
|
|
278
283
|
reset () {
|
|
279
284
|
this.$__secret = ''
|
|
280
285
|
this.$__bundle = ''
|
|
286
|
+
this.$__secretStorage = null
|
|
281
287
|
this.remainderWallet = null
|
|
282
288
|
this.$__capabilityCache = {}
|
|
283
289
|
this.$__defaultRequestPolicy = null
|
|
@@ -376,7 +382,7 @@ export default class KnishIOClient {
|
|
|
376
382
|
* @return {boolean}
|
|
377
383
|
*/
|
|
378
384
|
hasSecret () {
|
|
379
|
-
return !!this.$__secret
|
|
385
|
+
return (!!this.$__secret && this.$__secret.length > 0) || (!!this.$__secretStorage && !!this.$__bundle && this.$__bundle.length > 0)
|
|
380
386
|
}
|
|
381
387
|
|
|
382
388
|
/**
|
|
@@ -387,6 +393,13 @@ export default class KnishIOClient {
|
|
|
387
393
|
setSecret (secret) {
|
|
388
394
|
this.$__secret = secret
|
|
389
395
|
this.$__bundle = this.hashSecret(secret, 'setSecret')
|
|
396
|
+
if (!this.$__secretStorage) {
|
|
397
|
+
const memStorage = new MemorySecretStorageProvider()
|
|
398
|
+
memStorage.storeSecret(this.$__bundle, secret)
|
|
399
|
+
this.$__secretStorage = memStorage
|
|
400
|
+
} else {
|
|
401
|
+
this.$__secretStorage.storeSecret(this.$__bundle, secret)
|
|
402
|
+
}
|
|
390
403
|
}
|
|
391
404
|
|
|
392
405
|
/**
|
|
@@ -412,6 +425,44 @@ export default class KnishIOClient {
|
|
|
412
425
|
return this.$__secret
|
|
413
426
|
}
|
|
414
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Sets the secret storage provider and optionally sets the bundle hash
|
|
430
|
+
*
|
|
431
|
+
* @param {object} storage
|
|
432
|
+
* @param {string|null} [bundleHash]
|
|
433
|
+
*/
|
|
434
|
+
setSecretStorage (storage, bundleHash = null) {
|
|
435
|
+
this.$__secretStorage = storage
|
|
436
|
+
if (bundleHash) {
|
|
437
|
+
this.$__bundle = bundleHash
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Returns current secret storage provider
|
|
443
|
+
*
|
|
444
|
+
* @returns {object|null}
|
|
445
|
+
*/
|
|
446
|
+
getSecretStorage () {
|
|
447
|
+
return this.$__secretStorage
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Asynchronously retrieves the secret from storage or returns in-memory secret
|
|
452
|
+
*
|
|
453
|
+
* @param {object} [options]
|
|
454
|
+
* @returns {Promise<string|null>}
|
|
455
|
+
*/
|
|
456
|
+
async retrieveSecret (options = {}) {
|
|
457
|
+
if (this.$__secret && this.$__secret.length > 0) {
|
|
458
|
+
return this.$__secret
|
|
459
|
+
}
|
|
460
|
+
if (this.$__secretStorage && this.$__bundle && this.$__bundle.length > 0) {
|
|
461
|
+
return await this.$__secretStorage.retrieveSecret(this.$__bundle, options)
|
|
462
|
+
}
|
|
463
|
+
return null
|
|
464
|
+
}
|
|
465
|
+
|
|
415
466
|
/**
|
|
416
467
|
* Returns whether a bundle hash is being stored for this session
|
|
417
468
|
*
|
|
@@ -496,10 +547,16 @@ export default class KnishIOClient {
|
|
|
496
547
|
remainderWallet = null
|
|
497
548
|
}) {
|
|
498
549
|
this.log('info', 'KnishIOClient::createMolecule() - Creating a new molecule...')
|
|
550
|
+
if (!secret) {
|
|
551
|
+
if (this.$__secret && this.$__secret.length > 0) {
|
|
552
|
+
secret = this.getSecret()
|
|
553
|
+
} else if (this.$__secretStorage && this.$__bundle && this.$__bundle.length > 0) {
|
|
554
|
+
secret = await this.$__secretStorage.retrieveSecret(this.$__bundle)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
499
557
|
|
|
500
558
|
secret = secret || this.getSecret()
|
|
501
559
|
bundle = bundle || this.getBundle()
|
|
502
|
-
|
|
503
560
|
// For non-USER source wallets (V/B isotopes), capture the current USER
|
|
504
561
|
// ContinuID position BEFORE overwriting the client's remainder wallet.
|
|
505
562
|
// This position is needed by addContinuIdAtom() for previousPosition metadata.
|
|
@@ -546,6 +603,7 @@ export default class KnishIOClient {
|
|
|
546
603
|
|
|
547
604
|
return new Molecule({
|
|
548
605
|
secret,
|
|
606
|
+
bundle,
|
|
549
607
|
sourceWallet,
|
|
550
608
|
remainderWallet: this.getRemainderWallet(),
|
|
551
609
|
cellSlug: this.getCellSlug(),
|
|
@@ -611,8 +669,9 @@ export default class KnishIOClient {
|
|
|
611
669
|
// Guard with $__authInProcess to prevent concurrent auth requests
|
|
612
670
|
if (this.$__authToken && this.$__authToken.isExpired() && !this.$__authInProcess) {
|
|
613
671
|
this.log('info', 'KnishIOClient::executeQuery() - Access token is expired. Getting new one...')
|
|
672
|
+
const authSecret = this.$__secret || (await this.retrieveSecret()) || ''
|
|
614
673
|
await this.requestAuthToken({
|
|
615
|
-
secret:
|
|
674
|
+
secret: authSecret,
|
|
616
675
|
cellSlug: this.$__cellSlug,
|
|
617
676
|
encrypt: this.$__encrypt
|
|
618
677
|
})
|
|
@@ -2464,6 +2523,11 @@ export default class KnishIOClient {
|
|
|
2464
2523
|
this.setCellSlug(cellSlug)
|
|
2465
2524
|
}
|
|
2466
2525
|
|
|
2526
|
+
// Retrieve secret from storage provider if available
|
|
2527
|
+
if (secret === null && this.$__secretStorage && this.$__bundle) {
|
|
2528
|
+
secret = await this.$__secretStorage.retrieveSecret(this.$__bundle)
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2467
2531
|
// Auth in process...
|
|
2468
2532
|
this.$__authInProcess = true
|
|
2469
2533
|
|
package/src/Molecule.js
CHANGED
|
@@ -1080,7 +1080,7 @@ export default class Molecule {
|
|
|
1080
1080
|
bundle = null,
|
|
1081
1081
|
anonymous = false,
|
|
1082
1082
|
compressed = true
|
|
1083
|
-
}) {
|
|
1083
|
+
} = {}) {
|
|
1084
1084
|
// Do we have atoms?
|
|
1085
1085
|
if (this.atoms.length === 0 || this.atoms.filter(atom => !(atom instanceof Atom)).length !== 0) {
|
|
1086
1086
|
throw new AtomsMissingException()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*
|
|
2
|
+
(
|
|
3
|
+
(/(
|
|
4
|
+
(//(
|
|
5
|
+
(///(
|
|
6
|
+
(/////(
|
|
7
|
+
(//////( )
|
|
8
|
+
(////////( (/)
|
|
9
|
+
(////////( (///)
|
|
10
|
+
(//////////( (////)
|
|
11
|
+
(//////////( (//////)
|
|
12
|
+
(////////////( (///////)
|
|
13
|
+
(/////////////( (/////////)
|
|
14
|
+
(//////////////( (///////////)
|
|
15
|
+
(///////////////( (/////////////)
|
|
16
|
+
(////////////////( (//////////////)
|
|
17
|
+
((((((((((((((((((( (((((((((((((((
|
|
18
|
+
((((((((((((((((((( ((((((((((((((
|
|
19
|
+
((((((((((((((((((( ((((((((((((((
|
|
20
|
+
(((((((((((((((((((( (((((((((((((
|
|
21
|
+
(((((((((((((((((((( ((((((((((((
|
|
22
|
+
((((((((((((((((((( ((((((((((((
|
|
23
|
+
((((((((((((((((((( ((((((((((
|
|
24
|
+
((((((((((((((((((/ (((((((((
|
|
25
|
+
(((((((((((((((((( ((((((((
|
|
26
|
+
((((((((((((((((( (((((((
|
|
27
|
+
(((((((((((((((((( (((((
|
|
28
|
+
################# ##
|
|
29
|
+
################ #
|
|
30
|
+
################# ##
|
|
31
|
+
%################ ###
|
|
32
|
+
###############( ####
|
|
33
|
+
############### ####
|
|
34
|
+
############### ######
|
|
35
|
+
%#############( (#######
|
|
36
|
+
%############# #########
|
|
37
|
+
############( ##########
|
|
38
|
+
########### #############
|
|
39
|
+
######### ##############
|
|
40
|
+
%######
|
|
41
|
+
|
|
42
|
+
Powered by Knish.IO: Connecting a Decentralized World
|
|
43
|
+
|
|
44
|
+
Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import BaseException from './BaseException.js'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Exception thrown when secret storage or hardware envelope encryption fails
|
|
53
|
+
*/
|
|
54
|
+
export default class SecretStorageException extends BaseException {
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} message
|
|
57
|
+
* @param {string|null} fileName
|
|
58
|
+
* @param {number|null} lineNumber
|
|
59
|
+
*/
|
|
60
|
+
constructor (message = 'Secret storage operation failed', fileName = null, lineNumber = null) {
|
|
61
|
+
super(message, fileName, lineNumber)
|
|
62
|
+
this.name = 'SecretStorageException'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Factory method: secret not found for bundle
|
|
67
|
+
*
|
|
68
|
+
* @param {string} bundleHash
|
|
69
|
+
* @returns {SecretStorageException}
|
|
70
|
+
*/
|
|
71
|
+
static notFound (bundleHash) {
|
|
72
|
+
return new SecretStorageException(`Secret not found for bundle: ${bundleHash}`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Factory method: decryption failed
|
|
77
|
+
*
|
|
78
|
+
* @param {string} [reason]
|
|
79
|
+
* @returns {SecretStorageException}
|
|
80
|
+
*/
|
|
81
|
+
static decryptionFailed (reason = 'Invalid passphrase or corrupted ciphertext') {
|
|
82
|
+
return new SecretStorageException(`Failed to decrypt master secret: ${reason}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Factory method: provider unavailable
|
|
87
|
+
*
|
|
88
|
+
* @param {string} provider
|
|
89
|
+
* @param {string} [reason]
|
|
90
|
+
* @returns {SecretStorageException}
|
|
91
|
+
*/
|
|
92
|
+
static unavailable (provider, reason = 'Hardware or API not accessible') {
|
|
93
|
+
return new SecretStorageException(`Secret storage provider '${provider}' is unavailable: ${reason}`)
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/exception/index.js
CHANGED
|
@@ -70,6 +70,7 @@ import TransferUnbalancedException from './TransferUnbalancedException.js'
|
|
|
70
70
|
import UnauthenticatedException from './UnauthenticatedException.js'
|
|
71
71
|
import WalletShadowException from './WalletShadowException.js'
|
|
72
72
|
import WrongTokenTypeException from './WrongTokenTypeException.js'
|
|
73
|
+
import SecretStorageException from './SecretStorageException.js'
|
|
73
74
|
|
|
74
75
|
export {
|
|
75
76
|
AtomIndexException,
|
|
@@ -96,5 +97,6 @@ export {
|
|
|
96
97
|
TransferUnbalancedException,
|
|
97
98
|
UnauthenticatedException,
|
|
98
99
|
WalletShadowException,
|
|
100
|
+
SecretStorageException,
|
|
99
101
|
WrongTokenTypeException
|
|
100
102
|
}
|
package/src/index.js
CHANGED
|
@@ -207,6 +207,7 @@ export {
|
|
|
207
207
|
TransferUnbalancedException,
|
|
208
208
|
UnauthenticatedException,
|
|
209
209
|
WalletShadowException,
|
|
210
|
+
SecretStorageException,
|
|
210
211
|
WrongTokenTypeException
|
|
211
212
|
} from './exception/index.js'
|
|
212
213
|
|
|
@@ -325,3 +326,17 @@ export {
|
|
|
325
326
|
diff,
|
|
326
327
|
intersect
|
|
327
328
|
}
|
|
329
|
+
|
|
330
|
+
export {
|
|
331
|
+
MemorySecretStorageProvider,
|
|
332
|
+
WebCryptoSecretStorageProvider,
|
|
333
|
+
MemoryStorageBackend,
|
|
334
|
+
createDefaultSecretStorage
|
|
335
|
+
} from './storage/index.js'
|
|
336
|
+
|
|
337
|
+
export {
|
|
338
|
+
zeroizeBytes,
|
|
339
|
+
withSecureBytes,
|
|
340
|
+
withSecureString,
|
|
341
|
+
constantTimeCompare
|
|
342
|
+
} from './libraries/secureMemory.js'
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*
|
|
2
|
+
(
|
|
3
|
+
(/(
|
|
4
|
+
(//(
|
|
5
|
+
(///(
|
|
6
|
+
(/////(
|
|
7
|
+
(//////( )
|
|
8
|
+
(////////( (/)
|
|
9
|
+
(////////( (///)
|
|
10
|
+
(//////////( (////)
|
|
11
|
+
(//////////( (//////)
|
|
12
|
+
(////////////( (///////)
|
|
13
|
+
(/////////////( (/////////)
|
|
14
|
+
(//////////////( (///////////)
|
|
15
|
+
(///////////////( (/////////////)
|
|
16
|
+
(////////////////( (//////////////)
|
|
17
|
+
((((((((((((((((((( (((((((((((((((
|
|
18
|
+
((((((((((((((((((( ((((((((((((((
|
|
19
|
+
((((((((((((((((((( ((((((((((((((
|
|
20
|
+
(((((((((((((((((((( (((((((((((((
|
|
21
|
+
(((((((((((((((((((( ((((((((((((
|
|
22
|
+
((((((((((((((((((( ((((((((((((
|
|
23
|
+
((((((((((((((((((( ((((((((((
|
|
24
|
+
((((((((((((((((((/ (((((((((
|
|
25
|
+
(((((((((((((((((( ((((((((
|
|
26
|
+
((((((((((((((((( (((((((
|
|
27
|
+
(((((((((((((((((( (((((
|
|
28
|
+
################# ##
|
|
29
|
+
################ #
|
|
30
|
+
################# ##
|
|
31
|
+
%################ ###
|
|
32
|
+
###############( ####
|
|
33
|
+
############### ####
|
|
34
|
+
############### ######
|
|
35
|
+
%#############( (#######
|
|
36
|
+
%############# #########
|
|
37
|
+
############( ##########
|
|
38
|
+
########### #############
|
|
39
|
+
######### ##############
|
|
40
|
+
%######
|
|
41
|
+
|
|
42
|
+
Powered by Knish.IO: Connecting a Decentralized World
|
|
43
|
+
|
|
44
|
+
Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Memory hygiene and zeroization utilities for sensitive cryptographic material
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
const textEncoder = new TextEncoder()
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Overwrite byte array contents with zeros
|
|
57
|
+
*
|
|
58
|
+
* @param {Uint8Array|number[]} buffer
|
|
59
|
+
*/
|
|
60
|
+
export function zeroizeBytes (buffer) {
|
|
61
|
+
if (buffer instanceof Uint8Array) {
|
|
62
|
+
buffer.fill(0)
|
|
63
|
+
} else if (Array.isArray(buffer)) {
|
|
64
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
65
|
+
buffer[i] = 0
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Execute a callback with a byte buffer and guarantee zeroization upon completion
|
|
72
|
+
*
|
|
73
|
+
* @template T
|
|
74
|
+
* @param {Uint8Array} bytes
|
|
75
|
+
* @param {(bytes: Uint8Array) => Promise<T>|T} fn
|
|
76
|
+
* @returns {Promise<T>}
|
|
77
|
+
*/
|
|
78
|
+
export async function withSecureBytes (bytes, fn) {
|
|
79
|
+
try {
|
|
80
|
+
return await fn(bytes)
|
|
81
|
+
} finally {
|
|
82
|
+
zeroizeBytes(bytes)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Execute a callback with a secret string, ensuring temporary byte buffers are cleared
|
|
88
|
+
*
|
|
89
|
+
* @template T
|
|
90
|
+
* @param {string} secret
|
|
91
|
+
* @param {(cleanSecret: string) => Promise<T>|T} fn
|
|
92
|
+
* @returns {Promise<T>}
|
|
93
|
+
*/
|
|
94
|
+
export async function withSecureString (secret, fn) {
|
|
95
|
+
const bytes = textEncoder.encode(secret)
|
|
96
|
+
try {
|
|
97
|
+
return await fn(secret)
|
|
98
|
+
} finally {
|
|
99
|
+
zeroizeBytes(bytes)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Constant-time comparison of two byte arrays or strings to prevent timing attacks
|
|
105
|
+
*
|
|
106
|
+
* @param {Uint8Array|string} a
|
|
107
|
+
* @param {Uint8Array|string} b
|
|
108
|
+
* @returns {boolean}
|
|
109
|
+
*/
|
|
110
|
+
export function constantTimeCompare (a, b) {
|
|
111
|
+
const bytesA = typeof a === 'string' ? textEncoder.encode(a) : a
|
|
112
|
+
const bytesB = typeof b === 'string' ? textEncoder.encode(b) : b
|
|
113
|
+
|
|
114
|
+
let result = bytesA.length === bytesB.length ? 0 : 1
|
|
115
|
+
const len = Math.min(bytesA.length, bytesB.length)
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < len; i++) {
|
|
118
|
+
result |= (bytesA[i] ?? 0) ^ (bytesB[i] ?? 0)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof a === 'string') zeroizeBytes(bytesA)
|
|
122
|
+
if (typeof b === 'string') zeroizeBytes(bytesB)
|
|
123
|
+
|
|
124
|
+
return result === 0
|
|
125
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/*
|
|
2
|
+
(
|
|
3
|
+
(/(
|
|
4
|
+
(//(
|
|
5
|
+
(///(
|
|
6
|
+
(/////(
|
|
7
|
+
(//////( )
|
|
8
|
+
(////////( (/)
|
|
9
|
+
(////////( (///)
|
|
10
|
+
(//////////( (////)
|
|
11
|
+
(//////////( (//////)
|
|
12
|
+
(////////////( (///////)
|
|
13
|
+
(/////////////( (/////////)
|
|
14
|
+
(//////////////( (///////////)
|
|
15
|
+
(///////////////( (/////////////)
|
|
16
|
+
(////////////////( (//////////////)
|
|
17
|
+
((((((((((((((((((( (((((((((((((((
|
|
18
|
+
((((((((((((((((((( ((((((((((((((
|
|
19
|
+
((((((((((((((((((( ((((((((((((((
|
|
20
|
+
(((((((((((((((((((( (((((((((((((
|
|
21
|
+
(((((((((((((((((((( ((((((((((((
|
|
22
|
+
((((((((((((((((((( ((((((((((((
|
|
23
|
+
((((((((((((((((((( ((((((((((
|
|
24
|
+
((((((((((((((((((/ (((((((((
|
|
25
|
+
(((((((((((((((((( ((((((((
|
|
26
|
+
((((((((((((((((( (((((((
|
|
27
|
+
(((((((((((((((((( (((((
|
|
28
|
+
################# ##
|
|
29
|
+
################ #
|
|
30
|
+
################# ##
|
|
31
|
+
%################ ###
|
|
32
|
+
###############( ####
|
|
33
|
+
############### ####
|
|
34
|
+
############### ######
|
|
35
|
+
%#############( (#######
|
|
36
|
+
%############# #########
|
|
37
|
+
############( ##########
|
|
38
|
+
########### #############
|
|
39
|
+
######### ##############
|
|
40
|
+
%######
|
|
41
|
+
|
|
42
|
+
Powered by Knish.IO: Connecting a Decentralized World
|
|
43
|
+
|
|
44
|
+
Please visit https://github.com/WishKnish/KnishIO-Client-JS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-JS/blob/master/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import SecretStorageException from '../exception/SecretStorageException.js'
|
|
50
|
+
import { withSecureString } from '../libraries/secureMemory.js'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* In-memory secret storage provider
|
|
54
|
+
* Used for testing, headless runners, and backward-compatible fallback
|
|
55
|
+
*/
|
|
56
|
+
export default class MemorySecretStorageProvider {
|
|
57
|
+
constructor () {
|
|
58
|
+
this.providerType = 'memory'
|
|
59
|
+
this.secrets = new Map()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Memory storage is not hardware backed
|
|
64
|
+
*
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
isHardwareBacked () {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Memory storage is always available
|
|
73
|
+
*
|
|
74
|
+
* @returns {Promise<boolean>}
|
|
75
|
+
*/
|
|
76
|
+
async isAvailable () {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Store a secret in memory
|
|
82
|
+
*
|
|
83
|
+
* @param {string} bundleHash
|
|
84
|
+
* @param {string} secret
|
|
85
|
+
* @param {{ label?: string }} [options]
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async storeSecret (bundleHash, secret, options = {}) {
|
|
89
|
+
if (!bundleHash) {
|
|
90
|
+
throw new SecretStorageException('Bundle hash cannot be empty')
|
|
91
|
+
}
|
|
92
|
+
if (!secret) {
|
|
93
|
+
throw new SecretStorageException('Secret cannot be empty')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const metadata = {
|
|
97
|
+
bundleHash,
|
|
98
|
+
label: options.label,
|
|
99
|
+
createdAt: Date.now(),
|
|
100
|
+
hardwareBacked: false,
|
|
101
|
+
providerType: this.providerType
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.secrets.set(bundleHash, { secret, metadata })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Retrieve a secret from memory
|
|
109
|
+
*
|
|
110
|
+
* @param {string} bundleHash
|
|
111
|
+
* @returns {Promise<string|null>}
|
|
112
|
+
*/
|
|
113
|
+
async retrieveSecret (bundleHash) {
|
|
114
|
+
const entry = this.secrets.get(bundleHash)
|
|
115
|
+
return entry ? entry.secret : null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Delete a stored secret
|
|
120
|
+
*
|
|
121
|
+
* @param {string} bundleHash
|
|
122
|
+
* @returns {Promise<boolean>}
|
|
123
|
+
*/
|
|
124
|
+
async deleteSecret (bundleHash) {
|
|
125
|
+
return this.secrets.delete(bundleHash)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if a secret exists
|
|
130
|
+
*
|
|
131
|
+
* @param {string} bundleHash
|
|
132
|
+
* @returns {Promise<boolean>}
|
|
133
|
+
*/
|
|
134
|
+
async hasSecret (bundleHash) {
|
|
135
|
+
return this.secrets.has(bundleHash)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* List all stored secret metadata
|
|
140
|
+
*
|
|
141
|
+
* @returns {Promise<Array<{ bundleHash: string, label?: string, createdAt: number, hardwareBacked: boolean, providerType: string }>>}
|
|
142
|
+
*/
|
|
143
|
+
async listSecrets () {
|
|
144
|
+
return Array.from(this.secrets.values()).map(entry => ({ ...entry.metadata }))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Execute callback with unwrapped secret and ensure cleanup
|
|
149
|
+
*
|
|
150
|
+
* @template T
|
|
151
|
+
* @param {string} bundleHash
|
|
152
|
+
* @param {(secret: string) => Promise<T>|T} fn
|
|
153
|
+
* @returns {Promise<T>}
|
|
154
|
+
*/
|
|
155
|
+
async withSecret (bundleHash, fn) {
|
|
156
|
+
const entry = this.secrets.get(bundleHash)
|
|
157
|
+
if (!entry) {
|
|
158
|
+
throw SecretStorageException.notFound(bundleHash)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return withSecureString(entry.secret, fn)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Clear all secrets from memory
|
|
166
|
+
*/
|
|
167
|
+
clear () {
|
|
168
|
+
this.secrets.clear()
|
|
169
|
+
}
|
|
170
|
+
}
|