@igxjs/node-components 1.0.9 → 1.0.10
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 +6 -0
- package/components/jwt.js +89 -45
- package/components/session.js +36 -18
- package/index.d.ts +62 -38
- 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/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
|
-
});
|
package/tests/session.test.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from 'mocha';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
import sinon from 'sinon';
|
|
4
|
-
import { SessionManager, SessionConfig } from '../components/session.js';
|
|
5
|
-
import { CustomError, httpCodes } from '../components/http-handlers.js';
|
|
6
|
-
|
|
7
|
-
describe('SessionManager', () => {
|
|
8
|
-
let sessionManager;
|
|
9
|
-
let clock;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
clock = sinon.useFakeTimers();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
clock.restore();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('SessionConfig', () => {
|
|
20
|
-
it('should create SessionConfig instance with properties', () => {
|
|
21
|
-
const config = new SessionConfig();
|
|
22
|
-
expect(config).to.be.instanceOf(SessionConfig);
|
|
23
|
-
expect(config).to.have.property('SSO_ENDPOINT_URL');
|
|
24
|
-
expect(config).to.have.property('SESSION_SECRET');
|
|
25
|
-
expect(config).to.have.property('REDIS_URL');
|
|
26
|
-
sessionManager = new SessionManager(config);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('Lock Management', () => {
|
|
31
|
-
describe('hasLock', () => {
|
|
32
|
-
it('should return false for email without lock', () => {
|
|
33
|
-
const result = sessionManager.hasLock('test@example.com');
|
|
34
|
-
expect(result).to.be.false;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should return true for email with active lock', () => {
|
|
38
|
-
sessionManager.lock('test@example.com');
|
|
39
|
-
const result = sessionManager.hasLock('test@example.com');
|
|
40
|
-
expect(result).to.be.true;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should return false for expired lock', () => {
|
|
44
|
-
sessionManager.lock('test@example.com');
|
|
45
|
-
clock.tick(61000);
|
|
46
|
-
const result = sessionManager.hasLock('test@example.com');
|
|
47
|
-
expect(result).to.be.false;
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('lock', () => {
|
|
52
|
-
it('should create a lock for given email', () => {
|
|
53
|
-
sessionManager.lock('test@example.com');
|
|
54
|
-
expect(sessionManager.hasLock('test@example.com')).to.be.true;
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should not create lock for empty email', () => {
|
|
58
|
-
sessionManager.lock('');
|
|
59
|
-
sessionManager.lock(null);
|
|
60
|
-
expect(sessionManager.hasLock('')).to.be.false;
|
|
61
|
-
expect(sessionManager.hasLock(null)).to.be.false;
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('authenticate', () => {
|
|
67
|
-
let req, res, next;
|
|
68
|
-
|
|
69
|
-
beforeEach(() => {
|
|
70
|
-
req = { user: null };
|
|
71
|
-
res = { redirect: sinon.stub() };
|
|
72
|
-
next = sinon.stub();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should call next() if user is authorized', () => {
|
|
76
|
-
req.user = { authorized: true };
|
|
77
|
-
const middleware = sessionManager.authenticate();
|
|
78
|
-
middleware(req, res, next);
|
|
79
|
-
expect(next.calledOnce).to.be.true;
|
|
80
|
-
expect(next.firstCall.args).to.be.empty;
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should call next with error if user is not authorized', () => {
|
|
84
|
-
req.user = { authorized: false };
|
|
85
|
-
const middleware = sessionManager.authenticate();
|
|
86
|
-
middleware(req, res, next);
|
|
87
|
-
expect(next.calledOnce).to.be.true;
|
|
88
|
-
const error = next.firstCall.args[0];
|
|
89
|
-
expect(error).to.be.instanceOf(CustomError);
|
|
90
|
-
expect(error.code).to.equal(httpCodes.UNAUTHORIZED);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should redirect if redirectUrl is provided', () => {
|
|
94
|
-
req.user = { authorized: false };
|
|
95
|
-
const middleware = sessionManager.authenticate(false, '/login');
|
|
96
|
-
middleware(req, res, next);
|
|
97
|
-
expect(res.redirect.calledWith('/login')).to.be.true;
|
|
98
|
-
expect(next.called).to.be.false;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should allow access in debug mode', () => {
|
|
102
|
-
req.user = null;
|
|
103
|
-
const middleware = sessionManager.authenticate(true);
|
|
104
|
-
middleware(req, res, next);
|
|
105
|
-
expect(next.calledOnce).to.be.true;
|
|
106
|
-
expect(next.firstCall.args).to.be.empty;
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe('redisManager', () => {
|
|
111
|
-
it('should return null before initialization', () => {
|
|
112
|
-
const manager = sessionManager.redisManager();
|
|
113
|
-
expect(manager).to.be.null;
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|