@techbulls/encrypted-s3-store 1.0.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.
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @fileoverview Core S3ObjectStore client implementation.
3
+ * Provides transparent encryption/decryption for S3 objects using AES-256-GCM.
4
+ *
5
+ * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
6
+ * @license Apache-2.0
7
+ */
8
+ import { S3Client } from '@aws-sdk/client-s3';
9
+ import { IDownload, IUpload, ModeType } from './types.js';
10
+ import { Readable } from 'node:stream';
11
+ /** Re-export all AWS S3 SDK exports for convenience */
12
+ export * from '@aws-sdk/client-s3';
13
+ /**
14
+ * S3 client wrapper with transparent end-to-end encryption support.
15
+ *
16
+ * Wraps an AWS S3 client to provide automatic encryption on upload
17
+ * and decryption on download using AES-256-GCM with scrypt key derivation.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Create an S3 client
22
+ * const s3Client = new S3Client({ region: 'us-east-1' });
23
+ *
24
+ * // Create an encrypted store wrapper
25
+ * const store = new S3ObjectStore(s3Client, 'my-secret-key');
26
+ *
27
+ * // Upload encrypted data
28
+ * await store.upload({ Bucket: 'my-bucket', Key: 'secret.txt', Body: 'Sensitive data' });
29
+ *
30
+ * // Download and decrypt
31
+ * const { Body } = await store.download({ Bucket: 'my-bucket', Key: 'secret.txt' });
32
+ * ```
33
+ */
34
+ export declare class S3ObjectStore {
35
+ /** @type {ModeType} Current operation mode - 'encrypt' or 'passthrough' */
36
+ mode: ModeType;
37
+ /** @type {string} Encryption key used for AES-256-GCM encryption */
38
+ key: string;
39
+ /** @type {string | undefined} Default S3 bucket for all operations */
40
+ bucket?: string;
41
+ /** @type {string} Encryption algorithm identifier (always 'aes-256-gcm') */
42
+ algorithm: string;
43
+ /** @type {S3Client} The underlying AWS S3 client instance */
44
+ client: S3Client;
45
+ /**
46
+ * Creates a new S3ObjectStore instance.
47
+ *
48
+ * @param client - An initialized AWS S3Client instance
49
+ * @param key - Encryption key for AES-256-GCM. Falls back to ENCRYPTION_KEY environment variable if not provided.
50
+ * @param mode - Operation mode ('encrypt' or 'passthrough'). Defaults to 'encrypt'.
51
+ * @throws {Error} If no encryption key is provided and ENCRYPTION_KEY environment variable is not set
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * // With explicit key
56
+ * const store = new S3ObjectStore(s3Client, 'my-secret-key');
57
+ *
58
+ * // With environment variable (ENCRYPTION_KEY)
59
+ * const store = new S3ObjectStore(s3Client);
60
+ *
61
+ * // With passthrough mode (no encryption)
62
+ * const store = new S3ObjectStore(s3Client, 'key', 'passthrough');
63
+ * ```
64
+ */
65
+ constructor(client: S3Client, key?: string, mode?: ModeType);
66
+ /**
67
+ * Uploads data to S3 with optional encryption.
68
+ *
69
+ * In 'encrypt' mode:
70
+ * - Generates a random 12-byte IV (Initialization Vector)
71
+ * - Generates a random 16-byte salt for key derivation
72
+ * - Derives a 256-bit key using scrypt
73
+ * - Encrypts the data using AES-256-GCM
74
+ * - Stores encryption metadata (IV, salt, auth tag) in S3 object metadata
75
+ *
76
+ * In 'passthrough' mode:
77
+ * - Uploads data directly without encryption
78
+ *
79
+ * @param input - S3 PutObject parameters with optional mode override
80
+ * @returns Promise resolving to the S3 PutObject response
81
+ * @throws {Error} If Body is not provided
82
+ * @throws {Error} If Body type is not supported (only string, Buffer, Uint8Array)
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * // Upload a string
87
+ * await store.upload({ Bucket: 'my-bucket', Key: 'file.txt', Body: 'Hello World' });
88
+ *
89
+ * // Upload a Buffer
90
+ * await store.upload({ Bucket: 'my-bucket', Key: 'data.bin', Body: Buffer.from([0x01, 0x02]) });
91
+ *
92
+ * // Upload with custom metadata
93
+ * await store.upload({
94
+ * Bucket: 'my-bucket',
95
+ * Key: 'file.txt',
96
+ * Body: 'content',
97
+ * ContentType: 'text/plain',
98
+ * Metadata: { 'custom-header': 'value' }
99
+ * });
100
+ *
101
+ * // Upload without encryption (per-operation override)
102
+ * await store.upload({
103
+ * Bucket: 'my-bucket',
104
+ * Key: 'public.txt',
105
+ * Body: 'not sensitive',
106
+ * mode: 'passthrough'
107
+ * });
108
+ * ```
109
+ */
110
+ upload(input: IUpload): Promise<import("@aws-sdk/client-s3").PutObjectCommandOutput>;
111
+ /**
112
+ * Downloads data from S3 with optional decryption.
113
+ *
114
+ * In 'encrypt' mode:
115
+ * - Retrieves encryption metadata (IV, salt, auth tag) from S3 object metadata
116
+ * - Derives the decryption key using scrypt with the stored salt
117
+ * - Decrypts the data using AES-256-GCM
118
+ * - Verifies data integrity using the authentication tag
119
+ *
120
+ * In 'passthrough' mode:
121
+ * - Returns the data stream directly without decryption
122
+ *
123
+ * @param input - S3 GetObject parameters with optional mode override
124
+ * @returns Promise resolving to an object containing the decrypted Body stream and metadata
125
+ * @throws {Error} If no body is returned from S3
126
+ * @throws {Error} If authentication tag verification fails (data tampering detected)
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * const { Body, ContentType } = await store.download({ Bucket: 'my-bucket', Key: 'file.txt' });
131
+ *
132
+ * // Read the stream
133
+ * const chunks: Buffer[] = [];
134
+ * for await (const chunk of Body) {
135
+ * chunks.push(chunk);
136
+ * }
137
+ * const content = Buffer.concat(chunks).toString('utf8');
138
+ * ```
139
+ */
140
+ download(input: IDownload): Promise<{
141
+ Body: Readable;
142
+ ContentType: string | undefined;
143
+ ContentLength: number | undefined;
144
+ Metadata: Record<string, string> | undefined;
145
+ }>;
146
+ }
147
+ //# 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,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG1D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,uDAAuD;AACvD,cAAc,oBAAoB,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,aAAa;IACxB,2EAA2E;IAC3E,IAAI,EAAE,QAAQ,CAAC;IAEf,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IAEZ,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,4EAA4E;IAC5E,SAAS,SAAiB;IAE1B,6DAA6D;IAC7D,MAAM,EAAE,QAAQ,CAAC;IAEjB;;;;;;;;;;;;;;;;;;;OAmBG;gBACS,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ;IAW3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACG,MAAM,CAAC,KAAK,EAAE,OAAO;IAwE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACG,QAAQ,CAAC,KAAK,EAAE,SAAS;;;;;;CAiEhC"}
package/dist/client.js ADDED
@@ -0,0 +1,251 @@
1
+ /**
2
+ * @fileoverview Core S3ObjectStore client implementation.
3
+ * Provides transparent encryption/decryption for S3 objects using AES-256-GCM.
4
+ *
5
+ * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
6
+ * @license Apache-2.0
7
+ */
8
+ import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
9
+ import { deriveKey } from './utils.js';
10
+ import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
11
+ import { Readable } from 'node:stream';
12
+ /** Re-export all AWS S3 SDK exports for convenience */
13
+ export * from '@aws-sdk/client-s3';
14
+ /**
15
+ * S3 client wrapper with transparent end-to-end encryption support.
16
+ *
17
+ * Wraps an AWS S3 client to provide automatic encryption on upload
18
+ * and decryption on download using AES-256-GCM with scrypt key derivation.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Create an S3 client
23
+ * const s3Client = new S3Client({ region: 'us-east-1' });
24
+ *
25
+ * // Create an encrypted store wrapper
26
+ * const store = new S3ObjectStore(s3Client, 'my-secret-key');
27
+ *
28
+ * // Upload encrypted data
29
+ * await store.upload({ Bucket: 'my-bucket', Key: 'secret.txt', Body: 'Sensitive data' });
30
+ *
31
+ * // Download and decrypt
32
+ * const { Body } = await store.download({ Bucket: 'my-bucket', Key: 'secret.txt' });
33
+ * ```
34
+ */
35
+ export class S3ObjectStore {
36
+ /**
37
+ * Creates a new S3ObjectStore instance.
38
+ *
39
+ * @param client - An initialized AWS S3Client instance
40
+ * @param key - Encryption key for AES-256-GCM. Falls back to ENCRYPTION_KEY environment variable if not provided.
41
+ * @param mode - Operation mode ('encrypt' or 'passthrough'). Defaults to 'encrypt'.
42
+ * @throws {Error} If no encryption key is provided and ENCRYPTION_KEY environment variable is not set
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // With explicit key
47
+ * const store = new S3ObjectStore(s3Client, 'my-secret-key');
48
+ *
49
+ * // With environment variable (ENCRYPTION_KEY)
50
+ * const store = new S3ObjectStore(s3Client);
51
+ *
52
+ * // With passthrough mode (no encryption)
53
+ * const store = new S3ObjectStore(s3Client, 'key', 'passthrough');
54
+ * ```
55
+ */
56
+ constructor(client, key, mode) {
57
+ /** @type {string} Encryption algorithm identifier (always 'aes-256-gcm') */
58
+ this.algorithm = 'aes-256-gcm';
59
+ this.client = client;
60
+ const envEncryptionKey = process.env.ENCRYPTION_KEY;
61
+ if (!key && !envEncryptionKey) {
62
+ throw new Error('Encryption key not provided.');
63
+ }
64
+ this.key = (key || envEncryptionKey);
65
+ this.mode = mode ?? 'encrypt';
66
+ }
67
+ /**
68
+ * Uploads data to S3 with optional encryption.
69
+ *
70
+ * In 'encrypt' mode:
71
+ * - Generates a random 12-byte IV (Initialization Vector)
72
+ * - Generates a random 16-byte salt for key derivation
73
+ * - Derives a 256-bit key using scrypt
74
+ * - Encrypts the data using AES-256-GCM
75
+ * - Stores encryption metadata (IV, salt, auth tag) in S3 object metadata
76
+ *
77
+ * In 'passthrough' mode:
78
+ * - Uploads data directly without encryption
79
+ *
80
+ * @param input - S3 PutObject parameters with optional mode override
81
+ * @returns Promise resolving to the S3 PutObject response
82
+ * @throws {Error} If Body is not provided
83
+ * @throws {Error} If Body type is not supported (only string, Buffer, Uint8Array)
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * // Upload a string
88
+ * await store.upload({ Bucket: 'my-bucket', Key: 'file.txt', Body: 'Hello World' });
89
+ *
90
+ * // Upload a Buffer
91
+ * await store.upload({ Bucket: 'my-bucket', Key: 'data.bin', Body: Buffer.from([0x01, 0x02]) });
92
+ *
93
+ * // Upload with custom metadata
94
+ * await store.upload({
95
+ * Bucket: 'my-bucket',
96
+ * Key: 'file.txt',
97
+ * Body: 'content',
98
+ * ContentType: 'text/plain',
99
+ * Metadata: { 'custom-header': 'value' }
100
+ * });
101
+ *
102
+ * // Upload without encryption (per-operation override)
103
+ * await store.upload({
104
+ * Bucket: 'my-bucket',
105
+ * Key: 'public.txt',
106
+ * Body: 'not sensitive',
107
+ * mode: 'passthrough'
108
+ * });
109
+ * ```
110
+ */
111
+ async upload(input) {
112
+ if (!input.Body) {
113
+ throw new Error('Body is required for upload');
114
+ }
115
+ /** Resolve mode: input.mode takes precedence, fallback to client default */
116
+ const mode = input?.mode === 'encrypt' || input?.mode === 'passthrough' ? input.mode : this.mode;
117
+ /** Passthrough mode: upload without encryption */
118
+ if (mode === 'passthrough') {
119
+ return this.client.send(new PutObjectCommand({
120
+ ...input,
121
+ Bucket: input.Bucket || this.bucket,
122
+ }));
123
+ }
124
+ /** Encrypt mode: encrypt data before uploading */
125
+ /** @type {Buffer} iv - 96-bit IV for GCM mode */
126
+ const iv = randomBytes(12);
127
+ /** @type {Buffer} salt - 128-bit salt for scrypt key derivation */
128
+ const salt = randomBytes(16);
129
+ /** Derive encryption key using scrypt (memory-hard, resistant to GPU attacks) */
130
+ const secretKey = await deriveKey(this.key, salt);
131
+ /** Create AES-256-GCM cipher */
132
+ const cipher = createCipheriv('aes-256-gcm', secretKey, iv);
133
+ /** Convert input body to Buffer for encryption */
134
+ let data;
135
+ if (typeof input.Body === 'string') {
136
+ data = Buffer.from(input.Body, 'utf8');
137
+ }
138
+ else if (Buffer.isBuffer(input.Body)) {
139
+ data = input.Body;
140
+ }
141
+ else if (input.Body instanceof Uint8Array) {
142
+ data = Buffer.from(input.Body);
143
+ }
144
+ else {
145
+ throw new Error('Unsupported Body type: only string, Buffer, or Uint8Array supported');
146
+ }
147
+ /** Encrypt the data */
148
+ const encryptedBody = Buffer.concat([cipher.update(data), cipher.final()]);
149
+ /** Get the GCM authentication tag (ensures data integrity and authenticity) */
150
+ const authTag = cipher.getAuthTag();
151
+ /** Upload encrypted data with encryption metadata stored in S3 object metadata */
152
+ return this.client.send(new PutObjectCommand({
153
+ ...input,
154
+ Bucket: input.Bucket || this.bucket,
155
+ Body: encryptedBody,
156
+ ContentLength: encryptedBody.length,
157
+ Metadata: {
158
+ ...(input.Metadata || {}),
159
+ /** @description Base64-encoded IV */
160
+ 'x-amz-iv': iv.toString('base64'),
161
+ /** @description Base64-encoded salt */
162
+ 'x-amz-salt': salt.toString('base64'),
163
+ /** @description Base64-encoded GCM auth tag */
164
+ 'x-amz-auth-tag': authTag.toString('base64'),
165
+ /** @description Encryption algorithm identifier */
166
+ 'x-amz-encryption': 'aes-256-gcm',
167
+ },
168
+ }));
169
+ }
170
+ /**
171
+ * Downloads data from S3 with optional decryption.
172
+ *
173
+ * In 'encrypt' mode:
174
+ * - Retrieves encryption metadata (IV, salt, auth tag) from S3 object metadata
175
+ * - Derives the decryption key using scrypt with the stored salt
176
+ * - Decrypts the data using AES-256-GCM
177
+ * - Verifies data integrity using the authentication tag
178
+ *
179
+ * In 'passthrough' mode:
180
+ * - Returns the data stream directly without decryption
181
+ *
182
+ * @param input - S3 GetObject parameters with optional mode override
183
+ * @returns Promise resolving to an object containing the decrypted Body stream and metadata
184
+ * @throws {Error} If no body is returned from S3
185
+ * @throws {Error} If authentication tag verification fails (data tampering detected)
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * const { Body, ContentType } = await store.download({ Bucket: 'my-bucket', Key: 'file.txt' });
190
+ *
191
+ * // Read the stream
192
+ * const chunks: Buffer[] = [];
193
+ * for await (const chunk of Body) {
194
+ * chunks.push(chunk);
195
+ * }
196
+ * const content = Buffer.concat(chunks).toString('utf8');
197
+ * ```
198
+ */
199
+ async download(input) {
200
+ /** Fetch the object from S3 */
201
+ const response = await this.client.send(new GetObjectCommand({ ...input, Bucket: input.Bucket || this.bucket }));
202
+ if (!response.Body) {
203
+ throw new Error('No body returned from S3');
204
+ }
205
+ /** Resolve mode: input.mode takes precedence, fallback to client default */
206
+ const mode = input?.mode === 'encrypt' || input?.mode === 'passthrough' ? input.mode : this.mode;
207
+ /** Passthrough mode: download without encryption */
208
+ if (mode === 'passthrough') {
209
+ return {
210
+ Body: Readable.from(response.Body),
211
+ ContentType: response.ContentType,
212
+ ContentLength: response.ContentLength,
213
+ Metadata: response.Metadata,
214
+ };
215
+ }
216
+ /** Encrypt mode: decrypt the response */
217
+ /** Extract encryption metadata from S3 object */
218
+ const metadata = response.Metadata || {};
219
+ const ivBase64 = metadata['x-amz-iv'];
220
+ const saltBase64 = metadata['x-amz-salt'];
221
+ const authTagBase64 = metadata['x-amz-auth-tag'];
222
+ const encryption = metadata['x-amz-encryption'];
223
+ /** If encryption metadata is missing, return unencrypted stream */
224
+ if (!ivBase64 || !saltBase64 || !authTagBase64 || encryption !== 'aes-256-gcm') {
225
+ return {
226
+ Body: Readable.from(response.Body),
227
+ ContentType: response.ContentType,
228
+ ContentLength: response.ContentLength,
229
+ Metadata: response.Metadata,
230
+ };
231
+ }
232
+ /** Decode encryption parameters from Base64 */
233
+ const iv = Buffer.from(ivBase64, 'base64');
234
+ const salt = Buffer.from(saltBase64, 'base64');
235
+ const authTag = Buffer.from(authTagBase64, 'base64');
236
+ /** Derive the decryption key using the same salt used during encryption */
237
+ const secretKey = await deriveKey(this.key, salt);
238
+ /** Create AES-256-GCM decipher and set the authentication tag */
239
+ const decipher = createDecipheriv('aes-256-gcm', secretKey, iv);
240
+ decipher.setAuthTag(authTag);
241
+ /** Pipe the encrypted stream through the decipher to get decrypted output */
242
+ const decryptedStream = response.Body.pipe(decipher);
243
+ return {
244
+ Body: decryptedStream,
245
+ ContentType: response.ContentType,
246
+ ContentLength: response.ContentLength,
247
+ Metadata: response.Metadata,
248
+ };
249
+ }
250
+ }
251
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @fileoverview Main entry point for the Encrypted S3 Store library.
3
+ *
4
+ * This library provides transparent end-to-end encryption for AWS S3 objects
5
+ * using AES-256-GCM encryption with scrypt key derivation.
6
+ *
7
+ * @module encrypted-s3-store
8
+ * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
9
+ * @license Apache-2.0
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { S3ObjectStore, S3Client } from '@techbulls/encrypted-s3-store';
14
+ *
15
+ * const s3Client = new S3Client({ region: 'us-east-1' });
16
+ * const store = new S3ObjectStore(s3Client, 'my-secret-key');
17
+ *
18
+ * // Upload with automatic encryption
19
+ * await store.upload({ Bucket: 'my-bucket', Key: 'file.txt', Body: 'Hello World' });
20
+ *
21
+ * // Download with automatic decryption
22
+ * const result = await store.download({ Bucket: 'my-bucket', Key: 'file.txt' });
23
+ * ```
24
+ */
25
+ export * from './client.js';
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 ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @fileoverview Main entry point for the Encrypted S3 Store library.
3
+ *
4
+ * This library provides transparent end-to-end encryption for AWS S3 objects
5
+ * using AES-256-GCM encryption with scrypt key derivation.
6
+ *
7
+ * @module encrypted-s3-store
8
+ * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
9
+ * @license Apache-2.0
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { S3ObjectStore, S3Client } from '@techbulls/encrypted-s3-store';
14
+ *
15
+ * const s3Client = new S3Client({ region: 'us-east-1' });
16
+ * const store = new S3ObjectStore(s3Client, 'my-secret-key');
17
+ *
18
+ * // Upload with automatic encryption
19
+ * await store.upload({ Bucket: 'my-bucket', Key: 'file.txt', Body: 'Hello World' });
20
+ *
21
+ * // Download with automatic decryption
22
+ * const result = await store.download({ Bucket: 'my-bucket', Key: 'file.txt' });
23
+ * ```
24
+ */
25
+ export * from './client.js';
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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @fileoverview Type definitions for the Encrypted S3 Store library.
3
+ * Contains configuration interfaces and utility types used throughout the library.
4
+ *
5
+ * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
6
+ * @license Apache-2.0
7
+ */
8
+ import { GetObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/client-s3';
9
+ /**
10
+ * Operation mode for the S3ObjectStore.
11
+ * - 'encrypt': Enables transparent AES-256-GCM encryption for all uploads/downloads
12
+ * - 'passthrough': Data is stored and retrieved without encryption
13
+ */
14
+ export type ModeType = 'encrypt' | 'passthrough';
15
+ /**
16
+ * Upload input options extending S3 PutObjectCommandInput.
17
+ * Allows specifying a per-operation encryption mode override.
18
+ *
19
+ * @extends PutObjectCommandInput
20
+ *
21
+ * @property mode - Optional override for the operation mode. If not specified, uses the client's default mode.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const uploadInput: IUpload = {
26
+ * Bucket: 'my-bucket',
27
+ * Key: 'file.txt',
28
+ * Body: 'content',
29
+ * mode: 'passthrough' // Override to skip encryption for this upload
30
+ * };
31
+ * ```
32
+ */
33
+ export interface IUpload extends PutObjectCommandInput {
34
+ mode?: ModeType;
35
+ }
36
+ /**
37
+ * Download input options extending S3 GetObjectCommandInput.
38
+ * Allows specifying a per-operation encryption mode override.
39
+ *
40
+ * @extends GetObjectCommandInput
41
+ *
42
+ * @property mode - Optional override for the operation mode. If not specified, uses the client's default mode.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const downloadInput: IDownload = {
47
+ * Bucket: 'my-bucket',
48
+ * Key: 'file.txt',
49
+ * mode: 'passthrough' // Override to skip decryption for this download
50
+ * };
51
+ * ```
52
+ */
53
+ export interface IDownload extends GetObjectCommandInput {
54
+ mode?: ModeType;
55
+ }
56
+ //# 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;AAElF;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,OAAQ,SAAQ,qBAAqB;IACpD,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,SAAU,SAAQ,qBAAqB;IACtD,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Type definitions for the Encrypted S3 Store library.
3
+ * Contains configuration interfaces and utility types used throughout the library.
4
+ *
5
+ * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
6
+ * @license Apache-2.0
7
+ */
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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Derives a 256-bit encryption key from a password using scrypt.
3
+ *
4
+ * Scrypt is a memory-hard key derivation function that provides strong
5
+ * resistance against brute-force attacks, including GPU-based attacks.
6
+ *
7
+ * Security parameters:
8
+ * - N (cost): 16384 (2^14) - CPU/memory cost parameter
9
+ * - r (blockSize): 8 - block size parameter
10
+ * - p (parallelization): 1 - parallelization parameter
11
+ * - Output: 32 bytes (256 bits for AES-256)
12
+ *
13
+ * @param key - The user's encryption password/key
14
+ * @param salt - Random salt (should be 16 bytes, stored with encrypted data)
15
+ * @returns Promise resolving to 32-byte Buffer containing the derived key
16
+ * @throws {Error} If encryption key is empty or not provided
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const salt = crypto.randomBytes(16);
21
+ * const derivedKey = await deriveKey('my-secret-password', salt);
22
+ * // derivedKey is a 32-byte Buffer suitable for AES-256
23
+ * ```
24
+ *
25
+ * @see https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
26
+ */
27
+ export declare const deriveKey: (key: string, salt: Buffer) => Promise<Buffer>;
28
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,SAAS,GAAU,KAAK,MAAM,EAAE,MAAM,MAAM,KAAG,OAAO,CAAC,MAAM,CAkBzE,CAAC"}
package/dist/utils.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @fileoverview Utility functions for encryption key derivation.
3
+ *
4
+ * @author Aditya Chaphekar <aditya.chaphekar@techbulls.com>
5
+ * @license Apache-2.0
6
+ */
7
+ import { scrypt } from 'node:crypto';
8
+ import { promisify } from 'node:util';
9
+ /** Promisified version of Node.js scrypt function */
10
+ const scryptAsync = promisify(scrypt);
11
+ /**
12
+ * Derives a 256-bit encryption key from a password using scrypt.
13
+ *
14
+ * Scrypt is a memory-hard key derivation function that provides strong
15
+ * resistance against brute-force attacks, including GPU-based attacks.
16
+ *
17
+ * Security parameters:
18
+ * - N (cost): 16384 (2^14) - CPU/memory cost parameter
19
+ * - r (blockSize): 8 - block size parameter
20
+ * - p (parallelization): 1 - parallelization parameter
21
+ * - Output: 32 bytes (256 bits for AES-256)
22
+ *
23
+ * @param key - The user's encryption password/key
24
+ * @param salt - Random salt (should be 16 bytes, stored with encrypted data)
25
+ * @returns Promise resolving to 32-byte Buffer containing the derived key
26
+ * @throws {Error} If encryption key is empty or not provided
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const salt = crypto.randomBytes(16);
31
+ * const derivedKey = await deriveKey('my-secret-password', salt);
32
+ * // derivedKey is a 32-byte Buffer suitable for AES-256
33
+ * ```
34
+ *
35
+ * @see https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
36
+ */
37
+ export const deriveKey = async (key, salt) => {
38
+ if (!key) {
39
+ throw new Error('Encryption key is required');
40
+ }
41
+ /**
42
+ * 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
46
+ */
47
+ const derivedKey = await scryptAsync(key, salt, 32, {
48
+ N: 16384,
49
+ r: 8,
50
+ p: 1,
51
+ });
52
+ return derivedKey;
53
+ };
54
+ //# 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,EAAiB,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,qDAAqD;AACrD,MAAM,WAAW,GAAG,SAAS,CAAyD,MAAM,CAAC,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,GAAW,EAAE,IAAY,EAAmB,EAAE;IAC5E,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED;;;;;OAKG;IACH,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE;QAClD,CAAC,EAAE,KAAK;QACR,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;KACL,CAAC,CAAC;IAEH,OAAO,UAAoB,CAAC;AAC9B,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+ import eslintConfigPrettier from 'eslint-config-prettier';
4
+ import eslintPluginPrettier from 'eslint-plugin-prettier/recommended';
5
+
6
+ /** @type {import('typescript-eslint').Config} */
7
+ export default [
8
+ eslint.configs.recommended,
9
+ ...tseslint.configs.recommended,
10
+ eslintConfigPrettier,
11
+ eslintPluginPrettier,
12
+ {
13
+ ignores: ['dist/**', 'node_modules/**'],
14
+ },
15
+ {
16
+ files: ['**/*.ts'],
17
+ languageOptions: {
18
+ parserOptions: {
19
+ project: './tsconfig.json',
20
+ },
21
+ },
22
+ },
23
+ ];