@techbulls/encrypted-s3-store 1.0.6 → 1.1.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
@@ -308,16 +308,17 @@ interface CustomEncryption {
308
308
  }
309
309
 
310
310
  interface EncryptResult {
311
- data: Buffer; // The encrypted data
312
- metadata: EncryptionMetadata; // Metadata to store for decryption
311
+ data: Buffer; // The encrypted data
312
+ metadata: EncryptionMetadata; // Metadata to store for decryption
313
313
  }
314
314
 
315
315
  interface EncryptionMetadata {
316
- [key: string]: string; // Key-value pairs stored in S3 object metadata
316
+ [key: string]: string; // Key-value pairs stored in S3 object metadata
317
317
  }
318
318
  ```
319
319
 
320
320
  **Notes:**
321
+
321
322
  - Both `encrypt` and `decrypt` functions must be provided together
322
323
  - Metadata keys are automatically prefixed with `x-amz-custom-` when stored in S3
323
324
  - Custom encryption uses `x-amz-encryption: 'custom'` as the marker in S3 metadata
@@ -356,30 +357,17 @@ Uses scrypt with the following parameters:
356
357
  Encryption metadata is stored in S3 object metadata:
357
358
 
358
359
  **Default AES-256-GCM:**
360
+
359
361
  - `x-amz-iv` - Base64-encoded initialization vector
360
362
  - `x-amz-salt` - Base64-encoded salt for key derivation
361
363
  - `x-amz-auth-tag` - Base64-encoded authentication tag
362
364
  - `x-amz-encryption` - Set to `aes-256-gcm`
363
365
 
364
366
  **Custom Encryption:**
367
+
365
368
  - `x-amz-custom-*` - Custom metadata keys (prefixed automatically)
366
369
  - `x-amz-encryption` - Set to `custom`
367
370
 
368
- ## Error Handling
369
-
370
- The library throws descriptive errors for common issues:
371
-
372
- ```typescript
373
- try {
374
- await store.upload({ Bucket: 'my-bucket', Key: 'test.txt', Body: 'content' });
375
- } catch (error) {
376
- // Handle errors:
377
- // - "Encryption key not provided."
378
- // - "Body is required for upload"
379
- // - "No body returned from S3"
380
- }
381
- ```
382
-
383
371
  ## TypeScript
384
372
 
385
373
  The library is written in TypeScript and exports all types:
@@ -426,6 +414,84 @@ await store.client.send(
426
414
  );
427
415
  ```
428
416
 
417
+ ## Advanced Configuration
418
+
419
+ ### Custom Scrypt Parameters
420
+
421
+ For higher security requirements, you can configure the scrypt key derivation parameters:
422
+
423
+ ```typescript
424
+ const store = new S3ObjectStore(
425
+ s3Client,
426
+ 'your-key',
427
+ 'encrypt',
428
+ undefined, // no custom encryption
429
+ {
430
+ N: 65536, // 2^16, 4x more secure than default (16384)
431
+ r: 8, // block size (default)
432
+ p: 1, // parallelization (default)
433
+ }
434
+ );
435
+ ```
436
+
437
+ Higher `N` values increase security but also increase computation time and memory usage.
438
+
439
+ ## Error Handling
440
+
441
+ The library throws descriptive errors. Here's how to handle common cases:
442
+
443
+ ```typescript
444
+ try {
445
+ await store.upload({
446
+ Bucket: 'my-bucket',
447
+ Key: 'file.txt',
448
+ Body: 'content',
449
+ });
450
+ } catch (error) {
451
+ if (error instanceof Error) {
452
+ if (error.message.includes('Bucket is required')) {
453
+ console.error('Missing bucket name');
454
+ } else if (error.message.includes('encryption metadata is missing')) {
455
+ console.error('Trying to decrypt an unencrypted file - use passthrough mode');
456
+ } else if (error.message.includes('Failed to upload')) {
457
+ console.error('S3 upload failed - check credentials and permissions');
458
+ } else {
459
+ console.error('Error:', error.message);
460
+ }
461
+ }
462
+ }
463
+ ```
464
+
465
+ ## Security Best Practices
466
+
467
+ 1. **Key Management**
468
+ - Never hardcode encryption keys in source code
469
+ - Use environment variables or secret management services (AWS Secrets Manager, HashiCorp Vault)
470
+ - Rotate keys periodically
471
+
472
+ 2. **Key Storage**
473
+ - Store keys in encrypted form when at rest
474
+ - Never commit keys to version control
475
+
476
+ 3. **Defense in Depth**
477
+ - Consider using this library WITH S3 server-side encryption for additional protection
478
+ - Use HTTPS for all S3 operations (default with AWS SDK)
479
+ - Enable AWS CloudTrail for audit logs
480
+
481
+ 4. **Access Control**
482
+ - Use least-privilege IAM policies
483
+ - Enable S3 bucket versioning for recovery
484
+
485
+ ## Platform Support
486
+
487
+ This library is **Node.js only** and does not support browsers. It uses Node.js built-in modules:
488
+
489
+ - `node:crypto` (for AES-256-GCM encryption and scrypt)
490
+ - `node:stream` (for streaming)
491
+ - `node:util` (for promisify)
492
+
493
+ For browser environments, consider using server-side encryption or alternative libraries that use the Web Crypto API.
494
+
429
495
  ## Requirements
430
496
 
431
497
  - Node.js 18+
package/dist/client.d.ts CHANGED
@@ -3,11 +3,10 @@
3
3
  * Provides transparent encryption/decryption for S3 objects using AES-256-GCM.
4
4
  *
5
5
  * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
6
- * @license Apache-2.0
6
+ * @license MIT
7
7
  */
8
8
  import { S3Client } from '@aws-sdk/client-s3';
9
- import { CustomEncryption, IDownload, IUpload, ModeType } from './types.js';
10
- import { Readable } from 'node:stream';
9
+ import { CustomEncryption, DownloadResult, IDownload, IUpload, ModeType, ScryptOptions } from './types.js';
11
10
  /**
12
11
  * S3 client wrapper with transparent end-to-end encryption support.
13
12
  *
@@ -31,17 +30,17 @@ import { Readable } from 'node:stream';
31
30
  */
32
31
  export declare class S3ObjectStore {
33
32
  /** @type {ModeType} Current operation mode - 'encrypt' or 'passthrough' */
34
- mode: ModeType;
33
+ readonly mode: ModeType;
35
34
  /** @type {string} Encryption key used for AES-256-GCM encryption */
36
- key: string;
35
+ private readonly key;
37
36
  /** @type {string | undefined} Default S3 bucket for all operations */
38
- bucket?: string;
39
- /** @type {string} Encryption algorithm identifier (always 'aes-256-gcm') */
40
- algorithm: string;
37
+ readonly bucket?: string;
41
38
  /** @type {S3Client} The underlying AWS S3 client instance */
42
- client: S3Client;
39
+ readonly client: S3Client;
43
40
  /** @type {CustomEncryption | undefined} Optional custom encryption/decryption functions */
44
- customEncryption?: CustomEncryption;
41
+ private readonly customEncryption?;
42
+ /** @type {ScryptOptions | undefined} Optional scrypt parameters for key derivation */
43
+ private readonly scryptOptions?;
45
44
  /**
46
45
  * Creates a new S3ObjectStore instance.
47
46
  *
@@ -49,7 +48,8 @@ export declare class S3ObjectStore {
49
48
  * @param key - Encryption key for AES-256-GCM. Falls back to ENCRYPTION_KEY environment variable if not provided.
50
49
  * @param mode - Operation mode ('encrypt' or 'passthrough'). Defaults to 'encrypt'.
51
50
  * @param customEncryption - Optional custom encryption/decryption functions to replace the default AES-256-GCM implementation.
52
- * @throws {Error} If no encryption key is provided and ENCRYPTION_KEY environment variable is not set
51
+ * @param scryptOptions - Optional scrypt parameters for key derivation (N, r, p). Defaults: N=16384, r=8, p=1.
52
+ * @throws {Error} If no encryption key is provided in 'encrypt' mode and ENCRYPTION_KEY environment variable is not set
53
53
  *
54
54
  * @example
55
55
  * ```typescript
@@ -59,17 +59,24 @@ export declare class S3ObjectStore {
59
59
  * // With environment variable (ENCRYPTION_KEY)
60
60
  * const store = new S3ObjectStore(s3Client);
61
61
  *
62
- * // With passthrough mode (no encryption)
63
- * const store = new S3ObjectStore(s3Client, 'key', 'passthrough');
62
+ * // With passthrough mode (no encryption key required)
63
+ * const store = new S3ObjectStore(s3Client, undefined, 'passthrough');
64
64
  *
65
65
  * // With custom encryption
66
66
  * const store = new S3ObjectStore(s3Client, 'key', 'encrypt', {
67
67
  * encrypt: (data, key) => ({ data: myEncrypt(data, key), metadata: { myParam: '...' } }),
68
68
  * decrypt: (data, key, metadata) => myDecrypt(data, key, metadata.myParam)
69
69
  * });
70
+ *
71
+ * // With custom scrypt parameters for higher security
72
+ * const store = new S3ObjectStore(s3Client, 'key', 'encrypt', undefined, {
73
+ * N: 65536, // 2^16, 4x more secure than default
74
+ * r: 8,
75
+ * p: 1,
76
+ * });
70
77
  * ```
71
78
  */
72
- constructor(client: S3Client, key?: string, mode?: ModeType, customEncryption?: CustomEncryption);
79
+ constructor(client: S3Client, key?: string, mode?: ModeType, customEncryption?: CustomEncryption, scryptOptions?: ScryptOptions);
73
80
  /**
74
81
  * Uploads data to S3 with optional encryption.
75
82
  *
@@ -154,10 +161,6 @@ export declare class S3ObjectStore {
154
161
  * const content = Buffer.concat(chunks).toString('utf8');
155
162
  * ```
156
163
  */
157
- download(input: IDownload): Promise<{
158
- Body: Readable;
159
- ContentType: string | undefined;
160
- ContentLength: number | undefined;
161
- Metadata: Record<string, string> | undefined;
162
- }>;
164
+ download(input: IDownload): Promise<DownloadResult>;
163
165
  }
166
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAsC,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAElF,OAAO,EACL,gBAAgB,EAChB,cAAc,EAEd,SAAS,EACT,OAAO,EACP,QAAQ,EACR,aAAa,EACd,MAAM,YAAY,CAAC;AA8BpB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,aAAa;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAExB,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAE1B,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAmB;IAErD,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAgB;IAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;gBAED,MAAM,EAAE,QAAQ,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,QAAQ,EACf,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,aAAa,CAAC,EAAE,aAAa;IAiB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACG,MAAM,CAAC,KAAK,EAAE,OAAO;IAiK3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACG,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC;CAwJ1D"}
package/dist/client.js CHANGED
@@ -3,12 +3,32 @@
3
3
  * Provides transparent encryption/decryption for S3 objects using AES-256-GCM.
4
4
  *
5
5
  * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
6
- * @license Apache-2.0
6
+ * @license MIT
7
7
  */
8
8
  import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
9
9
  import { deriveKey } from './utils.js';
10
10
  import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
11
11
  import { Readable } from 'node:stream';
12
+ /** S3 metadata size limit in bytes */
13
+ const S3_METADATA_SIZE_LIMIT = 2048;
14
+ /**
15
+ * Calculates the total size of S3 metadata in bytes.
16
+ * S3 counts both keys and values toward the 2KB limit.
17
+ */
18
+ function calculateMetadataSize(metadata) {
19
+ return Object.entries(metadata).reduce((sum, [key, value]) => sum + key.length + value.length, 0);
20
+ }
21
+ /**
22
+ * Validates that S3 metadata size doesn't exceed the 2KB limit.
23
+ * @throws {Error} If metadata exceeds the limit
24
+ */
25
+ function validateMetadataSize(metadata) {
26
+ const size = calculateMetadataSize(metadata);
27
+ if (size > S3_METADATA_SIZE_LIMIT) {
28
+ throw new Error(`S3 metadata size (${size} bytes) exceeds the 2KB limit. ` +
29
+ 'Reduce custom metadata or encryption metadata size.');
30
+ }
31
+ }
12
32
  /**
13
33
  * S3 client wrapper with transparent end-to-end encryption support.
14
34
  *
@@ -38,7 +58,8 @@ export class S3ObjectStore {
38
58
  * @param key - Encryption key for AES-256-GCM. Falls back to ENCRYPTION_KEY environment variable if not provided.
39
59
  * @param mode - Operation mode ('encrypt' or 'passthrough'). Defaults to 'encrypt'.
40
60
  * @param customEncryption - Optional custom encryption/decryption functions to replace the default AES-256-GCM implementation.
41
- * @throws {Error} If no encryption key is provided and ENCRYPTION_KEY environment variable is not set
61
+ * @param scryptOptions - Optional scrypt parameters for key derivation (N, r, p). Defaults: N=16384, r=8, p=1.
62
+ * @throws {Error} If no encryption key is provided in 'encrypt' mode and ENCRYPTION_KEY environment variable is not set
42
63
  *
43
64
  * @example
44
65
  * ```typescript
@@ -48,27 +69,34 @@ export class S3ObjectStore {
48
69
  * // With environment variable (ENCRYPTION_KEY)
49
70
  * const store = new S3ObjectStore(s3Client);
50
71
  *
51
- * // With passthrough mode (no encryption)
52
- * const store = new S3ObjectStore(s3Client, 'key', 'passthrough');
72
+ * // With passthrough mode (no encryption key required)
73
+ * const store = new S3ObjectStore(s3Client, undefined, 'passthrough');
53
74
  *
54
75
  * // With custom encryption
55
76
  * const store = new S3ObjectStore(s3Client, 'key', 'encrypt', {
56
77
  * encrypt: (data, key) => ({ data: myEncrypt(data, key), metadata: { myParam: '...' } }),
57
78
  * decrypt: (data, key, metadata) => myDecrypt(data, key, metadata.myParam)
58
79
  * });
80
+ *
81
+ * // With custom scrypt parameters for higher security
82
+ * const store = new S3ObjectStore(s3Client, 'key', 'encrypt', undefined, {
83
+ * N: 65536, // 2^16, 4x more secure than default
84
+ * r: 8,
85
+ * p: 1,
86
+ * });
59
87
  * ```
60
88
  */
61
- constructor(client, key, mode, customEncryption) {
62
- /** @type {string} Encryption algorithm identifier (always 'aes-256-gcm') */
63
- this.algorithm = 'aes-256-gcm';
89
+ constructor(client, key, mode, customEncryption, scryptOptions) {
64
90
  this.client = client;
65
- const envEncryptionKey = process.env.ENCRYPTION_KEY;
66
- if (!key && !envEncryptionKey) {
67
- throw new Error('Encryption key not provided.');
68
- }
69
- this.key = (key || envEncryptionKey);
70
91
  this.mode = mode ?? 'encrypt';
71
92
  this.customEncryption = customEncryption;
93
+ this.scryptOptions = scryptOptions;
94
+ const envEncryptionKey = process.env.ENCRYPTION_KEY;
95
+ // Only require encryption key in 'encrypt' mode
96
+ if (this.mode === 'encrypt' && !key && !envEncryptionKey) {
97
+ throw new Error('Encryption key is required when mode is "encrypt".');
98
+ }
99
+ this.key = (key || envEncryptionKey || '');
72
100
  }
73
101
  /**
74
102
  * Uploads data to S3 with optional encryption.
@@ -120,17 +148,36 @@ export class S3ObjectStore {
120
148
  * ```
121
149
  */
122
150
  async upload(input) {
151
+ /** Validate required input parameters */
152
+ const bucket = input.Bucket || this.bucket;
153
+ if (!bucket) {
154
+ throw new Error('Bucket is required. Provide it in upload() or set a default bucket.');
155
+ }
156
+ if (!input.Key) {
157
+ throw new Error('Key is required for upload.');
158
+ }
159
+ if (input.Key.startsWith('/')) {
160
+ throw new Error('S3 Key should not start with "/" - use relative paths only.');
161
+ }
162
+ if (input.Key.includes('\\')) {
163
+ throw new Error('S3 Key should not contain backslashes - use forward slashes for paths.');
164
+ }
123
165
  if (!input.Body) {
124
- throw new Error('Body is required for upload');
166
+ throw new Error('Body is required for upload.');
125
167
  }
126
168
  /** Resolve mode: input.mode takes precedence, fallback to client default */
127
169
  const mode = input?.Mode === 'encrypt' || input?.Mode === 'passthrough' ? input.Mode : this.mode;
128
170
  /** Passthrough mode: upload without encryption */
129
171
  if (mode === 'passthrough') {
130
- return this.client.send(new PutObjectCommand({
131
- ...input,
132
- Bucket: input.Bucket || this.bucket,
133
- }));
172
+ try {
173
+ return await this.client.send(new PutObjectCommand({
174
+ ...input,
175
+ Bucket: bucket,
176
+ }));
177
+ }
178
+ catch (error) {
179
+ throw new Error(`Failed to upload to s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
180
+ }
134
181
  }
135
182
  /** Encrypt mode: encrypt data before uploading */
136
183
  /** Convert input body to Buffer for encryption */
@@ -149,24 +196,53 @@ export class S3ObjectStore {
149
196
  }
150
197
  /** Use custom encryption if provided */
151
198
  if (this.customEncryption) {
152
- const result = await this.customEncryption.encrypt(data, this.key);
199
+ /** Call custom encryption function with validation */
200
+ let result;
201
+ try {
202
+ result = await this.customEncryption.encrypt(data, this.key);
203
+ }
204
+ catch (error) {
205
+ throw new Error(`Custom encryption function failed: ${error instanceof Error ? error.message : error}`);
206
+ }
207
+ /** Validate custom encryption result */
208
+ if (!result || typeof result !== 'object') {
209
+ throw new Error('Custom encryption function must return an object with data and metadata.');
210
+ }
211
+ if (!Buffer.isBuffer(result.data)) {
212
+ throw new Error('Custom encryption function must return a Buffer in result.data.');
213
+ }
214
+ if (!result.metadata || typeof result.metadata !== 'object') {
215
+ throw new Error('Custom encryption function must return a metadata object.');
216
+ }
153
217
  /** Prefix custom metadata keys to avoid conflicts */
154
218
  const customMetadata = {};
155
219
  for (const [key, value] of Object.entries(result.metadata)) {
220
+ /** Validate custom metadata keys don't use reserved prefix */
221
+ if (key.startsWith('x-amz-')) {
222
+ throw new Error(`Custom encryption metadata key "${key}" cannot start with "x-amz-" as it is reserved. ` +
223
+ 'Please use a different key name.');
224
+ }
156
225
  customMetadata[`x-amz-custom-${key}`] = value;
157
226
  }
158
- return this.client.send(new PutObjectCommand({
159
- ...input,
160
- Bucket: input.Bucket || this.bucket,
161
- Body: result.data,
162
- ContentLength: result.data.length,
163
- Metadata: {
164
- ...(input.Metadata || {}),
165
- ...customMetadata,
166
- /** @description Marker indicating custom encryption was used */
167
- 'x-amz-encryption': 'custom',
168
- },
169
- }));
227
+ /** Construct final metadata and validate size */
228
+ const finalMetadata = {
229
+ ...(input.Metadata || {}),
230
+ ...customMetadata,
231
+ 'x-amz-encryption': 'custom',
232
+ };
233
+ validateMetadataSize(finalMetadata);
234
+ try {
235
+ return await this.client.send(new PutObjectCommand({
236
+ ...input,
237
+ Bucket: bucket,
238
+ Body: result.data,
239
+ ContentLength: result.data.length,
240
+ Metadata: finalMetadata,
241
+ }));
242
+ }
243
+ catch (error) {
244
+ throw new Error(`Failed to upload to s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
245
+ }
170
246
  }
171
247
  /** Default AES-256-GCM encryption */
172
248
  /** @type {Buffer} iv - 96-bit IV for GCM mode */
@@ -174,31 +250,35 @@ export class S3ObjectStore {
174
250
  /** @type {Buffer} salt - 128-bit salt for scrypt key derivation */
175
251
  const salt = randomBytes(16);
176
252
  /** Derive encryption key using scrypt (memory-hard, resistant to GPU attacks) */
177
- const secretKey = await deriveKey(this.key, salt);
253
+ const secretKey = await deriveKey(this.key, salt, this.scryptOptions);
178
254
  /** Create AES-256-GCM cipher */
179
255
  const cipher = createCipheriv('aes-256-gcm', secretKey, iv);
180
256
  /** Encrypt the data */
181
257
  const encryptedBody = Buffer.concat([cipher.update(data), cipher.final()]);
182
258
  /** Get the GCM authentication tag (ensures data integrity and authenticity) */
183
259
  const authTag = cipher.getAuthTag();
260
+ /** Construct final metadata and validate size */
261
+ const finalMetadata = {
262
+ ...(input.Metadata || {}),
263
+ 'x-amz-iv': iv.toString('base64'),
264
+ 'x-amz-salt': salt.toString('base64'),
265
+ 'x-amz-auth-tag': authTag.toString('base64'),
266
+ 'x-amz-encryption': 'aes-256-gcm',
267
+ };
268
+ validateMetadataSize(finalMetadata);
184
269
  /** Upload encrypted data with encryption metadata stored in S3 object metadata */
185
- return this.client.send(new PutObjectCommand({
186
- ...input,
187
- Bucket: input.Bucket || this.bucket,
188
- Body: encryptedBody,
189
- ContentLength: encryptedBody.length,
190
- Metadata: {
191
- ...(input.Metadata || {}),
192
- /** @description Base64-encoded IV */
193
- 'x-amz-iv': iv.toString('base64'),
194
- /** @description Base64-encoded salt */
195
- 'x-amz-salt': salt.toString('base64'),
196
- /** @description Base64-encoded GCM auth tag */
197
- 'x-amz-auth-tag': authTag.toString('base64'),
198
- /** @description Encryption algorithm identifier */
199
- 'x-amz-encryption': 'aes-256-gcm',
200
- },
201
- }));
270
+ try {
271
+ return await this.client.send(new PutObjectCommand({
272
+ ...input,
273
+ Bucket: bucket,
274
+ Body: encryptedBody,
275
+ ContentLength: encryptedBody.length,
276
+ Metadata: finalMetadata,
277
+ }));
278
+ }
279
+ catch (error) {
280
+ throw new Error(`Failed to upload to s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
281
+ }
202
282
  }
203
283
  /**
204
284
  * Downloads data from S3 with optional decryption.
@@ -235,10 +315,24 @@ export class S3ObjectStore {
235
315
  * ```
236
316
  */
237
317
  async download(input) {
318
+ /** Validate required input parameters */
319
+ const bucket = input.Bucket || this.bucket;
320
+ if (!bucket) {
321
+ throw new Error('Bucket is required. Provide it in download() or set a default bucket.');
322
+ }
323
+ if (!input.Key) {
324
+ throw new Error('Key is required for download.');
325
+ }
238
326
  /** Fetch the object from S3 */
239
- const response = await this.client.send(new GetObjectCommand({ ...input, Bucket: input.Bucket || this.bucket }));
327
+ let response;
328
+ try {
329
+ response = await this.client.send(new GetObjectCommand({ ...input, Bucket: bucket }));
330
+ }
331
+ catch (error) {
332
+ throw new Error(`Failed to download from s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
333
+ }
240
334
  if (!response.Body) {
241
- throw new Error('No body returned from S3');
335
+ throw new Error(`No body returned from S3 for s3://${bucket}/${input.Key}`);
242
336
  }
243
337
  /** Resolve mode: input.mode takes precedence, fallback to client default */
244
338
  const mode = input?.Mode === 'encrypt' || input?.Mode === 'passthrough' ? input.Mode : this.mode;
@@ -270,8 +364,17 @@ export class S3ObjectStore {
270
364
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
271
365
  }
272
366
  const encryptedData = Buffer.concat(chunks);
273
- /** Decrypt using custom decryption function */
274
- const decryptedData = await this.customEncryption.decrypt(encryptedData, this.key, customMetadata);
367
+ /** Decrypt using custom decryption function with validation */
368
+ let decryptedData;
369
+ try {
370
+ decryptedData = await this.customEncryption.decrypt(encryptedData, this.key, customMetadata);
371
+ }
372
+ catch (error) {
373
+ throw new Error(`Custom decryption function failed for s3://${bucket}/${input.Key}: ${error instanceof Error ? error.message : error}`);
374
+ }
375
+ if (!Buffer.isBuffer(decryptedData)) {
376
+ throw new Error('Custom decryption function must return a Buffer.');
377
+ }
275
378
  return {
276
379
  Body: Readable.from(decryptedData),
277
380
  ContentType: response.ContentType,
@@ -283,26 +386,43 @@ export class S3ObjectStore {
283
386
  const ivBase64 = metadata['x-amz-iv'];
284
387
  const saltBase64 = metadata['x-amz-salt'];
285
388
  const authTagBase64 = metadata['x-amz-auth-tag'];
286
- /** If encryption metadata is missing, return unencrypted stream */
389
+ /** If encryption metadata is missing, throw error in encrypt mode */
287
390
  if (!ivBase64 || !saltBase64 || !authTagBase64 || encryption !== 'aes-256-gcm') {
288
- return {
289
- Body: Readable.from(response.Body),
290
- ContentType: response.ContentType,
291
- ContentLength: response.ContentLength,
292
- Metadata: response.Metadata,
293
- };
391
+ throw new Error(`Expected encrypted object but encryption metadata is missing for s3://${input.Bucket}/${input.Key}. ` +
392
+ 'This object may not be encrypted or was encrypted with a different method. ' +
393
+ 'Use mode: "passthrough" to download unencrypted objects.');
294
394
  }
295
395
  /** Decode encryption parameters from Base64 */
296
396
  const iv = Buffer.from(ivBase64, 'base64');
297
397
  const salt = Buffer.from(saltBase64, 'base64');
298
398
  const authTag = Buffer.from(authTagBase64, 'base64');
399
+ /** Validate encryption parameter lengths */
400
+ if (iv.length !== 12) {
401
+ throw new Error(`Invalid IV length: expected 12 bytes for GCM mode, got ${iv.length} bytes. ` +
402
+ 'The encrypted data may be corrupted.');
403
+ }
404
+ if (salt.length !== 16) {
405
+ throw new Error(`Invalid salt length: expected 16 bytes, got ${salt.length} bytes. ` +
406
+ 'The encrypted data may be corrupted.');
407
+ }
408
+ if (authTag.length !== 16) {
409
+ throw new Error(`Invalid authentication tag length: expected 16 bytes, got ${authTag.length} bytes. ` +
410
+ 'The encrypted data may be corrupted.');
411
+ }
299
412
  /** Derive the decryption key using the same salt used during encryption */
300
- const secretKey = await deriveKey(this.key, salt);
413
+ const secretKey = await deriveKey(this.key, salt, this.scryptOptions);
301
414
  /** Create AES-256-GCM decipher and set the authentication tag */
302
415
  const decipher = createDecipheriv('aes-256-gcm', secretKey, iv);
303
416
  decipher.setAuthTag(authTag);
304
417
  /** Pipe the encrypted stream through the decipher to get decrypted output */
305
418
  const decryptedStream = response.Body.pipe(decipher);
419
+ /** Add error handler for decryption stream errors */
420
+ decryptedStream.on('error', (error) => {
421
+ const errorMessage = `Decryption failed for s3://${bucket}/${input.Key}. ` +
422
+ 'This may indicate data corruption or wrong encryption key. ' +
423
+ `Error: ${error.message}`;
424
+ decryptedStream.destroy(new Error(errorMessage));
425
+ });
306
426
  return {
307
427
  Body: decryptedStream,
308
428
  ContentType: response.ContentType,
@@ -311,3 +431,4 @@ export class S3ObjectStore {
311
431
  };
312
432
  }
313
433
  }
434
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module encrypted-s3-store
8
8
  * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
9
- * @license Apache-2.0
9
+ * @license MIT
10
10
  *
11
11
  * @example
12
12
  * ```typescript
@@ -24,3 +24,4 @@
24
24
  */
25
25
  export * from './client.js';
26
26
  export * from './types.js';
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module encrypted-s3-store
8
8
  * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
9
- * @license Apache-2.0
9
+ * @license MIT
10
10
  *
11
11
  * @example
12
12
  * ```typescript
@@ -24,3 +24,4 @@
24
24
  */
25
25
  export * from './client.js';
26
26
  export * from './types.js';
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
package/dist/types.d.ts CHANGED
@@ -3,22 +3,30 @@
3
3
  * Contains configuration interfaces and utility types used throughout the library.
4
4
  *
5
5
  * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
6
- * @license Apache-2.0
6
+ * @license MIT
7
7
  */
8
8
  import { GetObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/client-s3';
9
+ import { Readable } from 'node:stream';
9
10
  /**
10
11
  * Operation mode for the S3ObjectStore.
11
12
  * - 'encrypt': Enables transparent AES-256-GCM encryption for all uploads/downloads
12
13
  * - 'passthrough': Data is stored and retrieved without encryption
13
14
  */
14
15
  export type ModeType = 'encrypt' | 'passthrough';
16
+ /**
17
+ * Supported body types for upload operations.
18
+ */
19
+ export type UploadBody = string | Buffer | Uint8Array;
15
20
  /**
16
21
  * Upload input options extending S3 PutObjectCommandInput.
17
22
  * Allows specifying a per-operation encryption mode override.
18
23
  *
19
- * @extends PutObjectCommandInput
24
+ * @extends Omit<PutObjectCommandInput, 'Body'>
20
25
  *
21
- * @property mode - Optional override for the operation mode. If not specified, uses the client's default mode.
26
+ * @property Bucket - S3 bucket name (required if not set as default)
27
+ * @property Key - S3 object key (required)
28
+ * @property Body - Content to upload (string, Buffer, or Uint8Array)
29
+ * @property Mode - Optional override for the operation mode. If not specified, uses the client's default mode.
22
30
  *
23
31
  * @example
24
32
  * ```typescript
@@ -26,11 +34,12 @@ export type ModeType = 'encrypt' | 'passthrough';
26
34
  * Bucket: 'my-bucket',
27
35
  * Key: 'file.txt',
28
36
  * Body: 'content',
29
- * mode: 'passthrough' // Override to skip encryption for this upload
37
+ * Mode: 'passthrough' // Override to skip encryption for this upload
30
38
  * };
31
39
  * ```
32
40
  */
33
- export interface IUpload extends PutObjectCommandInput {
41
+ export interface IUpload extends Omit<PutObjectCommandInput, 'Body'> {
42
+ Body?: UploadBody;
34
43
  Mode?: ModeType;
35
44
  }
36
45
  /**
@@ -39,20 +48,36 @@ export interface IUpload extends PutObjectCommandInput {
39
48
  *
40
49
  * @extends GetObjectCommandInput
41
50
  *
42
- * @property mode - Optional override for the operation mode. If not specified, uses the client's default mode.
51
+ * @property Bucket - S3 bucket name (required if not set as default)
52
+ * @property Key - S3 object key (required)
53
+ * @property Mode - Optional override for the operation mode. If not specified, uses the client's default mode.
43
54
  *
44
55
  * @example
45
56
  * ```typescript
46
57
  * const downloadInput: IDownload = {
47
58
  * Bucket: 'my-bucket',
48
59
  * Key: 'file.txt',
49
- * mode: 'passthrough' // Override to skip decryption for this download
60
+ * Mode: 'passthrough' // Override to skip decryption for this download
50
61
  * };
51
62
  * ```
52
63
  */
53
64
  export interface IDownload extends GetObjectCommandInput {
54
65
  Mode?: ModeType;
55
66
  }
67
+ /**
68
+ * Result returned from download operations.
69
+ *
70
+ * @property Body - Readable stream containing decrypted content
71
+ * @property ContentType - MIME type of the content (if available)
72
+ * @property ContentLength - Size of the content in bytes (if available)
73
+ * @property Metadata - S3 object metadata (key-value pairs)
74
+ */
75
+ export interface DownloadResult {
76
+ Body: Readable;
77
+ ContentType?: string;
78
+ ContentLength?: number;
79
+ Metadata?: Record<string, string>;
80
+ }
56
81
  /**
57
82
  * Metadata returned by custom encrypt function, stored in S3 object metadata.
58
83
  * All values must be strings as they are stored in S3 metadata headers.
@@ -112,3 +137,28 @@ export interface CustomEncryption {
112
137
  encrypt: EncryptFunction;
113
138
  decrypt: DecryptFunction;
114
139
  }
140
+ /**
141
+ * Scrypt key derivation parameters.
142
+ * Higher values increase security but also increase computation time and memory usage.
143
+ *
144
+ * @property N - CPU/memory cost parameter. Must be a power of 2. Default: 16384 (2^14).
145
+ * Higher values increase resistance to brute-force attacks.
146
+ * @property r - Block size parameter. Default: 8.
147
+ * @property p - Parallelization parameter. Default: 1.
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * // For higher security (slower)
152
+ * const store = new S3ObjectStore(s3Client, 'key', 'encrypt', undefined, {
153
+ * N: 65536, // 2^16, 4x more secure than default
154
+ * r: 8,
155
+ * p: 1,
156
+ * });
157
+ * ```
158
+ */
159
+ export interface ScryptOptions {
160
+ N?: number;
161
+ r?: number;
162
+ p?: number;
163
+ }
164
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,OAAQ,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC;IAClE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,SAAU,SAAQ,qBAAqB;IACtD,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC;AAEpG;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,kBAAkB,KACzB,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ"}
package/dist/types.js CHANGED
@@ -3,6 +3,7 @@
3
3
  * Contains configuration interfaces and utility types used throughout the library.
4
4
  *
5
5
  * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
6
- * @license Apache-2.0
6
+ * @license MIT
7
7
  */
8
8
  export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/dist/utils.d.ts CHANGED
@@ -1,10 +1,11 @@
1
+ import { ScryptOptions } from './types.js';
1
2
  /**
2
3
  * Derives a 256-bit encryption key from a password using scrypt.
3
4
  *
4
5
  * Scrypt is a memory-hard key derivation function that provides strong
5
6
  * resistance against brute-force attacks, including GPU-based attacks.
6
7
  *
7
- * Security parameters:
8
+ * Default security parameters:
8
9
  * - N (cost): 16384 (2^14) - CPU/memory cost parameter
9
10
  * - r (blockSize): 8 - block size parameter
10
11
  * - p (parallelization): 1 - parallelization parameter
@@ -12,6 +13,7 @@
12
13
  *
13
14
  * @param key - The user's encryption password/key
14
15
  * @param salt - Random salt (should be 16 bytes, stored with encrypted data)
16
+ * @param options - Optional scrypt parameters to override defaults
15
17
  * @returns Promise resolving to 32-byte Buffer containing the derived key
16
18
  * @throws {Error} If encryption key is empty or not provided
17
19
  *
@@ -20,8 +22,12 @@
20
22
  * const salt = crypto.randomBytes(16);
21
23
  * const derivedKey = await deriveKey('my-secret-password', salt);
22
24
  * // derivedKey is a 32-byte Buffer suitable for AES-256
25
+ *
26
+ * // With custom parameters for higher security
27
+ * const derivedKey = await deriveKey('password', salt, { N: 65536 });
23
28
  * ```
24
29
  *
25
30
  * @see https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
26
31
  */
27
- export declare const deriveKey: (key: string, salt: Buffer) => Promise<Buffer>;
32
+ export declare const deriveKey: (key: string, salt: Buffer, options?: ScryptOptions) => Promise<Buffer>;
33
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAU3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,SAAS,GACpB,KAAK,MAAM,EACX,MAAM,MAAM,EACZ,UAAU,aAAa,KACtB,OAAO,CAAC,MAAM,CAkBhB,CAAC"}
package/dist/utils.js CHANGED
@@ -2,19 +2,23 @@
2
2
  * @fileoverview Utility functions for encryption key derivation.
3
3
  *
4
4
  * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
5
- * @license Apache-2.0
5
+ * @license MIT
6
6
  */
7
7
  import { scrypt } from 'node:crypto';
8
8
  import { promisify } from 'node:util';
9
9
  /** Promisified version of Node.js scrypt function */
10
10
  const scryptAsync = promisify(scrypt);
11
+ /** Default scrypt parameters */
12
+ const DEFAULT_SCRYPT_N = 16384; // 2^14
13
+ const DEFAULT_SCRYPT_R = 8;
14
+ const DEFAULT_SCRYPT_P = 1;
11
15
  /**
12
16
  * Derives a 256-bit encryption key from a password using scrypt.
13
17
  *
14
18
  * Scrypt is a memory-hard key derivation function that provides strong
15
19
  * resistance against brute-force attacks, including GPU-based attacks.
16
20
  *
17
- * Security parameters:
21
+ * Default security parameters:
18
22
  * - N (cost): 16384 (2^14) - CPU/memory cost parameter
19
23
  * - r (blockSize): 8 - block size parameter
20
24
  * - p (parallelization): 1 - parallelization parameter
@@ -22,6 +26,7 @@ const scryptAsync = promisify(scrypt);
22
26
  *
23
27
  * @param key - The user's encryption password/key
24
28
  * @param salt - Random salt (should be 16 bytes, stored with encrypted data)
29
+ * @param options - Optional scrypt parameters to override defaults
25
30
  * @returns Promise resolving to 32-byte Buffer containing the derived key
26
31
  * @throws {Error} If encryption key is empty or not provided
27
32
  *
@@ -30,24 +35,27 @@ const scryptAsync = promisify(scrypt);
30
35
  * const salt = crypto.randomBytes(16);
31
36
  * const derivedKey = await deriveKey('my-secret-password', salt);
32
37
  * // derivedKey is a 32-byte Buffer suitable for AES-256
38
+ *
39
+ * // With custom parameters for higher security
40
+ * const derivedKey = await deriveKey('password', salt, { N: 65536 });
33
41
  * ```
34
42
  *
35
43
  * @see https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
36
44
  */
37
- export const deriveKey = async (key, salt) => {
45
+ export const deriveKey = async (key, salt, options) => {
38
46
  if (!key) {
39
47
  throw new Error('Encryption key is required');
40
48
  }
49
+ const N = options?.N ?? DEFAULT_SCRYPT_N;
50
+ const r = options?.r ?? DEFAULT_SCRYPT_R;
51
+ const p = options?.p ?? DEFAULT_SCRYPT_P;
41
52
  /**
42
53
  * Derive key using scrypt with secure parameters.
43
- * - N=16384: Memory cost (must be power of 2)
44
- * - r=8: Block size
45
- * - p=1: Parallelization factor
54
+ * - N: Memory cost (must be power of 2)
55
+ * - r: Block size
56
+ * - p: Parallelization factor
46
57
  */
47
- const derivedKey = await scryptAsync(key, salt, 32, {
48
- N: 16384,
49
- r: 8,
50
- p: 1,
51
- });
58
+ const derivedKey = await scryptAsync(key, salt, 32, { N, r, p });
52
59
  return derivedKey;
53
60
  };
61
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,EAAsC,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,qDAAqD;AACrD,MAAM,WAAW,GAAG,SAAS,CAA6D,MAAM,CAAC,CAAC;AAElG,gCAAgC;AAChC,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,OAAO;AACvC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAC3B,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,GAAW,EACX,IAAY,EACZ,OAAuB,EACN,EAAE;IACnB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,gBAAgB,CAAC;IACzC,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,gBAAgB,CAAC;IACzC,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,gBAAgB,CAAC;IAEzC;;;;;OAKG;IACH,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAEjE,OAAO,UAAoB,CAAC;AAC9B,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techbulls/encrypted-s3-store",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Encrypt and decrypt S3 objects with ease",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,12 @@
18
18
  "lint:fix": "eslint . --fix",
19
19
  "format": "prettier --write .",
20
20
  "format:check": "prettier --check .",
21
- "prepublishOnly": "npm run clean && npm run lint && npm run build"
21
+ "test": "vitest run",
22
+ "test:unit": "vitest run --exclude='**/integration.test.ts'",
23
+ "test:integration": "vitest run src/__tests__/integration.test.ts",
24
+ "test:watch": "vitest",
25
+ "test:coverage": "vitest run --coverage",
26
+ "prepublishOnly": "npm run clean && npm run lint && npm run test:unit && npm run build"
22
27
  },
23
28
  "keywords": [
24
29
  "s3",
@@ -55,14 +60,19 @@
55
60
  "@aws-sdk/client-s3": "^3.958.0",
56
61
  "@aws-sdk/types": "^3.957.0",
57
62
  "@eslint/js": "^9.39.2",
63
+ "@smithy/util-stream": "^4.5.10",
58
64
  "@types/node": "^25.0.3",
59
65
  "@typescript-eslint/eslint-plugin": "^8.50.1",
60
66
  "@typescript-eslint/parser": "^8.50.1",
67
+ "@vitest/coverage-v8": "^4.0.17",
68
+ "aws-sdk-client-mock": "^4.1.0",
69
+ "aws-sdk-client-mock-jest": "^4.1.0",
61
70
  "eslint": "^9.39.2",
62
71
  "eslint-config-prettier": "^10.1.8",
63
72
  "eslint-plugin-prettier": "^5.5.4",
64
73
  "prettier": "^3.7.4",
65
74
  "typescript": "^5.9.3",
66
- "typescript-eslint": "^8.50.1"
75
+ "typescript-eslint": "^8.50.1",
76
+ "vitest": "^4.0.17"
67
77
  }
68
78
  }