@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/docs/session-manager.md
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# SessionManager
|
|
2
|
-
|
|
3
|
-
Provides SSO (Single Sign-On) session management with support for Redis and memory-based session stores.
|
|
4
|
-
|
|
5
|
-
## Configuration Options
|
|
6
|
-
|
|
7
|
-
```javascript
|
|
8
|
-
// Example configuration object
|
|
9
|
-
// All fields are strings except SESSION_AGE which is a number
|
|
10
|
-
|
|
11
|
-
const config = {
|
|
12
|
-
// SSO Configuration
|
|
13
|
-
SSO_ENDPOINT_URL: 'https://sso.example.com',
|
|
14
|
-
SSO_CLIENT_ID: 'your-client-id',
|
|
15
|
-
SSO_CLIENT_SECRET: 'your-client-secret',
|
|
16
|
-
SSO_SUCCESS_URL: '/dashboard',
|
|
17
|
-
SSO_FAILURE_URL: '/login',
|
|
18
|
-
|
|
19
|
-
// Session Configuration
|
|
20
|
-
SESSION_AGE: 64800000, // 18 hours in milliseconds
|
|
21
|
-
SESSION_COOKIE_PATH: '/',
|
|
22
|
-
SESSION_SECRET: 'your-session-secret',
|
|
23
|
-
SESSION_PREFIX: 'ibmid:', // Default value when not provided
|
|
24
|
-
|
|
25
|
-
// Redis Configuration (optional - uses memory store if not provided)
|
|
26
|
-
REDIS_URL: 'redis://localhost:6379',
|
|
27
|
-
REDIS_CERT_PATH: '/path/to/cert.pem' // For TLS connections
|
|
28
|
-
};
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## ⚠️ Important: Singleton Pattern
|
|
32
|
-
|
|
33
|
-
**SessionManager should be instantiated once and exported as a singleton.**
|
|
34
|
-
|
|
35
|
-
**Why?**
|
|
36
|
-
- SessionManager manages Redis connections (if configured)
|
|
37
|
-
- Multiple instances can lead to connection pool exhaustion
|
|
38
|
-
- Session state consistency requires a single source of truth
|
|
39
|
-
- Middleware functions need to reference the same instance
|
|
40
|
-
|
|
41
|
-
**✅ Recommended:** Create a separate module that exports a single SessionManager instance
|
|
42
|
-
**❌ Avoid:** Creating new SessionManager instances in multiple files
|
|
43
|
-
|
|
44
|
-
## Recommended File Structure
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
your-project/
|
|
48
|
-
├── src/
|
|
49
|
-
│ ├── config/
|
|
50
|
-
│ │ └── session-manager.js ← Create SessionManager singleton here
|
|
51
|
-
│ ├── routes/
|
|
52
|
-
│ │ └── auth.js ← Import session from config
|
|
53
|
-
│ └── app.js ← Import and setup session
|
|
54
|
-
└── package.json
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Usage Example
|
|
58
|
-
|
|
59
|
-
### Step 1: Create SessionManager Singleton
|
|
60
|
-
|
|
61
|
-
Create a dedicated file for your SessionManager instance:
|
|
62
|
-
|
|
63
|
-
```javascript
|
|
64
|
-
// config/session-manager.js
|
|
65
|
-
import { SessionManager } from '@igxjs/node-components';
|
|
66
|
-
|
|
67
|
-
// Create and export a single SessionManager instance
|
|
68
|
-
export const session = new SessionManager({
|
|
69
|
-
SSO_ENDPOINT_URL: process.env.SSO_ENDPOINT_URL,
|
|
70
|
-
SSO_CLIENT_ID: process.env.SSO_CLIENT_ID,
|
|
71
|
-
SSO_CLIENT_SECRET: process.env.SSO_CLIENT_SECRET,
|
|
72
|
-
SSO_SUCCESS_URL: '/dashboard',
|
|
73
|
-
SSO_FAILURE_URL: '/login',
|
|
74
|
-
SESSION_AGE: 64800000, // 18 hours in milliseconds
|
|
75
|
-
SESSION_COOKIE_PATH: '/',
|
|
76
|
-
SESSION_SECRET: process.env.SESSION_SECRET,
|
|
77
|
-
SESSION_PREFIX: 'ibmid:',
|
|
78
|
-
REDIS_URL: process.env.REDIS_URL,
|
|
79
|
-
REDIS_CERT_PATH: process.env.REDIS_CERT_PATH
|
|
80
|
-
});
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Step 2: Setup and Use in Your Application
|
|
84
|
-
|
|
85
|
-
Import and use the singleton instance throughout your application:
|
|
86
|
-
|
|
87
|
-
```javascript
|
|
88
|
-
// app.js or index.js
|
|
89
|
-
import express from 'express';
|
|
90
|
-
import { session } from './config/session-manager.js';
|
|
91
|
-
|
|
92
|
-
const app = express();
|
|
93
|
-
|
|
94
|
-
// Initialize session middleware (call once during app startup)
|
|
95
|
-
await session.setup(app, (user) => {
|
|
96
|
-
// Process user object - compute permissions, avatar URL, etc.
|
|
97
|
-
return {
|
|
98
|
-
...user,
|
|
99
|
-
displayName: user.email?.split('@')[0],
|
|
100
|
-
hasAdminAccess: user.authorized && user.email?.endsWith('@admin.com')
|
|
101
|
-
};
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Use session instance in your routes
|
|
105
|
-
app.get('/protected', session.authenticate(), (req, res) => {
|
|
106
|
-
res.json({ user: req.user });
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
app.get('/auth/callback', session.callback((user) => {
|
|
110
|
-
return {
|
|
111
|
-
...user,
|
|
112
|
-
loginTime: new Date()
|
|
113
|
-
};
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
|
-
app.get('/auth/providers', session.identityProviders());
|
|
117
|
-
app.post('/auth/refresh', session.refresh((user) => {
|
|
118
|
-
return { ...user, refreshedAt: new Date() };
|
|
119
|
-
}));
|
|
120
|
-
app.get('/auth/logout', session.logout());
|
|
121
|
-
|
|
122
|
-
app.listen(3000, () => {
|
|
123
|
-
console.log('Server running on port 3000');
|
|
124
|
-
});
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Step 3: Import in Other Files
|
|
128
|
-
|
|
129
|
-
```javascript
|
|
130
|
-
// routes/auth.js
|
|
131
|
-
import { Router } from 'express';
|
|
132
|
-
import { session } from '../config/session-manager.js';
|
|
133
|
-
|
|
134
|
-
const router = Router();
|
|
135
|
-
|
|
136
|
-
// Reuse the same session instance
|
|
137
|
-
router.get('/profile', session.authenticate(), (req, res) => {
|
|
138
|
-
res.json({ profile: req.user });
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
export default router;
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## API Methods
|
|
145
|
-
|
|
146
|
-
- **`setup(app, updateUser)`** - Initialize session configurations
|
|
147
|
-
- **`authenticate(isDebugging?, redirectUrl?)`** - Resource protection middleware
|
|
148
|
-
- **`callback(initUser)`** - SSO callback handler for successful login
|
|
149
|
-
- **`identityProviders()`** - Get available identity providers
|
|
150
|
-
- **`logout()`** - Application logout handler (not SSO logout)
|
|
151
|
-
- **`refresh(initUser)`** - Refresh user session with new token
|
|
152
|
-
- **`redisManager()`** - Get the RedisManager instance (returns RedisManager or null)
|
|
153
|
-
- **`hasLock(email)`** - Check if email has a session refresh lock
|
|
154
|
-
- **`lock(email)`** - Lock email for session refresh (prevents concurrent refreshes)
|
|
155
|
-
- **`clearLocks()`** - Clear expired session refresh locks
|
|
156
|
-
|
|
157
|
-
## Related Documentation
|
|
158
|
-
|
|
159
|
-
- [RedisManager](./redis-manager.md) - Used internally by SessionManager for Redis storage
|
|
160
|
-
- [Back to main documentation](../README.md)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'mocha';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
import { httpCodes, httpMessages, CustomError } from '../components/http-handlers.js';
|
|
4
|
-
|
|
5
|
-
describe('HTTP Handlers', () => {
|
|
6
|
-
describe('httpCodes', () => {
|
|
7
|
-
it('should have correct HTTP status codes', () => {
|
|
8
|
-
expect(httpCodes.OK).to.equal(200);
|
|
9
|
-
expect(httpCodes.BAD_REQUEST).to.equal(400);
|
|
10
|
-
expect(httpCodes.NOT_FOUND).to.equal(404);
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('CustomError', () => {
|
|
15
|
-
it('should create a CustomError', () => {
|
|
16
|
-
const error = new CustomError(404, 'Not found');
|
|
17
|
-
expect(error.code).to.equal(404);
|
|
18
|
-
expect(error.message).to.equal('Not found');
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
});
|
package/tests/jwt.test.js
DELETED
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'mocha';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
import { JwtManager } from '../components/jwt.js';
|
|
4
|
-
|
|
5
|
-
describe('JwtManager', () => {
|
|
6
|
-
let jwtManager;
|
|
7
|
-
const testSecret = 'test-secret-key-12345';
|
|
8
|
-
const testData = {
|
|
9
|
-
userId: '123',
|
|
10
|
-
email: 'test@example.com',
|
|
11
|
-
role: 'user'
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
jwtManager = new JwtManager();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('Constructor', () => {
|
|
19
|
-
it('should create JwtManager instance with default options', () => {
|
|
20
|
-
const manager = new JwtManager();
|
|
21
|
-
expect(manager).to.be.instanceOf(JwtManager);
|
|
22
|
-
expect(manager.algorithm).to.equal('dir');
|
|
23
|
-
expect(manager.encryption).to.equal('A256GCM');
|
|
24
|
-
expect(manager.expirationTime).to.equal('10m');
|
|
25
|
-
expect(manager.clockTolerance).to.equal(30);
|
|
26
|
-
expect(manager.secretHashAlgorithm).to.equal('SHA-256');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should create JwtManager instance with custom options', () => {
|
|
30
|
-
const options = {
|
|
31
|
-
algorithm: 'A128KW',
|
|
32
|
-
encryption: 'A128GCM',
|
|
33
|
-
expirationTime: '1h',
|
|
34
|
-
clockTolerance: 60,
|
|
35
|
-
secretHashAlgorithm: 'SHA-512',
|
|
36
|
-
issuer: 'test-issuer',
|
|
37
|
-
audience: 'test-audience',
|
|
38
|
-
subject: 'test-subject'
|
|
39
|
-
};
|
|
40
|
-
const manager = new JwtManager(options);
|
|
41
|
-
expect(manager.algorithm).to.equal('A128KW');
|
|
42
|
-
expect(manager.encryption).to.equal('A128GCM');
|
|
43
|
-
expect(manager.expirationTime).to.equal('1h');
|
|
44
|
-
expect(manager.clockTolerance).to.equal(60);
|
|
45
|
-
expect(manager.secretHashAlgorithm).to.equal('SHA-512');
|
|
46
|
-
expect(manager.issuer).to.equal('test-issuer');
|
|
47
|
-
expect(manager.audience).to.equal('test-audience');
|
|
48
|
-
expect(manager.subject).to.equal('test-subject');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should handle clockTolerance of 0', () => {
|
|
52
|
-
const manager = new JwtManager({ clockTolerance: 0 });
|
|
53
|
-
expect(manager.clockTolerance).to.equal(0);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('encrypt', () => {
|
|
58
|
-
it('should encrypt data and return a JWT token', async () => {
|
|
59
|
-
const token = await jwtManager.encrypt(testData, testSecret);
|
|
60
|
-
expect(token).to.be.a('string');
|
|
61
|
-
expect(token.split('.').length).to.equal(5); // JWE has 5 parts
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should encrypt with custom encryption method', async () => {
|
|
65
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
66
|
-
algorithm: 'dir',
|
|
67
|
-
encryption: 'A256GCM'
|
|
68
|
-
});
|
|
69
|
-
expect(token).to.be.a('string');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should encrypt with custom expiration time', async () => {
|
|
73
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
74
|
-
expirationTime: '1h'
|
|
75
|
-
});
|
|
76
|
-
expect(token).to.be.a('string');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should encrypt with issuer claim', async () => {
|
|
80
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
81
|
-
issuer: 'test-issuer'
|
|
82
|
-
});
|
|
83
|
-
expect(token).to.be.a('string');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should encrypt with audience claim', async () => {
|
|
87
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
88
|
-
audience: 'test-audience'
|
|
89
|
-
});
|
|
90
|
-
expect(token).to.be.a('string');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should encrypt with subject claim', async () => {
|
|
94
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
95
|
-
subject: 'test-subject'
|
|
96
|
-
});
|
|
97
|
-
expect(token).to.be.a('string');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should encrypt with all optional claims', async () => {
|
|
101
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
102
|
-
issuer: 'test-issuer',
|
|
103
|
-
audience: 'test-audience',
|
|
104
|
-
subject: 'test-subject'
|
|
105
|
-
});
|
|
106
|
-
expect(token).to.be.a('string');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should use default claims from constructor if not overridden', async () => {
|
|
110
|
-
const manager = new JwtManager({
|
|
111
|
-
issuer: 'default-issuer',
|
|
112
|
-
audience: 'default-audience'
|
|
113
|
-
});
|
|
114
|
-
const token = await manager.encrypt(testData, testSecret);
|
|
115
|
-
expect(token).to.be.a('string');
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('decrypt', () => {
|
|
120
|
-
it('should decrypt a valid JWT token', async () => {
|
|
121
|
-
const token = await jwtManager.encrypt(testData, testSecret);
|
|
122
|
-
const result = await jwtManager.decrypt(token, testSecret);
|
|
123
|
-
|
|
124
|
-
expect(result).to.have.property('payload');
|
|
125
|
-
expect(result).to.have.property('protectedHeader');
|
|
126
|
-
expect(result.payload).to.include(testData);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should decrypt token with correct payload data', async () => {
|
|
130
|
-
const token = await jwtManager.encrypt(testData, testSecret);
|
|
131
|
-
const result = await jwtManager.decrypt(token, testSecret);
|
|
132
|
-
|
|
133
|
-
expect(result.payload.userId).to.equal(testData.userId);
|
|
134
|
-
expect(result.payload.email).to.equal(testData.email);
|
|
135
|
-
expect(result.payload.role).to.equal(testData.role);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should include standard JWT claims in decrypted payload', async () => {
|
|
139
|
-
const token = await jwtManager.encrypt(testData, testSecret);
|
|
140
|
-
const result = await jwtManager.decrypt(token, testSecret);
|
|
141
|
-
|
|
142
|
-
expect(result.payload).to.have.property('iat');
|
|
143
|
-
expect(result.payload).to.have.property('exp');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should fail to decrypt with wrong secret', async () => {
|
|
147
|
-
const token = await jwtManager.encrypt(testData, testSecret);
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
await jwtManager.decrypt(token, 'wrong-secret');
|
|
151
|
-
expect.fail('Should have thrown an error');
|
|
152
|
-
} catch (error) {
|
|
153
|
-
expect(error).to.exist;
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('should decrypt token with custom clock tolerance', async () => {
|
|
158
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
159
|
-
expirationTime: '1s'
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Use higher clock tolerance to allow slightly expired tokens
|
|
163
|
-
const result = await jwtManager.decrypt(token, testSecret, {
|
|
164
|
-
clockTolerance: 120
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
expect(result.payload).to.include(testData);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should validate issuer claim when provided', async () => {
|
|
171
|
-
const issuer = 'test-issuer';
|
|
172
|
-
const token = await jwtManager.encrypt(testData, testSecret, { issuer });
|
|
173
|
-
|
|
174
|
-
const result = await jwtManager.decrypt(token, testSecret, { issuer });
|
|
175
|
-
expect(result.payload.iss).to.equal(issuer);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should fail when issuer claim does not match', async () => {
|
|
179
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
180
|
-
issuer: 'correct-issuer'
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
await jwtManager.decrypt(token, testSecret, {
|
|
185
|
-
issuer: 'wrong-issuer'
|
|
186
|
-
});
|
|
187
|
-
expect.fail('Should have thrown an error');
|
|
188
|
-
} catch (error) {
|
|
189
|
-
expect(error).to.exist;
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('should validate audience claim when provided', async () => {
|
|
194
|
-
const audience = 'test-audience';
|
|
195
|
-
const token = await jwtManager.encrypt(testData, testSecret, { audience });
|
|
196
|
-
|
|
197
|
-
const result = await jwtManager.decrypt(token, testSecret, { audience });
|
|
198
|
-
expect(result.payload.aud).to.equal(audience);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('should fail when audience claim does not match', async () => {
|
|
202
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
203
|
-
audience: 'correct-audience'
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
await jwtManager.decrypt(token, testSecret, {
|
|
208
|
-
audience: 'wrong-audience'
|
|
209
|
-
});
|
|
210
|
-
expect.fail('Should have thrown an error');
|
|
211
|
-
} catch (error) {
|
|
212
|
-
expect(error).to.exist;
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('should validate subject claim when provided', async () => {
|
|
217
|
-
const subject = 'test-subject';
|
|
218
|
-
const token = await jwtManager.encrypt(testData, testSecret, { subject });
|
|
219
|
-
|
|
220
|
-
const result = await jwtManager.decrypt(token, testSecret, { subject });
|
|
221
|
-
expect(result.payload.sub).to.equal(subject);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('should fail when subject claim does not match', async () => {
|
|
225
|
-
const token = await jwtManager.encrypt(testData, testSecret, {
|
|
226
|
-
subject: 'correct-subject'
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
await jwtManager.decrypt(token, testSecret, {
|
|
231
|
-
subject: 'wrong-subject'
|
|
232
|
-
});
|
|
233
|
-
expect.fail('Should have thrown an error');
|
|
234
|
-
} catch (error) {
|
|
235
|
-
expect(error).to.exist;
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('should use default claims from constructor for validation', async () => {
|
|
240
|
-
const manager = new JwtManager({
|
|
241
|
-
issuer: 'default-issuer',
|
|
242
|
-
audience: 'default-audience',
|
|
243
|
-
subject: 'default-subject'
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const token = await manager.encrypt(testData, testSecret);
|
|
247
|
-
const result = await manager.decrypt(token, testSecret);
|
|
248
|
-
|
|
249
|
-
expect(result.payload.iss).to.equal('default-issuer');
|
|
250
|
-
expect(result.payload.aud).to.equal('default-audience');
|
|
251
|
-
expect(result.payload.sub).to.equal('default-subject');
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe('encrypt/decrypt round-trip', () => {
|
|
256
|
-
it('should successfully encrypt and decrypt data', async () => {
|
|
257
|
-
const originalData = {
|
|
258
|
-
id: 'user-123',
|
|
259
|
-
name: 'John Doe',
|
|
260
|
-
permissions: ['read', 'write']
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const token = await jwtManager.encrypt(originalData, testSecret);
|
|
264
|
-
const result = await jwtManager.decrypt(token, testSecret);
|
|
265
|
-
|
|
266
|
-
expect(result.payload.id).to.equal(originalData.id);
|
|
267
|
-
expect(result.payload.name).to.equal(originalData.name);
|
|
268
|
-
expect(result.payload.permissions).to.deep.equal(originalData.permissions);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('should handle SHA-256 hash algorithm', async () => {
|
|
272
|
-
const manager = new JwtManager({
|
|
273
|
-
secretHashAlgorithm: 'SHA-256',
|
|
274
|
-
encryption: 'A256GCM' // SHA-256 produces 256 bits, matches A256GCM
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
const token = await manager.encrypt(testData, testSecret);
|
|
278
|
-
const result = await manager.decrypt(token, testSecret);
|
|
279
|
-
|
|
280
|
-
expect(result.payload).to.include(testData);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('should handle empty payload', async () => {
|
|
284
|
-
const emptyData = {};
|
|
285
|
-
const token = await jwtManager.encrypt(emptyData, testSecret);
|
|
286
|
-
const result = await jwtManager.decrypt(token, testSecret);
|
|
287
|
-
|
|
288
|
-
expect(result.payload).to.have.property('iat');
|
|
289
|
-
expect(result.payload).to.have.property('exp');
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('should preserve different data types', async () => {
|
|
293
|
-
const complexData = {
|
|
294
|
-
string: 'test',
|
|
295
|
-
number: 42,
|
|
296
|
-
boolean: true,
|
|
297
|
-
array: [1, 2, 3],
|
|
298
|
-
object: { nested: 'value' },
|
|
299
|
-
null: null
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
const token = await jwtManager.encrypt(complexData, testSecret);
|
|
303
|
-
const result = await jwtManager.decrypt(token, testSecret);
|
|
304
|
-
|
|
305
|
-
expect(result.payload.string).to.equal(complexData.string);
|
|
306
|
-
expect(result.payload.number).to.equal(complexData.number);
|
|
307
|
-
expect(result.payload.boolean).to.equal(complexData.boolean);
|
|
308
|
-
expect(result.payload.array).to.deep.equal(complexData.array);
|
|
309
|
-
expect(result.payload.object).to.deep.equal(complexData.object);
|
|
310
|
-
expect(result.payload.null).to.equal(complexData.null);
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
describe('Error Handling', () => {
|
|
315
|
-
it('should fail with invalid token format', async () => {
|
|
316
|
-
try {
|
|
317
|
-
await jwtManager.decrypt('invalid.token.format', testSecret);
|
|
318
|
-
expect.fail('Should have thrown an error');
|
|
319
|
-
} catch (error) {
|
|
320
|
-
expect(error).to.exist;
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('should fail with empty token', async () => {
|
|
325
|
-
try {
|
|
326
|
-
await jwtManager.decrypt('', testSecret);
|
|
327
|
-
expect.fail('Should have thrown an error');
|
|
328
|
-
} catch (error) {
|
|
329
|
-
expect(error).to.exist;
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('should fail with corrupted token', async () => {
|
|
334
|
-
const token = await jwtManager.encrypt(testData, testSecret);
|
|
335
|
-
const corruptedToken = token.slice(0, -10) + 'corrupted';
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
await jwtManager.decrypt(corruptedToken, testSecret);
|
|
339
|
-
expect.fail('Should have thrown an error');
|
|
340
|
-
} catch (error) {
|
|
341
|
-
expect(error).to.exist;
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
});
|
package/tests/redis.test.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from 'mocha';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
import sinon from 'sinon';
|
|
4
|
-
import { RedisManager } from '../components/redis.js';
|
|
5
|
-
|
|
6
|
-
describe('RedisManager', () => {
|
|
7
|
-
let redisManager;
|
|
8
|
-
let consoleStubs;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
redisManager = new RedisManager();
|
|
12
|
-
consoleStubs = {
|
|
13
|
-
info: sinon.stub(console, 'info'),
|
|
14
|
-
warn: sinon.stub(console, 'warn'),
|
|
15
|
-
error: sinon.stub(console, 'error')
|
|
16
|
-
};
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
consoleStubs.info.restore();
|
|
21
|
-
consoleStubs.warn.restore();
|
|
22
|
-
consoleStubs.error.restore();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('connect', () => {
|
|
26
|
-
it('should return false if redisUrl is empty', async () => {
|
|
27
|
-
const result = await redisManager.connect('', null);
|
|
28
|
-
expect(result).to.be.false;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should return false if redisUrl is null', async () => {
|
|
32
|
-
const result = await redisManager.connect(null, null);
|
|
33
|
-
expect(result).to.be.false;
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('getClient', () => {
|
|
38
|
-
it('should return null when not connected', () => {
|
|
39
|
-
const client = redisManager.getClient();
|
|
40
|
-
expect(client).to.be.null;
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('isConnected', () => {
|
|
45
|
-
it('should return false when client is null', async () => {
|
|
46
|
-
const result = await redisManager.isConnected();
|
|
47
|
-
expect(result).to.be.false;
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
});
|
package/tests/router.test.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'mocha';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
import sinon from 'sinon';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
import { FlexRouter } from '../components/router.js';
|
|
6
|
-
|
|
7
|
-
describe('FlexRouter', () => {
|
|
8
|
-
let app, router, middleware1, middleware2;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
app = express();
|
|
12
|
-
router = express.Router();
|
|
13
|
-
middleware1 = sinon.stub().callsFake((req, res, next) => next());
|
|
14
|
-
middleware2 = sinon.stub().callsFake((req, res, next) => next());
|
|
15
|
-
sinon.spy(app, 'use');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('constructor', () => {
|
|
19
|
-
it('should create FlexRouter with context and router', () => {
|
|
20
|
-
const flexRouter = new FlexRouter('/api', router);
|
|
21
|
-
expect(flexRouter.context).to.equal('/api');
|
|
22
|
-
expect(flexRouter.router).to.equal(router);
|
|
23
|
-
expect(flexRouter.handlers).to.deep.equal([]);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should create FlexRouter with handlers', () => {
|
|
27
|
-
const handlers = [middleware1, middleware2];
|
|
28
|
-
const flexRouter = new FlexRouter('/api', router, handlers);
|
|
29
|
-
expect(flexRouter.handlers).to.deep.equal(handlers);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('mount', () => {
|
|
34
|
-
it('should mount router to app with correct path', () => {
|
|
35
|
-
const flexRouter = new FlexRouter('/users', router);
|
|
36
|
-
flexRouter.mount(app, '/api/v1');
|
|
37
|
-
expect(app.use.calledOnce).to.be.true;
|
|
38
|
-
expect(app.use.firstCall.args[0]).to.equal('/api/v1/users');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should mount router with handlers', () => {
|
|
42
|
-
const handlers = [middleware1, middleware2];
|
|
43
|
-
const flexRouter = new FlexRouter('/protected', router, handlers);
|
|
44
|
-
flexRouter.mount(app, '/api');
|
|
45
|
-
expect(app.use.calledOnce).to.be.true;
|
|
46
|
-
expect(app.use.firstCall.args[0]).to.equal('/api/protected');
|
|
47
|
-
expect(app.use.firstCall.args[1]).to.deep.equal(handlers);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
});
|