@wishknish/knishio-client-ts 0.9.6 → 0.9.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishknish/knishio-client-ts",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
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",
@@ -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: this.$__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)
@@ -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
+ }
@@ -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.6'
392
+ export const SDK_VERSION = '0.9.7'
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
+ }
@@ -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
+ }