@stonecrop/casl-middleware 0.14.0 → 0.15.0

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.
Files changed (40) hide show
  1. package/package.json +23 -23
  2. package/dist/index.js +0 -3
  3. package/dist/middleware/ability.js +0 -139
  4. package/dist/middleware/graphql.js +0 -120
  5. package/dist/middleware/introspection.js +0 -169
  6. package/dist/middleware/jwt.js +0 -291
  7. package/dist/middleware/postgraphile.js +0 -80
  8. package/dist/middleware/yoga.js +0 -32
  9. package/dist/src/tsdoc-metadata.json +0 -11
  10. package/dist/tests/ability.test.d.ts +0 -2
  11. package/dist/tests/ability.test.d.ts.map +0 -1
  12. package/dist/tests/ability.test.js +0 -402
  13. package/dist/tests/helpers/test-utils.d.ts +0 -46
  14. package/dist/tests/helpers/test-utils.d.ts.map +0 -1
  15. package/dist/tests/helpers/test-utils.js +0 -92
  16. package/dist/tests/introspection.test.d.ts +0 -2
  17. package/dist/tests/introspection.test.d.ts.map +0 -1
  18. package/dist/tests/introspection.test.js +0 -368
  19. package/dist/tests/jwt.test.d.ts +0 -2
  20. package/dist/tests/jwt.test.d.ts.map +0 -1
  21. package/dist/tests/jwt.test.js +0 -700
  22. package/dist/tests/middleware.test.d.ts +0 -2
  23. package/dist/tests/middleware.test.d.ts.map +0 -1
  24. package/dist/tests/middleware.test.js +0 -184
  25. package/dist/tests/postgraphile-plugin.test.d.ts +0 -2
  26. package/dist/tests/postgraphile-plugin.test.d.ts.map +0 -1
  27. package/dist/tests/postgraphile-plugin.test.js +0 -56
  28. package/dist/tests/setup.d.ts +0 -2
  29. package/dist/tests/setup.d.ts.map +0 -1
  30. package/dist/tests/setup.js +0 -11
  31. package/dist/tests/types.test.d.ts +0 -2
  32. package/dist/tests/types.test.d.ts.map +0 -1
  33. package/dist/tests/types.test.js +0 -143
  34. package/dist/tests/user-roles.test.d.ts +0 -2
  35. package/dist/tests/user-roles.test.d.ts.map +0 -1
  36. package/dist/tests/user-roles.test.js +0 -157
  37. package/dist/tests/yoga-plugin.test.d.ts +0 -2
  38. package/dist/tests/yoga-plugin.test.d.ts.map +0 -1
  39. package/dist/tests/yoga-plugin.test.js +0 -125
  40. package/dist/types/index.js +0 -0
@@ -1,700 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import jwt from 'jsonwebtoken';
3
- import { createJWTMiddleware, createJWT, createHTTPJWTMiddleware, refreshTokenUtils, } from '../src/middleware/jwt';
4
- describe('JWT Middleware', () => {
5
- const TEST_SECRET = 'test-secret-key-for-testing';
6
- const TEST_USER = {
7
- id: 'user-123',
8
- roles: ['user', 'editor'],
9
- };
10
- describe('createJWT', () => {
11
- it('should create a valid JWT token', () => {
12
- const token = createJWT(TEST_USER, {
13
- secret: TEST_SECRET,
14
- expiresIn: '1h',
15
- });
16
- expect(token).toBeDefined();
17
- expect(token.split('.')).toHaveLength(3);
18
- // Verify the token
19
- const decoded = jwt.verify(token, TEST_SECRET);
20
- expect(decoded.sub).toBe(TEST_USER.id);
21
- expect(decoded.roles).toEqual(TEST_USER.roles);
22
- });
23
- it('should include additional claims', () => {
24
- const token = createJWT(TEST_USER, {
25
- secret: TEST_SECRET,
26
- additionalClaims: {
27
- email: 'user@example.com',
28
- permissions: ['read:posts', 'write:posts'],
29
- },
30
- });
31
- const decoded = jwt.verify(token, TEST_SECRET);
32
- expect(decoded.email).toBe('user@example.com');
33
- expect(decoded.permissions).toEqual(['read:posts', 'write:posts']);
34
- });
35
- it('should set issuer and audience', () => {
36
- const token = createJWT(TEST_USER, {
37
- secret: TEST_SECRET,
38
- issuer: 'test-app',
39
- audience: 'api.example.com',
40
- });
41
- const decoded = jwt.verify(token, TEST_SECRET, {
42
- issuer: 'test-app',
43
- audience: 'api.example.com',
44
- });
45
- expect(decoded.iss).toBe('test-app');
46
- expect(decoded.aud).toBe('api.example.com');
47
- });
48
- });
49
- describe('createJWTMiddleware', () => {
50
- let mockContext;
51
- let mockNext;
52
- beforeEach(() => {
53
- mockContext = {
54
- req: {
55
- headers: {
56
- get: vi.fn(),
57
- },
58
- },
59
- user: undefined,
60
- };
61
- mockNext = vi.fn().mockResolvedValue('next-result');
62
- });
63
- it('should extract and verify valid JWT token', async () => {
64
- const token = createJWT(TEST_USER, { secret: TEST_SECRET });
65
- const middleware = createJWTMiddleware({
66
- secret: TEST_SECRET,
67
- });
68
- mockContext.req.headers.get = vi.fn().mockReturnValue(`Bearer ${token}`);
69
- await middleware(mockContext, mockNext);
70
- expect(mockContext.user).toBeDefined();
71
- expect(mockContext.user?.id).toBe(TEST_USER.id);
72
- expect(mockContext.user?.roles).toEqual(TEST_USER.roles);
73
- expect(mockNext).toHaveBeenCalled();
74
- });
75
- it('should handle token without Bearer prefix', async () => {
76
- const token = createJWT(TEST_USER, { secret: TEST_SECRET });
77
- const middleware = createJWTMiddleware({
78
- secret: TEST_SECRET,
79
- });
80
- mockContext.req.headers.get = vi.fn().mockReturnValue(token);
81
- await middleware(mockContext, mockNext);
82
- expect(mockContext.user?.id).toBe(TEST_USER.id);
83
- expect(mockNext).toHaveBeenCalled();
84
- });
85
- it('should throw error for invalid token', async () => {
86
- const middleware = createJWTMiddleware({
87
- secret: TEST_SECRET,
88
- });
89
- mockContext.req.headers.get = vi.fn().mockReturnValue('Bearer invalid-token');
90
- await expect(middleware(mockContext, mockNext)).rejects.toThrow('Invalid token');
91
- expect(mockNext).not.toHaveBeenCalled();
92
- });
93
- it('should throw error for expired token', async () => {
94
- const token = jwt.sign({ sub: TEST_USER.id, roles: TEST_USER.roles }, TEST_SECRET, { expiresIn: '-1h' } // Already expired
95
- );
96
- const middleware = createJWTMiddleware({
97
- secret: TEST_SECRET,
98
- });
99
- mockContext.req.headers.get = vi.fn().mockReturnValue(`Bearer ${token}`);
100
- await expect(middleware(mockContext, mockNext)).rejects.toThrow('Token has expired');
101
- expect(mockNext).not.toHaveBeenCalled();
102
- });
103
- it('should handle optional mode when no token is present', async () => {
104
- const middleware = createJWTMiddleware({
105
- secret: TEST_SECRET,
106
- optional: true,
107
- });
108
- mockContext.req.headers.get = vi.fn().mockReturnValue(null);
109
- await middleware(mockContext, mockNext);
110
- expect(mockContext.user).toBeUndefined();
111
- expect(mockNext).toHaveBeenCalled();
112
- });
113
- it('should handle optional mode with invalid token', async () => {
114
- const middleware = createJWTMiddleware({
115
- secret: TEST_SECRET,
116
- optional: true,
117
- });
118
- mockContext.req.headers.get = vi.fn().mockReturnValue('Bearer invalid-token');
119
- // Should not throw in optional mode
120
- await middleware(mockContext, mockNext);
121
- expect(mockContext.user).toBeUndefined();
122
- expect(mockNext).toHaveBeenCalled();
123
- });
124
- it('should skip when disabled', async () => {
125
- const middleware = createJWTMiddleware({
126
- enabled: false,
127
- secret: TEST_SECRET,
128
- });
129
- await middleware(mockContext, mockNext);
130
- expect(mockContext.user).toBeUndefined();
131
- expect(mockNext).toHaveBeenCalled();
132
- expect(mockContext.req.headers.get).not.toHaveBeenCalled();
133
- });
134
- it('should use custom user extractor', async () => {
135
- const customUser = {
136
- id: 'custom-id',
137
- roles: ['custom-role'],
138
- customField: 'custom-value',
139
- };
140
- const token = jwt.sign({ user_id: 'custom-id', user_roles: ['custom-role'], extra: 'custom-value' }, TEST_SECRET);
141
- const middleware = createJWTMiddleware({
142
- secret: TEST_SECRET,
143
- extractUser: payload => ({
144
- id: payload.user_id,
145
- roles: payload.user_roles,
146
- customField: payload.extra,
147
- }),
148
- });
149
- mockContext.req.headers.get = vi.fn().mockReturnValue(`Bearer ${token}`);
150
- await middleware(mockContext, mockNext);
151
- expect(mockContext.user).toEqual(customUser);
152
- });
153
- it('should verify with specific algorithms', async () => {
154
- const token = jwt.sign({ sub: TEST_USER.id, roles: TEST_USER.roles }, TEST_SECRET, { algorithm: 'HS256' });
155
- const middleware = createJWTMiddleware({
156
- secret: TEST_SECRET,
157
- algorithms: ['HS256'],
158
- });
159
- mockContext.req.headers.get = vi.fn().mockReturnValue(`Bearer ${token}`);
160
- await middleware(mockContext, mockNext);
161
- expect(mockContext.user?.id).toBe(TEST_USER.id);
162
- });
163
- it('should verify issuer and audience', async () => {
164
- const token = jwt.sign({ sub: TEST_USER.id, roles: TEST_USER.roles }, TEST_SECRET, {
165
- issuer: 'test-app',
166
- audience: 'api.example.com',
167
- });
168
- const middleware = createJWTMiddleware({
169
- secret: TEST_SECRET,
170
- issuer: 'test-app',
171
- audience: 'api.example.com',
172
- });
173
- mockContext.req.headers.get = vi.fn().mockReturnValue(`Bearer ${token}`);
174
- await middleware(mockContext, mockNext);
175
- expect(mockContext.user?.id).toBe(TEST_USER.id);
176
- });
177
- it('should store JWT payload in context', async () => {
178
- const token = createJWT(TEST_USER, {
179
- secret: TEST_SECRET,
180
- additionalClaims: { extra: 'data' },
181
- });
182
- const middleware = createJWTMiddleware({
183
- secret: TEST_SECRET,
184
- });
185
- mockContext.req.headers.get = vi.fn().mockReturnValue(`Bearer ${token}`);
186
- await middleware(mockContext, mockNext);
187
- expect(mockContext.jwtPayload).toBeDefined();
188
- expect(mockContext.jwtPayload.extra).toBe('data');
189
- });
190
- });
191
- describe('createHTTPJWTMiddleware', () => {
192
- it('should work with Express-style middleware', async () => {
193
- const token = createJWT(TEST_USER, { secret: TEST_SECRET });
194
- const middleware = createHTTPJWTMiddleware({
195
- secret: TEST_SECRET,
196
- });
197
- const req = {
198
- headers: {
199
- authorization: `Bearer ${token}`,
200
- },
201
- user: undefined,
202
- };
203
- const res = {
204
- status: vi.fn().mockReturnThis(),
205
- json: vi.fn(),
206
- };
207
- const next = vi.fn();
208
- await middleware(req, res, next);
209
- expect(req.user).toBeDefined();
210
- expect(req.user?.id).toBe(TEST_USER.id);
211
- expect(next).toHaveBeenCalled();
212
- expect(res.status).not.toHaveBeenCalled();
213
- });
214
- it('should return 401 for invalid token', async () => {
215
- const middleware = createHTTPJWTMiddleware({
216
- secret: TEST_SECRET,
217
- });
218
- const req = {
219
- headers: {
220
- authorization: 'Bearer invalid-token',
221
- },
222
- };
223
- const res = {
224
- status: vi.fn().mockReturnThis(),
225
- json: vi.fn(),
226
- };
227
- const next = vi.fn();
228
- await middleware(req, res, next);
229
- expect(res.status).toHaveBeenCalledWith(401);
230
- expect(res.json).toHaveBeenCalledWith({
231
- error: 'Invalid token',
232
- code: 'UNAUTHORIZED',
233
- });
234
- expect(next).not.toHaveBeenCalled();
235
- });
236
- it('should continue in optional mode without token', async () => {
237
- const middleware = createHTTPJWTMiddleware({
238
- secret: TEST_SECRET,
239
- optional: true,
240
- });
241
- const req = {
242
- headers: {},
243
- user: undefined,
244
- };
245
- const res = {
246
- status: vi.fn().mockReturnThis(),
247
- json: vi.fn(),
248
- };
249
- const next = vi.fn();
250
- await middleware(req, res, next);
251
- expect(req.user).toBeUndefined();
252
- expect(next).toHaveBeenCalled();
253
- expect(res.status).not.toHaveBeenCalled();
254
- });
255
- });
256
- describe('refreshTokenUtils', () => {
257
- it('should create access and refresh token pair', () => {
258
- const { accessToken, refreshToken } = refreshTokenUtils.createTokenPair(TEST_USER, {
259
- accessSecret: TEST_SECRET,
260
- refreshSecret: TEST_SECRET + '-refresh',
261
- });
262
- expect(accessToken).toBeDefined();
263
- expect(refreshToken).toBeDefined();
264
- // Verify access token
265
- const accessPayload = jwt.verify(accessToken, TEST_SECRET);
266
- expect(accessPayload.sub).toBe(TEST_USER.id);
267
- expect(accessPayload.type).toBe('access');
268
- // Verify refresh token
269
- const refreshPayload = jwt.verify(refreshToken, TEST_SECRET + '-refresh');
270
- expect(refreshPayload.sub).toBe(TEST_USER.id);
271
- expect(refreshPayload.type).toBe('refresh');
272
- });
273
- it('should refresh access token with valid refresh token', async () => {
274
- const refreshToken = jwt.sign({ sub: TEST_USER.id, type: 'refresh' }, TEST_SECRET + '-refresh', {
275
- expiresIn: '7d',
276
- });
277
- const mockGetUserById = vi.fn().mockResolvedValue(TEST_USER);
278
- const result = await refreshTokenUtils.refreshAccessToken(refreshToken, {
279
- accessSecret: TEST_SECRET,
280
- refreshSecret: TEST_SECRET + '-refresh',
281
- getUserById: mockGetUserById,
282
- });
283
- expect(result.accessToken).toBeDefined();
284
- expect(result.user).toEqual(TEST_USER);
285
- expect(mockGetUserById).toHaveBeenCalledWith(TEST_USER.id);
286
- // Verify new access token
287
- const decoded = jwt.verify(result.accessToken, TEST_SECRET);
288
- expect(decoded.sub).toBe(TEST_USER.id);
289
- expect(decoded.type).toBe('access');
290
- });
291
- it('should reject invalid refresh token type', async () => {
292
- // Use access token as refresh token (wrong type)
293
- const wrongToken = jwt.sign({ sub: TEST_USER.id, type: 'access' }, TEST_SECRET + '-refresh');
294
- const mockGetUserById = vi.fn();
295
- await expect(refreshTokenUtils.refreshAccessToken(wrongToken, {
296
- accessSecret: TEST_SECRET,
297
- refreshSecret: TEST_SECRET + '-refresh',
298
- getUserById: mockGetUserById,
299
- })).rejects.toThrow('Invalid token type');
300
- expect(mockGetUserById).not.toHaveBeenCalled();
301
- });
302
- it('should reject expired refresh token', async () => {
303
- const expiredToken = jwt.sign({ sub: TEST_USER.id, type: 'refresh' }, TEST_SECRET + '-refresh', {
304
- expiresIn: '-1h',
305
- });
306
- const mockGetUserById = vi.fn();
307
- await expect(refreshTokenUtils.refreshAccessToken(expiredToken, {
308
- accessSecret: TEST_SECRET,
309
- refreshSecret: TEST_SECRET + '-refresh',
310
- getUserById: mockGetUserById,
311
- })).rejects.toThrow('Refresh token expired');
312
- });
313
- it('should reject if user not found', async () => {
314
- const refreshToken = jwt.sign({ sub: 'non-existent', type: 'refresh' }, TEST_SECRET + '-refresh');
315
- const mockGetUserById = vi.fn().mockResolvedValue(null);
316
- await expect(refreshTokenUtils.refreshAccessToken(refreshToken, {
317
- accessSecret: TEST_SECRET,
318
- refreshSecret: TEST_SECRET + '-refresh',
319
- getUserById: mockGetUserById,
320
- })).rejects.toThrow('User not found');
321
- });
322
- });
323
- describe('JWT with permissions', () => {
324
- it('should handle JWT with embedded permissions', async () => {
325
- const tokenWithPermissions = jwt.sign({
326
- sub: TEST_USER.id,
327
- roles: TEST_USER.roles,
328
- permissions: [
329
- { action: 'create', subject: 'Post' },
330
- { action: 'update', subject: 'Post', conditions: { authorId: TEST_USER.id } },
331
- ],
332
- }, TEST_SECRET);
333
- const middleware = createJWTMiddleware({
334
- secret: TEST_SECRET,
335
- });
336
- const mockContext = {
337
- req: {
338
- headers: {
339
- get: vi.fn().mockReturnValue(`Bearer ${tokenWithPermissions}`),
340
- },
341
- },
342
- user: undefined,
343
- };
344
- await middleware(mockContext, vi.fn());
345
- expect(mockContext.user).toBeDefined();
346
- expect(mockContext.user.permissions).toHaveLength(2);
347
- expect(mockContext.user.permissions[0]).toEqual({
348
- action: 'create',
349
- subject: 'Post',
350
- });
351
- });
352
- });
353
- });
354
- describe('JWT Configuration Validation', () => {
355
- it('should throw error if neither secret nor publicKey is provided', () => {
356
- expect(() => {
357
- createJWTMiddleware({
358
- enabled: true,
359
- // No secret or publicKey
360
- });
361
- }).toThrow('JWT middleware requires either secret or publicKey');
362
- });
363
- it('should not throw when disabled without secret', () => {
364
- expect(() => {
365
- createJWTMiddleware({
366
- enabled: false,
367
- // No secret, but disabled
368
- });
369
- }).not.toThrow();
370
- });
371
- });
372
- describe('JWT Middleware - Advanced Tests', () => {
373
- const SECRET = 'test-secret-key';
374
- beforeEach(() => {
375
- vi.clearAllMocks();
376
- // Clear environment for tests
377
- delete process.env.NODE_ENV;
378
- });
379
- describe('Configuration validation', () => {
380
- it('should throw error when enabled without secret or publicKey', () => {
381
- expect(() => createJWTMiddleware({ enabled: true })).toThrow('JWT middleware requires either secret or publicKey');
382
- });
383
- it('should not throw when disabled without secret', () => {
384
- expect(() => createJWTMiddleware({ enabled: false })).not.toThrow();
385
- });
386
- it('should accept publicKey instead of secret', () => {
387
- const publicKey = '-----BEGIN PUBLIC KEY-----\ntest\n-----END PUBLIC KEY-----';
388
- expect(() => createJWTMiddleware({ enabled: true, publicKey })).not.toThrow();
389
- });
390
- });
391
- describe('Token extraction from different header sources', () => {
392
- it('should extract token from req.headers.get', async () => {
393
- const middleware = createJWTMiddleware({ secret: SECRET });
394
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET);
395
- const context = {
396
- req: {
397
- headers: {
398
- get: vi.fn((name) => (name === 'authorization' ? `Bearer ${token}` : null)),
399
- },
400
- },
401
- };
402
- const next = vi.fn().mockResolvedValue('success');
403
- await middleware(context, next);
404
- expect(context.user).toEqual({ id: '123', roles: ['user'], sub: '123', iat: expect.any(Number) });
405
- expect(next).toHaveBeenCalled();
406
- });
407
- it('should extract token from request.headers.get', async () => {
408
- const middleware = createJWTMiddleware({ secret: SECRET });
409
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET);
410
- const context = {
411
- request: {
412
- headers: {
413
- get: vi.fn((name) => (name === 'authorization' ? `Bearer ${token}` : null)),
414
- },
415
- },
416
- };
417
- const next = vi.fn().mockResolvedValue('success');
418
- await middleware(context, next);
419
- expect(context.user).toEqual({ id: '123', roles: ['user'], sub: '123', iat: expect.any(Number) });
420
- expect(next).toHaveBeenCalled();
421
- });
422
- it('should extract token from context.headers object', async () => {
423
- const middleware = createJWTMiddleware({ secret: SECRET });
424
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET);
425
- const context = {
426
- headers: {
427
- authorization: `Bearer ${token}`,
428
- },
429
- };
430
- const next = vi.fn().mockResolvedValue('success');
431
- await middleware(context, next);
432
- expect(context.user).toEqual({ id: '123', roles: ['user'], sub: '123', iat: expect.any(Number) });
433
- expect(next).toHaveBeenCalled();
434
- });
435
- it('should use custom header name', async () => {
436
- const middleware = createJWTMiddleware({ secret: SECRET, headerName: 'x-auth-token' });
437
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET);
438
- const context = {
439
- headers: {
440
- 'x-auth-token': `Bearer ${token}`,
441
- },
442
- };
443
- const next = vi.fn().mockResolvedValue('success');
444
- await middleware(context, next);
445
- expect(context.user).toBeDefined();
446
- expect(next).toHaveBeenCalled();
447
- });
448
- });
449
- describe('Token prefix handling', () => {
450
- it('should handle token without Bearer prefix', async () => {
451
- const middleware = createJWTMiddleware({ secret: SECRET, tokenPrefix: '' });
452
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET);
453
- const context = {
454
- headers: {
455
- authorization: token,
456
- },
457
- };
458
- const next = vi.fn().mockResolvedValue('success');
459
- await middleware(context, next);
460
- expect(context.user).toBeDefined();
461
- expect(next).toHaveBeenCalled();
462
- });
463
- it('should handle custom token prefix', async () => {
464
- const middleware = createJWTMiddleware({ secret: SECRET, tokenPrefix: 'Token ' });
465
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET);
466
- const context = {
467
- headers: {
468
- authorization: `Token ${token}`,
469
- },
470
- };
471
- const next = vi.fn().mockResolvedValue('success');
472
- await middleware(context, next);
473
- expect(context.user).toBeDefined();
474
- expect(next).toHaveBeenCalled();
475
- });
476
- });
477
- describe('Optional mode', () => {
478
- it('should continue without error when optional and no token provided', async () => {
479
- const middleware = createJWTMiddleware({ secret: SECRET, optional: true });
480
- const context = {
481
- headers: {},
482
- };
483
- const next = vi.fn().mockResolvedValue('success');
484
- const result = await middleware(context, next);
485
- expect(result).toBe('success');
486
- expect(context.user).toBeUndefined();
487
- expect(next).toHaveBeenCalled();
488
- });
489
- it('should log warning in development mode when token is invalid', async () => {
490
- process.env.NODE_ENV = 'development';
491
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
492
- const middleware = createJWTMiddleware({ secret: SECRET, optional: true });
493
- const context = {
494
- headers: {
495
- authorization: 'Bearer invalid-token',
496
- },
497
- };
498
- const next = vi.fn().mockResolvedValue('success');
499
- await middleware(context, next);
500
- expect(consoleSpy).toHaveBeenCalled();
501
- expect(next).toHaveBeenCalled();
502
- consoleSpy.mockRestore();
503
- });
504
- it('should not log warning in production mode when token is invalid', async () => {
505
- process.env.NODE_ENV = 'production';
506
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
507
- const middleware = createJWTMiddleware({ secret: SECRET, optional: true });
508
- const context = {
509
- headers: {
510
- authorization: 'Bearer invalid-token',
511
- },
512
- };
513
- const next = vi.fn().mockResolvedValue('success');
514
- await middleware(context, next);
515
- expect(consoleSpy).not.toHaveBeenCalled();
516
- expect(next).toHaveBeenCalled();
517
- consoleSpy.mockRestore();
518
- });
519
- });
520
- describe('Token verification options', () => {
521
- it('should verify token with issuer', async () => {
522
- const middleware = createJWTMiddleware({ secret: SECRET, issuer: 'test-issuer' });
523
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET, { issuer: 'test-issuer' });
524
- const context = {
525
- headers: {
526
- authorization: `Bearer ${token}`,
527
- },
528
- };
529
- const next = vi.fn().mockResolvedValue('success');
530
- await middleware(context, next);
531
- expect(context.user).toBeDefined();
532
- expect(next).toHaveBeenCalled();
533
- });
534
- it('should reject token with wrong issuer', async () => {
535
- const middleware = createJWTMiddleware({ secret: SECRET, issuer: 'expected-issuer' });
536
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET, { issuer: 'wrong-issuer' });
537
- const context = {
538
- headers: {
539
- authorization: `Bearer ${token}`,
540
- },
541
- };
542
- const next = vi.fn();
543
- await expect(middleware(context, next)).rejects.toThrow();
544
- });
545
- it('should verify token with audience', async () => {
546
- const middleware = createJWTMiddleware({ secret: SECRET, audience: 'test-audience' });
547
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET, { audience: 'test-audience' });
548
- const context = {
549
- headers: {
550
- authorization: `Bearer ${token}`,
551
- },
552
- };
553
- const next = vi.fn().mockResolvedValue('success');
554
- await middleware(context, next);
555
- expect(context.user).toBeDefined();
556
- expect(next).toHaveBeenCalled();
557
- });
558
- it('should handle maxAge verification', async () => {
559
- const middleware = createJWTMiddleware({ secret: SECRET, maxAge: '1h' });
560
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET);
561
- const context = {
562
- headers: {
563
- authorization: `Bearer ${token}`,
564
- },
565
- };
566
- const next = vi.fn().mockResolvedValue('success');
567
- await middleware(context, next);
568
- expect(context.user).toBeDefined();
569
- expect(next).toHaveBeenCalled();
570
- });
571
- it('should use custom algorithms', async () => {
572
- const middleware = createJWTMiddleware({ secret: SECRET, algorithms: ['HS384'] });
573
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET, { algorithm: 'HS384' });
574
- const context = {
575
- headers: {
576
- authorization: `Bearer ${token}`,
577
- },
578
- };
579
- const next = vi.fn().mockResolvedValue('success');
580
- await middleware(context, next);
581
- expect(context.user).toBeDefined();
582
- expect(next).toHaveBeenCalled();
583
- });
584
- });
585
- describe('Custom user extractor', () => {
586
- it('should use custom extractUser function', async () => {
587
- const customExtractor = (payload) => ({
588
- id: payload.user_id,
589
- roles: payload.user_roles,
590
- email: payload.email,
591
- });
592
- const middleware = createJWTMiddleware({ secret: SECRET, extractUser: customExtractor });
593
- const token = jwt.sign({ user_id: '123', user_roles: ['admin'], email: 'test@example.com' }, SECRET);
594
- const context = {
595
- headers: {
596
- authorization: `Bearer ${token}`,
597
- },
598
- };
599
- const next = vi.fn().mockResolvedValue('success');
600
- await middleware(context, next);
601
- expect(context.user).toEqual({
602
- id: '123',
603
- roles: ['admin'],
604
- email: 'test@example.com',
605
- });
606
- expect(next).toHaveBeenCalled();
607
- });
608
- it('should handle extractUser returning undefined', async () => {
609
- const customExtractor = () => undefined;
610
- const middleware = createJWTMiddleware({ secret: SECRET, extractUser: customExtractor });
611
- const token = jwt.sign({ sub: '123' }, SECRET);
612
- const context = {
613
- headers: {
614
- authorization: `Bearer ${token}`,
615
- },
616
- };
617
- const next = vi.fn().mockResolvedValue('success');
618
- await middleware(context, next);
619
- expect(context.user).toBeUndefined();
620
- // jwtPayload is only set when user is defined
621
- expect(context.jwtPayload).toBeUndefined();
622
- expect(next).toHaveBeenCalled();
623
- });
624
- });
625
- describe('Error handling', () => {
626
- it('should throw error when required and no token provided', async () => {
627
- const middleware = createJWTMiddleware({ secret: SECRET, optional: false });
628
- const context = {
629
- headers: {},
630
- };
631
- const next = vi.fn();
632
- await expect(middleware(context, next)).rejects.toThrow('No authorization header found');
633
- });
634
- it('should throw specific error for expired token', async () => {
635
- const middleware = createJWTMiddleware({ secret: SECRET });
636
- const token = jwt.sign({ sub: '123', roles: ['user'] }, SECRET, { expiresIn: '-1s' });
637
- const context = {
638
- headers: {
639
- authorization: `Bearer ${token}`,
640
- },
641
- };
642
- const next = vi.fn();
643
- await expect(middleware(context, next)).rejects.toThrow('Token has expired');
644
- });
645
- it('should throw specific error for invalid signature', async () => {
646
- const middleware = createJWTMiddleware({ secret: SECRET });
647
- const token = jwt.sign({ sub: '123', roles: ['user'] }, 'wrong-secret');
648
- const context = {
649
- headers: {
650
- authorization: `Bearer ${token}`,
651
- },
652
- };
653
- const next = vi.fn();
654
- await expect(middleware(context, next)).rejects.toThrow('Invalid token');
655
- });
656
- it('should throw error for malformed token', async () => {
657
- const middleware = createJWTMiddleware({ secret: SECRET });
658
- const context = {
659
- headers: {
660
- authorization: 'Bearer malformed.token',
661
- },
662
- };
663
- const next = vi.fn();
664
- await expect(middleware(context, next)).rejects.toThrow();
665
- });
666
- });
667
- describe('JWT payload storage', () => {
668
- it('should store JWT payload in context', async () => {
669
- const middleware = createJWTMiddleware({ secret: SECRET });
670
- const payload = { sub: '123', roles: ['user'], custom: 'data' };
671
- const token = jwt.sign(payload, SECRET);
672
- const context = {
673
- headers: {
674
- authorization: `Bearer ${token}`,
675
- },
676
- };
677
- const next = vi.fn().mockResolvedValue('success');
678
- await middleware(context, next);
679
- expect(context.jwtPayload).toBeDefined();
680
- expect(context.jwtPayload.sub).toBe('123');
681
- expect(context.jwtPayload.custom).toBe('data');
682
- expect(next).toHaveBeenCalled();
683
- });
684
- });
685
- describe('Disabled mode', () => {
686
- it('should skip JWT verification when disabled', async () => {
687
- const middleware = createJWTMiddleware({ enabled: false, secret: SECRET });
688
- const context = {
689
- headers: {
690
- authorization: 'Bearer invalid-token',
691
- },
692
- };
693
- const next = vi.fn().mockResolvedValue('success');
694
- const result = await middleware(context, next);
695
- expect(result).toBe('success');
696
- expect(context.user).toBeUndefined();
697
- expect(next).toHaveBeenCalled();
698
- });
699
- });
700
- });