wolfronix-sdk 1.3.1 β†’ 2.3.0

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/README.md CHANGED
@@ -11,7 +11,7 @@ Official JavaScript/TypeScript SDK for Wolfronix - Zero-knowledge encryption mad
11
11
  - 🏒 **Enterprise Ready** - Seamless integration with your existing storage
12
12
  - πŸš€ **Simple API** - Encrypt files in 2 lines of code
13
13
  - πŸ“¦ **TypeScript Native** - Full type definitions included
14
- - 🌐 **Universal** - Works in Node.js 16+ and modern browsers
14
+ - 🌐 **Universal** - Works in Node.js 18+ and modern browsers
15
15
  - πŸ”„ **Auto Retry** - Built-in retry logic with exponential backoff
16
16
 
17
17
  ## Backend Integration (Enterprise Mode)
@@ -43,7 +43,8 @@ import Wolfronix from 'wolfronix-sdk';
43
43
  // Initialize client
44
44
  const wfx = new Wolfronix({
45
45
  baseUrl: 'https://your-wolfronix-server:5002',
46
- clientId: 'your-enterprise-client-id'
46
+ clientId: 'your-enterprise-client-id',
47
+ wolfronixKey: 'your-api-key'
47
48
  });
48
49
 
49
50
  // Register (First time only) - Generates keys client-side
@@ -105,6 +106,7 @@ import * as fs from 'fs';
105
106
  const wfx = new Wolfronix({
106
107
  baseUrl: 'https://wolfronix-server:5002',
107
108
  clientId: 'your-client-id',
109
+ wolfronixKey: 'your-api-key',
108
110
  insecure: true // For self-signed certs in development
109
111
  });
110
112
 
@@ -126,12 +128,6 @@ async function main() {
126
128
  fs.writeFileSync('decrypted.pdf', Buffer.from(decrypted));
127
129
  }
128
130
 
129
- main();
130
- // Decrypt and save
131
- const decrypted = await wfx.decryptToBuffer(file_id);
132
- fs.writeFileSync('decrypted.pdf', Buffer.from(decrypted));
133
- }
134
-
135
131
  main();
136
132
  ```
137
133
 
@@ -182,9 +178,10 @@ new Wolfronix(config: WolfronixConfig | string)
182
178
  |--------|------|---------|-------------|
183
179
  | `baseUrl` | string | required | Wolfronix server URL |
184
180
  | `clientId` | string | `''` | Enterprise client ID |
181
+ | `wolfronixKey` | string | `''` | API key for X-Wolfronix-Key auth |
185
182
  | `timeout` | number | `30000` | Request timeout (ms) |
186
183
  | `retries` | number | `3` | Max retry attempts |
187
- | `insecure` | boolean | `false` | Skip SSL verification |
184
+ | `insecure` | boolean | `false` | Skip SSL verification (Node.js: uses undici Agent, or set `NODE_TLS_REJECT_UNAUTHORIZED=0`) |
188
185
 
189
186
  ### Authentication
190
187
 
@@ -202,8 +199,9 @@ new Wolfronix(config: WolfronixConfig | string)
202
199
  | Method | Description |
203
200
  |--------|-------------|
204
201
  | `encrypt(file, filename?)` | Encrypt and store file |
205
- | `decrypt(fileId)` | Decrypt file (returns Blob) |
206
- | `decryptToBuffer(fileId)` | Decrypt file (returns ArrayBuffer) |
202
+ | `decrypt(fileId, role?)` | Decrypt file (zero-knowledge, returns Blob) |
203
+ | `decryptToBuffer(fileId, role?)` | Decrypt file (zero-knowledge, returns ArrayBuffer) |
204
+ | `getFileKey(fileId)` | Get encrypted key_part_a for client-side decryption |
207
205
  | `listFiles()` | List user's encrypted files |
208
206
  | `deleteFile(fileId)` | Delete encrypted file |
209
207
 
@@ -211,7 +209,7 @@ new Wolfronix(config: WolfronixConfig | string)
211
209
 
212
210
  | Method | Description |
213
211
  |--------|-------------|
214
- | `getPublicKey(userId)` | Fetch a user's RSA public key |
212
+ | `getPublicKey(userId, clientId?)` | Fetch a user's RSA public key |
215
213
  | `encryptMessage(text, recipientId)` | Encrypt text for a recipient (returns packet string) |
216
214
  | `decryptMessage(packetString)` | Decrypt a received message packet |
217
215
 
@@ -222,6 +220,42 @@ new Wolfronix(config: WolfronixConfig | string)
222
220
  | `getMetrics()` | Get encryption/decryption stats |
223
221
  | `healthCheck()` | Check server availability |
224
222
 
223
+ ## Security Architecture (v2.0)
224
+
225
+ ### Zero-Knowledge Decryption Flow
226
+
227
+ In v2.0, the private key **never leaves the client**. The decrypt flow works as follows:
228
+
229
+ ```
230
+ Client Wolfronix Server
231
+ β”‚ β”‚
232
+ β”‚ GET /files/{id}/key β”‚
233
+ │──────────────────────────────────────>β”‚
234
+ β”‚ { key_part_a: "<RSA-OAEP encrypted>"}β”‚
235
+ β”‚<──────────────────────────────────────│
236
+ β”‚ β”‚
237
+ β”‚ [Decrypt key_part_a locally β”‚
238
+ β”‚ with private key (RSA-OAEP)] β”‚
239
+ β”‚ β”‚
240
+ β”‚ POST /files/{id}/decrypt β”‚
241
+ β”‚ { decrypted_key_a: "<base64>" } β”‚
242
+ │──────────────────────────────────────>β”‚
243
+ β”‚ β”‚
244
+ β”‚ [Server combines key_a + key_b, β”‚
245
+ β”‚ decrypts with AES-256-GCM] β”‚
246
+ β”‚ β”‚
247
+ β”‚ <decrypted file bytes> β”‚
248
+ β”‚<──────────────────────────────────────│
249
+ ```
250
+
251
+ ### Key Security Properties
252
+ - **AES-256-GCM** authenticated encryption (tamper-proof, replaces AES-CTR)
253
+ - **RSA-OAEP** with SHA-256 for key transport (replaces PKCS1v15)
254
+ - **API key authentication** via `X-Wolfronix-Key` header on all endpoints
255
+ - **Configurable CORS** origins (no more wildcard `*`)
256
+ - **Dual-key split**: AES key split in half, each half encrypted with different RSA key
257
+ - **Zero-knowledge key wrapping**: Private keys wrapped with PBKDF2-derived keys, server never sees raw private keys
258
+
225
259
  ## Error Handling
226
260
 
227
261
  The SDK provides specific error types for different scenarios:
@@ -278,7 +312,8 @@ import Wolfronix, {
278
312
  // All methods are fully typed
279
313
  const config: WolfronixConfig = {
280
314
  baseUrl: 'https://server:5002',
281
- clientId: 'my-client'
315
+ clientId: 'my-client',
316
+ wolfronixKey: 'my-api-key'
282
317
  };
283
318
 
284
319
  const wfx = new Wolfronix(config);
@@ -292,12 +327,12 @@ const response: EncryptResponse = await wfx.encrypt(file);
292
327
  import { useState, useCallback, useMemo } from 'react';
293
328
  import Wolfronix, { FileInfo as WolfronixFile } from 'wolfronix-sdk';
294
329
 
295
- export function useWolfronix(baseUrl: string, clientId?: string) {
330
+ export function useWolfronix(baseUrl: string, clientId?: string, wolfronixKey?: string) {
296
331
  const [isLoading, setIsLoading] = useState(false);
297
332
  const [error, setError] = useState<Error | null>(null);
298
333
  const [files, setFiles] = useState<WolfronixFile[]>([]);
299
334
 
300
- const client = useMemo(() => new Wolfronix({ baseUrl, clientId }), [baseUrl, clientId]);
335
+ const client = useMemo(() => new Wolfronix({ baseUrl, clientId, wolfronixKey }), [baseUrl, clientId, wolfronixKey]);
301
336
 
302
337
  const login = useCallback(async (email: string, password: string) => {
303
338
  setIsLoading(true);
@@ -395,7 +430,7 @@ Wolfronix can be integrated into **any application** that handles sensitive data
395
430
 
396
431
  ## Requirements
397
432
 
398
- - Node.js 16+ (for Node.js usage)
433
+ - Node.js 18+ (for Node.js usage)
399
434
  - Modern browser with Web Crypto API support
400
435
 
401
436
  ## License
package/dist/index.d.mts CHANGED
@@ -3,13 +3,15 @@
3
3
  * Zero-knowledge encryption made simple
4
4
  *
5
5
  * @package @wolfronix/sdk
6
- * @version 1.3.0
6
+ * @version 2.3.0
7
7
  */
8
8
  interface WolfronixConfig {
9
9
  /** Wolfronix server base URL */
10
10
  baseUrl: string;
11
11
  /** Your enterprise client ID (optional for self-hosted) */
12
12
  clientId?: string;
13
+ /** API key for authentication (X-Wolfronix-Key header) */
14
+ wolfronixKey?: string;
13
15
  /** Request timeout in milliseconds (default: 30000) */
14
16
  timeout?: number;
15
17
  /** Retry failed requests (default: 3) */
@@ -44,6 +46,11 @@ interface DeleteResponse {
44
46
  success: boolean;
45
47
  message: string;
46
48
  }
49
+ interface KeyPartResponse {
50
+ file_id: string;
51
+ key_part_a: string;
52
+ message: string;
53
+ }
47
54
  interface MetricsResponse {
48
55
  success: boolean;
49
56
  total_encryptions: number;
@@ -56,6 +63,51 @@ interface EncryptMessagePacket {
56
63
  iv: string;
57
64
  msg: string;
58
65
  }
66
+ interface ServerEncryptResult {
67
+ /** Base64-encoded ciphertext */
68
+ encrypted_message: string;
69
+ /** Base64-encoded nonce */
70
+ nonce: string;
71
+ /** Base64-encoded client key half (Layer 4) or full key (Layer 3) */
72
+ key_part_a: string;
73
+ /** Tag for server's key_part_b lookup (Layer 4 only, empty for Layer 3) */
74
+ message_tag: string;
75
+ /** Unix timestamp */
76
+ timestamp: number;
77
+ }
78
+ interface ServerDecryptParams {
79
+ /** Base64-encoded ciphertext (from ServerEncryptResult) */
80
+ encryptedMessage: string;
81
+ /** Base64-encoded nonce */
82
+ nonce: string;
83
+ /** Base64-encoded key_part_a */
84
+ keyPartA: string;
85
+ /** Message tag for Layer 4 (omit for Layer 3) */
86
+ messageTag?: string;
87
+ }
88
+ interface ServerBatchEncryptResult {
89
+ results: Array<{
90
+ id: string;
91
+ encrypted_message: string;
92
+ nonce: string;
93
+ seq: number;
94
+ }>;
95
+ /** Shared key_part_a for the batch */
96
+ key_part_a: string;
97
+ /** Shared batch tag for key_part_b lookup (Layer 4) */
98
+ batch_tag: string;
99
+ timestamp: number;
100
+ }
101
+ interface StreamSession {
102
+ /** Client's key half (encrypt direction only) */
103
+ keyPartA?: string;
104
+ /** Stream tag for the session */
105
+ streamTag?: string;
106
+ }
107
+ interface StreamChunk {
108
+ data: string;
109
+ seq: number;
110
+ }
59
111
  declare class WolfronixError extends Error {
60
112
  readonly code: string;
61
113
  readonly statusCode?: number;
@@ -85,6 +137,8 @@ declare class Wolfronix {
85
137
  private publicKey;
86
138
  private privateKey;
87
139
  private publicKeyPEM;
140
+ /** Expose private key status for testing */
141
+ hasPrivateKey(): boolean;
88
142
  /**
89
143
  * Create a new Wolfronix client
90
144
  *
@@ -156,7 +210,14 @@ declare class Wolfronix {
156
210
  */
157
211
  encrypt(file: File | Blob | ArrayBuffer | Uint8Array, filename?: string): Promise<EncryptResponse>;
158
212
  /**
159
- * Decrypt and retrieve a file
213
+ * Decrypt and retrieve a file using zero-knowledge flow.
214
+ *
215
+ * Flow:
216
+ * 1. GET /api/v1/files/{id}/key β†’ encrypted key_part_a
217
+ * 2. Decrypt key_part_a client-side with private key (RSA-OAEP)
218
+ * 3. POST /api/v1/files/{id}/decrypt with { decrypted_key_a } in body
219
+ *
220
+ * The private key NEVER leaves the client.
160
221
  *
161
222
  * @example
162
223
  * ```typescript
@@ -169,11 +230,18 @@ declare class Wolfronix {
169
230
  * fs.writeFileSync('decrypted.pdf', buffer);
170
231
  * ```
171
232
  */
172
- decrypt(fileId: string): Promise<Blob>;
233
+ decrypt(fileId: string, role?: string): Promise<Blob>;
173
234
  /**
174
- * Decrypt and return as ArrayBuffer
235
+ * Decrypt and return as ArrayBuffer (zero-knowledge flow)
175
236
  */
176
- decryptToBuffer(fileId: string): Promise<ArrayBuffer>;
237
+ decryptToBuffer(fileId: string, role?: string): Promise<ArrayBuffer>;
238
+ /**
239
+ * Fetch the encrypted key_part_a for a file (for client-side decryption)
240
+ *
241
+ * @param fileId The file ID to get the key for
242
+ * @returns KeyPartResponse containing the RSA-OAEP encrypted key_part_a
243
+ */
244
+ getFileKey(fileId: string): Promise<KeyPartResponse>;
177
245
  /**
178
246
  * List all encrypted files for current user
179
247
  *
@@ -196,8 +264,9 @@ declare class Wolfronix {
196
264
  /**
197
265
  * Get another user's public key (for E2E encryption)
198
266
  * @param userId The ID of the recipient
267
+ * @param clientId Optional: override the configured clientId
199
268
  */
200
- getPublicKey(userId: string): Promise<string>;
269
+ getPublicKey(userId: string, clientId?: string): Promise<string>;
201
270
  /**
202
271
  * Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
203
272
  * Returns a secure JSON string (packet) to send via chat
@@ -212,6 +281,105 @@ declare class Wolfronix {
212
281
  * @param packetJson The secure JSON string packet
213
282
  */
214
283
  decryptMessage(packetJson: string): Promise<string>;
284
+ /**
285
+ * Encrypt a text message via the Wolfronix server (dual-key split).
286
+ * The server generates an AES key, encrypts the message, and splits the key β€”
287
+ * you get key_part_a, the server holds key_part_b.
288
+ *
289
+ * Use this for server-managed message encryption (e.g., stored encrypted messages).
290
+ * For true E2E (where the server never sees plaintext), use encryptMessage() instead.
291
+ *
292
+ * @param message The plaintext message to encrypt
293
+ * @param options.layer 3 = AES only (full key returned), 4 = dual-key split (default)
294
+ *
295
+ * @example
296
+ * ```typescript
297
+ * const result = await wfx.serverEncrypt('Hello, World!');
298
+ * // Store result.encrypted_message, result.nonce, result.key_part_a, result.message_tag
299
+ * ```
300
+ */
301
+ serverEncrypt(message: string, options?: {
302
+ layer?: number;
303
+ }): Promise<ServerEncryptResult>;
304
+ /**
305
+ * Decrypt a message previously encrypted via serverEncrypt().
306
+ *
307
+ * @param params The encrypted message data (from serverEncrypt result)
308
+ * @returns The decrypted plaintext message
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * const text = await wfx.serverDecrypt({
313
+ * encryptedMessage: result.encrypted_message,
314
+ * nonce: result.nonce,
315
+ * keyPartA: result.key_part_a,
316
+ * messageTag: result.message_tag,
317
+ * });
318
+ * ```
319
+ */
320
+ serverDecrypt(params: ServerDecryptParams): Promise<string>;
321
+ /**
322
+ * Encrypt multiple messages in a single round-trip (batch).
323
+ * All messages share one AES key (different nonce per message).
324
+ * Efficient for chat history encryption or bulk operations.
325
+ *
326
+ * @param messages Array of { id, message } objects (max 100)
327
+ * @param options.layer 3 or 4 (default: 4)
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * const result = await wfx.serverEncryptBatch([
332
+ * { id: 'msg1', message: 'Hello' },
333
+ * { id: 'msg2', message: 'World' },
334
+ * ]);
335
+ * // result.results[0].encrypted_message, result.key_part_a, result.batch_tag
336
+ * ```
337
+ */
338
+ serverEncryptBatch(messages: Array<{
339
+ id: string;
340
+ message: string;
341
+ }>, options?: {
342
+ layer?: number;
343
+ }): Promise<ServerBatchEncryptResult>;
344
+ /**
345
+ * Decrypt a single message from a batch result.
346
+ * Uses the shared key_part_a and batch_tag from the batch result.
347
+ *
348
+ * @param batchResult The batch encrypt result
349
+ * @param index The index of the message to decrypt
350
+ */
351
+ serverDecryptBatchItem(batchResult: ServerBatchEncryptResult, index: number): Promise<string>;
352
+ /**
353
+ * Create a streaming encryption/decryption session over WebSocket.
354
+ * Data flows in real-time: send chunks, receive encrypted/decrypted chunks back.
355
+ *
356
+ * @param direction 'encrypt' for plaintext→ciphertext, 'decrypt' for reverse
357
+ * @param streamKey Required for decrypt β€” the key_part_a and stream_tag from the encrypt session
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * // Encrypt stream
362
+ * const stream = await wfx.createStream('encrypt');
363
+ * stream.onData((chunk, seq) => console.log('Encrypted chunk', seq));
364
+ * stream.send('Hello chunk 1');
365
+ * stream.send('Hello chunk 2');
366
+ * const summary = await stream.end();
367
+ * // Save stream.keyPartA and stream.streamTag for decryption
368
+ *
369
+ * // Decrypt stream
370
+ * const dStream = await wfx.createStream('decrypt', {
371
+ * keyPartA: stream.keyPartA!,
372
+ * streamTag: stream.streamTag!,
373
+ * });
374
+ * dStream.onData((chunk, seq) => console.log('Decrypted:', chunk));
375
+ * dStream.send(encryptedChunk1);
376
+ * await dStream.end();
377
+ * ```
378
+ */
379
+ createStream(direction: 'encrypt' | 'decrypt', streamKey?: {
380
+ keyPartA: string;
381
+ streamTag: string;
382
+ }): Promise<WolfronixStream>;
215
383
  /**
216
384
  * Get encryption/decryption metrics
217
385
  *
@@ -227,6 +395,77 @@ declare class Wolfronix {
227
395
  */
228
396
  healthCheck(): Promise<boolean>;
229
397
  }
398
+ type StreamDataCallback = (data: string, seq: number) => void;
399
+ type StreamErrorCallback = (error: Error) => void;
400
+ /**
401
+ * Real-time streaming encryption/decryption over WebSocket.
402
+ * Each chunk is individually encrypted with AES-256-GCM using counter-based nonces.
403
+ *
404
+ * @example
405
+ * ```typescript
406
+ * const stream = await wfx.createStream('encrypt');
407
+ * stream.onData((chunk, seq) => sendToRecipient(chunk));
408
+ * stream.onError((err) => console.error(err));
409
+ * await stream.send('audio chunk data...');
410
+ * const summary = await stream.end();
411
+ * ```
412
+ */
413
+ declare class WolfronixStream {
414
+ private readonly config;
415
+ private readonly userId;
416
+ private ws;
417
+ private dataCallbacks;
418
+ private errorCallbacks;
419
+ private pendingChunks;
420
+ private seqCounter;
421
+ /** Client's key half (available after encrypt stream init) */
422
+ keyPartA: string | null;
423
+ /** Stream tag (available after encrypt stream init) */
424
+ streamTag: string | null;
425
+ /** @internal */
426
+ constructor(config: Required<WolfronixConfig>, userId: string);
427
+ /** @internal Connect and initialize the stream session */
428
+ connect(direction: 'encrypt' | 'decrypt', streamKey?: {
429
+ keyPartA: string;
430
+ streamTag: string;
431
+ }): Promise<void>;
432
+ /**
433
+ * Send a data chunk for encryption/decryption.
434
+ * Returns a promise that resolves with the processed (encrypted/decrypted) chunk.
435
+ *
436
+ * @param data String or base64-encoded binary data
437
+ * @returns The processed chunk (base64-encoded)
438
+ */
439
+ send(data: string): Promise<string>;
440
+ /**
441
+ * Send raw binary data for encryption/decryption.
442
+ *
443
+ * @param buffer ArrayBuffer or Uint8Array
444
+ * @returns The processed chunk (base64-encoded)
445
+ */
446
+ sendBinary(buffer: ArrayBuffer | Uint8Array): Promise<string>;
447
+ /**
448
+ * Register a callback for incoming data chunks.
449
+ *
450
+ * @param callback Called with (base64Data, sequenceNumber) for each chunk
451
+ */
452
+ onData(callback: StreamDataCallback): void;
453
+ /**
454
+ * Register a callback for stream errors.
455
+ */
456
+ onError(callback: StreamErrorCallback): void;
457
+ /**
458
+ * End the stream session. Returns the total number of chunks processed.
459
+ */
460
+ end(): Promise<{
461
+ chunksProcessed: number;
462
+ }>;
463
+ /**
464
+ * Close the stream immediately without sending an end message.
465
+ */
466
+ close(): void;
467
+ private isBase64;
468
+ }
230
469
  /**
231
470
  * Create a new Wolfronix client
232
471
  *
@@ -242,4 +481,4 @@ declare class Wolfronix {
242
481
  */
243
482
  declare function createClient(config: WolfronixConfig | string): Wolfronix;
244
483
 
245
- export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, createClient, Wolfronix as default };
484
+ export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type FileInfo, FileNotFoundError, type KeyPartResponse, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, type ServerBatchEncryptResult, type ServerDecryptParams, type ServerEncryptResult, type StreamChunk, type StreamSession, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, WolfronixStream, createClient, Wolfronix as default };