@igxjs/node-components 1.0.12 → 1.0.13
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/components/jwt.js +11 -8
- package/components/redis.js +20 -1
- package/components/router.js +13 -1
- package/components/session.js +85 -21
- package/index.d.ts +310 -32
- package/package.json +27 -3
package/components/jwt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
1
|
+
import { webcrypto as crypto } from 'node:crypto';
|
|
2
2
|
|
|
3
3
|
import { jwtDecrypt, EncryptJWT } from 'jose';
|
|
4
4
|
|
|
@@ -37,7 +37,7 @@ export class JwtManager {
|
|
|
37
37
|
* @typedef {Object} JwtManagerOptions JwtManager configuration options
|
|
38
38
|
* @property {string} [JWT_ALGORITHM='dir'] JWE algorithm (default: 'dir')
|
|
39
39
|
* @property {string} [JWT_ENCRYPTION='A256GCM'] Encryption method (default: 'A256GCM')
|
|
40
|
-
* @property {number} [
|
|
40
|
+
* @property {number|string} [JWT_EXPIRATION_TIME=64800] Token expiration time - number in seconds (e.g., 64800) or string with time suffix (e.g., '18h', '7d', '1080m') (default: 64800 = 18 hours)
|
|
41
41
|
* @property {string} [JWT_SECRET_HASH_ALGORITHM='SHA-256'] Hash algorithm (default: 'SHA-256')
|
|
42
42
|
* @property {string?} [JWT_ISSUER] Optional JWT issuer claim
|
|
43
43
|
* @property {string?} [JWT_AUDIENCE] Optional JWT audience claim
|
|
@@ -48,8 +48,8 @@ export class JwtManager {
|
|
|
48
48
|
constructor(options = {}) {
|
|
49
49
|
this.algorithm = options.JWT_ALGORITHM || 'dir';
|
|
50
50
|
this.encryption = options.JWT_ENCRYPTION || 'A256GCM';
|
|
51
|
-
//
|
|
52
|
-
this.expirationTime =
|
|
51
|
+
// JWT_EXPIRATION_TIME is in seconds
|
|
52
|
+
this.expirationTime = options.JWT_EXPIRATION_TIME || 64800;
|
|
53
53
|
this.secretHashAlgorithm = options.JWT_SECRET_HASH_ALGORITHM || 'SHA-256';
|
|
54
54
|
this.issuer = options.JWT_ISSUER;
|
|
55
55
|
this.audience = options.JWT_AUDIENCE;
|
|
@@ -63,7 +63,7 @@ export class JwtManager {
|
|
|
63
63
|
* @typedef {Object} JwtEncryptOptions Encryption method options
|
|
64
64
|
* @property {string} [algorithm='dir'] JWE algorithm (overrides instance JWT_ALGORITHM)
|
|
65
65
|
* @property {string} [encryption='A256GCM'] Encryption method (overrides instance JWT_ENCRYPTION)
|
|
66
|
-
* @property {number} [expirationTime] Token expiration time in seconds (overrides instance
|
|
66
|
+
* @property {number|string} [expirationTime] Token expiration time - number in seconds or string with time suffix like '1h', '30m', '7d' (overrides instance JWT_EXPIRATION_TIME)
|
|
67
67
|
* @property {string} [secretHashAlgorithm='SHA-256'] Hash algorithm for secret derivation (overrides instance JWT_SECRET_HASH_ALGORITHM)
|
|
68
68
|
* @property {string?} [issuer] Optional JWT issuer claim (overrides instance JWT_ISSUER)
|
|
69
69
|
* @property {string?} [audience] Optional JWT audience claim (overrides instance JWT_AUDIENCE)
|
|
@@ -91,6 +91,9 @@ export class JwtManager {
|
|
|
91
91
|
new TextEncoder().encode(secret)
|
|
92
92
|
);
|
|
93
93
|
|
|
94
|
+
// Convert number to string with 's' suffix, pass strings directly
|
|
95
|
+
const expTime = typeof expirationTime === 'number' ? `${expirationTime}s` : expirationTime;
|
|
96
|
+
|
|
94
97
|
const jwt = new EncryptJWT(data)
|
|
95
98
|
.setProtectedHeader({
|
|
96
99
|
alg: algorithm,
|
|
@@ -98,7 +101,7 @@ export class JwtManager {
|
|
|
98
101
|
typ: 'JWT',
|
|
99
102
|
})
|
|
100
103
|
.setIssuedAt()
|
|
101
|
-
.setExpirationTime(
|
|
104
|
+
.setExpirationTime(expTime); // Accepts: number (as seconds) or string (e.g., '18h', '7d')
|
|
102
105
|
|
|
103
106
|
// Add optional claims if provided
|
|
104
107
|
if (issuer) jwt.setIssuer(issuer);
|
|
@@ -124,7 +127,7 @@ export class JwtManager {
|
|
|
124
127
|
* @param {string} token JWT token to decrypt
|
|
125
128
|
* @param {string} secret Secret key or password for decryption
|
|
126
129
|
* @param {JwtDecryptOptions} [options] Per-call configuration overrides (camelCase naming convention)
|
|
127
|
-
* @returns {Promise<import('jose').JWTDecryptResult
|
|
130
|
+
* @returns {Promise<import('jose').JWTDecryptResult>} Returns decrypted JWT token
|
|
128
131
|
*/
|
|
129
132
|
async decrypt(token, secret, options = {}) {
|
|
130
133
|
const clockTolerance = options.clockTolerance ?? this.clockTolerance;
|
|
@@ -147,4 +150,4 @@ export class JwtManager {
|
|
|
147
150
|
|
|
148
151
|
return await jwtDecrypt(token, new Uint8Array(secretHash), decryptOptions);
|
|
149
152
|
}
|
|
150
|
-
}
|
|
153
|
+
}
|
package/components/redis.js
CHANGED
|
@@ -11,17 +11,36 @@ export class RedisManager {
|
|
|
11
11
|
#client = null;
|
|
12
12
|
/**
|
|
13
13
|
* Connect with Redis
|
|
14
|
+
* @param {string} redisUrl Redis connection URL
|
|
15
|
+
* @param {string} certPath Path to TLS certificate (required when using rediss:// protocol)
|
|
14
16
|
* @returns {Promise<boolean>} Returns `true` if Redis server is connected
|
|
17
|
+
* @throws {TypeError} If redisUrl is not a string
|
|
18
|
+
* @throws {Error} If TLS is enabled but certPath is invalid
|
|
15
19
|
*/
|
|
16
20
|
async connect(redisUrl, certPath) {
|
|
17
21
|
if (redisUrl?.length > 0) {
|
|
18
22
|
try {
|
|
23
|
+
// Validate redisUrl is a string
|
|
24
|
+
if (typeof redisUrl !== 'string') {
|
|
25
|
+
throw new TypeError(`Invalid redisUrl: expected string but received ${typeof redisUrl}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
/** @type {import('@redis/client').RedisClientOptions} */
|
|
20
29
|
const options = {
|
|
21
30
|
url: redisUrl,
|
|
22
31
|
socket: { tls: redisUrl.includes('rediss:'), ca: null },
|
|
23
32
|
};
|
|
24
|
-
|
|
33
|
+
|
|
34
|
+
if (options.socket.tls) {
|
|
35
|
+
// Validate certPath when TLS is enabled
|
|
36
|
+
if (!certPath || typeof certPath !== 'string') {
|
|
37
|
+
throw new Error('TLS certificate path is required when using rediss:// protocol');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(certPath)) {
|
|
41
|
+
throw new Error(`TLS certificate file not found at path: ${certPath}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
const caCert = fs.readFileSync(certPath);
|
|
26
45
|
options.socket.ca = caCert;
|
|
27
46
|
}
|
package/components/router.js
CHANGED
|
@@ -43,11 +43,23 @@ export class FlexRouter {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Mount router
|
|
46
|
+
* Mount router to Express application
|
|
47
47
|
* @param {import('@types/express').Express} app Express app
|
|
48
48
|
* @param {string} basePath Base path
|
|
49
|
+
* @throws {TypeError} If app is not a valid Express instance
|
|
50
|
+
* @throws {TypeError} If basePath is not a string
|
|
49
51
|
*/
|
|
50
52
|
mount(app, basePath) {
|
|
53
|
+
// Validate app is an Express instance (has 'use' method)
|
|
54
|
+
if (!app || typeof app.use !== 'function') {
|
|
55
|
+
throw new TypeError('Invalid Express app: app must be an Express application instance with a "use" method');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validate basePath is a string
|
|
59
|
+
if (typeof basePath !== 'string') {
|
|
60
|
+
throw new TypeError(`Invalid basePath: expected string but received ${typeof basePath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
const path = basePath.concat(this.context);
|
|
52
64
|
app.use(path, this.handlers, this.router);
|
|
53
65
|
}
|
package/components/session.js
CHANGED
|
@@ -34,7 +34,20 @@ export class SessionConfig {
|
|
|
34
34
|
* @default SessionMode.SESSION
|
|
35
35
|
*/
|
|
36
36
|
SESSION_MODE;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* @type {string} Identity Provider microservice endpoint URL
|
|
39
|
+
*
|
|
40
|
+
* This is a fully customized, independent microservice that provides SSO authentication.
|
|
41
|
+
* The endpoint serves multiple applications and provides the following APIs:
|
|
42
|
+
* - GET /auth/providers - List supported identity providers
|
|
43
|
+
* - POST /auth/login/:idp - Generate login URL for specific identity provider
|
|
44
|
+
* - POST /auth/verify - Verify JWT token validity
|
|
45
|
+
* - GET /auth/callback/:idp - Validate authentication and return user data
|
|
46
|
+
* - POST /auth/refresh - Refresh access tokens
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* SSO_ENDPOINT_URL: 'https://idp.example.com/open/api/v1'
|
|
50
|
+
*/
|
|
38
51
|
SSO_ENDPOINT_URL;
|
|
39
52
|
/** @type {string} */
|
|
40
53
|
SSO_CLIENT_ID;
|
|
@@ -128,8 +141,43 @@ export class SessionManager {
|
|
|
128
141
|
/**
|
|
129
142
|
* Create a new session manager
|
|
130
143
|
* @param {SessionConfig} config Session configuration
|
|
144
|
+
* @throws {TypeError} If config is not an object
|
|
145
|
+
* @throws {Error} If required configuration fields are missing
|
|
131
146
|
*/
|
|
132
147
|
constructor(config) {
|
|
148
|
+
// Validate config is an object
|
|
149
|
+
if (!config || typeof config !== 'object') {
|
|
150
|
+
throw new TypeError('SessionManager configuration must be an object');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Validate required fields based on SESSION_MODE
|
|
154
|
+
const mode = config.SESSION_MODE || SessionMode.SESSION;
|
|
155
|
+
|
|
156
|
+
// SESSION_SECRET is always required for both modes
|
|
157
|
+
if (!config.SESSION_SECRET) {
|
|
158
|
+
throw new Error('SESSION_SECRET is required for SessionManager');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Validate SSO configuration if SSO endpoints are used
|
|
162
|
+
if (config.SSO_ENDPOINT_URL) {
|
|
163
|
+
if (!config.SSO_CLIENT_ID) {
|
|
164
|
+
throw new Error('SSO_CLIENT_ID is required when SSO_ENDPOINT_URL is provided');
|
|
165
|
+
}
|
|
166
|
+
if (!config.SSO_CLIENT_SECRET) {
|
|
167
|
+
throw new Error('SSO_CLIENT_SECRET is required when SSO_ENDPOINT_URL is provided');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Validate TOKEN mode specific requirements
|
|
172
|
+
if (mode === SessionMode.TOKEN) {
|
|
173
|
+
if (!config.SSO_SUCCESS_URL) {
|
|
174
|
+
throw new Error('SSO_SUCCESS_URL is required for TOKEN authentication mode');
|
|
175
|
+
}
|
|
176
|
+
if (!config.SSO_FAILURE_URL) {
|
|
177
|
+
throw new Error('SSO_FAILURE_URL is required for TOKEN authentication mode');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
133
181
|
this.#config = {
|
|
134
182
|
// Session Mode
|
|
135
183
|
SESSION_MODE: config.SESSION_MODE || SessionMode.SESSION,
|
|
@@ -238,9 +286,16 @@ export class SessionManager {
|
|
|
238
286
|
* Generate and store JWT token in Redis
|
|
239
287
|
* - JWT payload contains only { email, tid } for minimal size
|
|
240
288
|
* - Full user data is stored in Redis as single source of truth
|
|
241
|
-
* @param {object} user User object
|
|
289
|
+
* @param {object} user User object with email and attributes
|
|
242
290
|
* @returns {Promise<string>} Returns the generated JWT token
|
|
291
|
+
* @throws {Error} If JWT encryption fails
|
|
292
|
+
* @throws {Error} If Redis storage fails
|
|
243
293
|
* @private
|
|
294
|
+
* @example
|
|
295
|
+
* const token = await this.#generateAndStoreToken({
|
|
296
|
+
* email: 'user@example.com',
|
|
297
|
+
* attributes: { /* user data * / }
|
|
298
|
+
* });
|
|
244
299
|
*/
|
|
245
300
|
async #generateAndStoreToken(user) {
|
|
246
301
|
// Generate unique token ID for this device/session
|
|
@@ -266,13 +321,17 @@ export class SessionManager {
|
|
|
266
321
|
}
|
|
267
322
|
|
|
268
323
|
/**
|
|
269
|
-
* Verify token authentication
|
|
270
|
-
* @param {import('@types/express').Request} req Request
|
|
271
|
-
* @param {import('@types/express').Response} res Response
|
|
272
|
-
* @param {import('@types/express').NextFunction} next Next function
|
|
273
|
-
* @param {boolean} isDebugging
|
|
274
|
-
* @param {string} redirectUrl
|
|
324
|
+
* Verify token authentication - extracts and validates JWT from Authorization header
|
|
325
|
+
* @param {import('@types/express').Request} req Request with Authorization header
|
|
326
|
+
* @param {import('@types/express').Response} res Response object
|
|
327
|
+
* @param {import('@types/express').NextFunction} next Next middleware function
|
|
328
|
+
* @param {boolean} isDebugging If true, allows unauthenticated requests
|
|
329
|
+
* @param {string} redirectUrl URL to redirect to on authentication failure
|
|
330
|
+
* @throws {CustomError} If token is missing, invalid, or expired
|
|
275
331
|
* @private
|
|
332
|
+
* @example
|
|
333
|
+
* // Authorization header format: "Bearer {jwt_token}"
|
|
334
|
+
* await this.#verifyToken(req, res, next, false, '/login');
|
|
276
335
|
*/
|
|
277
336
|
async #verifyToken(req, res, next, isDebugging, redirectUrl) {
|
|
278
337
|
try {
|
|
@@ -306,7 +365,6 @@ export class SessionManager {
|
|
|
306
365
|
|
|
307
366
|
// Parse and attach user to request
|
|
308
367
|
req.user = JSON.parse(userData);
|
|
309
|
-
res.locals.user = req.user;
|
|
310
368
|
|
|
311
369
|
// Validate authorization
|
|
312
370
|
const { authorized = isDebugging } = req.user ?? { authorized: isDebugging };
|
|
@@ -337,12 +395,13 @@ export class SessionManager {
|
|
|
337
395
|
}
|
|
338
396
|
|
|
339
397
|
/**
|
|
340
|
-
* Verify session authentication
|
|
341
|
-
* @param {import('@types/express').Request} req Request
|
|
342
|
-
* @param {import('@types/express').Response} res Response
|
|
343
|
-
* @param {import('@types/express').NextFunction} next Next function
|
|
344
|
-
* @param {boolean} isDebugging
|
|
345
|
-
* @param {string} redirectUrl
|
|
398
|
+
* Verify session authentication - checks if user is authorized in session
|
|
399
|
+
* @param {import('@types/express').Request} req Request with session data
|
|
400
|
+
* @param {import('@types/express').Response} res Response object
|
|
401
|
+
* @param {import('@types/express').NextFunction} next Next middleware function
|
|
402
|
+
* @param {boolean} isDebugging If true, allows unauthorized users
|
|
403
|
+
* @param {string} redirectUrl URL to redirect to if user is unauthorized
|
|
404
|
+
* @throws {CustomError} If user is not authorized
|
|
346
405
|
* @private
|
|
347
406
|
*/
|
|
348
407
|
async #verifySession(req, res, next, isDebugging, redirectUrl) {
|
|
@@ -357,13 +416,18 @@ export class SessionManager {
|
|
|
357
416
|
}
|
|
358
417
|
|
|
359
418
|
/**
|
|
360
|
-
* Refresh token authentication
|
|
361
|
-
*
|
|
362
|
-
* @param {import('@types/express').
|
|
363
|
-
* @param {import('@types/express').
|
|
364
|
-
* @param {(
|
|
365
|
-
* @param {
|
|
419
|
+
* Refresh token authentication - exchanges refresh token for new JWT
|
|
420
|
+
* Invalidates old token and generates a new one with updated user data
|
|
421
|
+
* @param {import('@types/express').Request} req Request with Authorization header
|
|
422
|
+
* @param {import('@types/express').Response} res Response object
|
|
423
|
+
* @param {import('@types/express').NextFunction} next Next middleware function
|
|
424
|
+
* @param {(user: object) => object} initUser Function to initialize/transform user object
|
|
425
|
+
* @param {string} idpUrl Identity provider refresh endpoint URL
|
|
426
|
+
* @throws {CustomError} If refresh lock is active or SSO refresh fails
|
|
366
427
|
* @private
|
|
428
|
+
* @example
|
|
429
|
+
* // Response format:
|
|
430
|
+
* // { token: "new_jwt", user: {...}, expiresIn: 64800, tokenType: "Bearer" }
|
|
367
431
|
*/
|
|
368
432
|
async #refreshToken(req, res, next, initUser, idpUrl) {
|
|
369
433
|
try {
|
package/index.d.ts
CHANGED
|
@@ -79,33 +79,160 @@ export const SessionMode: {
|
|
|
79
79
|
|
|
80
80
|
// Session Configuration - uses strict UPPERCASE naming convention for all property names
|
|
81
81
|
export interface SessionConfig {
|
|
82
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* SSO Identity Provider endpoint URL
|
|
84
|
+
* @example 'https://idp.example.com/open/api/v1'
|
|
85
|
+
* @required Required when using SSO authentication
|
|
86
|
+
*/
|
|
83
87
|
SSO_ENDPOINT_URL?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* OAuth2 Client ID registered with the Identity Provider
|
|
91
|
+
* @example 'my-app-client-id'
|
|
92
|
+
* @required Required when using SSO authentication
|
|
93
|
+
*/
|
|
84
94
|
SSO_CLIENT_ID?: string;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* OAuth2 Client Secret for authentication
|
|
98
|
+
* @example 'super-secret-client-key'
|
|
99
|
+
* @required Required when using SSO authentication
|
|
100
|
+
*/
|
|
85
101
|
SSO_CLIENT_SECRET?: string;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Redirect URL after successful SSO login
|
|
105
|
+
* @example '/dashboard' or 'https://myapp.com/home'
|
|
106
|
+
* @default '/'
|
|
107
|
+
*/
|
|
86
108
|
SSO_SUCCESS_URL?: string;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Redirect URL after failed SSO login
|
|
112
|
+
* @example '/login?error=auth_failed'
|
|
113
|
+
* @default '/login'
|
|
114
|
+
*/
|
|
87
115
|
SSO_FAILURE_URL?: string;
|
|
88
116
|
|
|
89
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Authentication mode: 'session' for cookie-based sessions, 'token' for JWT token-based auth
|
|
119
|
+
* @example 'session' | 'token'
|
|
120
|
+
* @default 'session'
|
|
121
|
+
*/
|
|
90
122
|
SESSION_MODE?: string;
|
|
91
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Session expiration time in milliseconds
|
|
126
|
+
* @example 3600000 (1 hour) or 86400000 (24 hours)
|
|
127
|
+
* @default 3600000 (1 hour)
|
|
128
|
+
*/
|
|
92
129
|
SESSION_AGE?: number;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Cookie path for session cookies
|
|
133
|
+
* @example '/' for entire site or '/app' for specific path
|
|
134
|
+
* @default '/'
|
|
135
|
+
*/
|
|
93
136
|
SESSION_COOKIE_PATH?: string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Secret key used for signing session cookies (should be a strong random string)
|
|
140
|
+
* @example 'your-super-secret-session-key-change-this-in-production'
|
|
141
|
+
* @required Required for session-based authentication
|
|
142
|
+
*/
|
|
94
143
|
SESSION_SECRET?: string;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Redis key prefix for storing session data
|
|
147
|
+
* @example 'myapp:session:' (will result in keys like 'myapp:session:user@example.com')
|
|
148
|
+
* @default 'sess:'
|
|
149
|
+
*/
|
|
95
150
|
SESSION_PREFIX?: string;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Redis key name for storing session data
|
|
154
|
+
* @example 'user' (results in session.user containing user data)
|
|
155
|
+
* @default 'user'
|
|
156
|
+
*/
|
|
96
157
|
SESSION_KEY?: string;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Redis key name for storing session expiry timestamp
|
|
161
|
+
* @example 'expires' (results in session.expires containing expiry time)
|
|
162
|
+
* @default 'expires'
|
|
163
|
+
*/
|
|
97
164
|
SESSION_EXPIRY_KEY?: string;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Path to custom HTML template for token storage page (TOKEN mode only)
|
|
168
|
+
* @example './templates/token-storage.html'
|
|
169
|
+
* @default Built-in template included in the library
|
|
170
|
+
*/
|
|
98
171
|
TOKEN_STORAGE_TEMPLATE_PATH?: string;
|
|
99
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Redis connection URL
|
|
175
|
+
* @example 'redis://localhost:6379' or 'rediss://user:pass@host:6380/0' (rediss for TLS)
|
|
176
|
+
* @required Required when using Redis for session storage
|
|
177
|
+
*/
|
|
100
178
|
REDIS_URL?: string;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Path to TLS certificate file for secure Redis connections
|
|
182
|
+
* @example '/path/to/redis-ca-cert.pem'
|
|
183
|
+
* @optional Only needed for TLS-enabled Redis connections
|
|
184
|
+
*/
|
|
101
185
|
REDIS_CERT_PATH?: string;
|
|
102
186
|
|
|
187
|
+
/**
|
|
188
|
+
* JWE algorithm for token encryption
|
|
189
|
+
* @example 'dir' (Direct Key Agreement) or 'RSA-OAEP' or 'A256KW'
|
|
190
|
+
* @default 'dir'
|
|
191
|
+
* @see https://tools.ietf.org/html/rfc7518#section-4.1
|
|
192
|
+
*/
|
|
103
193
|
JWT_ALGORITHM?: string;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* JWE encryption method
|
|
197
|
+
* @example 'A256GCM' (AES-256 GCM) or 'A128GCM' or 'A192GCM'
|
|
198
|
+
* @default 'A256GCM'
|
|
199
|
+
* @see https://tools.ietf.org/html/rfc7518#section-5.1
|
|
200
|
+
*/
|
|
104
201
|
JWT_ENCRYPTION?: string;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Clock tolerance in seconds for JWT token validation (allows for time drift between servers)
|
|
205
|
+
* @example 30 (allows 30 seconds of clock skew)
|
|
206
|
+
* @default 30
|
|
207
|
+
*/
|
|
105
208
|
JWT_CLOCK_TOLERANCE?: number;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Hash algorithm for deriving encryption key from secret
|
|
212
|
+
* @example 'SHA-256' or 'SHA-384' or 'SHA-512'
|
|
213
|
+
* @default 'SHA-256'
|
|
214
|
+
*/
|
|
106
215
|
JWT_SECRET_HASH_ALGORITHM?: string;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* JWT issuer claim (iss) - identifies the principal that issued the token
|
|
219
|
+
* @example 'https://myapp.com' or 'my-auth-service'
|
|
220
|
+
* @optional Adds additional security validation
|
|
221
|
+
*/
|
|
107
222
|
JWT_ISSUER?: string;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* JWT audience claim (aud) - identifies the recipients that the token is intended for
|
|
226
|
+
* @example 'https://api.myapp.com' or 'my-api-service'
|
|
227
|
+
* @optional Adds additional security validation
|
|
228
|
+
*/
|
|
108
229
|
JWT_AUDIENCE?: string;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* JWT subject claim (sub) - identifies the principal that is the subject of the token
|
|
233
|
+
* @example 'user-authentication' or 'api-access'
|
|
234
|
+
* @optional Adds additional context to tokens
|
|
235
|
+
*/
|
|
109
236
|
JWT_SUBJECT?: string;
|
|
110
237
|
}
|
|
111
238
|
|
|
@@ -144,8 +271,19 @@ export interface SessionUser {
|
|
|
144
271
|
// Session Manager
|
|
145
272
|
export class SessionManager {
|
|
146
273
|
/**
|
|
147
|
-
*
|
|
148
|
-
* @param config
|
|
274
|
+
* Create a new SessionManager instance
|
|
275
|
+
* @param config Session configuration object with UPPERCASE property names
|
|
276
|
+
* @example
|
|
277
|
+
* ```javascript
|
|
278
|
+
* const sessionManager = new SessionManager({
|
|
279
|
+
* SSO_ENDPOINT_URL: 'https://idp.example.com/open/api/v1',
|
|
280
|
+
* SSO_CLIENT_ID: 'my-app-client-id',
|
|
281
|
+
* SSO_CLIENT_SECRET: 'secret-key',
|
|
282
|
+
* SESSION_MODE: 'session', // or 'token' for JWT-based auth
|
|
283
|
+
* SESSION_SECRET: 'your-session-secret',
|
|
284
|
+
* REDIS_URL: 'redis://localhost:6379'
|
|
285
|
+
* });
|
|
286
|
+
* ```
|
|
149
287
|
*/
|
|
150
288
|
constructor(config: SessionConfig);
|
|
151
289
|
|
|
@@ -312,75 +450,191 @@ export class RedisManager {
|
|
|
312
450
|
|
|
313
451
|
// JWT Manager Configuration - uses strict UPPERCASE naming convention with JWT_ prefix for all property names
|
|
314
452
|
export interface JwtManagerOptions {
|
|
315
|
-
/**
|
|
453
|
+
/**
|
|
454
|
+
* JWE algorithm for token encryption
|
|
455
|
+
* @example 'dir' (Direct Key Agreement - symmetric encryption, recommended for most cases)
|
|
456
|
+
* @example 'RSA-OAEP' (RSA with OAEP padding - for asymmetric encryption)
|
|
457
|
+
* @example 'A256KW' (AES Key Wrap with 256-bit key)
|
|
458
|
+
* @default 'dir'
|
|
459
|
+
* @see https://tools.ietf.org/html/rfc7518#section-4.1
|
|
460
|
+
*/
|
|
316
461
|
JWT_ALGORITHM?: string;
|
|
317
462
|
|
|
318
|
-
/**
|
|
463
|
+
/**
|
|
464
|
+
* JWE content encryption method
|
|
465
|
+
* @example 'A256GCM' (AES-256 GCM - recommended, highest security)
|
|
466
|
+
* @example 'A128GCM' (AES-128 GCM - faster, lower security)
|
|
467
|
+
* @example 'A192GCM' (AES-192 GCM - balanced)
|
|
468
|
+
* @default 'A256GCM'
|
|
469
|
+
* @see https://tools.ietf.org/html/rfc7518#section-5.1
|
|
470
|
+
*/
|
|
319
471
|
JWT_ENCRYPTION?: string;
|
|
320
472
|
|
|
321
|
-
/**
|
|
473
|
+
/**
|
|
474
|
+
* Token expiration time - accepts number (seconds) or string with time suffix
|
|
475
|
+
* @example 64800 (18 hours as number)
|
|
476
|
+
* @example '18h' (18 hours)
|
|
477
|
+
* @example '7d' (7 days)
|
|
478
|
+
* @example '1080m' (1080 minutes = 18 hours)
|
|
479
|
+
* @example '30s' (30 seconds)
|
|
480
|
+
* @default 64800 (18 hours)
|
|
481
|
+
*/
|
|
482
|
+
JWT_EXPIRATION_TIME?: number | string;
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Clock tolerance in seconds for token validation
|
|
486
|
+
* Allows for small time differences between client and server clocks
|
|
487
|
+
* @example 30 (allows 30 seconds of clock drift)
|
|
488
|
+
* @example 60 (more lenient, allows 1 minute of drift)
|
|
489
|
+
* @default 30
|
|
490
|
+
*/
|
|
322
491
|
JWT_CLOCK_TOLERANCE?: number;
|
|
323
492
|
|
|
324
|
-
/**
|
|
493
|
+
/**
|
|
494
|
+
* Hash algorithm used for deriving encryption key from secret string
|
|
495
|
+
* @example 'SHA-256' (recommended for most cases)
|
|
496
|
+
* @example 'SHA-384' (higher security)
|
|
497
|
+
* @example 'SHA-512' (highest security, slower)
|
|
498
|
+
* @default 'SHA-256'
|
|
499
|
+
*/
|
|
325
500
|
JWT_SECRET_HASH_ALGORITHM?: string;
|
|
326
501
|
|
|
327
|
-
/**
|
|
502
|
+
/**
|
|
503
|
+
* JWT issuer claim (iss) - identifies who issued the token
|
|
504
|
+
* Used for token validation to ensure tokens are from expected source
|
|
505
|
+
* @example 'https://myapp.com'
|
|
506
|
+
* @example 'my-auth-service'
|
|
507
|
+
* @optional Recommended for production environments
|
|
508
|
+
*/
|
|
328
509
|
JWT_ISSUER?: string;
|
|
329
510
|
|
|
330
|
-
/**
|
|
511
|
+
/**
|
|
512
|
+
* JWT audience claim (aud) - identifies the intended recipients
|
|
513
|
+
* Used for token validation to ensure tokens are for the correct service
|
|
514
|
+
* @example 'https://api.myapp.com'
|
|
515
|
+
* @example ['api.myapp.com', 'admin.myapp.com'] (can be array)
|
|
516
|
+
* @optional Recommended for production environments
|
|
517
|
+
*/
|
|
331
518
|
JWT_AUDIENCE?: string;
|
|
332
519
|
|
|
333
|
-
/**
|
|
520
|
+
/**
|
|
521
|
+
* JWT subject claim (sub) - identifies the principal (subject) of the token
|
|
522
|
+
* Provides context about what the token represents
|
|
523
|
+
* @example 'user-authentication'
|
|
524
|
+
* @example 'api-access-token'
|
|
525
|
+
* @optional Adds semantic meaning to tokens
|
|
526
|
+
*/
|
|
334
527
|
JWT_SUBJECT?: string;
|
|
335
528
|
}
|
|
336
529
|
|
|
337
530
|
/**
|
|
338
531
|
* Options for encrypt() method - uses camelCase naming convention
|
|
532
|
+
* These options allow you to override the defaults set in JwtManagerOptions for specific encryption operations
|
|
339
533
|
*/
|
|
340
534
|
export interface JwtEncryptOptions {
|
|
341
|
-
/**
|
|
535
|
+
/**
|
|
536
|
+
* Override default JWE algorithm for this specific token
|
|
537
|
+
* @example 'dir' or 'RSA-OAEP'
|
|
538
|
+
* @use-case Use when you need different encryption algorithms for different token types
|
|
539
|
+
*/
|
|
342
540
|
algorithm?: string;
|
|
343
541
|
|
|
344
|
-
/**
|
|
542
|
+
/**
|
|
543
|
+
* Override default encryption method for this specific token
|
|
544
|
+
* @example 'A256GCM' or 'A128GCM'
|
|
545
|
+
* @use-case Use when you need different encryption strength for different token types
|
|
546
|
+
*/
|
|
345
547
|
encryption?: string;
|
|
346
548
|
|
|
347
|
-
/**
|
|
348
|
-
|
|
549
|
+
/**
|
|
550
|
+
* Override default expiration time for this specific token
|
|
551
|
+
* @example '1h' (short-lived token for sensitive operations)
|
|
552
|
+
* @example '30m' (half hour)
|
|
553
|
+
* @example '7d' (long-lived token for remember-me functionality)
|
|
554
|
+
* @example 3600 (1 hour as number in seconds)
|
|
555
|
+
* @use-case Use when different tokens need different lifetimes (e.g., access token vs refresh token)
|
|
556
|
+
*/
|
|
557
|
+
expirationTime?: number | string;
|
|
349
558
|
|
|
350
|
-
/**
|
|
559
|
+
/**
|
|
560
|
+
* Override default hash algorithm for deriving encryption key
|
|
561
|
+
* @example 'SHA-256' or 'SHA-512'
|
|
562
|
+
* @use-case Use when you need stronger hashing for specific high-security tokens
|
|
563
|
+
*/
|
|
351
564
|
secretHashAlgorithm?: string;
|
|
352
565
|
|
|
353
|
-
/**
|
|
566
|
+
/**
|
|
567
|
+
* Override default issuer claim for this specific token
|
|
568
|
+
* @example 'https://admin.myapp.com' (for admin tokens)
|
|
569
|
+
* @use-case Use when tokens are issued by different services
|
|
570
|
+
*/
|
|
354
571
|
issuer?: string;
|
|
355
572
|
|
|
356
|
-
/**
|
|
573
|
+
/**
|
|
574
|
+
* Override default audience claim for this specific token
|
|
575
|
+
* @example 'https://api.myapp.com' (for API access tokens)
|
|
576
|
+
* @example ['service1.myapp.com', 'service2.myapp.com'] (multiple audiences)
|
|
577
|
+
* @use-case Use when tokens are intended for different services
|
|
578
|
+
*/
|
|
357
579
|
audience?: string;
|
|
358
580
|
|
|
359
|
-
/**
|
|
581
|
+
/**
|
|
582
|
+
* Override default subject claim for this specific token
|
|
583
|
+
* @example 'password-reset' (for password reset tokens)
|
|
584
|
+
* @example 'email-verification' (for verification tokens)
|
|
585
|
+
* @use-case Use when different token types serve different purposes
|
|
586
|
+
*/
|
|
360
587
|
subject?: string;
|
|
361
588
|
}
|
|
362
589
|
|
|
363
590
|
/**
|
|
364
591
|
* Options for decrypt() method - uses camelCase naming convention
|
|
592
|
+
* These options allow you to override validation settings and verify specific claims during decryption
|
|
365
593
|
*/
|
|
366
594
|
export interface JwtDecryptOptions {
|
|
367
|
-
/**
|
|
595
|
+
/**
|
|
596
|
+
* Override default clock tolerance for this specific token validation
|
|
597
|
+
* @example 30 (seconds) - allows 30 seconds of clock drift
|
|
598
|
+
* @example 60 (seconds) - more lenient for distributed systems
|
|
599
|
+
* @use-case Use when validating tokens from systems with known clock drift issues
|
|
600
|
+
*/
|
|
368
601
|
clockTolerance?: number;
|
|
369
602
|
|
|
370
|
-
/**
|
|
603
|
+
/**
|
|
604
|
+
* Override default hash algorithm for deriving decryption key
|
|
605
|
+
* Must match the algorithm used during encryption
|
|
606
|
+
* @example 'SHA-256' or 'SHA-512'
|
|
607
|
+
* @use-case Use when tokens were encrypted with different hash algorithms
|
|
608
|
+
*/
|
|
371
609
|
secretHashAlgorithm?: string;
|
|
372
610
|
|
|
373
|
-
/**
|
|
611
|
+
/**
|
|
612
|
+
* Expected issuer claim (iss) for validation
|
|
613
|
+
* Token will be rejected if issuer doesn't match
|
|
614
|
+
* @example 'https://myapp.com'
|
|
615
|
+
* @use-case Use to ensure tokens come from a specific trusted source
|
|
616
|
+
*/
|
|
374
617
|
issuer?: string;
|
|
375
618
|
|
|
376
|
-
/**
|
|
619
|
+
/**
|
|
620
|
+
* Expected audience claim (aud) for validation
|
|
621
|
+
* Token will be rejected if audience doesn't match
|
|
622
|
+
* @example 'https://api.myapp.com'
|
|
623
|
+
* @example ['service1.myapp.com', 'service2.myapp.com'] (multiple valid audiences)
|
|
624
|
+
* @use-case Use to ensure tokens are intended for your service
|
|
625
|
+
*/
|
|
377
626
|
audience?: string;
|
|
378
627
|
|
|
379
|
-
/**
|
|
628
|
+
/**
|
|
629
|
+
* Expected subject claim (sub) for validation
|
|
630
|
+
* Token will be rejected if subject doesn't match
|
|
631
|
+
* @example 'user-authentication'
|
|
632
|
+
* @use-case Use to validate tokens are of the expected type/purpose
|
|
633
|
+
*/
|
|
380
634
|
subject?: string;
|
|
381
635
|
}
|
|
382
636
|
|
|
383
|
-
export type JwtDecryptResult = JWTDecryptResult
|
|
637
|
+
export type JwtDecryptResult = JWTDecryptResult;
|
|
384
638
|
|
|
385
639
|
// JwtManager class for JWT encryption and decryption
|
|
386
640
|
export class JwtManager {
|
|
@@ -396,6 +650,16 @@ export class JwtManager {
|
|
|
396
650
|
/**
|
|
397
651
|
* Create a new JwtManager instance with configurable defaults
|
|
398
652
|
* @param options Configuration options (uses strict UPPERCASE with JWT_ prefix property names)
|
|
653
|
+
* @example
|
|
654
|
+
* ```javascript
|
|
655
|
+
* const jwtManager = new JwtManager({
|
|
656
|
+
* JWT_ALGORITHM: 'dir',
|
|
657
|
+
* JWT_ENCRYPTION: 'A256GCM',
|
|
658
|
+
* JWT_EXPIRATION_TIME: '18h', // or 64800 seconds
|
|
659
|
+
* JWT_ISSUER: 'https://myapp.com',
|
|
660
|
+
* JWT_AUDIENCE: 'https://api.myapp.com'
|
|
661
|
+
* });
|
|
662
|
+
* ```
|
|
399
663
|
*/
|
|
400
664
|
constructor(options?: JwtManagerOptions);
|
|
401
665
|
|
|
@@ -403,17 +667,31 @@ export class JwtManager {
|
|
|
403
667
|
* Generate JWT token for user session
|
|
404
668
|
* @param data User data payload
|
|
405
669
|
* @param input Secret key or password for encryption
|
|
406
|
-
* @param options Per-call configuration overrides (uses
|
|
670
|
+
* @param options Per-call configuration overrides (uses camelCase property names)
|
|
407
671
|
* @returns Returns encrypted JWT token
|
|
408
672
|
*/
|
|
409
673
|
encrypt(data: JWTPayload, input: string, options?: JwtEncryptOptions): Promise<string>;
|
|
410
674
|
|
|
411
675
|
/**
|
|
412
|
-
* Decrypt JWT token for user session
|
|
413
|
-
* @param token JWT token to decrypt
|
|
414
|
-
* @param input Secret key or password for decryption
|
|
415
|
-
* @param options Per-call
|
|
416
|
-
* @returns Returns decrypted JWT token
|
|
676
|
+
* Decrypt and validate JWT token for user session
|
|
677
|
+
* @param token JWT token string to decrypt and validate
|
|
678
|
+
* @param input Secret key or password for decryption (must match the key used for encryption)
|
|
679
|
+
* @param options Per-call validation overrides (uses camelCase property names)
|
|
680
|
+
* @returns Returns decrypted JWT token result containing payload and protected header
|
|
681
|
+
* @throws Will throw error if token is invalid, expired, or validation fails
|
|
682
|
+
* @example
|
|
683
|
+
* ```javascript
|
|
684
|
+
* try {
|
|
685
|
+
* const result = await jwtManager.decrypt(
|
|
686
|
+
* tokenString,
|
|
687
|
+
* 'my-secret-key',
|
|
688
|
+
* { issuer: 'https://myapp.com' } // Optional: validate issuer claim
|
|
689
|
+
* );
|
|
690
|
+
* console.log(result.payload); // Access decrypted data
|
|
691
|
+
* } catch (error) {
|
|
692
|
+
* console.error('Token validation failed:', error.message);
|
|
693
|
+
* }
|
|
694
|
+
* ```
|
|
417
695
|
*/
|
|
418
696
|
decrypt(token: string, input: string, options?: JwtDecryptOptions): Promise<JwtDecryptResult>;
|
|
419
697
|
}
|
|
@@ -533,4 +811,4 @@ declare module 'express-session' {
|
|
|
533
811
|
[key: string]: any;
|
|
534
812
|
user?: SessionUser;
|
|
535
813
|
}
|
|
536
|
-
}
|
|
814
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igxjs/node-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Node components for igxjs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,21 @@
|
|
|
12
12
|
"url": "git+https://github.com/igxjs/node-components.git"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
|
-
"igxjs"
|
|
15
|
+
"igxjs",
|
|
16
|
+
"express",
|
|
17
|
+
"session-management",
|
|
18
|
+
"jwt",
|
|
19
|
+
"jwe",
|
|
20
|
+
"redis",
|
|
21
|
+
"authentication",
|
|
22
|
+
"sso",
|
|
23
|
+
"oauth",
|
|
24
|
+
"middleware",
|
|
25
|
+
"node-components",
|
|
26
|
+
"session",
|
|
27
|
+
"token-auth",
|
|
28
|
+
"bearer-token",
|
|
29
|
+
"express-middleware"
|
|
16
30
|
],
|
|
17
31
|
"author": "Michael",
|
|
18
32
|
"license": "Apache-2.0",
|
|
@@ -20,6 +34,9 @@
|
|
|
20
34
|
"url": "https://github.com/igxjs/node-components/issues"
|
|
21
35
|
},
|
|
22
36
|
"homepage": "https://github.com/igxjs/node-components#readme",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0"
|
|
39
|
+
},
|
|
23
40
|
"dependencies": {
|
|
24
41
|
"@redis/client": "^5.11.0",
|
|
25
42
|
"@types/express": "^5.0.6",
|
|
@@ -31,11 +48,18 @@
|
|
|
31
48
|
},
|
|
32
49
|
"devDependencies": {
|
|
33
50
|
"chai": "^6.2.2",
|
|
34
|
-
"express": "^5.2.1",
|
|
35
51
|
"mocha": "^12.0.0-beta-10",
|
|
36
52
|
"sinon": "^21.0.3",
|
|
37
53
|
"supertest": "^7.0.0"
|
|
38
54
|
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"express": "^4.0.0 || ^5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"express": {
|
|
60
|
+
"optional": false
|
|
61
|
+
}
|
|
62
|
+
},
|
|
39
63
|
"files": [
|
|
40
64
|
"index.js",
|
|
41
65
|
"index.d.ts",
|