@bhandari88/express-auth 1.1.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
@@ -34,6 +34,7 @@ npm install @bhandari88/express-auth
34
34
  - ✅ **Easy Integration**
35
35
  - Plug-and-play API
36
36
  - Express middleware for protected routes
37
+ - Request data validation middleware with custom messages
37
38
  - Role-based access control
38
39
  - Automatic route setup
39
40
  - TypeScript support
@@ -44,6 +45,14 @@ npm install @bhandari88/express-auth
44
45
  - Optional refresh tokens
45
46
  - Customizable user fields
46
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
+
47
56
  ## Peer Dependencies
48
57
 
49
58
  Make sure you have the following peer dependencies installed:
@@ -170,6 +179,394 @@ app.get('/api/admin',
170
179
  );
171
180
  ```
172
181
 
182
+ ### 5. Use Validation Middleware
183
+
184
+ The package includes a powerful validation middleware for request data validation with custom error messages. No third-party dependencies required!
185
+
186
+ #### Basic Usage
187
+
188
+ ```typescript
189
+ import { validate, validateBody, validateQuery, validateParams } from '@bhandari88/express-auth';
190
+
191
+ // Validate request body
192
+ app.post('/api/users', validateBody({
193
+ email: {
194
+ rules: [
195
+ { type: 'required', message: 'Email is required' },
196
+ { type: 'email', message: 'Invalid email format' }
197
+ ]
198
+ },
199
+ password: {
200
+ rules: [
201
+ { type: 'required', message: 'Password is required' },
202
+ { type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }
203
+ ]
204
+ },
205
+ age: {
206
+ rules: [
207
+ { type: 'number', message: 'Age must be a number' },
208
+ { type: 'min', value: 18, message: 'Age must be at least 18' },
209
+ { type: 'max', value: 120, message: 'Age must be at most 120' }
210
+ ]
211
+ }
212
+ }), (req, res) => {
213
+ // req.body is validated
214
+ res.json({ message: 'User created', data: req.body });
215
+ });
216
+ ```
217
+
218
+ #### Shorthand Syntax
219
+
220
+ You can also use a simpler array syntax:
221
+
222
+ ```typescript
223
+ app.post('/api/register', validateBody({
224
+ username: [
225
+ { type: 'required', message: 'Username is required' },
226
+ { type: 'minLength', value: 3, message: 'Username must be at least 3 characters' },
227
+ { type: 'maxLength', value: 30, message: 'Username must be at most 30 characters' },
228
+ { type: 'pattern', value: /^[a-zA-Z0-9_-]+$/, message: 'Username can only contain letters, numbers, underscores, and hyphens' }
229
+ ],
230
+ email: [
231
+ { type: 'required' },
232
+ { type: 'email', message: 'Invalid email address' }
233
+ ]
234
+ }), handler);
235
+ ```
236
+
237
+ #### Validate Query Parameters
238
+
239
+ ```typescript
240
+ app.get('/api/users', validateQuery({
241
+ page: [
242
+ { type: 'number', message: 'Page must be a number' },
243
+ { type: 'min', value: 1, message: 'Page must be at least 1' }
244
+ ],
245
+ limit: [
246
+ { type: 'number' },
247
+ { type: 'min', value: 1 },
248
+ { type: 'max', value: 100, message: 'Limit cannot exceed 100' }
249
+ ],
250
+ status: [
251
+ { type: 'enum', values: ['active', 'inactive', 'pending'], message: 'Status must be active, inactive, or pending' }
252
+ ]
253
+ }), (req, res) => {
254
+ // req.query is validated
255
+ res.json({ users: [] });
256
+ });
257
+ ```
258
+
259
+ #### Validate Route Parameters
260
+
261
+ ```typescript
262
+ app.get('/api/users/:id', validateParams({
263
+ id: [
264
+ { type: 'required', message: 'User ID is required' },
265
+ { type: 'pattern', value: /^[a-f\d]{24}$/i, message: 'Invalid user ID format' }
266
+ ]
267
+ }), (req, res) => {
268
+ // req.params is validated
269
+ res.json({ user: {} });
270
+ });
271
+ ```
272
+
273
+ #### Validate Multiple Sources
274
+
275
+ ```typescript
276
+ app.put('/api/users/:id', validate({
277
+ id: [
278
+ { type: 'required' },
279
+ { type: 'pattern', value: /^[a-f\d]{24}$/i }
280
+ ],
281
+ email: [
282
+ { type: 'email' }
283
+ ],
284
+ status: [
285
+ { type: 'enum', values: ['active', 'inactive'] }
286
+ ]
287
+ }, {
288
+ source: ['params', 'body'] // Validate both params and body
289
+ }), handler);
290
+ ```
291
+
292
+ #### Custom Validation
293
+
294
+ ```typescript
295
+ app.post('/api/custom', validateBody({
296
+ password: [
297
+ { type: 'required' },
298
+ { type: 'minLength', value: 8 },
299
+ {
300
+ type: 'custom',
301
+ validator: (value) => {
302
+ // Custom validation logic
303
+ return /[A-Z]/.test(value) && /[a-z]/.test(value) && /[0-9]/.test(value);
304
+ },
305
+ message: 'Password must contain uppercase, lowercase, and number'
306
+ }
307
+ ],
308
+ confirmPassword: [
309
+ { type: 'required' },
310
+ {
311
+ type: 'custom',
312
+ validator: (value, data) => {
313
+ // Access other fields from the request
314
+ return value === data.password;
315
+ },
316
+ message: 'Passwords do not match'
317
+ }
318
+ ]
319
+ }), handler);
320
+ ```
321
+
322
+ #### Available Validation Rules
323
+
324
+ | Rule | Description | Example |
325
+ |------|-------------|---------|
326
+ | `required` | Field must be present and not empty | `{ type: 'required', message: 'Field is required' }` |
327
+ | `string` | Value must be a string | `{ type: 'string' }` |
328
+ | `number` | Value must be a number | `{ type: 'number' }` |
329
+ | `boolean` | Value must be a boolean | `{ type: 'boolean' }` |
330
+ | `email` | Value must be a valid email | `{ type: 'email', message: 'Invalid email' }` |
331
+ | `min` | Number must be >= value | `{ type: 'min', value: 1 }` |
332
+ | `max` | Number must be <= value | `{ type: 'max', value: 100 }` |
333
+ | `minLength` | String length must be >= value | `{ type: 'minLength', value: 8 }` |
334
+ | `maxLength` | String length must be <= value | `{ type: 'maxLength', value: 255 }` |
335
+ | `pattern` | String must match regex | `{ type: 'pattern', value: /^[a-z]+$/ }` |
336
+ | `enum` | Value must be in array | `{ type: 'enum', values: ['a', 'b', 'c'] }` |
337
+ | `array` | Value must be an array | `{ type: 'array' }` |
338
+ | `object` | Value must be an object | `{ type: 'object' }` |
339
+ | `custom` | Custom validation function | `{ type: 'custom', validator: (val) => boolean }` |
340
+
341
+ #### Validation Options
342
+
343
+ ```typescript
344
+ validate(schema, {
345
+ source: 'body' | 'query' | 'params' | ['body', 'query'], // What to validate (default: 'body')
346
+ abortEarly: boolean // Stop on first error or collect all errors (default: false)
347
+ })
348
+ ```
349
+
350
+ #### Error Response Format
351
+
352
+ When validation fails, the middleware returns a 400 status with:
353
+
354
+ ```json
355
+ {
356
+ "success": false,
357
+ "error": "Validation failed",
358
+ "errors": {
359
+ "email": "Invalid email format",
360
+ "password": "Password must be at least 8 characters"
361
+ }
362
+ }
363
+ ```
364
+
365
+ Or with `abortEarly: true`, only the first error:
366
+
367
+ ```json
368
+ {
369
+ "success": false,
370
+ "error": "Validation failed",
371
+ "errors": {
372
+ "email": "Email is required"
373
+ }
374
+ }
375
+ ```
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
+
173
570
  ## API Endpoints
174
571
 
175
572
  ### Register User
@@ -389,6 +786,13 @@ interface AuthConfig {
389
786
 
390
787
  5. **Input Validation**: All inputs are validated and sanitized automatically
391
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
+
392
796
  ## Types
393
797
 
394
798
  ```typescript
package/dist/index.d.ts CHANGED
@@ -56,6 +56,8 @@ 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';
61
+ export { validate, validateBody, validateQuery, validateParams, ValidationOptions, ValidationSource, ValidationSchema, ValidationRule, FieldValidation } from './middleware/validation.middleware';
60
62
  export { LocalAuthHandler } from './handlers/local-auth';
61
63
  export { SocialAuthHandler } from './handlers/social-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.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,8 +224,15 @@ 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; } });
231
+ var validation_middleware_1 = require("./middleware/validation.middleware");
232
+ Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return validation_middleware_1.validate; } });
233
+ Object.defineProperty(exports, "validateBody", { enumerable: true, get: function () { return validation_middleware_1.validateBody; } });
234
+ Object.defineProperty(exports, "validateQuery", { enumerable: true, get: function () { return validation_middleware_1.validateQuery; } });
235
+ Object.defineProperty(exports, "validateParams", { enumerable: true, get: function () { return validation_middleware_1.validateParams; } });
229
236
  var local_auth_2 = require("./handlers/local-auth");
230
237
  Object.defineProperty(exports, "LocalAuthHandler", { enumerable: true, get: function () { return local_auth_2.LocalAuthHandler; } });
231
238
  var social_auth_2 = require("./handlers/social-auth");
@@ -0,0 +1,114 @@
1
+ import { RequestHandler } from 'express';
2
+ export type ValidationSource = 'body' | 'query' | 'params';
3
+ export type ValidationRule = {
4
+ type: 'required';
5
+ message?: string;
6
+ } | {
7
+ type: 'string';
8
+ message?: string;
9
+ } | {
10
+ type: 'number';
11
+ message?: string;
12
+ } | {
13
+ type: 'boolean';
14
+ message?: string;
15
+ } | {
16
+ type: 'email';
17
+ message?: string;
18
+ } | {
19
+ type: 'min';
20
+ value: number;
21
+ message?: string;
22
+ } | {
23
+ type: 'max';
24
+ value: number;
25
+ message?: string;
26
+ } | {
27
+ type: 'minLength';
28
+ value: number;
29
+ message?: string;
30
+ } | {
31
+ type: 'maxLength';
32
+ value: number;
33
+ message?: string;
34
+ } | {
35
+ type: 'pattern';
36
+ value: RegExp;
37
+ message?: string;
38
+ } | {
39
+ type: 'enum';
40
+ values: any[];
41
+ message?: string;
42
+ } | {
43
+ type: 'custom';
44
+ validator: (value: any) => boolean | Promise<boolean>;
45
+ message?: string;
46
+ } | {
47
+ type: 'array';
48
+ message?: string;
49
+ } | {
50
+ type: 'object';
51
+ message?: string;
52
+ };
53
+ export interface FieldValidation {
54
+ rules: ValidationRule[];
55
+ message?: string;
56
+ }
57
+ export interface ValidationSchema {
58
+ [field: string]: FieldValidation | ValidationRule[];
59
+ }
60
+ export interface ValidationOptions {
61
+ source?: ValidationSource | ValidationSource[];
62
+ abortEarly?: boolean;
63
+ }
64
+ /**
65
+ * Validation middleware factory for request data validation
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * import { validate } from '@bhandari88/express-auth';
70
+ *
71
+ * // Simple validation
72
+ * app.post('/login', validate({
73
+ * email: {
74
+ * rules: [
75
+ * { type: 'required', message: 'Email is required' },
76
+ * { type: 'email', message: 'Invalid email format' }
77
+ * ]
78
+ * },
79
+ * password: {
80
+ * rules: [
81
+ * { type: 'required', message: 'Password is required' },
82
+ * { type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }
83
+ * ]
84
+ * }
85
+ * }), (req, res) => {
86
+ * // req.body is validated
87
+ * });
88
+ *
89
+ * // Validate query parameters
90
+ * app.get('/users', validate({
91
+ * page: {
92
+ * rules: [
93
+ * { type: 'number', message: 'Page must be a number' },
94
+ * { type: 'min', value: 1, message: 'Page must be at least 1' }
95
+ * ]
96
+ * }
97
+ * }, { source: 'query' }), (req, res) => {
98
+ * // req.query is validated
99
+ * });
100
+ * ```
101
+ */
102
+ export declare function validate(schema: ValidationSchema, options?: ValidationOptions): RequestHandler;
103
+ /**
104
+ * Validate request body
105
+ */
106
+ export declare function validateBody(schema: ValidationSchema, options?: Omit<ValidationOptions, 'source'>): RequestHandler;
107
+ /**
108
+ * Validate request query parameters
109
+ */
110
+ export declare function validateQuery(schema: ValidationSchema, options?: Omit<ValidationOptions, 'source'>): RequestHandler;
111
+ /**
112
+ * Validate request route parameters
113
+ */
114
+ export declare function validateParams(schema: ValidationSchema, options?: Omit<ValidationOptions, 'source'>): RequestHandler;
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validate = validate;
4
+ exports.validateBody = validateBody;
5
+ exports.validateQuery = validateQuery;
6
+ exports.validateParams = validateParams;
7
+ /**
8
+ * Validation middleware factory for request data validation
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { validate } from '@bhandari88/express-auth';
13
+ *
14
+ * // Simple validation
15
+ * app.post('/login', validate({
16
+ * email: {
17
+ * rules: [
18
+ * { type: 'required', message: 'Email is required' },
19
+ * { type: 'email', message: 'Invalid email format' }
20
+ * ]
21
+ * },
22
+ * password: {
23
+ * rules: [
24
+ * { type: 'required', message: 'Password is required' },
25
+ * { type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }
26
+ * ]
27
+ * }
28
+ * }), (req, res) => {
29
+ * // req.body is validated
30
+ * });
31
+ *
32
+ * // Validate query parameters
33
+ * app.get('/users', validate({
34
+ * page: {
35
+ * rules: [
36
+ * { type: 'number', message: 'Page must be a number' },
37
+ * { type: 'min', value: 1, message: 'Page must be at least 1' }
38
+ * ]
39
+ * }
40
+ * }, { source: 'query' }), (req, res) => {
41
+ * // req.query is validated
42
+ * });
43
+ * ```
44
+ */
45
+ function validate(schema, options = {}) {
46
+ const { source = 'body', abortEarly = false, } = options;
47
+ const sources = Array.isArray(source) ? source : [source];
48
+ return async (req, res, next) => {
49
+ try {
50
+ const errors = [];
51
+ // Collect data from all specified sources
52
+ const dataToValidate = {};
53
+ for (const src of sources) {
54
+ if (src === 'body') {
55
+ Object.assign(dataToValidate, req.body);
56
+ }
57
+ else if (src === 'query') {
58
+ Object.assign(dataToValidate, req.query);
59
+ }
60
+ else if (src === 'params') {
61
+ Object.assign(dataToValidate, req.params);
62
+ }
63
+ }
64
+ // Validate each field in the schema
65
+ for (const [field, fieldValidation] of Object.entries(schema)) {
66
+ const value = dataToValidate[field];
67
+ // Get rules array
68
+ let rules;
69
+ let fieldMessage;
70
+ if (Array.isArray(fieldValidation)) {
71
+ rules = fieldValidation;
72
+ }
73
+ else {
74
+ rules = fieldValidation.rules;
75
+ fieldMessage = fieldValidation.message;
76
+ }
77
+ // Validate each rule
78
+ for (const rule of rules) {
79
+ const error = await validateRule(field, value, rule, fieldMessage);
80
+ if (error) {
81
+ errors.push(error);
82
+ if (abortEarly) {
83
+ break;
84
+ }
85
+ }
86
+ }
87
+ if (abortEarly && errors.length > 0) {
88
+ break;
89
+ }
90
+ }
91
+ if (errors.length > 0) {
92
+ res.status(400).json({
93
+ success: false,
94
+ error: 'Validation failed',
95
+ errors: errors.length === 1
96
+ ? { [errors[0].field]: errors[0].message }
97
+ : errors.reduce((acc, err) => {
98
+ acc[err.field] = err.message;
99
+ return acc;
100
+ }, {}),
101
+ });
102
+ return;
103
+ }
104
+ next();
105
+ }
106
+ catch (error) {
107
+ res.status(500).json({
108
+ success: false,
109
+ error: 'Validation error',
110
+ });
111
+ }
112
+ };
113
+ }
114
+ /**
115
+ * Validate a single rule
116
+ */
117
+ async function validateRule(field, value, rule, fieldMessage) {
118
+ const getMessage = (defaultMsg) => {
119
+ return rule.message || fieldMessage || defaultMsg;
120
+ };
121
+ switch (rule.type) {
122
+ case 'required':
123
+ if (value === undefined || value === null || value === '') {
124
+ return {
125
+ field,
126
+ message: getMessage(`${field} is required`),
127
+ };
128
+ }
129
+ break;
130
+ case 'string':
131
+ if (value !== undefined && typeof value !== 'string') {
132
+ return {
133
+ field,
134
+ message: getMessage(`${field} must be a string`),
135
+ };
136
+ }
137
+ break;
138
+ case 'number':
139
+ if (value !== undefined && (typeof value !== 'number' || isNaN(value))) {
140
+ // Try to parse string numbers
141
+ if (typeof value === 'string' && !isNaN(Number(value))) {
142
+ return null; // Valid number string
143
+ }
144
+ return {
145
+ field,
146
+ message: getMessage(`${field} must be a number`),
147
+ };
148
+ }
149
+ break;
150
+ case 'boolean':
151
+ if (value !== undefined && typeof value !== 'boolean') {
152
+ // Accept string booleans
153
+ if (typeof value === 'string' && (value === 'true' || value === 'false')) {
154
+ return null;
155
+ }
156
+ return {
157
+ field,
158
+ message: getMessage(`${field} must be a boolean`),
159
+ };
160
+ }
161
+ break;
162
+ case 'email':
163
+ if (value !== undefined && value !== '') {
164
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
165
+ if (!emailRegex.test(String(value))) {
166
+ return {
167
+ field,
168
+ message: getMessage(`${field} must be a valid email`),
169
+ };
170
+ }
171
+ }
172
+ break;
173
+ case 'min':
174
+ if (value !== undefined && value !== '') {
175
+ const numValue = typeof value === 'string' ? Number(value) : value;
176
+ if (typeof numValue === 'number' && !isNaN(numValue) && numValue < rule.value) {
177
+ return {
178
+ field,
179
+ message: getMessage(`${field} must be at least ${rule.value}`),
180
+ };
181
+ }
182
+ }
183
+ break;
184
+ case 'max':
185
+ if (value !== undefined && value !== '') {
186
+ const numValue = typeof value === 'string' ? Number(value) : value;
187
+ if (typeof numValue === 'number' && !isNaN(numValue) && numValue > rule.value) {
188
+ return {
189
+ field,
190
+ message: getMessage(`${field} must be at most ${rule.value}`),
191
+ };
192
+ }
193
+ }
194
+ break;
195
+ case 'minLength':
196
+ if (value !== undefined && value !== '') {
197
+ const strValue = String(value);
198
+ if (strValue.length < rule.value) {
199
+ return {
200
+ field,
201
+ message: getMessage(`${field} must be at least ${rule.value} characters`),
202
+ };
203
+ }
204
+ }
205
+ break;
206
+ case 'maxLength':
207
+ if (value !== undefined && value !== '') {
208
+ const strValue = String(value);
209
+ if (strValue.length > rule.value) {
210
+ return {
211
+ field,
212
+ message: getMessage(`${field} must be at most ${rule.value} characters`),
213
+ };
214
+ }
215
+ }
216
+ break;
217
+ case 'pattern':
218
+ if (value !== undefined && value !== '') {
219
+ if (!rule.value.test(String(value))) {
220
+ return {
221
+ field,
222
+ message: getMessage(`${field} format is invalid`),
223
+ };
224
+ }
225
+ }
226
+ break;
227
+ case 'enum':
228
+ if (value !== undefined && value !== '') {
229
+ if (!rule.values.includes(value)) {
230
+ return {
231
+ field,
232
+ message: getMessage(`${field} must be one of: ${rule.values.join(', ')}`),
233
+ };
234
+ }
235
+ }
236
+ break;
237
+ case 'array':
238
+ if (value !== undefined && !Array.isArray(value)) {
239
+ return {
240
+ field,
241
+ message: getMessage(`${field} must be an array`),
242
+ };
243
+ }
244
+ break;
245
+ case 'object':
246
+ if (value !== undefined && (typeof value !== 'object' || Array.isArray(value) || value === null)) {
247
+ return {
248
+ field,
249
+ message: getMessage(`${field} must be an object`),
250
+ };
251
+ }
252
+ break;
253
+ case 'custom':
254
+ try {
255
+ const isValid = await rule.validator(value);
256
+ if (!isValid) {
257
+ return {
258
+ field,
259
+ message: getMessage(`${field} is invalid`),
260
+ };
261
+ }
262
+ }
263
+ catch (error) {
264
+ return {
265
+ field,
266
+ message: getMessage(`${field} validation failed`),
267
+ };
268
+ }
269
+ break;
270
+ }
271
+ return null;
272
+ }
273
+ /**
274
+ * Validate request body
275
+ */
276
+ function validateBody(schema, options = {}) {
277
+ return validate(schema, { ...options, source: 'body' });
278
+ }
279
+ /**
280
+ * Validate request query parameters
281
+ */
282
+ function validateQuery(schema, options = {}) {
283
+ return validate(schema, { ...options, source: 'query' });
284
+ }
285
+ /**
286
+ * Validate request route parameters
287
+ */
288
+ function validateParams(schema, options = {}) {
289
+ return validate(schema, { ...options, source: 'params' });
290
+ }
@@ -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.1.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",
@@ -23,19 +23,19 @@
23
23
  "dependencies": {
24
24
  "jsonwebtoken": "^9.0.2",
25
25
  "passport": "^0.7.0",
26
- "passport-facebook": "^3.0.0",
27
26
  "passport-google-oauth20": "^2.0.0",
27
+ "passport-facebook": "^3.0.0",
28
28
  "validator": "^13.11.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/express": "^4.17.21",
32
32
  "@types/jsonwebtoken": "^9.0.5",
33
- "@types/node": "^20.10.5",
34
33
  "@types/passport": "^1.0.16",
35
- "@types/passport-facebook": "^3.0.3",
36
34
  "@types/passport-google-oauth20": "^2.0.14",
35
+ "@types/passport-facebook": "^3.0.3",
37
36
  "@types/validator": "^13.11.7",
38
- "typescript": "^5.9.3"
37
+ "@types/node": "^20.10.5",
38
+ "typescript": "^5.3.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "express": "^4.18.0"