@wishknish/knishio-client-ts 0.9.6 → 0.9.8
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/index.cjs +557 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +128 -7
- package/dist/index.d.ts +128 -7
- package/dist/index.iife.js +557 -9
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +550 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/KnishIOClient.ts +85 -7
- package/src/exception/SecretStorageException.ts +97 -0
- package/src/exception/index.ts +1 -0
- package/src/index.ts +28 -1
- package/src/libraries/secureMemory.ts +121 -0
- package/src/mutation/MutationProposeMolecule.ts +7 -4
- package/src/schemas/index.ts +2 -1
- package/src/storage/MemorySecretStorageProvider.ts +156 -0
- package/src/storage/WebCryptoSecretStorageProvider.ts +407 -0
- package/src/storage/index.ts +87 -0
- package/src/types/index.ts +1 -1
- package/src/types/storage.ts +129 -0
- package/src/validation/schemas.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wishknish/knishio-client-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"productName": "Knish.IO TypeScript SDK Client",
|
|
5
5
|
"description": "TypeScript implementation of the Knish.IO SDK to consume Knish.IO GraphQL APIs with enhanced type safety.",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
package/src/KnishIOClient.ts
CHANGED
|
@@ -128,6 +128,8 @@ import {
|
|
|
128
128
|
SignatureMismatchException,
|
|
129
129
|
AtomIndexException
|
|
130
130
|
} from '@/exception'
|
|
131
|
+
import type { ISecretStorageProvider } from '@/types/storage'
|
|
132
|
+
import MemorySecretStorageProvider from '@/storage/MemorySecretStorageProvider'
|
|
131
133
|
|
|
132
134
|
// Type imports
|
|
133
135
|
import type {
|
|
@@ -150,6 +152,7 @@ import type {
|
|
|
150
152
|
export default class KnishIOClient {
|
|
151
153
|
private $__secret: string = ''
|
|
152
154
|
private $__bundle: string = ''
|
|
155
|
+
private $__secretStorage: ISecretStorageProvider | null = null
|
|
153
156
|
private $__cellSlug: string | null = null
|
|
154
157
|
private $__encrypt: boolean = false
|
|
155
158
|
private $__uris: string[] = []
|
|
@@ -185,6 +188,7 @@ export default class KnishIOClient {
|
|
|
185
188
|
serverSdkVersion?: number
|
|
186
189
|
logging?: boolean
|
|
187
190
|
defaultRequestPolicy?: RequestPolicy | null
|
|
191
|
+
secretStorage?: ISecretStorageProvider | null
|
|
188
192
|
}) {
|
|
189
193
|
// Phase 2 Enhancement: Use standardized configuration validation
|
|
190
194
|
const standardValidationResult = ConfigValidator.validateClientConfig(config)
|
|
@@ -236,6 +240,10 @@ export default class KnishIOClient {
|
|
|
236
240
|
logging,
|
|
237
241
|
defaultRequestPolicy
|
|
238
242
|
})
|
|
243
|
+
|
|
244
|
+
if (config.secretStorage) {
|
|
245
|
+
this.$__secretStorage = config.secretStorage
|
|
246
|
+
}
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
/**
|
|
@@ -365,6 +373,7 @@ export default class KnishIOClient {
|
|
|
365
373
|
reset(): void {
|
|
366
374
|
this.$__secret = ''
|
|
367
375
|
this.$__bundle = ''
|
|
376
|
+
this.$__secretStorage = null
|
|
368
377
|
this.$__encrypt = false
|
|
369
378
|
this.$__cellSlug = null
|
|
370
379
|
this.$__authToken = null
|
|
@@ -434,7 +443,7 @@ export default class KnishIOClient {
|
|
|
434
443
|
* Returns whether a secret is stored for this session
|
|
435
444
|
*/
|
|
436
445
|
hasSecret(): boolean {
|
|
437
|
-
return !!this.$__secret && this.$__secret.length > 0
|
|
446
|
+
return (!!this.$__secret && this.$__secret.length > 0) || (!!this.$__secretStorage && !!this.$__bundle && this.$__bundle.length > 0)
|
|
438
447
|
}
|
|
439
448
|
|
|
440
449
|
/**
|
|
@@ -447,6 +456,36 @@ export default class KnishIOClient {
|
|
|
447
456
|
return this.$__secret
|
|
448
457
|
}
|
|
449
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Sets the secret storage provider and optionally sets the bundle hash
|
|
461
|
+
*/
|
|
462
|
+
setSecretStorage(storage: ISecretStorageProvider, bundleHash?: string): void {
|
|
463
|
+
this.$__secretStorage = storage
|
|
464
|
+
if (bundleHash) {
|
|
465
|
+
this.$__bundle = bundleHash
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Returns current secret storage provider
|
|
471
|
+
*/
|
|
472
|
+
getSecretStorage(): ISecretStorageProvider | null {
|
|
473
|
+
return this.$__secretStorage
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Asynchronously retrieves the secret from storage or returns in-memory secret
|
|
478
|
+
*/
|
|
479
|
+
async retrieveSecret(options?: { passphrase?: string }): Promise<string | null> {
|
|
480
|
+
if (this.$__secret && this.$__secret.length > 0) {
|
|
481
|
+
return this.$__secret
|
|
482
|
+
}
|
|
483
|
+
if (this.$__secretStorage && this.$__bundle && this.$__bundle.length > 0) {
|
|
484
|
+
return await this.$__secretStorage.retrieveSecret(this.$__bundle, options)
|
|
485
|
+
}
|
|
486
|
+
return null
|
|
487
|
+
}
|
|
488
|
+
|
|
450
489
|
/**
|
|
451
490
|
* Returns whether a bundle hash is being stored for this session
|
|
452
491
|
*/
|
|
@@ -495,6 +534,14 @@ export default class KnishIOClient {
|
|
|
495
534
|
} = {}): Promise<Molecule> {
|
|
496
535
|
this.log('info', 'KnishIOClient::createMolecule() - Creating a new molecule...')
|
|
497
536
|
|
|
537
|
+
if (!secret) {
|
|
538
|
+
if (this.$__secret && this.$__secret.length > 0) {
|
|
539
|
+
secret = this.getSecret()
|
|
540
|
+
} else if (this.$__secretStorage && this.$__bundle && this.$__bundle.length > 0) {
|
|
541
|
+
secret = await this.$__secretStorage.retrieveSecret(this.$__bundle)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
498
545
|
secret = secret || this.getSecret()
|
|
499
546
|
bundle = bundle || this.getBundle()
|
|
500
547
|
|
|
@@ -547,6 +594,7 @@ export default class KnishIOClient {
|
|
|
547
594
|
|
|
548
595
|
return new Molecule({
|
|
549
596
|
secret,
|
|
597
|
+
bundle,
|
|
550
598
|
sourceWallet,
|
|
551
599
|
remainderWallet: this.getRemainderWallet()!,
|
|
552
600
|
cellSlug: this.getCellSlug(),
|
|
@@ -612,8 +660,9 @@ export default class KnishIOClient {
|
|
|
612
660
|
// Guard with $__authInProcess to prevent recursive auth refresh
|
|
613
661
|
if (this.$__authToken && this.$__authToken.isExpired() && !this.$__authInProcess) {
|
|
614
662
|
this.log('info', 'KnishIOClient::executeQuery() - Access token is expired. Getting new one...')
|
|
663
|
+
const authSecret = this.$__secret || (await this.retrieveSecret()) || ''
|
|
615
664
|
await this.requestAuthToken({
|
|
616
|
-
secret:
|
|
665
|
+
secret: authSecret,
|
|
617
666
|
cellSlug: this.$__cellSlug,
|
|
618
667
|
encrypt: this.$__encrypt
|
|
619
668
|
})
|
|
@@ -673,6 +722,13 @@ export default class KnishIOClient {
|
|
|
673
722
|
setSecret(secret: string): void {
|
|
674
723
|
this.$__secret = secret
|
|
675
724
|
this.$__bundle = generateBundleHash(secret)
|
|
725
|
+
if (!this.$__secretStorage) {
|
|
726
|
+
const memStorage = new MemorySecretStorageProvider()
|
|
727
|
+
memStorage.storeSecret(this.$__bundle, secret)
|
|
728
|
+
this.$__secretStorage = memStorage
|
|
729
|
+
} else {
|
|
730
|
+
this.$__secretStorage.storeSecret(this.$__bundle, secret)
|
|
731
|
+
}
|
|
676
732
|
}
|
|
677
733
|
|
|
678
734
|
/**
|
|
@@ -924,6 +980,11 @@ export default class KnishIOClient {
|
|
|
924
980
|
secret = generateSecret(seed)
|
|
925
981
|
}
|
|
926
982
|
|
|
983
|
+
// Retrieve secret from storage provider if available
|
|
984
|
+
if (secret === null && this.$__secretStorage && this.$__bundle) {
|
|
985
|
+
secret = await this.$__secretStorage.retrieveSecret(this.$__bundle)
|
|
986
|
+
}
|
|
987
|
+
|
|
927
988
|
// Set cell slug if provided
|
|
928
989
|
if (cellSlug) {
|
|
929
990
|
this.setCellSlug(cellSlug)
|
|
@@ -2134,7 +2195,7 @@ export default class KnishIOClient {
|
|
|
2134
2195
|
async createPolicy({
|
|
2135
2196
|
metaType,
|
|
2136
2197
|
metaId,
|
|
2137
|
-
policy =
|
|
2198
|
+
policy = {}
|
|
2138
2199
|
}: {
|
|
2139
2200
|
metaType: MetaType | string
|
|
2140
2201
|
metaId: MetaId | string
|
|
@@ -2142,13 +2203,30 @@ export default class KnishIOClient {
|
|
|
2142
2203
|
}): Promise<Response> {
|
|
2143
2204
|
this.log('info', `KnishIOClient::createPolicy() - Creating policy for ${metaType}:${metaId}...`)
|
|
2144
2205
|
|
|
2145
|
-
|
|
2146
|
-
|
|
2206
|
+
const molecule = await this.createMolecule({})
|
|
2207
|
+
molecule.addPolicyAtom({
|
|
2147
2208
|
metaType,
|
|
2148
2209
|
metaId,
|
|
2149
|
-
meta:
|
|
2150
|
-
policy
|
|
2210
|
+
meta: {},
|
|
2211
|
+
policy: policy || {}
|
|
2151
2212
|
})
|
|
2213
|
+
molecule.addContinuIdAtom()
|
|
2214
|
+
molecule.sign({
|
|
2215
|
+
bundle: this.getBundle()
|
|
2216
|
+
})
|
|
2217
|
+
molecule.check()
|
|
2218
|
+
|
|
2219
|
+
const query = await this.createMoleculeMutation({
|
|
2220
|
+
mutationClass: MutationProposeMolecule,
|
|
2221
|
+
molecule
|
|
2222
|
+
})
|
|
2223
|
+
const response = await this.executeQuery(query)
|
|
2224
|
+
|
|
2225
|
+
if (!response) {
|
|
2226
|
+
throw new CodeException('Policy creation failed')
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
return response
|
|
2152
2230
|
}
|
|
2153
2231
|
|
|
2154
2232
|
/**
|
|
@@ -0,0 +1,97 @@
|
|
|
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-TS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import BaseException, { type BaseExceptionOptions } from './BaseException'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Exception thrown when a secret storage or hardware envelope encryption operation fails
|
|
53
|
+
*/
|
|
54
|
+
export default class SecretStorageException extends BaseException {
|
|
55
|
+
constructor(message = 'Secret storage operation failed', options: BaseExceptionOptions = {}) {
|
|
56
|
+
super('WALLET_CREDENTIAL_ERROR', message, {
|
|
57
|
+
code: 'SECRET_STORAGE_ERROR',
|
|
58
|
+
...options
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Secret not found for the requested bundle hash
|
|
64
|
+
*/
|
|
65
|
+
static notFound(bundleHash: string): SecretStorageException {
|
|
66
|
+
return new SecretStorageException(`Secret not found for bundle: ${bundleHash}`, {
|
|
67
|
+
code: 'SECRET_NOT_FOUND',
|
|
68
|
+
details: { bundleHash }
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Decryption failed (wrong passphrase or corrupted payload)
|
|
74
|
+
*/
|
|
75
|
+
static decryptionFailed(reason?: string): SecretStorageException {
|
|
76
|
+
return new SecretStorageException(
|
|
77
|
+
`Failed to decrypt master secret: ${reason || 'Invalid passphrase or corrupted ciphertext'}`,
|
|
78
|
+
{
|
|
79
|
+
code: 'DECRYPTION_FAILED',
|
|
80
|
+
details: { reason }
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Provider is unavailable in current platform
|
|
87
|
+
*/
|
|
88
|
+
static unavailable(provider: string, reason?: string): SecretStorageException {
|
|
89
|
+
return new SecretStorageException(
|
|
90
|
+
`Secret storage provider '${provider}' is unavailable: ${reason || 'Hardware or API not accessible'}`,
|
|
91
|
+
{
|
|
92
|
+
code: 'STORAGE_UNAVAILABLE',
|
|
93
|
+
details: { provider, reason }
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/exception/index.ts
CHANGED
|
@@ -62,6 +62,7 @@ export { default as MolecularHashMismatchException } from './MolecularHashMismat
|
|
|
62
62
|
export { default as SignatureMismatchException } from './SignatureMismatchException'
|
|
63
63
|
export { default as TransferBalanceException } from './TransferBalanceException'
|
|
64
64
|
export { default as WalletCredentialException } from './WalletCredentialException'
|
|
65
|
+
export { default as SecretStorageException } from './SecretStorageException'
|
|
65
66
|
export { default as InvalidResponseException } from './InvalidResponseException'
|
|
66
67
|
|
|
67
68
|
// Additional exceptions (now implemented)
|
package/src/index.ts
CHANGED
|
@@ -261,12 +261,39 @@ export {
|
|
|
261
261
|
SignatureMismatchException,
|
|
262
262
|
TransferBalanceException,
|
|
263
263
|
WalletCredentialException,
|
|
264
|
+
SecretStorageException,
|
|
264
265
|
InvalidResponseException,
|
|
265
266
|
ExceptionFactory,
|
|
266
267
|
EXCEPTION_TYPES,
|
|
267
268
|
EXCEPTION_CODES
|
|
268
269
|
} from './exception'
|
|
269
270
|
|
|
271
|
+
// =============================================================================
|
|
272
|
+
// SECRET STORAGE & HARDWARE ENVELOPE ENCRYPTION
|
|
273
|
+
// =============================================================================
|
|
274
|
+
|
|
275
|
+
export {
|
|
276
|
+
MemorySecretStorageProvider,
|
|
277
|
+
WebCryptoSecretStorageProvider,
|
|
278
|
+
MemoryStorageBackend,
|
|
279
|
+
createDefaultSecretStorage,
|
|
280
|
+
type IStorageBackend,
|
|
281
|
+
type CreateSecretStorageOptions
|
|
282
|
+
} from './storage'
|
|
283
|
+
|
|
284
|
+
export {
|
|
285
|
+
zeroizeBytes,
|
|
286
|
+
withSecureBytes,
|
|
287
|
+
withSecureString,
|
|
288
|
+
constantTimeCompare
|
|
289
|
+
} from './libraries/secureMemory'
|
|
290
|
+
|
|
291
|
+
export type {
|
|
292
|
+
SecretStorageMetadata,
|
|
293
|
+
EncryptedSecretPayload,
|
|
294
|
+
ISecretStorageProvider
|
|
295
|
+
} from './types/storage'
|
|
296
|
+
|
|
270
297
|
// =============================================================================
|
|
271
298
|
// TYPE DEFINITIONS
|
|
272
299
|
// =============================================================================
|
|
@@ -362,7 +389,7 @@ export {
|
|
|
362
389
|
// MUST equal package.json's "version". The CI `version consistency` job enforces this via
|
|
363
390
|
// .github/scripts/check-version.sh, which parses this exact line — keep the literal form
|
|
364
391
|
// `export const SDK_VERSION = '<semver>'` intact so the gate can read it.
|
|
365
|
-
export const SDK_VERSION = '0.9.
|
|
392
|
+
export const SDK_VERSION = '0.9.8'
|
|
366
393
|
export const SDK_NAME = 'KnishIO-Client-TS'
|
|
367
394
|
export const COMPATIBLE_SERVER_VERSIONS = [4, 5]
|
|
368
395
|
|
|
@@ -0,0 +1,121 @@
|
|
|
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-TS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-TS/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
|
+
export function zeroizeBytes(buffer: Uint8Array | number[]): void {
|
|
59
|
+
if (buffer instanceof Uint8Array) {
|
|
60
|
+
buffer.fill(0)
|
|
61
|
+
} else if (Array.isArray(buffer)) {
|
|
62
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
63
|
+
buffer[i] = 0
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Execute a callback with a byte buffer and guarantee zeroization upon completion
|
|
70
|
+
*/
|
|
71
|
+
export async function withSecureBytes<T>(
|
|
72
|
+
bytes: Uint8Array,
|
|
73
|
+
fn: (bytes: Uint8Array) => Promise<T> | T
|
|
74
|
+
): Promise<T> {
|
|
75
|
+
try {
|
|
76
|
+
return await fn(bytes)
|
|
77
|
+
} finally {
|
|
78
|
+
zeroizeBytes(bytes)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Execute a callback with a secret string, ensuring temporary byte buffers are cleared
|
|
84
|
+
*/
|
|
85
|
+
export async function withSecureString<T>(
|
|
86
|
+
secret: string,
|
|
87
|
+
fn: (cleanSecret: string) => Promise<T> | T
|
|
88
|
+
): Promise<T> {
|
|
89
|
+
const bytes = textEncoder.encode(secret)
|
|
90
|
+
try {
|
|
91
|
+
return await fn(secret)
|
|
92
|
+
} finally {
|
|
93
|
+
zeroizeBytes(bytes)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Constant-time comparison of two byte arrays or strings to prevent timing attacks
|
|
99
|
+
*/
|
|
100
|
+
export function constantTimeCompare(
|
|
101
|
+
a: Uint8Array | string,
|
|
102
|
+
b: Uint8Array | string
|
|
103
|
+
): boolean {
|
|
104
|
+
const bytesA = typeof a === 'string' ? textEncoder.encode(a) : a
|
|
105
|
+
const bytesB = typeof b === 'string' ? textEncoder.encode(b) : b
|
|
106
|
+
|
|
107
|
+
let result = bytesA.length === bytesB.length ? 0 : 1
|
|
108
|
+
const len = Math.min(bytesA.length, bytesB.length)
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < len; i++) {
|
|
111
|
+
const byteA = bytesA[i] ?? 0
|
|
112
|
+
const byteB = bytesB[i] ?? 0
|
|
113
|
+
result |= byteA ^ byteB
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Zeroize temporary buffers if they were converted from strings
|
|
117
|
+
if (typeof a === 'string') zeroizeBytes(bytesA)
|
|
118
|
+
if (typeof b === 'string') zeroizeBytes(bytesB)
|
|
119
|
+
|
|
120
|
+
return result === 0
|
|
121
|
+
}
|
|
@@ -57,7 +57,7 @@ import type Molecule from '../core/Molecule'
|
|
|
57
57
|
* MutationProposeMolecule - Foundation for all molecular proposals
|
|
58
58
|
* Matches JavaScript SDK MutationProposeMolecule implementation exactly
|
|
59
59
|
*/
|
|
60
|
-
export default
|
|
60
|
+
export default class MutationProposeMolecule extends Mutation {
|
|
61
61
|
protected $__molecule: Molecule
|
|
62
62
|
protected $__remainderWallet: any | null = null
|
|
63
63
|
|
|
@@ -141,8 +141,11 @@ export default abstract class MutationProposeMolecule extends Mutation {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
144
|
+
* Fills the molecule with specific mutation data.
|
|
145
|
+
* Subclasses override to build domain-specific atoms.
|
|
146
|
+
* Default implementation is a no-op for pre-assembled molecules.
|
|
146
147
|
*/
|
|
147
|
-
|
|
148
|
+
fillMolecule(_params?: any): void {
|
|
149
|
+
// Intentionally empty for pre-assembled molecules proposed directly
|
|
150
|
+
}
|
|
148
151
|
}
|
package/src/schemas/index.ts
CHANGED
|
@@ -207,7 +207,8 @@ export const KnishIOClientConfigSchema = z.object({
|
|
|
207
207
|
socket: z.unknown().optional(),
|
|
208
208
|
serverSdkVersion: z.number().int().min(1).optional(),
|
|
209
209
|
logging: z.boolean().optional(),
|
|
210
|
-
defaultRequestPolicy: z.enum(['cache-first', 'cache-only', 'network-only', 'cache-and-network']).nullable().optional()
|
|
210
|
+
defaultRequestPolicy: z.enum(['cache-first', 'cache-only', 'network-only', 'cache-and-network']).nullable().optional(),
|
|
211
|
+
secretStorage: z.unknown().optional()
|
|
211
212
|
}).strict()
|
|
212
213
|
|
|
213
214
|
// =============================================================================
|
|
@@ -0,0 +1,156 @@
|
|
|
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-TS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import type { ISecretStorageProvider, SecretStorageMetadata } from '@/types/storage'
|
|
50
|
+
import SecretStorageException from '@/exception/SecretStorageException'
|
|
51
|
+
import { withSecureString } from '@/libraries/secureMemory'
|
|
52
|
+
|
|
53
|
+
interface MemorySecretEntry {
|
|
54
|
+
secret: string
|
|
55
|
+
metadata: SecretStorageMetadata
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* In-memory secret storage provider
|
|
60
|
+
* Used for testing, headless runners, and backward-compatible fallback
|
|
61
|
+
*/
|
|
62
|
+
export default class MemorySecretStorageProvider implements ISecretStorageProvider {
|
|
63
|
+
public readonly providerType = 'memory'
|
|
64
|
+
private secrets: Map<string, MemorySecretEntry> = new Map()
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Memory storage is not hardware backed
|
|
68
|
+
*/
|
|
69
|
+
isHardwareBacked(): boolean {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Memory storage is always available
|
|
75
|
+
*/
|
|
76
|
+
async isAvailable(): Promise<boolean> {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Store a secret in memory
|
|
82
|
+
*/
|
|
83
|
+
async storeSecret(
|
|
84
|
+
bundleHash: string,
|
|
85
|
+
secret: string,
|
|
86
|
+
options?: { label?: string; passphrase?: string }
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
if (!bundleHash) {
|
|
89
|
+
throw new SecretStorageException('Bundle hash cannot be empty')
|
|
90
|
+
}
|
|
91
|
+
if (!secret) {
|
|
92
|
+
throw new SecretStorageException('Secret cannot be empty')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const metadata: SecretStorageMetadata = {
|
|
96
|
+
bundleHash,
|
|
97
|
+
label: options?.label,
|
|
98
|
+
createdAt: Date.now(),
|
|
99
|
+
hardwareBacked: false,
|
|
100
|
+
providerType: this.providerType
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.secrets.set(bundleHash, { secret, metadata })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Retrieve a secret from memory
|
|
108
|
+
*/
|
|
109
|
+
async retrieveSecret(bundleHash: string): Promise<string | null> {
|
|
110
|
+
const entry = this.secrets.get(bundleHash)
|
|
111
|
+
return entry ? entry.secret : null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Delete a stored secret
|
|
116
|
+
*/
|
|
117
|
+
async deleteSecret(bundleHash: string): Promise<boolean> {
|
|
118
|
+
return this.secrets.delete(bundleHash)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if a secret exists
|
|
123
|
+
*/
|
|
124
|
+
async hasSecret(bundleHash: string): Promise<boolean> {
|
|
125
|
+
return this.secrets.has(bundleHash)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* List all stored secret metadata
|
|
130
|
+
*/
|
|
131
|
+
async listSecrets(): Promise<SecretStorageMetadata[]> {
|
|
132
|
+
return Array.from(this.secrets.values()).map(entry => ({ ...entry.metadata }))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Execute callback with unwrapped secret and ensure cleanup
|
|
137
|
+
*/
|
|
138
|
+
async withSecret<T>(
|
|
139
|
+
bundleHash: string,
|
|
140
|
+
fn: (secret: string) => Promise<T> | T
|
|
141
|
+
): Promise<T> {
|
|
142
|
+
const entry = this.secrets.get(bundleHash)
|
|
143
|
+
if (!entry) {
|
|
144
|
+
throw SecretStorageException.notFound(bundleHash)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return withSecureString(entry.secret, fn)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Clear all secrets from memory
|
|
152
|
+
*/
|
|
153
|
+
clear(): void {
|
|
154
|
+
this.secrets.clear()
|
|
155
|
+
}
|
|
156
|
+
}
|