@bhandari88/express-auth 1.2.0 → 1.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
@@ -45,6 +45,14 @@ npm install @bhandari88/express-auth
45
45
  - Optional refresh tokens
46
46
  - Customizable user fields
47
47
 
48
+ - ✅ **End-to-End Encryption** (NEW in v1.3.0)
49
+ - AES-256-GCM authenticated encryption
50
+ - Automatic chunking for large data
51
+ - Password-based key derivation (PBKDF2)
52
+ - Secure IV/nonce generation
53
+ - Support for strings and Buffers
54
+ - Easy-to-use API for encryption/decryption
55
+
48
56
  ## Peer Dependencies
49
57
 
50
58
  Make sure you have the following peer dependencies installed:
@@ -366,6 +374,199 @@ Or with `abortEarly: true`, only the first error:
366
374
  }
367
375
  ```
368
376
 
377
+ ### 6. End-to-End Data Encryption & Decryption
378
+
379
+ The package includes a powerful encryption service for secure end-to-end encryption and decryption of large data using AES-256-GCM with automatic chunking support.
380
+
381
+ #### Basic Usage
382
+
383
+ ```typescript
384
+ import { EncryptionService } from '@bhandari88/express-auth';
385
+
386
+ // Create encryption service with default settings
387
+ const encryption = new EncryptionService();
388
+
389
+ // Encrypt data
390
+ const data = 'Sensitive information that needs to be encrypted';
391
+ const password = 'my-secure-password-123';
392
+
393
+ const encrypted = await encryption.encrypt(data, password);
394
+ console.log(encrypted);
395
+ // {
396
+ // encrypted: 'base64-encrypted-data...',
397
+ // salt: 'base64-salt...',
398
+ // iv: 'base64-iv...',
399
+ // authTag: 'base64-auth-tag...',
400
+ // algorithm: 'aes-256-gcm'
401
+ // }
402
+
403
+ // Decrypt data
404
+ const decrypted = await encryption.decrypt(encrypted, password);
405
+ console.log(decrypted.toString('utf8')); // 'Sensitive information that needs to be encrypted'
406
+ ```
407
+
408
+ #### Encrypt/Decrypt Strings (Convenience Methods)
409
+
410
+ ```typescript
411
+ // Encrypt to string (includes all metadata as JSON)
412
+ const encryptedString = await encryption.encryptToString(data, password);
413
+ // Store encryptedString in database, file, etc.
414
+
415
+ // Decrypt from string
416
+ const decryptedString = await encryption.decryptFromString(encryptedString, password);
417
+ console.log(decryptedString); // Original data
418
+ ```
419
+
420
+ #### Encrypt/Decrypt Large Data
421
+
422
+ The encryption service automatically handles large data by chunking it into smaller pieces. This is transparent to you:
423
+
424
+ ```typescript
425
+ // Encrypt large file or data
426
+ const fs = require('fs');
427
+ const largeData = fs.readFileSync('large-file.pdf');
428
+
429
+ const encrypted = await encryption.encrypt(largeData, password);
430
+ // Automatically chunked and encrypted
431
+
432
+ // Decrypt large data
433
+ const decrypted = await encryption.decrypt(encrypted, password);
434
+ fs.writeFileSync('decrypted-file.pdf', decrypted);
435
+ ```
436
+
437
+ #### Encrypt/Decrypt Buffers
438
+
439
+ ```typescript
440
+ // Encrypt Buffer
441
+ const buffer = Buffer.from('Binary data', 'utf8');
442
+ const encrypted = await encryption.encrypt(buffer, password);
443
+
444
+ // Decrypt to Buffer
445
+ const decrypted = await encryption.decrypt(encrypted, password);
446
+ console.log(decrypted); // Buffer
447
+ ```
448
+
449
+ #### Custom Configuration
450
+
451
+ ```typescript
452
+ import { EncryptionService, EncryptionConfig } from '@bhandari88/express-auth';
453
+
454
+ const config: EncryptionConfig = {
455
+ algorithm: 'aes-256-gcm', // 'aes-256-gcm' | 'aes-192-gcm' | 'aes-128-gcm'
456
+ keyDerivationIterations: 100000, // PBKDF2 iterations (higher = more secure but slower)
457
+ saltLength: 32, // Salt length in bytes
458
+ ivLength: 16, // IV length in bytes
459
+ authTagLength: 16, // Auth tag length in bytes
460
+ chunkSize: 64 * 1024, // Chunk size for large data (64KB default)
461
+ };
462
+
463
+ const encryption = new EncryptionService(config);
464
+ ```
465
+
466
+ #### Generate Secure Passwords
467
+
468
+ ```typescript
469
+ import { EncryptionService } from '@bhandari88/express-auth';
470
+
471
+ // Generate secure random password (base64)
472
+ const password = EncryptionService.generateSecurePassword(32);
473
+ console.log(password); // Random base64 string
474
+
475
+ // Generate secure random password (hex)
476
+ const hexPassword = EncryptionService.generateSecurePasswordHex(32);
477
+ console.log(hexPassword); // Random hex string
478
+ ```
479
+
480
+ #### Express Route Example
481
+
482
+ ```typescript
483
+ import express from 'express';
484
+ import { EncryptionService } from '@bhandari88/express-auth';
485
+
486
+ const app = express();
487
+ app.use(express.json());
488
+
489
+ const encryption = new EncryptionService();
490
+
491
+ // Encrypt endpoint
492
+ app.post('/api/encrypt', async (req, res) => {
493
+ try {
494
+ const { data, password } = req.body;
495
+
496
+ if (!data || !password) {
497
+ return res.status(400).json({
498
+ success: false,
499
+ error: 'Data and password are required'
500
+ });
501
+ }
502
+
503
+ const encrypted = await encryption.encryptToString(data, password);
504
+ res.json({ success: true, encrypted });
505
+ } catch (error) {
506
+ res.status(500).json({
507
+ success: false,
508
+ error: error instanceof Error ? error.message : 'Encryption failed'
509
+ });
510
+ }
511
+ });
512
+
513
+ // Decrypt endpoint
514
+ app.post('/api/decrypt', async (req, res) => {
515
+ try {
516
+ const { encrypted, password } = req.body;
517
+
518
+ if (!encrypted || !password) {
519
+ return res.status(400).json({
520
+ success: false,
521
+ error: 'Encrypted data and password are required'
522
+ });
523
+ }
524
+
525
+ const decrypted = await encryption.decryptFromString(encrypted, password);
526
+ res.json({ success: true, decrypted });
527
+ } catch (error) {
528
+ res.status(500).json({
529
+ success: false,
530
+ error: error instanceof Error ? error.message : 'Decryption failed'
531
+ });
532
+ }
533
+ });
534
+ ```
535
+
536
+ #### Security Features
537
+
538
+ - **AES-256-GCM**: Industry-standard authenticated encryption
539
+ - **Automatic Chunking**: Handles large data efficiently without memory issues
540
+ - **PBKDF2 Key Derivation**: 100,000 iterations by default (configurable)
541
+ - **Random Salt & IV**: Unique salt and IV for each encryption
542
+ - **Authentication Tags**: Prevents tampering with encrypted data
543
+ - **No External Dependencies**: Uses Node.js built-in crypto module
544
+
545
+ #### Encryption Configuration Options
546
+
547
+ ```typescript
548
+ interface EncryptionConfig {
549
+ algorithm?: 'aes-256-gcm' | 'aes-192-gcm' | 'aes-128-gcm'; // Default: 'aes-256-gcm'
550
+ keyDerivationIterations?: number; // Default: 100000
551
+ saltLength?: number; // Default: 32 bytes
552
+ ivLength?: number; // Default: 16 bytes
553
+ authTagLength?: number; // Default: 16 bytes
554
+ chunkSize?: number; // Default: 64KB
555
+ }
556
+ ```
557
+
558
+ #### Encryption Result Format
559
+
560
+ ```typescript
561
+ interface EncryptionResult {
562
+ encrypted: string; // Base64 encoded encrypted data
563
+ salt: string; // Base64 encoded salt used for key derivation
564
+ iv: string; // Base64 encoded IV/nonce
565
+ authTag?: string; // Base64 encoded authentication tag (for single chunk)
566
+ algorithm: string; // Algorithm used ('aes-256-gcm')
567
+ }
568
+ ```
569
+
369
570
  ## API Endpoints
370
571
 
371
572
  ### Register User
@@ -585,6 +786,13 @@ interface AuthConfig {
585
786
 
586
787
  5. **Input Validation**: All inputs are validated and sanitized automatically
587
788
 
789
+ 6. **Data Encryption**:
790
+ - Use AES-256-GCM for authenticated encryption
791
+ - Minimum 8 character password for encryption
792
+ - Store encrypted data securely (database, files, etc.)
793
+ - Never share encryption passwords in plain text
794
+ - Use strong, randomly generated passwords when possible
795
+
588
796
  ## Types
589
797
 
590
798
  ```typescript
package/dist/index.d.ts CHANGED
@@ -56,6 +56,7 @@ export * from './types';
56
56
  export { JwtService } from './utils/jwt';
57
57
  export { PasswordService } from './utils/password';
58
58
  export { ValidatorService } from './utils/validator';
59
+ export { EncryptionService, EncryptionConfig, EncryptionResult } from './utils/encryption';
59
60
  export { AuthMiddleware } from './middleware/auth.middleware';
60
61
  export { validate, validateBody, validateQuery, validateParams, ValidationOptions, ValidationSource, ValidationSchema, ValidationRule, FieldValidation } from './middleware/validation.middleware';
61
62
  export { LocalAuthHandler } from './handlers/local-auth';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.SocialAuthHandler = exports.LocalAuthHandler = exports.validateParams = exports.validateQuery = exports.validateBody = exports.validate = exports.AuthMiddleware = exports.ValidatorService = exports.PasswordService = exports.JwtService = exports.Auth = void 0;
17
+ exports.SocialAuthHandler = exports.LocalAuthHandler = exports.validateParams = exports.validateQuery = exports.validateBody = exports.validate = exports.AuthMiddleware = exports.EncryptionService = exports.ValidatorService = exports.PasswordService = exports.JwtService = exports.Auth = void 0;
18
18
  const jwt_1 = require("./utils/jwt");
19
19
  const password_1 = require("./utils/password");
20
20
  const local_auth_1 = require("./handlers/local-auth");
@@ -224,6 +224,8 @@ var password_2 = require("./utils/password");
224
224
  Object.defineProperty(exports, "PasswordService", { enumerable: true, get: function () { return password_2.PasswordService; } });
225
225
  var validator_1 = require("./utils/validator");
226
226
  Object.defineProperty(exports, "ValidatorService", { enumerable: true, get: function () { return validator_1.ValidatorService; } });
227
+ var encryption_1 = require("./utils/encryption");
228
+ Object.defineProperty(exports, "EncryptionService", { enumerable: true, get: function () { return encryption_1.EncryptionService; } });
227
229
  var auth_middleware_2 = require("./middleware/auth.middleware");
228
230
  Object.defineProperty(exports, "AuthMiddleware", { enumerable: true, get: function () { return auth_middleware_2.AuthMiddleware; } });
229
231
  var validation_middleware_1 = require("./middleware/validation.middleware");
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Configuration options for encryption service
3
+ */
4
+ export interface EncryptionConfig {
5
+ /**
6
+ * Algorithm to use for encryption (default: 'aes-256-gcm')
7
+ */
8
+ algorithm?: 'aes-256-gcm' | 'aes-192-gcm' | 'aes-128-gcm';
9
+ /**
10
+ * Key derivation iterations for PBKDF2 (default: 100000)
11
+ * Higher values are more secure but slower
12
+ */
13
+ keyDerivationIterations?: number;
14
+ /**
15
+ * Salt length in bytes (default: 32)
16
+ */
17
+ saltLength?: number;
18
+ /**
19
+ * IV length in bytes (default: 16 for GCM)
20
+ */
21
+ ivLength?: number;
22
+ /**
23
+ * Authentication tag length in bytes (default: 16 for GCM)
24
+ */
25
+ authTagLength?: number;
26
+ /**
27
+ * Chunk size for large data encryption in bytes (default: 64KB)
28
+ * Data larger than this will be encrypted in chunks
29
+ */
30
+ chunkSize?: number;
31
+ }
32
+ /**
33
+ * Encryption result containing encrypted data and metadata
34
+ */
35
+ export interface EncryptionResult {
36
+ /**
37
+ * Encrypted data as base64 string
38
+ */
39
+ encrypted: string;
40
+ /**
41
+ * Salt used for key derivation (base64)
42
+ */
43
+ salt: string;
44
+ /**
45
+ * IV/nonce used for encryption (base64)
46
+ */
47
+ iv: string;
48
+ /**
49
+ * Authentication tag for GCM mode (base64)
50
+ */
51
+ authTag?: string;
52
+ /**
53
+ * Algorithm used
54
+ */
55
+ algorithm: string;
56
+ }
57
+ /**
58
+ * Service for end-to-end encryption and decryption of large data
59
+ * Uses AES-256-GCM for authenticated encryption with chunking support
60
+ */
61
+ export declare class EncryptionService {
62
+ private algorithm;
63
+ private keyDerivationIterations;
64
+ private saltLength;
65
+ private ivLength;
66
+ private authTagLength;
67
+ private chunkSize;
68
+ private keyLength;
69
+ constructor(config?: EncryptionConfig);
70
+ /**
71
+ * Derive encryption key from password using PBKDF2
72
+ */
73
+ private deriveKey;
74
+ /**
75
+ * Encrypt data (string or Buffer) with password-based encryption
76
+ * Automatically handles chunking for large data
77
+ *
78
+ * @param data - Data to encrypt (string or Buffer)
79
+ * @param password - Password for encryption
80
+ * @returns Encryption result with encrypted data and metadata
81
+ */
82
+ encrypt(data: string | Buffer, password: string): Promise<EncryptionResult>;
83
+ /**
84
+ * Encrypt a single chunk of data
85
+ */
86
+ private encryptChunk;
87
+ /**
88
+ * Encrypt large data in chunks
89
+ */
90
+ private encryptLargeData;
91
+ /**
92
+ * Decrypt data that was encrypted with encrypt()
93
+ *
94
+ * @param encryptedData - Encryption result from encrypt()
95
+ * @param password - Password used for encryption
96
+ * @returns Decrypted data as Buffer
97
+ */
98
+ decrypt(encryptedData: EncryptionResult, password: string): Promise<Buffer>;
99
+ /**
100
+ * Decrypt a single chunk
101
+ */
102
+ private decryptChunk;
103
+ /**
104
+ * Decrypt large data that was encrypted in chunks
105
+ */
106
+ private decryptLargeData;
107
+ /**
108
+ * Encrypt data and return as string (convenience method)
109
+ *
110
+ * @param data - Data to encrypt (string or Buffer)
111
+ * @param password - Password for encryption
112
+ * @returns Encrypted data as base64 string (includes all metadata)
113
+ */
114
+ encryptToString(data: string | Buffer, password: string): Promise<string>;
115
+ /**
116
+ * Decrypt data from string format (convenience method)
117
+ *
118
+ * @param encryptedString - Encrypted data as JSON string from encryptToString()
119
+ * @param password - Password used for encryption
120
+ * @returns Decrypted data as string
121
+ */
122
+ decryptFromString(encryptedString: string, password: string): Promise<string>;
123
+ /**
124
+ * Decrypt data from string format and return as Buffer
125
+ *
126
+ * @param encryptedString - Encrypted data as JSON string from encryptToString()
127
+ * @param password - Password used for encryption
128
+ * @returns Decrypted data as Buffer
129
+ */
130
+ decryptFromStringToBuffer(encryptedString: string, password: string): Promise<Buffer>;
131
+ /**
132
+ * Generate a secure random password/key
133
+ *
134
+ * @param length - Length of password in bytes (default: 32)
135
+ * @returns Random password as base64 string
136
+ */
137
+ static generateSecurePassword(length?: number): string;
138
+ /**
139
+ * Generate a secure random password/key as hex string
140
+ *
141
+ * @param length - Length of password in bytes (default: 32)
142
+ * @returns Random password as hex string
143
+ */
144
+ static generateSecurePasswordHex(length?: number): string;
145
+ }
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.EncryptionService = void 0;
37
+ const crypto = __importStar(require("crypto"));
38
+ /**
39
+ * Service for end-to-end encryption and decryption of large data
40
+ * Uses AES-256-GCM for authenticated encryption with chunking support
41
+ */
42
+ class EncryptionService {
43
+ constructor(config = {}) {
44
+ this.algorithm = config.algorithm || 'aes-256-gcm';
45
+ this.keyDerivationIterations = config.keyDerivationIterations || 100000;
46
+ this.saltLength = config.saltLength || 32;
47
+ this.ivLength = config.ivLength || 16;
48
+ this.authTagLength = config.authTagLength || 16;
49
+ this.chunkSize = config.chunkSize || 64 * 1024; // 64KB default
50
+ // Determine key length based on algorithm
51
+ if (this.algorithm.includes('256')) {
52
+ this.keyLength = 32; // 256 bits
53
+ }
54
+ else if (this.algorithm.includes('192')) {
55
+ this.keyLength = 24; // 192 bits
56
+ }
57
+ else {
58
+ this.keyLength = 16; // 128 bits
59
+ }
60
+ }
61
+ /**
62
+ * Derive encryption key from password using PBKDF2
63
+ */
64
+ async deriveKey(password, salt) {
65
+ return new Promise((resolve, reject) => {
66
+ crypto.pbkdf2(password, salt, this.keyDerivationIterations, this.keyLength, 'sha256', (err, derivedKey) => {
67
+ if (err)
68
+ reject(err);
69
+ else
70
+ resolve(derivedKey);
71
+ });
72
+ });
73
+ }
74
+ /**
75
+ * Encrypt data (string or Buffer) with password-based encryption
76
+ * Automatically handles chunking for large data
77
+ *
78
+ * @param data - Data to encrypt (string or Buffer)
79
+ * @param password - Password for encryption
80
+ * @returns Encryption result with encrypted data and metadata
81
+ */
82
+ async encrypt(data, password) {
83
+ if (!data) {
84
+ throw new Error('Data to encrypt cannot be empty');
85
+ }
86
+ if (!password || password.length < 8) {
87
+ throw new Error('Password must be at least 8 characters long');
88
+ }
89
+ // Convert string to Buffer if needed
90
+ const dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
91
+ // Generate random salt and IV
92
+ const salt = crypto.randomBytes(this.saltLength);
93
+ const iv = crypto.randomBytes(this.ivLength);
94
+ // Derive encryption key from password
95
+ const key = await this.deriveKey(password, salt);
96
+ // For small data, encrypt directly
97
+ if (dataBuffer.length <= this.chunkSize) {
98
+ const result = this.encryptChunk(dataBuffer, key, iv);
99
+ result.salt = salt.toString('base64');
100
+ return result;
101
+ }
102
+ // For large data, encrypt in chunks
103
+ const result = await this.encryptLargeData(dataBuffer, key, iv);
104
+ result.salt = salt.toString('base64');
105
+ return result;
106
+ }
107
+ /**
108
+ * Encrypt a single chunk of data
109
+ */
110
+ encryptChunk(data, key, iv) {
111
+ const cipher = crypto.createCipheriv(this.algorithm, key, iv);
112
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
113
+ const authTag = cipher.getAuthTag();
114
+ return {
115
+ encrypted: encrypted.toString('base64'),
116
+ salt: '', // Will be set by caller
117
+ iv: iv.toString('base64'),
118
+ authTag: authTag.toString('base64'),
119
+ algorithm: this.algorithm,
120
+ };
121
+ }
122
+ /**
123
+ * Encrypt large data in chunks
124
+ */
125
+ async encryptLargeData(data, key, iv) {
126
+ const chunks = [];
127
+ const totalChunks = Math.ceil(data.length / this.chunkSize);
128
+ // Generate a unique IV for each chunk (derive from base IV)
129
+ const chunkIVs = [];
130
+ for (let i = 0; i < totalChunks; i++) {
131
+ // Derive chunk IV from base IV and chunk index
132
+ const chunkIVBuffer = Buffer.allocUnsafe(this.ivLength);
133
+ crypto.createHash('sha256')
134
+ .update(iv)
135
+ .update(Buffer.from(i.toString()))
136
+ .digest()
137
+ .copy(chunkIVBuffer, 0, 0, this.ivLength);
138
+ chunkIVs.push(chunkIVBuffer);
139
+ }
140
+ // Encrypt each chunk
141
+ for (let i = 0; i < totalChunks; i++) {
142
+ const start = i * this.chunkSize;
143
+ const end = Math.min(start + this.chunkSize, data.length);
144
+ const chunk = data.slice(start, end);
145
+ const chunkIV = chunkIVs[i];
146
+ const cipher = crypto.createCipheriv(this.algorithm, key, chunkIV);
147
+ const encrypted = Buffer.concat([cipher.update(chunk), cipher.final()]);
148
+ const authTag = cipher.getAuthTag();
149
+ // Store chunk with its auth tag: base64(encrypted:authTag)
150
+ const chunkWithTag = Buffer.concat([encrypted, authTag]);
151
+ chunks.push(chunkWithTag.toString('base64'));
152
+ }
153
+ // Combine all chunks with delimiter
154
+ const encryptedData = chunks.join('|');
155
+ return {
156
+ encrypted: encryptedData,
157
+ salt: '', // Will be set by caller
158
+ iv: iv.toString('base64'),
159
+ authTag: undefined, // Not used for chunked encryption (each chunk has its own tag)
160
+ algorithm: this.algorithm,
161
+ };
162
+ }
163
+ /**
164
+ * Decrypt data that was encrypted with encrypt()
165
+ *
166
+ * @param encryptedData - Encryption result from encrypt()
167
+ * @param password - Password used for encryption
168
+ * @returns Decrypted data as Buffer
169
+ */
170
+ async decrypt(encryptedData, password) {
171
+ if (!encryptedData || !encryptedData.encrypted) {
172
+ throw new Error('Invalid encrypted data');
173
+ }
174
+ if (!password || password.length < 8) {
175
+ throw new Error('Password must be at least 8 characters long');
176
+ }
177
+ // Reconstruct salt and IV
178
+ if (!encryptedData.salt) {
179
+ throw new Error('Salt is required for decryption');
180
+ }
181
+ const salt = Buffer.from(encryptedData.salt, 'base64');
182
+ const iv = Buffer.from(encryptedData.iv, 'base64');
183
+ // Derive encryption key from password
184
+ const key = await this.deriveKey(password, salt);
185
+ // Check if data is chunked (contains '|' delimiter)
186
+ if (encryptedData.encrypted.includes('|')) {
187
+ return this.decryptLargeData(encryptedData.encrypted, key, iv);
188
+ }
189
+ // Decrypt single chunk
190
+ return this.decryptChunk(encryptedData, key, iv);
191
+ }
192
+ /**
193
+ * Decrypt a single chunk
194
+ */
195
+ decryptChunk(encryptedData, key, iv) {
196
+ const encrypted = Buffer.from(encryptedData.encrypted, 'base64');
197
+ if (!encryptedData.authTag) {
198
+ throw new Error('Authentication tag is required for decryption');
199
+ }
200
+ const authTag = Buffer.from(encryptedData.authTag, 'base64');
201
+ const decipher = crypto.createDecipheriv(encryptedData.algorithm || this.algorithm, key, iv);
202
+ decipher.setAuthTag(authTag);
203
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]);
204
+ }
205
+ /**
206
+ * Decrypt large data that was encrypted in chunks
207
+ */
208
+ decryptLargeData(encryptedData, key, iv) {
209
+ const chunks = encryptedData.split('|');
210
+ const decryptedChunks = [];
211
+ for (let i = 0; i < chunks.length; i++) {
212
+ // Derive chunk IV from base IV and chunk index
213
+ const chunkIVBuffer = Buffer.allocUnsafe(this.ivLength);
214
+ crypto.createHash('sha256')
215
+ .update(iv)
216
+ .update(Buffer.from(i.toString()))
217
+ .digest()
218
+ .copy(chunkIVBuffer, 0, 0, this.ivLength);
219
+ // Extract encrypted data and auth tag
220
+ const chunkWithTag = Buffer.from(chunks[i], 'base64');
221
+ const encrypted = chunkWithTag.slice(0, -this.authTagLength);
222
+ const authTag = chunkWithTag.slice(-this.authTagLength);
223
+ // Decrypt chunk
224
+ const decipher = crypto.createDecipheriv(this.algorithm, key, chunkIVBuffer);
225
+ decipher.setAuthTag(authTag);
226
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
227
+ decryptedChunks.push(decrypted);
228
+ }
229
+ return Buffer.concat(decryptedChunks);
230
+ }
231
+ /**
232
+ * Encrypt data and return as string (convenience method)
233
+ *
234
+ * @param data - Data to encrypt (string or Buffer)
235
+ * @param password - Password for encryption
236
+ * @returns Encrypted data as base64 string (includes all metadata)
237
+ */
238
+ async encryptToString(data, password) {
239
+ const result = await this.encrypt(data, password);
240
+ // Include salt in result for proper decryption
241
+ if (!result.salt) {
242
+ // Generate salt for this encryption
243
+ const salt = crypto.randomBytes(this.saltLength);
244
+ result.salt = salt.toString('base64');
245
+ }
246
+ // Return as JSON string (can be easily stored/transmitted)
247
+ return JSON.stringify(result);
248
+ }
249
+ /**
250
+ * Decrypt data from string format (convenience method)
251
+ *
252
+ * @param encryptedString - Encrypted data as JSON string from encryptToString()
253
+ * @param password - Password used for encryption
254
+ * @returns Decrypted data as string
255
+ */
256
+ async decryptFromString(encryptedString, password) {
257
+ const encryptedData = JSON.parse(encryptedString);
258
+ const decrypted = await this.decrypt(encryptedData, password);
259
+ return decrypted.toString('utf8');
260
+ }
261
+ /**
262
+ * Decrypt data from string format and return as Buffer
263
+ *
264
+ * @param encryptedString - Encrypted data as JSON string from encryptToString()
265
+ * @param password - Password used for encryption
266
+ * @returns Decrypted data as Buffer
267
+ */
268
+ async decryptFromStringToBuffer(encryptedString, password) {
269
+ const encryptedData = JSON.parse(encryptedString);
270
+ return this.decrypt(encryptedData, password);
271
+ }
272
+ /**
273
+ * Generate a secure random password/key
274
+ *
275
+ * @param length - Length of password in bytes (default: 32)
276
+ * @returns Random password as base64 string
277
+ */
278
+ static generateSecurePassword(length = 32) {
279
+ return crypto.randomBytes(length).toString('base64');
280
+ }
281
+ /**
282
+ * Generate a secure random password/key as hex string
283
+ *
284
+ * @param length - Length of password in bytes (default: 32)
285
+ * @returns Random password as hex string
286
+ */
287
+ static generateSecurePasswordHex(length = 32) {
288
+ return crypto.randomBytes(length).toString('hex');
289
+ }
290
+ }
291
+ exports.EncryptionService = EncryptionService;
@@ -1,3 +1,4 @@
1
1
  export { JwtService } from './jwt';
2
2
  export { PasswordService } from './password';
3
3
  export { ValidatorService } from './validator';
4
+ export { EncryptionService, EncryptionConfig, EncryptionResult } from './encryption';
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ValidatorService = exports.PasswordService = exports.JwtService = void 0;
3
+ exports.EncryptionService = exports.ValidatorService = exports.PasswordService = exports.JwtService = void 0;
4
4
  var jwt_1 = require("./jwt");
5
5
  Object.defineProperty(exports, "JwtService", { enumerable: true, get: function () { return jwt_1.JwtService; } });
6
6
  var password_1 = require("./password");
7
7
  Object.defineProperty(exports, "PasswordService", { enumerable: true, get: function () { return password_1.PasswordService; } });
8
8
  var validator_1 = require("./validator");
9
9
  Object.defineProperty(exports, "ValidatorService", { enumerable: true, get: function () { return validator_1.ValidatorService; } });
10
+ var encryption_1 = require("./encryption");
11
+ Object.defineProperty(exports, "EncryptionService", { enumerable: true, get: function () { return encryption_1.EncryptionService; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhandari88/express-auth",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Plug-and-play authentication handler for Express.js with TypeScript supporting email, username, phone, and social login",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",