@igxjs/node-components 1.0.9 → 1.0.11
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 +98 -4
- package/components/http-handlers.js +6 -0
- package/components/jwt.js +89 -45
- package/components/session.js +571 -101
- package/index.d.ts +114 -44
- package/index.js +1 -1
- package/package.json +10 -3
- package/.github/workflows/node.js.yml +0 -31
- package/.github/workflows/npm-publish.yml +0 -33
- package/docs/README.md +0 -54
- package/docs/flex-router.md +0 -167
- package/docs/http-handlers.md +0 -302
- package/docs/jwt-manager.md +0 -124
- package/docs/redis-manager.md +0 -210
- package/docs/session-manager.md +0 -160
- package/tests/http-handlers.test.js +0 -21
- package/tests/jwt.test.js +0 -345
- package/tests/redis.test.js +0 -50
- package/tests/router.test.js +0 -50
- package/tests/session.test.js +0 -116
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @igxjs/node-components
|
|
|
12
12
|
|
|
13
13
|
| Component | Description | Documentation |
|
|
14
14
|
|-----------|-------------|---------------|
|
|
15
|
-
| **SessionManager** | SSO session management with Redis/memory storage | [View docs](./docs/session-manager.md) |
|
|
15
|
+
| **SessionManager** | SSO session management with Redis/memory storage, supporting both session and token-based authentication | [View docs](./docs/session-manager.md) |
|
|
16
16
|
| **FlexRouter** | Flexible routing with context paths and middleware | [View docs](./docs/flex-router.md) |
|
|
17
17
|
| **RedisManager** | Redis connection management with TLS support | [View docs](./docs/redis-manager.md) |
|
|
18
18
|
| **JWT Manager** | Secure JWT encryption/decryption with JWE | [View docs](./docs/jwt-manager.md) |
|
|
@@ -23,9 +23,9 @@ npm install @igxjs/node-components
|
|
|
23
23
|
### SessionManager
|
|
24
24
|
|
|
25
25
|
```javascript
|
|
26
|
-
import { SessionManager } from '@igxjs/node-components';
|
|
26
|
+
import { SessionManager, SessionMode } from '@igxjs/node-components';
|
|
27
27
|
|
|
28
|
-
// Create singleton instance
|
|
28
|
+
// Create singleton instance with SESSION authentication (default)
|
|
29
29
|
export const session = new SessionManager({
|
|
30
30
|
SSO_ENDPOINT_URL: process.env.SSO_ENDPOINT_URL,
|
|
31
31
|
SSO_CLIENT_ID: process.env.SSO_CLIENT_ID,
|
|
@@ -34,6 +34,16 @@ export const session = new SessionManager({
|
|
|
34
34
|
REDIS_URL: process.env.REDIS_URL
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
// Create singleton instance with TOKEN authentication
|
|
38
|
+
export const tokenSession = new SessionManager({
|
|
39
|
+
SESSION_MODE: SessionMode.TOKEN, // Use token-based authentication
|
|
40
|
+
SSO_ENDPOINT_URL: process.env.SSO_ENDPOINT_URL,
|
|
41
|
+
SSO_CLIENT_ID: process.env.SSO_CLIENT_ID,
|
|
42
|
+
SSO_CLIENT_SECRET: process.env.SSO_CLIENT_SECRET,
|
|
43
|
+
SESSION_SECRET: process.env.SESSION_SECRET,
|
|
44
|
+
REDIS_URL: process.env.REDIS_URL,
|
|
45
|
+
});
|
|
46
|
+
|
|
37
47
|
// Setup in your app
|
|
38
48
|
await session.setup(app, (user) => ({ ...user, displayName: user.email }));
|
|
39
49
|
|
|
@@ -105,9 +115,93 @@ app.use(httpErrorHandler);
|
|
|
105
115
|
|
|
106
116
|
[📖 Full HTTP Handlers Documentation](./docs/http-handlers.md)
|
|
107
117
|
|
|
118
|
+
## SessionManager Authentication Modes
|
|
119
|
+
|
|
120
|
+
The `SessionManager` supports two authentication modes:
|
|
121
|
+
|
|
122
|
+
### SESSION Mode (Default)
|
|
123
|
+
|
|
124
|
+
Uses traditional server-side session cookies. When a user authenticates via SSO, their session is stored in Redis or memory storage. The client sends the session cookie with each request to prove authentication.
|
|
125
|
+
|
|
126
|
+
**Configuration:**
|
|
127
|
+
- `SESSION_MODE`: `SessionMode.SESSION` (default) - Uses session-based authentication
|
|
128
|
+
- `SESSION_AGE`: Session timeout in milliseconds (default: 64800000)
|
|
129
|
+
- `REDIS_URL`: Redis connection string for session storage
|
|
130
|
+
|
|
131
|
+
**Auth Methods:**
|
|
132
|
+
- `session.authenticate()` - Protect routes with SSO session verification
|
|
133
|
+
- `session.verifySession(isDebugging, redirectUrl)` - Explicit session verification method
|
|
134
|
+
- `session.logout(redirect?, all?)` - Logout current session (or logout all for token mode)
|
|
135
|
+
|
|
136
|
+
### TOKEN Mode
|
|
137
|
+
|
|
138
|
+
Uses JWT bearer tokens instead of session cookies. When a user authenticates via SSO, a JWT token is generated and stored in Redis. The client includes the token in the Authorization header (`Bearer {token}`) with each request.
|
|
139
|
+
|
|
140
|
+
**Configuration:**
|
|
141
|
+
- `SESSION_MODE`: `SessionMode.TOKEN` - Uses token-based authentication
|
|
142
|
+
- `SSO_SUCCESS_URL`: Redirect URL after successful SSO login
|
|
143
|
+
- `SSO_FAILURE_URL`: Redirect URL after failed SSO login
|
|
144
|
+
- `JWT_ALGORITHM`: JWT algorithm (default: `'dir'`)
|
|
145
|
+
- `JWT_ENCRYPTION`: Encryption algorithm (default: `'A256GCM'`)
|
|
146
|
+
- `JWT_EXPIRATION_TIME`: Token expiration time (default: `'10m'`)
|
|
147
|
+
- `JWT_CLOCK_TOLERANCE`: Clock skew tolerance in seconds (default: 30)
|
|
148
|
+
|
|
149
|
+
**Auth Methods:**
|
|
150
|
+
- `session.verifyToken(isDebugging, redirectUrl)` - Protect routes with token verification
|
|
151
|
+
- `session.callback(initUser)` - SSO callback handler for token generation
|
|
152
|
+
- `session.refresh(initUser)` - Refresh user authentication based on auth mode
|
|
153
|
+
- `session.logout(redirect?, all?)` - Logout current or all tokens
|
|
154
|
+
|
|
155
|
+
**Token Storage (Client-Side):**
|
|
156
|
+
|
|
157
|
+
When using token-based authentication, the client-side HTML page stores the token in `localStorage`:
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<script>
|
|
161
|
+
// Store auth data in localStorage
|
|
162
|
+
localStorage.setItem('authToken', ${JSON.stringify(token)});
|
|
163
|
+
localStorage.setItem('tokenExpiry', ${Date.now() + sessionAge});
|
|
164
|
+
localStorage.setItem('user', ${JSON.stringify({
|
|
165
|
+
email: user.email,
|
|
166
|
+
name: user.name,
|
|
167
|
+
})});
|
|
168
|
+
|
|
169
|
+
// Redirect to original destination
|
|
170
|
+
window.location.replace(redirectUrl);
|
|
171
|
+
</script>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## SessionManager Configuration Options
|
|
175
|
+
|
|
176
|
+
| Option | Type | Default | Description |
|
|
177
|
+
|--------|------|---------|-------------|
|
|
178
|
+
| `SSO_ENDPOINT_URL` | string | - | Identity provider endpoint URL |
|
|
179
|
+
| `SSO_CLIENT_ID` | string | - | SSO client ID |
|
|
180
|
+
| `SSO_CLIENT_SECRET` | string | - | SSO client secret |
|
|
181
|
+
| `SSO_SUCCESS_URL` | string | - | Redirect URL after successful login (token mode) |
|
|
182
|
+
| `SSO_FAILURE_URL` | string | - | Redirect URL after failed login (token mode) |
|
|
183
|
+
| `SESSION_MODE` | string | `SessionMode.SESSION` | Authentication mode: `SessionMode.SESSION` or `SessionMode.TOKEN` |
|
|
184
|
+
| `SESSION_AGE` | number | 64800000 | Session timeout in milliseconds |
|
|
185
|
+
| `SESSION_COOKIE_PATH` | string | `'/'` | Session cookie path |
|
|
186
|
+
| `SESSION_SECRET` | string | - | Session/JWT secret key |
|
|
187
|
+
| `SESSION_PREFIX` | string | `'ibmid:'` | Redis session/key prefix |
|
|
188
|
+
| `REDIS_URL` | string | - | Redis connection URL (optional) |
|
|
189
|
+
| `REDIS_CERT_PATH` | string | - | Path to Redis TLS certificate |
|
|
190
|
+
| `JWT_ALGORITHM` | string | `'dir'` | JWT signing algorithm |
|
|
191
|
+
| `JWT_ENCRYPTION` | string | `'A256GCM'` | JWE encryption algorithm |
|
|
192
|
+
| `JWT_EXPIRATION_TIME` | string | `'10m'` | Token expiration duration |
|
|
193
|
+
| `JWT_CLOCK_TOLERANCE` | number | 30 | Clock skew tolerance in seconds |
|
|
194
|
+
| `JWT_SECRET_HASH_ALGORITHM` | string | `'SHA-256'` | Algorithm for hashing secrets |
|
|
195
|
+
| `JWT_ISSUER` | string | - | JWT issuer identifier |
|
|
196
|
+
| `JWT_AUDIENCE` | string | - | JWT audience identifier |
|
|
197
|
+
| `JWT_SUBJECT` | string | - | JWT subject identifier |
|
|
198
|
+
|
|
108
199
|
## Features
|
|
109
200
|
|
|
110
201
|
- ✅ **SSO Integration** - Full SSO support with Redis or memory storage
|
|
202
|
+
- ✅ **Dual Authentication Modes** - SESSION (cookies) or TOKEN (Bearer tokens)
|
|
203
|
+
- ✅ **Token Refresh** - Automatic token refresh via SSO endpoints
|
|
204
|
+
- ✅ **Session Refresh Locks** - Prevent concurrent token/session refresh attacks
|
|
111
205
|
- ✅ **JWT Security** - Encrypted JWT tokens using JWE (jose library)
|
|
112
206
|
- ✅ **Flexible Routing** - Easy mounting with context paths and middleware
|
|
113
207
|
- ✅ **Redis Support** - TLS/SSL and automatic reconnection
|
|
@@ -148,4 +242,4 @@ import type {
|
|
|
148
242
|
|
|
149
243
|
## License
|
|
150
244
|
|
|
151
|
-
[Apache 2.0](LICENSE)
|
|
245
|
+
[Apache 2.0](LICENSE)
|
|
@@ -141,6 +141,12 @@ const _getErrorMessage = (error, defaultMessage) => {
|
|
|
141
141
|
};
|
|
142
142
|
|
|
143
143
|
export const httpHelper = {
|
|
144
|
+
/**
|
|
145
|
+
* Format a string with placeholders
|
|
146
|
+
* @param {string} str String with {0}, {1}, etc. placeholders
|
|
147
|
+
* @param {...string} args Values to replace placeholders
|
|
148
|
+
* @returns {string} Formatted string
|
|
149
|
+
*/
|
|
144
150
|
format (str, ...args) {
|
|
145
151
|
const matched = str.match(/{\d}/ig);
|
|
146
152
|
matched.forEach((element, index) => {
|
package/components/jwt.js
CHANGED
|
@@ -1,51 +1,89 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
1
3
|
import { jwtDecrypt, EncryptJWT } from 'jose';
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* JwtManager configuration options
|
|
7
|
+
* Uses strict UPPERCASE naming convention with JWT_ prefix for all property names.
|
|
8
|
+
*/
|
|
3
9
|
export class JwtManager {
|
|
10
|
+
/** @type {string} JWE algorithm */
|
|
11
|
+
algorithm;
|
|
12
|
+
|
|
13
|
+
/** @type {string} Encryption method */
|
|
14
|
+
encryption;
|
|
15
|
+
|
|
16
|
+
/** @type {string} Token expiration time */
|
|
17
|
+
expirationTime;
|
|
18
|
+
|
|
19
|
+
/** @type {number} Clock tolerance in seconds */
|
|
20
|
+
clockTolerance;
|
|
21
|
+
|
|
22
|
+
/** @type {string} Hash algorithm for secret derivation */
|
|
23
|
+
secretHashAlgorithm;
|
|
24
|
+
|
|
25
|
+
/** @type {string|null} Optional JWT issuer claim */
|
|
26
|
+
issuer;
|
|
27
|
+
|
|
28
|
+
/** @type {string|null} Optional JWT audience claim */
|
|
29
|
+
audience;
|
|
30
|
+
|
|
31
|
+
/** @type {string|null} Optional JWT subject claim */
|
|
32
|
+
subject;
|
|
4
33
|
/**
|
|
5
34
|
* Create a new JwtManager instance with configurable defaults
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @
|
|
9
|
-
* @
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
12
|
-
* @
|
|
13
|
-
* @
|
|
14
|
-
* @
|
|
35
|
+
* Constructor options use UPPERCASE naming convention with JWT_ prefix (e.g., JWT_ALGORITHM).
|
|
36
|
+
*
|
|
37
|
+
* @typedef {Object} JwtManagerOptions JwtManager configuration options
|
|
38
|
+
* @property {string} [JWT_ALGORITHM='dir'] JWE algorithm (default: 'dir')
|
|
39
|
+
* @property {string} [JWT_ENCRYPTION='A256GCM'] Encryption method (default: 'A256GCM')
|
|
40
|
+
* @property {string} [JWT_EXPIRATION_TIME='10m'] Token expiration time (default: '10m')
|
|
41
|
+
* @property {string} [JWT_SECRET_HASH_ALGORITHM='SHA-256'] Hash algorithm (default: 'SHA-256')
|
|
42
|
+
* @property {string?} [JWT_ISSUER] Optional JWT issuer claim
|
|
43
|
+
* @property {string?} [JWT_AUDIENCE] Optional JWT audience claim
|
|
44
|
+
* @property {string?} [JWT_SUBJECT] Optional JWT subject claim
|
|
45
|
+
* @property {number} [JWT_CLOCK_TOLERANCE=30] Clock tolerance in seconds (default: 30)
|
|
46
|
+
* @param {JwtManagerOptions} options Configuration options
|
|
15
47
|
*/
|
|
16
48
|
constructor(options = {}) {
|
|
17
|
-
this.algorithm = options.
|
|
18
|
-
this.encryption = options.
|
|
19
|
-
this.expirationTime = options.
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
49
|
+
this.algorithm = options.JWT_ALGORITHM || 'dir';
|
|
50
|
+
this.encryption = options.JWT_ENCRYPTION || 'A256GCM';
|
|
51
|
+
this.expirationTime = options.JWT_EXPIRATION_TIME || '10m';
|
|
52
|
+
this.secretHashAlgorithm = options.JWT_SECRET_HASH_ALGORITHM || 'SHA-256';
|
|
53
|
+
this.issuer = options.JWT_ISSUER;
|
|
54
|
+
this.audience = options.JWT_AUDIENCE;
|
|
55
|
+
this.subject = options.JWT_SUBJECT;
|
|
56
|
+
this.clockTolerance = options.JWT_CLOCK_TOLERANCE ?? 30;
|
|
25
57
|
}
|
|
26
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Encrypt method options (camelCase naming convention, uses instance defaults when not provided)
|
|
61
|
+
*
|
|
62
|
+
* @typedef {Object} JwtEncryptOptions Encryption method options
|
|
63
|
+
* @property {string} [algorithm='dir'] JWE algorithm (overrides instance JWT_ALGORITHM)
|
|
64
|
+
* @property {string} [encryption='A256GCM'] Encryption method (overrides instance JWT_ENCRYPTION)
|
|
65
|
+
* @property {string} [expirationTime='10m'] Token expiration time (overrides instance JWT_EXPIRATION_TIME)
|
|
66
|
+
* @property {string} [secretHashAlgorithm='SHA-256'] Hash algorithm for secret derivation (overrides instance JWT_SECRET_HASH_ALGORITHM)
|
|
67
|
+
* @property {string?} [issuer] Optional JWT issuer claim (overrides instance JWT_ISSUER)
|
|
68
|
+
* @property {string?} [audience] Optional JWT audience claim (overrides instance JWT_AUDIENCE)
|
|
69
|
+
* @property {string?} [subject] Optional JWT subject claim (overrides instance JWT_SUBJECT)
|
|
70
|
+
*/
|
|
27
71
|
/**
|
|
28
72
|
* Generate JWT token for user session
|
|
73
|
+
*
|
|
29
74
|
* @param {import('jose').JWTPayload} data User data payload
|
|
30
75
|
* @param {string} secret Secret key or password for encryption
|
|
31
|
-
* @param {
|
|
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
|
|
76
|
+
* @param {JwtEncryptOptions} [options] Per-call configuration overrides (camelCase naming convention)
|
|
39
77
|
* @returns {Promise<string>} Returns encrypted JWT token
|
|
40
78
|
*/
|
|
41
79
|
async encrypt(data, secret, options = {}) {
|
|
42
|
-
const algorithm = options.algorithm
|
|
43
|
-
const encryption = options.encryption
|
|
44
|
-
const expirationTime = options.expirationTime
|
|
45
|
-
const secretHashAlgorithm = options.secretHashAlgorithm
|
|
46
|
-
const issuer = options.issuer
|
|
47
|
-
const audience = options.audience
|
|
48
|
-
const subject = options.subject
|
|
80
|
+
const algorithm = options.algorithm ?? this.algorithm;
|
|
81
|
+
const encryption = options.encryption ?? this.encryption;
|
|
82
|
+
const expirationTime = options.expirationTime ?? this.expirationTime;
|
|
83
|
+
const secretHashAlgorithm = options.secretHashAlgorithm ?? this.secretHashAlgorithm;
|
|
84
|
+
const issuer = options.issuer ?? this.issuer;
|
|
85
|
+
const audience = options.audience ?? this.audience;
|
|
86
|
+
const subject = options.subject ?? this.subject;
|
|
49
87
|
|
|
50
88
|
const secretHash = await crypto.subtle.digest(
|
|
51
89
|
secretHashAlgorithm,
|
|
@@ -69,23 +107,29 @@ export class JwtManager {
|
|
|
69
107
|
}
|
|
70
108
|
|
|
71
109
|
/**
|
|
72
|
-
* Decrypt
|
|
110
|
+
* Decrypt method options (camelCase naming convention, uses instance defaults when not provided)
|
|
111
|
+
*
|
|
112
|
+
* @typedef {Object} JwtDecryptOptions Decryption method options
|
|
113
|
+
* @property {number} [clockTolerance=30] Clock tolerance in seconds (overrides instance JWT_CLOCK_TOLERANCE)
|
|
114
|
+
* @property {string} [secretHashAlgorithm='SHA-256'] Hash algorithm for secret derivation (overrides instance JWT_SECRET_HASH_ALGORITHM)
|
|
115
|
+
* @property {string?} [issuer] Optional JWT issuer claim for validation (overrides instance JWT_ISSUER)
|
|
116
|
+
* @property {string?} [audience] Optional JWT audience claim for validation (overrides instance JWT_AUDIENCE)
|
|
117
|
+
* @property {string?} [subject] Optional JWT subject claim for validation (overrides instance JWT_SUBJECT)
|
|
118
|
+
**/
|
|
119
|
+
/**
|
|
120
|
+
* Decrypt JWT
|
|
121
|
+
*
|
|
73
122
|
* @param {string} token JWT token to decrypt
|
|
74
123
|
* @param {string} secret Secret key or password for decryption
|
|
75
|
-
* @param {
|
|
76
|
-
* @
|
|
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
|
|
124
|
+
* @param {JwtDecryptOptions} [options] Per-call configuration overrides (camelCase naming convention)
|
|
125
|
+
* @returns {Promise<import('jose').JWTDecryptResult<import('jose').EncryptJWT>} Returns decrypted JWT token
|
|
82
126
|
*/
|
|
83
127
|
async decrypt(token, secret, options = {}) {
|
|
84
128
|
const clockTolerance = options.clockTolerance ?? this.clockTolerance;
|
|
85
|
-
const secretHashAlgorithm = options.secretHashAlgorithm
|
|
86
|
-
const issuer = options.issuer
|
|
87
|
-
const audience = options.audience
|
|
88
|
-
const subject = options.subject
|
|
129
|
+
const secretHashAlgorithm = options.secretHashAlgorithm ?? this.secretHashAlgorithm;
|
|
130
|
+
const issuer = options.issuer ?? this.issuer;
|
|
131
|
+
const audience = options.audience ?? this.audience;
|
|
132
|
+
const subject = options.subject ?? this.subject;
|
|
89
133
|
|
|
90
134
|
const secretHash = await crypto.subtle.digest(
|
|
91
135
|
secretHashAlgorithm,
|
|
@@ -101,4 +145,4 @@ export class JwtManager {
|
|
|
101
145
|
|
|
102
146
|
return await jwtDecrypt(token, new Uint8Array(secretHash), decryptOptions);
|
|
103
147
|
}
|
|
104
|
-
}
|
|
148
|
+
}
|