@igxjs/node-components 1.0.5 → 1.0.7
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 +236 -21
- package/components/http-handlers.js +4 -1
- package/components/jwt.js +104 -0
- package/index.d.ts +106 -0
- package/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -206,7 +206,173 @@ if (connected) {
|
|
|
206
206
|
|
|
207
207
|
---
|
|
208
208
|
|
|
209
|
-
### 4.
|
|
209
|
+
### 4. JWT Manager
|
|
210
|
+
|
|
211
|
+
Provides JWT (JSON Web Token) encryption and decryption utilities using the `jose` library with JWE (JSON Web Encryption) for secure token management.
|
|
212
|
+
|
|
213
|
+
#### Configuration Options
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
// Example configuration object
|
|
217
|
+
const jwtOptions = {
|
|
218
|
+
algorithm: 'dir', // JWE algorithm (default: 'dir')
|
|
219
|
+
encryption: 'A256GCM', // JWE encryption method (default: 'A256GCM')
|
|
220
|
+
expirationTime: '10m', // Token expiration (default: '10m')
|
|
221
|
+
clockTolerance: 30, // Clock tolerance in seconds (default: 30)
|
|
222
|
+
secretHashAlgorithm: 'SHA-256', // Hash algorithm for secret derivation (default: 'SHA-256')
|
|
223
|
+
issuer: 'your-app', // Optional JWT issuer claim
|
|
224
|
+
audience: 'your-users', // Optional JWT audience claim
|
|
225
|
+
subject: 'user-session' // Optional JWT subject claim
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Usage Example
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
import { JwtManager } from '@igxjs/node-components';
|
|
233
|
+
|
|
234
|
+
// Create instance with default configuration
|
|
235
|
+
const jwtManager = new JwtManager({
|
|
236
|
+
expirationTime: '1h',
|
|
237
|
+
issuer: 'my-app',
|
|
238
|
+
audience: 'my-users'
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Encrypt user data
|
|
242
|
+
const userData = {
|
|
243
|
+
userId: '12345',
|
|
244
|
+
email: 'user@example.com',
|
|
245
|
+
roles: ['admin', 'user']
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const secret = 'your-secret-key';
|
|
249
|
+
const token = await jwtManager.encrypt(userData, secret);
|
|
250
|
+
|
|
251
|
+
console.log('Encrypted Token:', token);
|
|
252
|
+
|
|
253
|
+
// Decrypt token
|
|
254
|
+
try {
|
|
255
|
+
const result = await jwtManager.decrypt(token, secret);
|
|
256
|
+
console.log('Decrypted Payload:', result.payload);
|
|
257
|
+
console.log('Protected Header:', result.protectedHeader);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error('Token validation failed:', error);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Override options per-call
|
|
263
|
+
const shortLivedToken = await jwtManager.encrypt(
|
|
264
|
+
userData,
|
|
265
|
+
secret,
|
|
266
|
+
{ expirationTime: '5m' } // Override default expiration
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Decrypt with custom validation
|
|
270
|
+
const validatedResult = await jwtManager.decrypt(
|
|
271
|
+
token,
|
|
272
|
+
secret,
|
|
273
|
+
{
|
|
274
|
+
clockTolerance: 60, // Allow more clock skew
|
|
275
|
+
issuer: 'my-app', // Validate issuer
|
|
276
|
+
audience: 'my-users' // Validate audience
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Advanced Usage with Express
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
import express from 'express';
|
|
285
|
+
import { JwtManager } from '@igxjs/node-components';
|
|
286
|
+
|
|
287
|
+
const app = express();
|
|
288
|
+
const jwt = new JwtManager({ expirationTime: '24h' });
|
|
289
|
+
const SECRET = process.env.JWT_SECRET;
|
|
290
|
+
|
|
291
|
+
// Login endpoint - create token
|
|
292
|
+
app.post('/api/login', async (req, res, next) => {
|
|
293
|
+
try {
|
|
294
|
+
const { username, password } = req.body;
|
|
295
|
+
|
|
296
|
+
// Verify credentials (your logic here)
|
|
297
|
+
const user = await verifyCredentials(username, password);
|
|
298
|
+
|
|
299
|
+
// Create JWT token
|
|
300
|
+
const token = await jwt.encrypt({
|
|
301
|
+
sub: user.id,
|
|
302
|
+
email: user.email,
|
|
303
|
+
roles: user.roles
|
|
304
|
+
}, SECRET);
|
|
305
|
+
|
|
306
|
+
res.json({ token });
|
|
307
|
+
} catch (error) {
|
|
308
|
+
next(error);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Protected endpoint - verify token
|
|
313
|
+
app.get('/api/profile', async (req, res, next) => {
|
|
314
|
+
try {
|
|
315
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
316
|
+
|
|
317
|
+
if (!token) {
|
|
318
|
+
return res.status(401).json({ error: 'No token provided' });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Decrypt and validate token
|
|
322
|
+
const { payload } = await jwt.decrypt(token, SECRET);
|
|
323
|
+
|
|
324
|
+
res.json({ user: payload });
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// Token expired or invalid
|
|
327
|
+
res.status(401).json({ error: 'Invalid or expired token' });
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### API Methods
|
|
333
|
+
|
|
334
|
+
**Constructor:**
|
|
335
|
+
- **`new JwtManager(options?)`** - Create a new JwtManager instance
|
|
336
|
+
- `options` (JwtManagerOptions, optional) - Configuration options
|
|
337
|
+
- All options are optional and have sensible defaults
|
|
338
|
+
|
|
339
|
+
**Methods:**
|
|
340
|
+
- **`encrypt(data, input, options?)`** - Generate encrypted JWT token
|
|
341
|
+
- `data` (JWTPayload) - User data payload to encrypt
|
|
342
|
+
- `input` (string) - Secret key or password for encryption
|
|
343
|
+
- `options` (JwtEncryptOptions, optional) - Per-call configuration overrides
|
|
344
|
+
- Returns: `Promise<string>` - Encrypted JWT token
|
|
345
|
+
|
|
346
|
+
- **`decrypt(token, input, options?)`** - Decrypt and validate JWT token
|
|
347
|
+
- `token` (string) - JWT token to decrypt
|
|
348
|
+
- `input` (string) - Secret key or password for decryption
|
|
349
|
+
- `options` (JwtDecryptOptions, optional) - Per-call configuration overrides
|
|
350
|
+
- Returns: `Promise<JWTDecryptResult>` - Object containing `payload` and `protectedHeader`
|
|
351
|
+
|
|
352
|
+
#### Configuration Details
|
|
353
|
+
|
|
354
|
+
**Algorithms:**
|
|
355
|
+
- `'dir'` (default) - Direct encryption with shared symmetric key
|
|
356
|
+
- `'A128KW'`, `'A192KW'`, `'A256KW'` - AES Key Wrap algorithms
|
|
357
|
+
|
|
358
|
+
**Encryption Methods:**
|
|
359
|
+
- `'A256GCM'` (default) - AES-GCM with 256-bit key
|
|
360
|
+
- `'A128GCM'`, `'A192GCM'` - AES-GCM with 128/192-bit keys
|
|
361
|
+
|
|
362
|
+
**Expiration Time Format:**
|
|
363
|
+
- `'10m'` - 10 minutes
|
|
364
|
+
- `'1h'` - 1 hour
|
|
365
|
+
- `'7d'` - 7 days
|
|
366
|
+
- `'30s'` - 30 seconds
|
|
367
|
+
|
|
368
|
+
**JWT Claims:**
|
|
369
|
+
- `issuer` (iss) - Token issuer identification
|
|
370
|
+
- `audience` (aud) - Intended token recipient
|
|
371
|
+
- `subject` (sub) - Token subject (usually user ID)
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
### 5. HTTP Handlers
|
|
210
376
|
|
|
211
377
|
Custom error handling utilities with standardized HTTP status codes and error responses.
|
|
212
378
|
|
|
@@ -215,7 +381,9 @@ Custom error handling utilities with standardized HTTP status codes and error re
|
|
|
215
381
|
```javascript
|
|
216
382
|
import {
|
|
217
383
|
CustomError,
|
|
218
|
-
|
|
384
|
+
httpError,
|
|
385
|
+
httpErrorHandler,
|
|
386
|
+
httpNotFoundHandler,
|
|
219
387
|
httpCodes,
|
|
220
388
|
httpMessages,
|
|
221
389
|
httpHelper
|
|
@@ -227,9 +395,12 @@ import {
|
|
|
227
395
|
**Creating Custom Errors:**
|
|
228
396
|
|
|
229
397
|
```javascript
|
|
230
|
-
//
|
|
398
|
+
// Using CustomError class
|
|
231
399
|
throw new CustomError(httpCodes.BAD_REQUEST, 'Invalid input data');
|
|
232
400
|
|
|
401
|
+
// Using httpError helper function (alias for new CustomError)
|
|
402
|
+
throw httpError(httpCodes.BAD_REQUEST, 'Invalid input data');
|
|
403
|
+
|
|
233
404
|
// With error details and additional data
|
|
234
405
|
throw new CustomError(
|
|
235
406
|
httpCodes.UNAUTHORIZED,
|
|
@@ -237,6 +408,14 @@ throw new CustomError(
|
|
|
237
408
|
{ originalError: err },
|
|
238
409
|
{ attemptedEmail: email }
|
|
239
410
|
);
|
|
411
|
+
|
|
412
|
+
// Using httpError with additional data
|
|
413
|
+
throw httpError(
|
|
414
|
+
httpCodes.UNAUTHORIZED,
|
|
415
|
+
'Authentication failed',
|
|
416
|
+
{ originalError: err },
|
|
417
|
+
{ attemptedEmail: email }
|
|
418
|
+
);
|
|
240
419
|
```
|
|
241
420
|
|
|
242
421
|
**Handling Axios/HTTP Errors:**
|
|
@@ -265,7 +444,7 @@ app.get('/api/data', async (req, res, next) => {
|
|
|
265
444
|
|
|
266
445
|
```javascript
|
|
267
446
|
import express from 'express';
|
|
268
|
-
import { httpErrorHandler } from '@igxjs/node-components';
|
|
447
|
+
import { httpErrorHandler, httpNotFoundHandler } from '@igxjs/node-components';
|
|
269
448
|
|
|
270
449
|
const app = express();
|
|
271
450
|
|
|
@@ -278,29 +457,31 @@ app.get('/api/data', async (req, res, next) => {
|
|
|
278
457
|
}
|
|
279
458
|
});
|
|
280
459
|
|
|
460
|
+
// Add 404 handler before error handler
|
|
461
|
+
app.use(httpNotFoundHandler);
|
|
462
|
+
|
|
281
463
|
// Add error handler as the last middleware
|
|
282
464
|
app.use(httpErrorHandler);
|
|
283
465
|
```
|
|
284
466
|
|
|
285
467
|
#### HTTP Codes & Messages
|
|
286
468
|
|
|
287
|
-
|
|
288
|
-
httpCodes.OK
|
|
289
|
-
httpCodes.CREATED
|
|
290
|
-
httpCodes.NO_CONTENT
|
|
291
|
-
httpCodes.BAD_REQUEST
|
|
292
|
-
httpCodes.UNAUTHORIZED
|
|
293
|
-
httpCodes.FORBIDDEN
|
|
294
|
-
httpCodes.NOT_FOUND
|
|
295
|
-
httpCodes.NOT_ACCEPTABLE
|
|
296
|
-
httpCodes.CONFLICT
|
|
297
|
-
httpCodes.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
```
|
|
469
|
+
**Available Status Codes:**
|
|
470
|
+
- `httpCodes.OK` (200)
|
|
471
|
+
- `httpCodes.CREATED` (201)
|
|
472
|
+
- `httpCodes.NO_CONTENT` (204)
|
|
473
|
+
- `httpCodes.BAD_REQUEST` (400)
|
|
474
|
+
- `httpCodes.UNAUTHORIZED` (401)
|
|
475
|
+
- `httpCodes.FORBIDDEN` (403)
|
|
476
|
+
- `httpCodes.NOT_FOUND` (404)
|
|
477
|
+
- `httpCodes.NOT_ACCEPTABLE` (406)
|
|
478
|
+
- `httpCodes.CONFLICT` (409)
|
|
479
|
+
- `httpCodes.LOCKED` (423)
|
|
480
|
+
- `httpCodes.SYSTEM_FAILURE` (500)
|
|
481
|
+
- `httpCodes.NOT_IMPLEMENTED` (501)
|
|
482
|
+
|
|
483
|
+
**Available Status Messages:**
|
|
484
|
+
- Corresponding `httpMessages` constants are available for each status code (e.g., `httpMessages.OK`, `httpMessages.BAD_REQUEST`, etc.)
|
|
304
485
|
|
|
305
486
|
#### CustomError API
|
|
306
487
|
|
|
@@ -319,6 +500,35 @@ httpMessages.BAD_REQUEST // 'Bad Request'
|
|
|
319
500
|
- **`error`** - Original error object (if provided)
|
|
320
501
|
- **`data`** - Additional error data (if provided)
|
|
321
502
|
|
|
503
|
+
#### httpError API
|
|
504
|
+
|
|
505
|
+
**Function:**
|
|
506
|
+
- **`httpError(code, message, error?, data?)`** - Convenience function to create CustomError instances
|
|
507
|
+
- `code` (number) - HTTP status code
|
|
508
|
+
- `message` (string) - Error message
|
|
509
|
+
- `error` (object, optional) - Original error object
|
|
510
|
+
- `data` (object, optional) - Additional error data
|
|
511
|
+
- Returns: `CustomError` instance
|
|
512
|
+
- **Note:** This is an alias for `new CustomError()` - use whichever syntax you prefer
|
|
513
|
+
|
|
514
|
+
#### httpErrorHandler API
|
|
515
|
+
|
|
516
|
+
**Middleware Function:**
|
|
517
|
+
- **`httpErrorHandler(err, req, res, next)`** - Express error handling middleware
|
|
518
|
+
- Handles `CustomError` instances and other errors
|
|
519
|
+
- Sets appropriate HTTP status codes and response format
|
|
520
|
+
- Adds CORS headers automatically
|
|
521
|
+
- Logs error details to console
|
|
522
|
+
- **Usage:** Add as the last middleware in your Express app
|
|
523
|
+
|
|
524
|
+
#### httpNotFoundHandler API
|
|
525
|
+
|
|
526
|
+
**Middleware Function:**
|
|
527
|
+
- **`httpNotFoundHandler(req, res, next)`** - Express 404 handler middleware
|
|
528
|
+
- Catches all unmatched routes and returns 404 error
|
|
529
|
+
- Creates a `CustomError` with NOT_FOUND status
|
|
530
|
+
- **Usage:** Add before the error handler middleware
|
|
531
|
+
|
|
322
532
|
#### httpHelper API
|
|
323
533
|
|
|
324
534
|
The `httpHelper` object provides utility methods for error handling:
|
|
@@ -344,6 +554,7 @@ The `httpHelper` object provides utility methods for error handling:
|
|
|
344
554
|
## Features
|
|
345
555
|
|
|
346
556
|
- ✅ **Session Management** - Full SSO integration with Redis or memory storage
|
|
557
|
+
- ✅ **JWT Manager** - Secure JWT encryption/decryption with JWE using the jose library
|
|
347
558
|
- ✅ **Flexible Routing** - Easy mounting of routers with context paths and middleware
|
|
348
559
|
- ✅ **Redis Integration** - Robust Redis connection management with TLS support
|
|
349
560
|
- ✅ **Error Handling** - Standardized error responses and HTTP status codes
|
|
@@ -367,6 +578,10 @@ import type {
|
|
|
367
578
|
SessionManager,
|
|
368
579
|
SessionUser,
|
|
369
580
|
SessionUserAttributes,
|
|
581
|
+
JwtManager,
|
|
582
|
+
JwtManagerOptions,
|
|
583
|
+
JwtEncryptOptions,
|
|
584
|
+
JwtDecryptOptions,
|
|
370
585
|
FlexRouter,
|
|
371
586
|
RedisManager,
|
|
372
587
|
CustomError
|
|
@@ -10,19 +10,22 @@ export const httpMessages = {
|
|
|
10
10
|
NOT_FOUND: 'Not Found',
|
|
11
11
|
NOT_ACCEPTABLE: 'Not Acceptable',
|
|
12
12
|
CONFLICT: 'Conflict',
|
|
13
|
+
LOCKED: 'Locked',
|
|
13
14
|
SYSTEM_FAILURE: 'System Error',
|
|
15
|
+
NOT_IMPLEMENTED: 'Not Implemented',
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export const httpCodes = {
|
|
17
19
|
OK: 200,
|
|
18
20
|
CREATED: 201,
|
|
19
|
-
NO_CONTENT:
|
|
21
|
+
NO_CONTENT: 204,
|
|
20
22
|
BAD_REQUEST: 400,
|
|
21
23
|
UNAUTHORIZED: 401,
|
|
22
24
|
FORBIDDEN: 403,
|
|
23
25
|
NOT_FOUND: 404,
|
|
24
26
|
NOT_ACCEPTABLE: 406,
|
|
25
27
|
CONFLICT: 409,
|
|
28
|
+
LOCKED: 423,
|
|
26
29
|
SYSTEM_FAILURE: 500,
|
|
27
30
|
NOT_IMPLEMENTED: 501,
|
|
28
31
|
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jwtDecrypt, EncryptJWT } from 'jose';
|
|
2
|
+
|
|
3
|
+
export class JwtManager {
|
|
4
|
+
/**
|
|
5
|
+
* Create a new JwtManager instance with configurable defaults
|
|
6
|
+
* @param {Object} options Configuration options
|
|
7
|
+
* @param {string} [options.algorithm='dir'] JWE algorithm (e.g., 'dir', 'A128KW', 'A192KW', 'A256KW')
|
|
8
|
+
* @param {string} [options.encryption='A256GCM'] JWE encryption method (e.g., 'A256GCM', 'A128GCM', 'A192GCM')
|
|
9
|
+
* @param {string} [options.expirationTime='10m'] Token expiration time (e.g., '10m', '1h', '7d')
|
|
10
|
+
* @param {number} [options.clockTolerance=30] Clock tolerance in seconds for token validation
|
|
11
|
+
* @param {string} [options.secretHashAlgorithm='SHA-256'] Hash algorithm for secret derivation
|
|
12
|
+
* @param {string} [options.issuer] Optional JWT issuer claim
|
|
13
|
+
* @param {string} [options.audience] Optional JWT audience claim
|
|
14
|
+
* @param {string} [options.subject] Optional JWT subject claim
|
|
15
|
+
*/
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.algorithm = options.algorithm || 'dir';
|
|
18
|
+
this.encryption = options.encryption || 'A256GCM';
|
|
19
|
+
this.expirationTime = options.expirationTime || '10m';
|
|
20
|
+
this.clockTolerance = options.clockTolerance ?? 30;
|
|
21
|
+
this.secretHashAlgorithm = options.secretHashAlgorithm || 'SHA-256';
|
|
22
|
+
this.issuer = options.issuer;
|
|
23
|
+
this.audience = options.audience;
|
|
24
|
+
this.subject = options.subject;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate JWT token for user session
|
|
29
|
+
* @param {import('jose').JWTPayload} data User data payload
|
|
30
|
+
* @param {string} input Secret key or password for encryption
|
|
31
|
+
* @param {Object} [options] Per-call configuration overrides
|
|
32
|
+
* @param {string} [options.algorithm] Override default algorithm
|
|
33
|
+
* @param {string} [options.encryption] Override default encryption method
|
|
34
|
+
* @param {string} [options.expirationTime] Override default expiration time
|
|
35
|
+
* @param {string} [options.secretHashAlgorithm] Override default hash algorithm
|
|
36
|
+
* @param {string} [options.issuer] Override default issuer claim
|
|
37
|
+
* @param {string} [options.audience] Override default audience claim
|
|
38
|
+
* @param {string} [options.subject] Override default subject claim
|
|
39
|
+
* @returns {Promise<string>} Returns encrypted JWT token
|
|
40
|
+
*/
|
|
41
|
+
async encrypt(data, input, options = {}) {
|
|
42
|
+
const algorithm = options.algorithm || this.algorithm;
|
|
43
|
+
const encryption = options.encryption || this.encryption;
|
|
44
|
+
const expirationTime = options.expirationTime || this.expirationTime;
|
|
45
|
+
const secretHashAlgorithm = options.secretHashAlgorithm || this.secretHashAlgorithm;
|
|
46
|
+
const issuer = options.issuer || this.issuer;
|
|
47
|
+
const audience = options.audience || this.audience;
|
|
48
|
+
const subject = options.subject || this.subject;
|
|
49
|
+
|
|
50
|
+
const secret = await crypto.subtle.digest(
|
|
51
|
+
secretHashAlgorithm,
|
|
52
|
+
new TextEncoder().encode(input)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const jwt = new EncryptJWT(data)
|
|
56
|
+
.setProtectedHeader({
|
|
57
|
+
alg: algorithm,
|
|
58
|
+
enc: encryption
|
|
59
|
+
})
|
|
60
|
+
.setIssuedAt()
|
|
61
|
+
.setExpirationTime(expirationTime);
|
|
62
|
+
|
|
63
|
+
// Add optional claims if provided
|
|
64
|
+
if (issuer) jwt.setIssuer(issuer);
|
|
65
|
+
if (audience) jwt.setAudience(audience);
|
|
66
|
+
if (subject) jwt.setSubject(subject);
|
|
67
|
+
|
|
68
|
+
return await jwt.encrypt(new Uint8Array(secret));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Decrypt JWT token for user session
|
|
73
|
+
* @param {string} token JWT token to decrypt
|
|
74
|
+
* @param {string} input Secret key or password for decryption
|
|
75
|
+
* @param {Object} [options] Per-call configuration overrides
|
|
76
|
+
* @param {number} [options.clockTolerance] Override default clock tolerance
|
|
77
|
+
* @param {string} [options.secretHashAlgorithm] Override default hash algorithm
|
|
78
|
+
* @param {string} [options.issuer] Expected issuer claim for validation
|
|
79
|
+
* @param {string} [options.audience] Expected audience claim for validation
|
|
80
|
+
* @param {string} [options.subject] Expected subject claim for validation
|
|
81
|
+
* @returns {Promise<import('jose').JWTDecryptResult<import('jose').EncryptJWT>>} Returns decrypted JWT token
|
|
82
|
+
*/
|
|
83
|
+
async decrypt(token, input, options = {}) {
|
|
84
|
+
const clockTolerance = options.clockTolerance ?? this.clockTolerance;
|
|
85
|
+
const secretHashAlgorithm = options.secretHashAlgorithm || this.secretHashAlgorithm;
|
|
86
|
+
const issuer = options.issuer || this.issuer;
|
|
87
|
+
const audience = options.audience || this.audience;
|
|
88
|
+
const subject = options.subject || this.subject;
|
|
89
|
+
|
|
90
|
+
const secret = await crypto.subtle.digest(
|
|
91
|
+
secretHashAlgorithm,
|
|
92
|
+
new TextEncoder().encode(input)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const decryptOptions = { clockTolerance };
|
|
96
|
+
|
|
97
|
+
// Add optional claim validations if provided
|
|
98
|
+
if (issuer) decryptOptions.issuer = issuer;
|
|
99
|
+
if (audience) decryptOptions.audience = audience;
|
|
100
|
+
if (subject) decryptOptions.subject = subject;
|
|
101
|
+
|
|
102
|
+
return await jwtDecrypt(token, new Uint8Array(secret), decryptOptions);
|
|
103
|
+
}
|
|
104
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'express-session';
|
|
2
2
|
|
|
3
3
|
import { AxiosError } from 'axios';
|
|
4
|
+
import { JWTPayload, JWTDecryptResult } from 'jose';
|
|
4
5
|
import { RedisClientType } from '@redis/client';
|
|
5
6
|
import { Application, RequestHandler, Request, Response, NextFunction, Router } from 'express';
|
|
6
7
|
|
|
@@ -197,6 +198,92 @@ export class RedisManager {
|
|
|
197
198
|
disConnect(): Promise<void>;
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
// JWT Manager Configuration
|
|
202
|
+
export interface JwtManagerOptions {
|
|
203
|
+
/** @type {string} JWE algorithm (default: 'dir') */
|
|
204
|
+
algorithm?: string;
|
|
205
|
+
/** @type {string} JWE encryption method (default: 'A256GCM') */
|
|
206
|
+
encryption?: string;
|
|
207
|
+
/** @type {string} Token expiration time (default: '10m') */
|
|
208
|
+
expirationTime?: string;
|
|
209
|
+
/** @type {number} Clock tolerance in seconds for token validation (default: 30) */
|
|
210
|
+
clockTolerance?: number;
|
|
211
|
+
/** @type {string} Hash algorithm for secret derivation (default: 'SHA-256') */
|
|
212
|
+
secretHashAlgorithm?: string;
|
|
213
|
+
/** @type {string} Optional JWT issuer claim */
|
|
214
|
+
issuer?: string;
|
|
215
|
+
/** @type {string} Optional JWT audience claim */
|
|
216
|
+
audience?: string;
|
|
217
|
+
/** @type {string} Optional JWT subject claim */
|
|
218
|
+
subject?: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface JwtEncryptOptions {
|
|
222
|
+
/** @type {string} Override default algorithm */
|
|
223
|
+
algorithm?: string;
|
|
224
|
+
/** @type {string} Override default encryption method */
|
|
225
|
+
encryption?: string;
|
|
226
|
+
/** @type {string} Override default expiration time */
|
|
227
|
+
expirationTime?: string;
|
|
228
|
+
/** @type {string} Override default hash algorithm */
|
|
229
|
+
secretHashAlgorithm?: string;
|
|
230
|
+
/** @type {string} Override default issuer claim */
|
|
231
|
+
issuer?: string;
|
|
232
|
+
/** @type {string} Override default audience claim */
|
|
233
|
+
audience?: string;
|
|
234
|
+
/** @type {string} Override default subject claim */
|
|
235
|
+
subject?: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface JwtDecryptOptions {
|
|
239
|
+
/** @type {number} Override default clock tolerance */
|
|
240
|
+
clockTolerance?: number;
|
|
241
|
+
/** @type {string} Override default hash algorithm */
|
|
242
|
+
secretHashAlgorithm?: string;
|
|
243
|
+
/** @type {string} Expected issuer claim for validation */
|
|
244
|
+
issuer?: string;
|
|
245
|
+
/** @type {string} Expected audience claim for validation */
|
|
246
|
+
audience?: string;
|
|
247
|
+
/** @type {string} Expected subject claim for validation */
|
|
248
|
+
subject?: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// JwtManager class for JWT encryption and decryption
|
|
252
|
+
export class JwtManager {
|
|
253
|
+
algorithm: string;
|
|
254
|
+
encryption: string;
|
|
255
|
+
expirationTime: string;
|
|
256
|
+
clockTolerance: number;
|
|
257
|
+
secretHashAlgorithm: string;
|
|
258
|
+
issuer?: string;
|
|
259
|
+
audience?: string;
|
|
260
|
+
subject?: string;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create a new JwtManager instance with configurable defaults
|
|
264
|
+
* @param options Configuration options
|
|
265
|
+
*/
|
|
266
|
+
constructor(options?: JwtManagerOptions);
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Generate JWT token for user session
|
|
270
|
+
* @param data User data payload
|
|
271
|
+
* @param input Secret key or password for encryption
|
|
272
|
+
* @param options Per-call configuration overrides
|
|
273
|
+
* @returns Returns encrypted JWT token
|
|
274
|
+
*/
|
|
275
|
+
encrypt( JWTPayload, input: string, options?: JwtEncryptOptions): Promise<string>;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Decrypt JWT token for user session
|
|
279
|
+
* @param token JWT token to decrypt
|
|
280
|
+
* @param input Secret key or password for decryption
|
|
281
|
+
* @param options Per-call configuration overrides
|
|
282
|
+
* @returns Returns decrypted JWT token
|
|
283
|
+
*/
|
|
284
|
+
decrypt(token: string, input: string, options?: JwtDecryptOptions): Promise<JWTDecryptResult>;
|
|
285
|
+
}
|
|
286
|
+
|
|
200
287
|
// HTTP status code keys (exposed for type safety)
|
|
201
288
|
export const httpCodes: {
|
|
202
289
|
OK: number;
|
|
@@ -208,7 +295,9 @@ export const httpCodes: {
|
|
|
208
295
|
NOT_FOUND: number;
|
|
209
296
|
NOT_ACCEPTABLE: number;
|
|
210
297
|
CONFLICT: number;
|
|
298
|
+
LOCKED: number;
|
|
211
299
|
SYSTEM_FAILURE: number;
|
|
300
|
+
NOT_IMPLEMENTED: number;
|
|
212
301
|
};
|
|
213
302
|
|
|
214
303
|
// HTTP message keys (exposed for type safety)
|
|
@@ -222,7 +311,9 @@ export const httpMessages: {
|
|
|
222
311
|
NOT_FOUND: string;
|
|
223
312
|
NOT_ACCEPTABLE: string;
|
|
224
313
|
CONFLICT: string;
|
|
314
|
+
LOCKED: string;
|
|
225
315
|
SYSTEM_FAILURE: string;
|
|
316
|
+
NOT_IMPLEMENTED: string;
|
|
226
317
|
};
|
|
227
318
|
|
|
228
319
|
/**
|
|
@@ -253,6 +344,21 @@ export const httpHelper: {
|
|
|
253
344
|
handleAxiosError(error: Error | AxiosError, defaultMessage?: string): CustomError;
|
|
254
345
|
};
|
|
255
346
|
|
|
347
|
+
/**
|
|
348
|
+
* HTTP Error - alias of `new CustomError()`
|
|
349
|
+
* @param code Error code
|
|
350
|
+
* @param message Error message
|
|
351
|
+
* @param error Original Error instance
|
|
352
|
+
* @param data Error data
|
|
353
|
+
* @returns Returns a new instance of CustomError
|
|
354
|
+
*/
|
|
355
|
+
export function httpError(
|
|
356
|
+
code: number,
|
|
357
|
+
message: string,
|
|
358
|
+
error?: object,
|
|
359
|
+
data?: object
|
|
360
|
+
): CustomError;
|
|
361
|
+
|
|
256
362
|
/**
|
|
257
363
|
* Custom error handler middleware
|
|
258
364
|
* @param err Error object
|
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SessionConfig, SessionManager } from './components/session.js';
|
|
2
|
-
export { httpCodes, httpMessages, httpErrorHandler, CustomError, httpHelper } from './components/http-handlers.js';
|
|
2
|
+
export { httpCodes, httpMessages, httpErrorHandler, httpNotFoundHandler, CustomError, httpHelper, httpError } from './components/http-handlers.js';
|
|
3
3
|
export { RedisManager } from './components/redis.js';
|
|
4
4
|
export { FlexRouter } from './components/router.js';
|
|
5
|
+
export { JwtManager } from './components/jwt.js';
|