@igxjs/node-components 1.0.16 → 1.0.18
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/http-handlers.js +4 -3
- package/components/jwt.js +12 -12
- package/components/logger.js +16 -16
- package/components/redis.js +1 -1
- package/components/router.js +5 -5
- package/components/session.js +73 -46
- package/index.js +1 -1
- package/package.json +9 -3
|
@@ -161,12 +161,13 @@ export const httpHelper = {
|
|
|
161
161
|
* @returns {string} Formatted string
|
|
162
162
|
*/
|
|
163
163
|
format (str, ...args) {
|
|
164
|
+
let result = str;
|
|
164
165
|
const matched = str.match(/{\d}/ig);
|
|
165
166
|
matched.forEach((element, index) => {
|
|
166
167
|
if(args.length > index)
|
|
167
|
-
|
|
168
|
+
result = result.replace(element, args[index]);
|
|
168
169
|
});
|
|
169
|
-
return
|
|
170
|
+
return result;
|
|
170
171
|
},
|
|
171
172
|
|
|
172
173
|
/**
|
|
@@ -209,4 +210,4 @@ export const httpHelper = {
|
|
|
209
210
|
*/
|
|
210
211
|
export const httpError = (code, message, error, data) => {
|
|
211
212
|
return new CustomError(code, message, error, data);
|
|
212
|
-
};
|
|
213
|
+
};
|
package/components/jwt.js
CHANGED
|
@@ -9,31 +9,31 @@ import { jwtDecrypt, EncryptJWT } from 'jose';
|
|
|
9
9
|
export class JwtManager {
|
|
10
10
|
/** @type {string} JWE algorithm */
|
|
11
11
|
algorithm;
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
/** @type {string} Encryption method */
|
|
14
14
|
encryption;
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
/** @type {number} Token expiration time */
|
|
17
17
|
expirationTime;
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
/** @type {number} Clock tolerance in seconds */
|
|
20
20
|
clockTolerance;
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
/** @type {string} Hash algorithm for secret derivation */
|
|
23
23
|
secretHashAlgorithm;
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
/** @type {string|null} Optional JWT issuer claim */
|
|
26
26
|
issuer;
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
/** @type {string|null} Optional JWT audience claim */
|
|
29
29
|
audience;
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
/** @type {string|null} Optional JWT subject claim */
|
|
32
32
|
subject;
|
|
33
33
|
/**
|
|
34
34
|
* Create a new JwtManager instance with configurable defaults
|
|
35
35
|
* Constructor options use UPPERCASE naming convention with JWT_ prefix (e.g., JWT_ALGORITHM).
|
|
36
|
-
*
|
|
36
|
+
*
|
|
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')
|
|
@@ -59,7 +59,7 @@ export class JwtManager {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Encrypt method options (camelCase naming convention, uses instance defaults when not provided)
|
|
62
|
-
*
|
|
62
|
+
*
|
|
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)
|
|
@@ -71,7 +71,7 @@ export class JwtManager {
|
|
|
71
71
|
*/
|
|
72
72
|
/**
|
|
73
73
|
* Generate JWT token for user session
|
|
74
|
-
*
|
|
74
|
+
*
|
|
75
75
|
* @param {import('jose').JWTPayload} data User data payload
|
|
76
76
|
* @param {string} secret Secret key or password for encryption
|
|
77
77
|
* @param {JwtEncryptOptions} [options] Per-call configuration overrides (camelCase naming convention)
|
|
@@ -113,7 +113,7 @@ export class JwtManager {
|
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
115
|
* Decrypt method options (camelCase naming convention, uses instance defaults when not provided)
|
|
116
|
-
*
|
|
116
|
+
*
|
|
117
117
|
* @typedef {Object} JwtDecryptOptions Decryption method options
|
|
118
118
|
* @property {number} [clockTolerance=30] Clock tolerance in seconds (overrides instance JWT_CLOCK_TOLERANCE)
|
|
119
119
|
* @property {string} [secretHashAlgorithm='SHA-256'] Hash algorithm for secret derivation (overrides instance JWT_SECRET_HASH_ALGORITHM)
|
|
@@ -123,7 +123,7 @@ export class JwtManager {
|
|
|
123
123
|
**/
|
|
124
124
|
/**
|
|
125
125
|
* Decrypt JWT
|
|
126
|
-
*
|
|
126
|
+
*
|
|
127
127
|
* @param {string} token JWT token to decrypt
|
|
128
128
|
* @param {string} secret Secret key or password for decryption
|
|
129
129
|
* @param {JwtDecryptOptions} [options] Per-call configuration overrides (camelCase naming convention)
|
package/components/logger.js
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
* Logger utility for node-components
|
|
3
3
|
* Provides configurable logging with enable/disable functionality
|
|
4
4
|
* Uses singleton pattern to manage logger instances per component
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @example
|
|
7
7
|
* import { Logger } from './logger.js';
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* // Recommended: Get logger instance (singleton pattern)
|
|
10
10
|
* const logger = Logger.getInstance('ComponentName');
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* // With explicit enable/disable
|
|
13
13
|
* const logger = Logger.getInstance('ComponentName', true); // Always enabled
|
|
14
14
|
* const logger = Logger.getInstance('ComponentName', false); // Always disabled
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* // Backward compatibility: Constructor still works
|
|
17
17
|
* const logger = new Logger('ComponentName');
|
|
18
|
-
*
|
|
18
|
+
*
|
|
19
19
|
* // Use logger
|
|
20
20
|
* logger.info('Operation completed');
|
|
21
21
|
* logger.error('Error occurred', error);
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
export class Logger {
|
|
24
24
|
/** @type {Map<string, Logger>} */
|
|
25
25
|
static #instances = new Map();
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
/** @type {boolean} - Global flag to enable/disable colors */
|
|
28
28
|
static #colorsEnabled = true;
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
/** ANSI color codes for different log levels */
|
|
31
31
|
static #colors = {
|
|
32
32
|
reset: '\x1b[0m',
|
|
@@ -35,7 +35,7 @@ export class Logger {
|
|
|
35
35
|
yellow: '\x1b[33m', // warn - yellow
|
|
36
36
|
red: '\x1b[31m', // error - red
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
/** @type {boolean} */
|
|
40
40
|
#enabled;
|
|
41
41
|
/** @type {string} */
|
|
@@ -51,11 +51,11 @@ export class Logger {
|
|
|
51
51
|
*/
|
|
52
52
|
static getInstance(componentName, enableLogging) {
|
|
53
53
|
const key = `${componentName}:${enableLogging ?? 'default'}`;
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
if (!Logger.#instances.has(key)) {
|
|
56
56
|
Logger.#instances.set(key, new Logger(componentName, enableLogging));
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
return Logger.#instances.get(key);
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -90,20 +90,20 @@ export class Logger {
|
|
|
90
90
|
// Otherwise, default to enabled in development, disabled in production
|
|
91
91
|
this.#enabled = enableLogging ?? (process.env.NODE_ENV !== 'production');
|
|
92
92
|
this.#prefix = `[${componentName}]`;
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
// Determine if colors should be used:
|
|
95
95
|
// - Colors are globally enabled
|
|
96
96
|
// - Output is a terminal (TTY)
|
|
97
97
|
// - NO_COLOR environment variable is not set
|
|
98
|
-
this.#useColors = Logger.#colorsEnabled &&
|
|
99
|
-
process.stdout.isTTY &&
|
|
98
|
+
this.#useColors = Logger.#colorsEnabled &&
|
|
99
|
+
process.stdout.isTTY &&
|
|
100
100
|
!process.env.NO_COLOR;
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
// Assign methods based on enabled flag to eliminate runtime checks
|
|
103
103
|
// This improves performance by avoiding conditional checks on every log call
|
|
104
104
|
if (this.#enabled) {
|
|
105
105
|
const colors = Logger.#colors;
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
if (this.#useColors) {
|
|
108
108
|
// Logging enabled with colors: colorize the prefix
|
|
109
109
|
this.debug = (...args) => console.debug(colors.dim + this.#prefix + colors.reset, ...args);
|
|
@@ -128,4 +128,4 @@ export class Logger {
|
|
|
128
128
|
this.log = () => {};
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
}
|
|
131
|
+
}
|
package/components/redis.js
CHANGED
package/components/router.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FlexRouter for expressjs
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* @example
|
|
5
5
|
* import { Router } from 'express';
|
|
6
6
|
* import { FlexRouter } from '../models/router.js';
|
|
7
7
|
* import { authenticate } from '../middlewares/common.js';
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* export const publicRouter = Router();
|
|
10
10
|
* export const privateRouter = Router();
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* publicRouter.get('/health', (req, res) => {
|
|
13
13
|
* res.send('OK');
|
|
14
14
|
* });
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* privateRouter.get('/', (req, res) => {
|
|
17
17
|
* res.send('Hello World!');
|
|
18
18
|
* });
|
|
19
|
-
*
|
|
19
|
+
*
|
|
20
20
|
* export const routers = [
|
|
21
21
|
* new FlexRouter('/api/v1/protected', privateRouter, [authenticate]),
|
|
22
22
|
* new FlexRouter('/api/v1/public', healthRouter),
|
package/components/session.js
CHANGED
|
@@ -28,15 +28,15 @@ export const SessionMode = {
|
|
|
28
28
|
* Session configuration options
|
|
29
29
|
*/
|
|
30
30
|
export class SessionConfig {
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
32
|
* @type {string} Authentication mode for protected routes
|
|
33
33
|
* - Supported values: SessionMode.SESSION | SessionMode.TOKEN
|
|
34
34
|
* @default SessionMode.SESSION
|
|
35
35
|
*/
|
|
36
36
|
SESSION_MODE;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
38
|
* @type {string} Identity Provider microservice endpoint URL
|
|
39
|
-
*
|
|
39
|
+
*
|
|
40
40
|
* This is a fully customized, independent microservice that provides SSO authentication.
|
|
41
41
|
* The endpoint serves multiple applications and provides the following APIs:
|
|
42
42
|
* - GET /auth/providers - List supported identity providers
|
|
@@ -44,7 +44,7 @@ export class SessionConfig {
|
|
|
44
44
|
* - POST /auth/verify - Verify JWT token validity
|
|
45
45
|
* - GET /auth/callback/:idp - Validate authentication and return user data
|
|
46
46
|
* - POST /auth/refresh - Refresh access tokens
|
|
47
|
-
*
|
|
47
|
+
*
|
|
48
48
|
* @example
|
|
49
49
|
* SSO_ENDPOINT_URL: 'https://idp.example.com/open/api/v1'
|
|
50
50
|
*/
|
|
@@ -67,19 +67,19 @@ export class SessionConfig {
|
|
|
67
67
|
SESSION_COOKIE_PATH;
|
|
68
68
|
/** @type {string} Session secret */
|
|
69
69
|
SESSION_SECRET;
|
|
70
|
-
/**
|
|
71
|
-
* @type {string}
|
|
70
|
+
/**
|
|
71
|
+
* @type {string}
|
|
72
72
|
* @default 'ibmid:'
|
|
73
73
|
*/
|
|
74
74
|
SESSION_PREFIX;
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
76
|
* @type {string} Session key
|
|
77
77
|
* - In the `SessionMode.SESSION` mode, this is the key used to store the user in the session.
|
|
78
78
|
* - In the `SessionMode.TOKEN` mode, this is the key of localStorage where the user is stored.
|
|
79
79
|
* @default 'session_token'
|
|
80
80
|
*/
|
|
81
81
|
SESSION_KEY;
|
|
82
|
-
/**
|
|
82
|
+
/**
|
|
83
83
|
* @type {string} Session expiry key
|
|
84
84
|
* - In the `SessionMode.TOKEN` mode, this is the key of localStorage where the session expiry timestamp is stored.
|
|
85
85
|
* @default 'session_expires_at'
|
|
@@ -141,26 +141,33 @@ export class SessionManager {
|
|
|
141
141
|
#htmlTemplate = null;
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
|
-
*
|
|
145
|
-
* @param {SessionConfig} config
|
|
146
|
-
* @throws {TypeError}
|
|
147
|
-
* @throws {Error} If required configuration fields are missing
|
|
144
|
+
* Validate config is an object
|
|
145
|
+
* @param {SessionConfig} config
|
|
146
|
+
* @throws {TypeError}
|
|
148
147
|
*/
|
|
149
|
-
|
|
150
|
-
// Validate config is an object
|
|
148
|
+
#validateConfigType(config) {
|
|
151
149
|
if (!config || typeof config !== 'object') {
|
|
152
150
|
throw new TypeError('SessionManager configuration must be an object');
|
|
153
151
|
}
|
|
152
|
+
}
|
|
154
153
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Validate required configuration fields
|
|
156
|
+
* @param {SessionConfig} config
|
|
157
|
+
* @throws {Error}
|
|
158
|
+
*/
|
|
159
|
+
#validateRequiredFields(config) {
|
|
159
160
|
if (!config.SESSION_SECRET) {
|
|
160
161
|
throw new Error('SESSION_SECRET is required for SessionManager');
|
|
161
162
|
}
|
|
163
|
+
}
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Validate SSO configuration
|
|
167
|
+
* @param {SessionConfig} config
|
|
168
|
+
* @throws {Error}
|
|
169
|
+
*/
|
|
170
|
+
#validateSsoConfig(config) {
|
|
164
171
|
if (config.SSO_ENDPOINT_URL) {
|
|
165
172
|
if (!config.SSO_CLIENT_ID) {
|
|
166
173
|
throw new Error('SSO_CLIENT_ID is required when SSO_ENDPOINT_URL is provided');
|
|
@@ -169,9 +176,15 @@ export class SessionManager {
|
|
|
169
176
|
throw new Error('SSO_CLIENT_SECRET is required when SSO_ENDPOINT_URL is provided');
|
|
170
177
|
}
|
|
171
178
|
}
|
|
179
|
+
}
|
|
172
180
|
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Validate TOKEN mode requirements
|
|
183
|
+
* @param {SessionConfig} config
|
|
184
|
+
* @throws {Error}
|
|
185
|
+
*/
|
|
186
|
+
#validateTokenMode(config) {
|
|
187
|
+
if (config.SESSION_MODE === SessionMode.TOKEN) {
|
|
175
188
|
if (!config.SSO_SUCCESS_URL) {
|
|
176
189
|
throw new Error('SSO_SUCCESS_URL is required for TOKEN authentication mode');
|
|
177
190
|
}
|
|
@@ -179,11 +192,16 @@ export class SessionManager {
|
|
|
179
192
|
throw new Error('SSO_FAILURE_URL is required for TOKEN authentication mode');
|
|
180
193
|
}
|
|
181
194
|
}
|
|
195
|
+
}
|
|
182
196
|
|
|
183
|
-
|
|
184
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Build configuration object
|
|
199
|
+
* @param {SessionConfig} config
|
|
200
|
+
* @returns {object}
|
|
201
|
+
*/
|
|
202
|
+
#buildConfig(config) {
|
|
203
|
+
return {
|
|
185
204
|
SESSION_MODE: config.SESSION_MODE || SessionMode.SESSION,
|
|
186
|
-
// Session - SESSION_AGE is now in seconds (default: 64800 = 18 hours)
|
|
187
205
|
SESSION_AGE: config.SESSION_AGE || 64800,
|
|
188
206
|
SESSION_COOKIE_PATH: config.SESSION_COOKIE_PATH || '/',
|
|
189
207
|
SESSION_SECRET: config.SESSION_SECRET,
|
|
@@ -191,18 +209,13 @@ export class SessionManager {
|
|
|
191
209
|
SESSION_KEY: config.SESSION_KEY || 'session_token',
|
|
192
210
|
SESSION_EXPIRY_KEY: config.SESSION_EXPIRY_KEY || 'session_expires_at',
|
|
193
211
|
TOKEN_STORAGE_TEMPLATE_PATH: config.TOKEN_STORAGE_TEMPLATE_PATH,
|
|
194
|
-
|
|
195
|
-
// Identity Provider
|
|
196
212
|
SSO_ENDPOINT_URL: config.SSO_ENDPOINT_URL,
|
|
197
213
|
SSO_CLIENT_ID: config.SSO_CLIENT_ID,
|
|
198
214
|
SSO_CLIENT_SECRET: config.SSO_CLIENT_SECRET,
|
|
199
215
|
SSO_SUCCESS_URL: config.SSO_SUCCESS_URL,
|
|
200
216
|
SSO_FAILURE_URL: config.SSO_FAILURE_URL,
|
|
201
|
-
// Redis
|
|
202
217
|
REDIS_URL: config.REDIS_URL,
|
|
203
218
|
REDIS_CERT_PATH: config.REDIS_CERT_PATH,
|
|
204
|
-
|
|
205
|
-
// JWT Manager
|
|
206
219
|
JWT_ALGORITHM: config.JWT_ALGORITHM || 'dir',
|
|
207
220
|
JWT_ENCRYPTION: config.JWT_ENCRYPTION || 'A256GCM',
|
|
208
221
|
JWT_CLOCK_TOLERANCE: config.JWT_CLOCK_TOLERANCE ?? 30,
|
|
@@ -213,6 +226,20 @@ export class SessionManager {
|
|
|
213
226
|
};
|
|
214
227
|
}
|
|
215
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Create a new session manager
|
|
231
|
+
* @param {SessionConfig} config Session configuration
|
|
232
|
+
* @throws {TypeError} If config is not an object
|
|
233
|
+
* @throws {Error} If required configuration fields are missing
|
|
234
|
+
*/
|
|
235
|
+
constructor(config) {
|
|
236
|
+
this.#validateConfigType(config);
|
|
237
|
+
this.#validateRequiredFields(config);
|
|
238
|
+
this.#validateSsoConfig(config);
|
|
239
|
+
this.#validateTokenMode(config);
|
|
240
|
+
this.#config = this.#buildConfig(config);
|
|
241
|
+
}
|
|
242
|
+
|
|
216
243
|
/**
|
|
217
244
|
* Check if the email has a session refresh lock
|
|
218
245
|
* @param {string} email Email address
|
|
@@ -462,7 +489,7 @@ export class SessionManager {
|
|
|
462
489
|
return next(new CustomError(httpCodes.UNAUTHORIZED, 'Token expired'));
|
|
463
490
|
}
|
|
464
491
|
|
|
465
|
-
return next(error instanceof CustomError ? error :
|
|
492
|
+
return next(error instanceof CustomError ? error :
|
|
466
493
|
new CustomError(httpCodes.UNAUTHORIZED, 'Token verification failed'));
|
|
467
494
|
}
|
|
468
495
|
}
|
|
@@ -515,7 +542,7 @@ export class SessionManager {
|
|
|
515
542
|
|
|
516
543
|
// Call SSO refresh endpoint
|
|
517
544
|
const response = await this.#idpRequest.post(idpRefreshUrl, {
|
|
518
|
-
idp: user?.attributes?.idp,
|
|
545
|
+
idp: user?.attributes?.idp,
|
|
519
546
|
refresh_token: user?.attributes?.refresh_token
|
|
520
547
|
}, {
|
|
521
548
|
headers: {
|
|
@@ -622,7 +649,7 @@ export class SessionManager {
|
|
|
622
649
|
if (isRedirect) {
|
|
623
650
|
return res.redirect(this.#config.SSO_SUCCESS_URL);
|
|
624
651
|
}
|
|
625
|
-
return res.json({
|
|
652
|
+
return res.json({
|
|
626
653
|
message: 'All tokens logged out successfully',
|
|
627
654
|
tokensRemoved: keys.length,
|
|
628
655
|
redirect_url: this.#config.SSO_SUCCESS_URL
|
|
@@ -632,7 +659,7 @@ export class SessionManager {
|
|
|
632
659
|
if (isRedirect) {
|
|
633
660
|
return res.redirect(this.#config.SSO_FAILURE_URL);
|
|
634
661
|
}
|
|
635
|
-
return res.status(httpCodes.SYSTEM_FAILURE).json({
|
|
662
|
+
return res.status(httpCodes.SYSTEM_FAILURE).json({
|
|
636
663
|
error: 'Logout all failed',
|
|
637
664
|
redirect_url: this.#config.SSO_FAILURE_URL
|
|
638
665
|
});
|
|
@@ -671,7 +698,7 @@ export class SessionManager {
|
|
|
671
698
|
if (isRedirect) {
|
|
672
699
|
return res.redirect(this.#config.SSO_SUCCESS_URL);
|
|
673
700
|
}
|
|
674
|
-
return res.json({
|
|
701
|
+
return res.json({
|
|
675
702
|
message: 'Logout successful',
|
|
676
703
|
redirect_url: this.#config.SSO_SUCCESS_URL
|
|
677
704
|
});
|
|
@@ -681,7 +708,7 @@ export class SessionManager {
|
|
|
681
708
|
if (isRedirect) {
|
|
682
709
|
return res.redirect(this.#config.SSO_FAILURE_URL);
|
|
683
710
|
}
|
|
684
|
-
return res.status(httpCodes.SYSTEM_FAILURE).json({
|
|
711
|
+
return res.status(httpCodes.SYSTEM_FAILURE).json({
|
|
685
712
|
error: 'Logout failed',
|
|
686
713
|
redirect_url: this.#config.SSO_FAILURE_URL
|
|
687
714
|
});
|
|
@@ -776,9 +803,9 @@ export class SessionManager {
|
|
|
776
803
|
* Resource protection based on configured SESSION_MODE
|
|
777
804
|
* - SESSION mode: Verifies user exists in session store and is authorized (checks req.session data)
|
|
778
805
|
* - TOKEN mode: Validates JWT token from Authorization header (lightweight validation)
|
|
779
|
-
*
|
|
806
|
+
*
|
|
780
807
|
* Note: This method verifies authentication only. Use requireUser() after this to populate req.user.
|
|
781
|
-
*
|
|
808
|
+
*
|
|
782
809
|
* @param {string} [errorRedirectUrl=''] Redirect URL on authentication failure
|
|
783
810
|
* @returns {import('@types/express').RequestHandler} Returns express Request Handler
|
|
784
811
|
* @example
|
|
@@ -786,9 +813,9 @@ export class SessionManager {
|
|
|
786
813
|
* app.get('/api/check', session.authenticate(), (req, res) => {
|
|
787
814
|
* res.json({ authenticated: true });
|
|
788
815
|
* });
|
|
789
|
-
*
|
|
816
|
+
*
|
|
790
817
|
* // Option 2: Verify authentication AND load user data into req.user
|
|
791
|
-
* app.get('/api/profile',
|
|
818
|
+
* app.get('/api/profile',
|
|
792
819
|
* session.authenticate(), // Verifies session/token
|
|
793
820
|
* session.requireUser(), // Loads user data into req.user
|
|
794
821
|
* (req, res) => {
|
|
@@ -852,7 +879,7 @@ export class SessionManager {
|
|
|
852
879
|
}
|
|
853
880
|
throw new CustomError(httpCodes.BAD_REQUEST, 'Invalid JWT payload');
|
|
854
881
|
};
|
|
855
|
-
|
|
882
|
+
|
|
856
883
|
/**
|
|
857
884
|
* Render HTML template for token storage
|
|
858
885
|
* @param {string} token JWT token
|
|
@@ -870,7 +897,7 @@ export class SessionManager {
|
|
|
870
897
|
.replaceAll('{{SSO_SUCCESS_URL}}', sucessRedirectUrl)
|
|
871
898
|
.replaceAll('{{SSO_FAILURE_URL}}', this.#config.SSO_FAILURE_URL);
|
|
872
899
|
}
|
|
873
|
-
|
|
900
|
+
|
|
874
901
|
/**
|
|
875
902
|
* SSO callback for successful login
|
|
876
903
|
* @param {(user: object) => object} initUser Initialize user object function
|
|
@@ -882,7 +909,7 @@ export class SessionManager {
|
|
|
882
909
|
if (!jwt) {
|
|
883
910
|
return next(new CustomError(httpCodes.BAD_REQUEST, 'Missing `jwt` in query parameters'));
|
|
884
911
|
}
|
|
885
|
-
|
|
912
|
+
|
|
886
913
|
try {
|
|
887
914
|
// Decrypt JWT from Identity Adapter
|
|
888
915
|
const { payload } = await this.#jwtManager.decrypt(jwt, this.#config.SSO_CLIENT_SECRET);
|
|
@@ -890,10 +917,10 @@ export class SessionManager {
|
|
|
890
917
|
if (!payload?.user) {
|
|
891
918
|
throw new CustomError(httpCodes.BAD_REQUEST, 'Invalid JWT payload');
|
|
892
919
|
}
|
|
893
|
-
|
|
920
|
+
|
|
894
921
|
/** @type {string} */
|
|
895
922
|
const callbackRedirectUrl = payload.redirect_url || this.#config.SSO_SUCCESS_URL;
|
|
896
|
-
|
|
923
|
+
|
|
897
924
|
// Token mode: Generate token and return HTML page
|
|
898
925
|
if (this.getSessionMode() === SessionMode.TOKEN) {
|
|
899
926
|
/** @type {import('../index.js').SessionUser} */
|
|
@@ -986,8 +1013,8 @@ export class SessionManager {
|
|
|
986
1013
|
if (isRedirect) {
|
|
987
1014
|
return res.redirect(this.#config.SSO_FAILURE_URL);
|
|
988
1015
|
}
|
|
989
|
-
return res.status(httpCodes.AUTHORIZATION_FAILED).send({
|
|
990
|
-
redirect_url: this.#config.SSO_FAILURE_URL
|
|
1016
|
+
return res.status(httpCodes.AUTHORIZATION_FAILED).send({
|
|
1017
|
+
redirect_url: this.#config.SSO_FAILURE_URL
|
|
991
1018
|
});
|
|
992
1019
|
}
|
|
993
1020
|
if (isRedirect) {
|
package/index.js
CHANGED
|
@@ -3,4 +3,4 @@ export { httpCodes, httpMessages, httpErrorHandler, httpNotFoundHandler, CustomE
|
|
|
3
3
|
export { RedisManager } from './components/redis.js';
|
|
4
4
|
export { FlexRouter } from './components/router.js';
|
|
5
5
|
export { JwtManager } from './components/jwt.js';
|
|
6
|
-
export { Logger } from './components/logger.js';
|
|
6
|
+
export { Logger } from './components/logger.js';
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igxjs/node-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "Node components for igxjs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "mocha tests/**/*.test.js --timeout 5000"
|
|
8
|
+
"test": "mocha tests/**/*.test.js --timeout 5000",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"lint:fix": "eslint . --fix"
|
|
9
11
|
},
|
|
10
12
|
"repository": {
|
|
11
13
|
"type": "git",
|
|
@@ -33,7 +35,10 @@
|
|
|
33
35
|
"memorystore": "^1.6.7"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
38
|
+
"@eslint/js": "^10.0.1",
|
|
36
39
|
"chai": "^6.2.2",
|
|
40
|
+
"eslint": "^10.1.0",
|
|
41
|
+
"globals": "^17.4.0",
|
|
37
42
|
"mocha": "^12.0.0-beta-10",
|
|
38
43
|
"sinon": "^21.0.3",
|
|
39
44
|
"supertest": "^7.0.0"
|
|
@@ -54,7 +59,8 @@
|
|
|
54
59
|
"README.md"
|
|
55
60
|
],
|
|
56
61
|
"publishConfig": {
|
|
57
|
-
"access": "public"
|
|
62
|
+
"access": "public",
|
|
63
|
+
"registry": "https://npm.pkg.github.com"
|
|
58
64
|
},
|
|
59
65
|
"types": "./index.d.ts"
|
|
60
66
|
}
|