@igxjs/node-components 1.0.7 → 1.0.8
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 +68 -510
- package/components/jwt.js +10 -10
- package/docs/README.md +54 -0
- package/docs/flex-router.md +167 -0
- package/docs/http-handlers.md +302 -0
- package/docs/jwt-manager.md +124 -0
- package/docs/redis-manager.md +210 -0
- package/docs/session-manager.md +160 -0
- package/index.d.ts +3 -2
- package/package.json +1 -1
- package/tests/jwt.test.js +345 -0
|
@@ -0,0 +1,345 @@
|
|
|
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
|
+
});
|