@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 +208 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/utils/encryption.d.ts +145 -0
- package/dist/utils/encryption.js +291 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +3 -1
- package/package.json +1 -1
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;
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -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.
|
|
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",
|