@geekmidas/auth 9.0.2 → 10.0.0-alpha.1

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 (100) hide show
  1. package/dist/{cacheTokenStorage-_tq4Kmhc.d.mts → cacheTokenStorage-DaqxdNxI.d.mts} +1 -1
  2. package/dist/cacheTokenStorage-DaqxdNxI.d.mts.map +1 -0
  3. package/dist/cacheTokenStorage.d.mts +1 -1
  4. package/dist/client.d.mts +1 -1
  5. package/dist/cookies-Bc6gUjb6.cjs +30 -0
  6. package/dist/cookies-Bc6gUjb6.cjs.map +1 -0
  7. package/dist/cookies-C80f8JtE.mjs +24 -0
  8. package/dist/cookies-C80f8JtE.mjs.map +1 -0
  9. package/dist/cookies.cjs +3 -0
  10. package/dist/cookies.d.cts +14 -0
  11. package/dist/cookies.d.cts.map +1 -0
  12. package/dist/cookies.d.mts +14 -0
  13. package/dist/cookies.d.mts.map +1 -0
  14. package/dist/cookies.mjs +3 -0
  15. package/dist/hono/jwt.cjs +4 -7
  16. package/dist/hono/jwt.cjs.map +1 -1
  17. package/dist/hono/jwt.d.cts.map +1 -1
  18. package/dist/hono/jwt.d.mts +1 -1
  19. package/dist/hono/jwt.d.mts.map +1 -1
  20. package/dist/hono/jwt.mjs +5 -7
  21. package/dist/hono/jwt.mjs.map +1 -1
  22. package/dist/hono/oidc.cjs +4 -7
  23. package/dist/hono/oidc.cjs.map +1 -1
  24. package/dist/hono/oidc.d.cts.map +1 -1
  25. package/dist/hono/oidc.d.mts +2 -2
  26. package/dist/hono/oidc.d.mts.map +1 -1
  27. package/dist/hono/oidc.mjs +5 -7
  28. package/dist/hono/oidc.mjs.map +1 -1
  29. package/dist/index.cjs +1 -1
  30. package/dist/index.d.mts +2 -2
  31. package/dist/index.mjs +1 -1
  32. package/dist/{jwt-D88cPJyT.mjs → jwt-bZR2_t8F.mjs} +1 -1
  33. package/dist/{jwt-D88cPJyT.mjs.map → jwt-bZR2_t8F.mjs.map} +1 -1
  34. package/dist/{jwt-Bo_eACEi.cjs → jwt-clq6I6vG.cjs} +1 -1
  35. package/dist/{jwt-Bo_eACEi.cjs.map → jwt-clq6I6vG.cjs.map} +1 -1
  36. package/dist/{jwt-BqLlRkw-.d.mts → jwt-kejix7rd.d.mts} +1 -1
  37. package/dist/{jwt-BqLlRkw-.d.mts.map → jwt-kejix7rd.d.mts.map} +1 -1
  38. package/dist/jwt.cjs +1 -1
  39. package/dist/jwt.d.mts +1 -1
  40. package/dist/jwt.mjs +1 -1
  41. package/dist/lambda/jwt.cjs +4 -5
  42. package/dist/lambda/jwt.cjs.map +1 -1
  43. package/dist/lambda/jwt.d.cts.map +1 -1
  44. package/dist/lambda/jwt.d.mts +1 -1
  45. package/dist/lambda/jwt.d.mts.map +1 -1
  46. package/dist/lambda/jwt.mjs +5 -5
  47. package/dist/lambda/jwt.mjs.map +1 -1
  48. package/dist/lambda/oidc.cjs +4 -5
  49. package/dist/lambda/oidc.cjs.map +1 -1
  50. package/dist/lambda/oidc.d.cts.map +1 -1
  51. package/dist/lambda/oidc.d.mts +2 -2
  52. package/dist/lambda/oidc.d.mts.map +1 -1
  53. package/dist/lambda/oidc.mjs +5 -5
  54. package/dist/lambda/oidc.mjs.map +1 -1
  55. package/dist/{oidc-KA30gMlx.mjs → oidc-BYHREHir.mjs} +1 -1
  56. package/dist/{oidc-KA30gMlx.mjs.map → oidc-BYHREHir.mjs.map} +1 -1
  57. package/dist/{oidc-DTRgBBl6.d.mts → oidc-Ca7GYwmM.d.mts} +2 -2
  58. package/dist/{oidc-DTRgBBl6.d.mts.map → oidc-Ca7GYwmM.d.mts.map} +1 -1
  59. package/dist/{oidc-C0KyYaAK.cjs → oidc-jdMfVyyC.cjs} +1 -1
  60. package/dist/{oidc-C0KyYaAK.cjs.map → oidc-jdMfVyyC.cjs.map} +1 -1
  61. package/dist/oidc.cjs +1 -1
  62. package/dist/oidc.d.mts +2 -2
  63. package/dist/oidc.mjs +1 -1
  64. package/dist/server.cjs +1 -1
  65. package/dist/server.d.mts +2 -2
  66. package/dist/server.mjs +1 -1
  67. package/dist/{tokenManager-Ci2Tgxp8.mjs → tokenManager-BbylTW7F.mjs} +1 -1
  68. package/dist/{tokenManager-Ci2Tgxp8.mjs.map → tokenManager-BbylTW7F.mjs.map} +1 -1
  69. package/dist/{tokenManager-RrwjL0y-.d.mts → tokenManager-CosB7J0i.d.mts} +1 -1
  70. package/dist/{tokenManager-RrwjL0y-.d.mts.map → tokenManager-CosB7J0i.d.mts.map} +1 -1
  71. package/dist/{tokenManager-CvEMgxOp.cjs → tokenManager-PQ-OI_NU.cjs} +1 -1
  72. package/dist/{tokenManager-CvEMgxOp.cjs.map → tokenManager-PQ-OI_NU.cjs.map} +1 -1
  73. package/dist/tokenManager.cjs +1 -1
  74. package/dist/tokenManager.d.mts +1 -1
  75. package/dist/tokenManager.mjs +1 -1
  76. package/package.json +6 -3
  77. package/CHANGELOG.md +0 -60
  78. package/dist/cacheTokenStorage-_tq4Kmhc.d.mts.map +0 -1
  79. package/src/__tests__/cacheTokenStorage.spec.ts +0 -213
  80. package/src/__tests__/client.spec.ts +0 -465
  81. package/src/__tests__/hono-jwt.spec.ts +0 -232
  82. package/src/__tests__/hono-oidc.spec.ts +0 -605
  83. package/src/__tests__/jwt.spec.ts +0 -197
  84. package/src/__tests__/lambda-jwt.spec.ts +0 -278
  85. package/src/__tests__/lambda-oidc.spec.ts +0 -624
  86. package/src/__tests__/oidc.spec.ts +0 -405
  87. package/src/__tests__/tokenManager.spec.ts +0 -259
  88. package/src/cacheTokenStorage.ts +0 -43
  89. package/src/client.ts +0 -220
  90. package/src/hono/jwt.ts +0 -142
  91. package/src/hono/oidc.ts +0 -170
  92. package/src/index.ts +0 -3
  93. package/src/jwt.ts +0 -106
  94. package/src/lambda/jwt.ts +0 -210
  95. package/src/lambda/oidc.ts +0 -203
  96. package/src/oidc.ts +0 -156
  97. package/src/server.ts +0 -8
  98. package/src/tokenManager.ts +0 -106
  99. package/tsconfig.json +0 -10
  100. package/tsdown.config.ts +0 -3
@@ -1,405 +0,0 @@
1
- // @vitest-environment node
2
- import * as jose from 'jose';
3
- import { HttpResponse, http } from 'msw';
4
- import { setupServer } from 'msw/node';
5
- import {
6
- afterAll,
7
- afterEach,
8
- beforeAll,
9
- describe,
10
- expect,
11
- it,
12
- vi,
13
- } from 'vitest';
14
- import { OidcVerifier } from '../oidc';
15
-
16
- // Mock discovery document
17
- const mockDiscovery = {
18
- issuer: 'https://auth.example.com',
19
- authorization_endpoint: 'https://auth.example.com/authorize',
20
- token_endpoint: 'https://auth.example.com/token',
21
- userinfo_endpoint: 'https://auth.example.com/userinfo',
22
- jwks_uri: 'https://auth.example.com/.well-known/jwks.json',
23
- scopes_supported: ['openid', 'profile', 'email'],
24
- response_types_supported: ['code', 'token'],
25
- claims_supported: ['sub', 'name', 'email'],
26
- };
27
-
28
- // Mock user info
29
- const mockUserInfo = {
30
- sub: 'user-123',
31
- name: 'Test User',
32
- email: 'test@example.com',
33
- email_verified: true,
34
- };
35
-
36
- // Test keys - stored in object so the mock can access after initialization
37
- const testKeys: {
38
- privateKey: jose.KeyLike | null;
39
- publicKey: jose.KeyLike | null;
40
- } = {
41
- privateKey: null,
42
- publicKey: null,
43
- };
44
- let jwks: jose.JSONWebKeySet;
45
-
46
- async function setupKeys() {
47
- const keyPair = await jose.generateKeyPair('RS256');
48
- testKeys.privateKey = keyPair.privateKey;
49
- testKeys.publicKey = keyPair.publicKey;
50
- const publicJwk = await jose.exportJWK(keyPair.publicKey);
51
- jwks = {
52
- keys: [{ ...publicJwk, kid: 'test-key-id', use: 'sig', alg: 'RS256' }],
53
- };
54
- }
55
-
56
- // Mock createRemoteJWKSet to return test keys directly.
57
- // MSW intercepts jose's fetch in standalone scripts but fails in vitest for unknown reasons.
58
- // TODO: Investigate vitest/MSW/jose interaction and remove this mock if possible.
59
- vi.mock('jose', async (importOriginal) => {
60
- const actual = await importOriginal<typeof jose>();
61
- return {
62
- ...actual,
63
- createRemoteJWKSet: () => {
64
- return async (protectedHeader: jose.JWSHeaderParameters) => {
65
- if (protectedHeader.kid === 'test-key-id' && testKeys.publicKey) {
66
- return testKeys.publicKey;
67
- }
68
- throw new Error(`Unknown key ID: ${protectedHeader.kid}`);
69
- };
70
- },
71
- };
72
- });
73
-
74
- async function createTestToken(
75
- claims: Record<string, unknown> = {},
76
- options: { expiresIn?: string } = {},
77
- ) {
78
- if (!testKeys.privateKey) {
79
- throw new Error('Keys not initialized');
80
- }
81
- return await new jose.SignJWT({ sub: 'user-123', ...claims })
82
- .setProtectedHeader({ alg: 'RS256', kid: 'test-key-id' })
83
- .setIssuedAt()
84
- .setIssuer('https://auth.example.com')
85
- .setAudience('my-api')
86
- .setExpirationTime(options.expiresIn ?? '1h')
87
- .sign(testKeys.privateKey);
88
- }
89
-
90
- // MSW server for discovery, userinfo, and other HTTP endpoints
91
- const server = setupServer(
92
- http.get('https://auth.example.com/.well-known/openid-configuration', () => {
93
- return HttpResponse.json(mockDiscovery);
94
- }),
95
- http.get('https://auth.example.com/.well-known/jwks.json', () => {
96
- return HttpResponse.json(jwks);
97
- }),
98
- http.get('https://auth.example.com/userinfo', () => {
99
- return HttpResponse.json(mockUserInfo);
100
- }),
101
- http.get(
102
- 'https://other-auth.example.com/.well-known/openid-configuration',
103
- () => {
104
- return HttpResponse.json({
105
- ...mockDiscovery,
106
- issuer: 'https://other-auth.example.com',
107
- jwks_uri: 'https://other-auth.example.com/.well-known/jwks.json',
108
- });
109
- },
110
- ),
111
- http.get('https://other-auth.example.com/.well-known/jwks.json', () => {
112
- return HttpResponse.json(jwks);
113
- }),
114
- );
115
-
116
- describe('OidcVerifier', () => {
117
- beforeAll(async () => {
118
- await setupKeys();
119
- server.listen({ onUnhandledRequest: 'error' });
120
- });
121
-
122
- afterEach(() => {
123
- server.resetHandlers();
124
- });
125
-
126
- afterAll(() => {
127
- server.close();
128
- });
129
-
130
- describe('getDiscovery', () => {
131
- it('should fetch OIDC discovery document', async () => {
132
- const verifier = new OidcVerifier({
133
- issuer: 'https://auth.example.com',
134
- audience: 'my-api',
135
- });
136
-
137
- const discovery = await verifier.getDiscovery();
138
-
139
- expect(discovery.issuer).toBe('https://auth.example.com');
140
- expect(discovery.jwks_uri).toBe(
141
- 'https://auth.example.com/.well-known/jwks.json',
142
- );
143
- });
144
-
145
- it('should cache discovery document by default', async () => {
146
- let callCount = 0;
147
- server.use(
148
- http.get(
149
- 'https://auth.example.com/.well-known/openid-configuration',
150
- () => {
151
- callCount++;
152
- return HttpResponse.json(mockDiscovery);
153
- },
154
- ),
155
- );
156
-
157
- const verifier = new OidcVerifier({
158
- issuer: 'https://auth.example.com',
159
- audience: 'my-api',
160
- });
161
-
162
- await verifier.getDiscovery();
163
- await verifier.getDiscovery();
164
-
165
- expect(callCount).toBe(1);
166
- });
167
-
168
- it('should handle issuer with trailing slash', async () => {
169
- let requestedUrl = '';
170
- server.use(
171
- http.get(
172
- 'https://auth.example.com/.well-known/openid-configuration',
173
- ({ request }) => {
174
- requestedUrl = request.url;
175
- return HttpResponse.json(mockDiscovery);
176
- },
177
- ),
178
- );
179
-
180
- const verifier = new OidcVerifier({
181
- issuer: 'https://auth.example.com/',
182
- audience: 'my-api',
183
- });
184
-
185
- await verifier.getDiscovery();
186
-
187
- expect(requestedUrl).toBe(
188
- 'https://auth.example.com/.well-known/openid-configuration',
189
- );
190
- });
191
-
192
- it('should throw on failed discovery fetch', async () => {
193
- server.use(
194
- http.get(
195
- 'https://auth.example.com/.well-known/openid-configuration',
196
- () => {
197
- return new HttpResponse(null, {
198
- status: 500,
199
- statusText: 'Internal Server Error',
200
- });
201
- },
202
- ),
203
- );
204
-
205
- const verifier = new OidcVerifier({
206
- issuer: 'https://auth.example.com',
207
- audience: 'my-api',
208
- });
209
-
210
- await expect(verifier.getDiscovery()).rejects.toThrow(
211
- 'Failed to fetch OIDC discovery',
212
- );
213
- });
214
- });
215
-
216
- describe('verify', () => {
217
- it('should verify a valid token', async () => {
218
- const verifier = new OidcVerifier({
219
- issuer: 'https://auth.example.com',
220
- audience: 'my-api',
221
- });
222
- const token = await createTestToken();
223
-
224
- const claims = await verifier.verify(token);
225
-
226
- expect(claims.sub).toBe('user-123');
227
- });
228
-
229
- it('should throw for invalid token', async () => {
230
- const verifier = new OidcVerifier({
231
- issuer: 'https://auth.example.com',
232
- audience: 'my-api',
233
- });
234
-
235
- await expect(verifier.verify('invalid-token')).rejects.toThrow();
236
- });
237
-
238
- it('should throw for expired token', async () => {
239
- const verifier = new OidcVerifier({
240
- issuer: 'https://auth.example.com',
241
- audience: 'my-api',
242
- });
243
- const token = await createTestToken({}, { expiresIn: '-1h' });
244
-
245
- await expect(verifier.verify(token)).rejects.toThrow();
246
- });
247
-
248
- it('should throw for wrong issuer', async () => {
249
- const verifier = new OidcVerifier({
250
- issuer: 'https://other-auth.example.com',
251
- audience: 'my-api',
252
- });
253
- const token = await createTestToken();
254
-
255
- await expect(verifier.verify(token)).rejects.toThrow();
256
- });
257
-
258
- it('should throw for wrong audience', async () => {
259
- const verifier = new OidcVerifier({
260
- issuer: 'https://auth.example.com',
261
- audience: 'other-api',
262
- });
263
- const token = await createTestToken();
264
-
265
- await expect(verifier.verify(token)).rejects.toThrow();
266
- });
267
- });
268
-
269
- describe('verifyOrNull', () => {
270
- it('should return claims for valid token', async () => {
271
- const verifier = new OidcVerifier({
272
- issuer: 'https://auth.example.com',
273
- audience: 'my-api',
274
- });
275
- const token = await createTestToken();
276
-
277
- const claims = await verifier.verifyOrNull(token);
278
-
279
- expect(claims).not.toBeNull();
280
- expect(claims?.sub).toBe('user-123');
281
- });
282
-
283
- it('should return null for invalid token', async () => {
284
- const verifier = new OidcVerifier({
285
- issuer: 'https://auth.example.com',
286
- audience: 'my-api',
287
- });
288
-
289
- const claims = await verifier.verifyOrNull('invalid-token');
290
-
291
- expect(claims).toBeNull();
292
- });
293
- });
294
-
295
- describe('fetchUserInfo', () => {
296
- it('should fetch user info with valid token', async () => {
297
- const verifier = new OidcVerifier({
298
- issuer: 'https://auth.example.com',
299
- audience: 'my-api',
300
- });
301
- const token = await createTestToken();
302
-
303
- const userInfo = await verifier.fetchUserInfo(token);
304
-
305
- expect(userInfo).not.toBeNull();
306
- expect(userInfo?.sub).toBe('user-123');
307
- expect(userInfo?.name).toBe('Test User');
308
- expect(userInfo?.email).toBe('test@example.com');
309
- });
310
-
311
- it('should return null when userinfo endpoint is not available', async () => {
312
- server.use(
313
- http.get(
314
- 'https://auth.example.com/.well-known/openid-configuration',
315
- () => {
316
- return HttpResponse.json({
317
- ...mockDiscovery,
318
- userinfo_endpoint: undefined,
319
- });
320
- },
321
- ),
322
- );
323
-
324
- const verifier = new OidcVerifier({
325
- issuer: 'https://auth.example.com',
326
- audience: 'my-api',
327
- });
328
-
329
- const userInfo = await verifier.fetchUserInfo('some-token');
330
-
331
- expect(userInfo).toBeNull();
332
- });
333
-
334
- it('should return null on userinfo fetch error', async () => {
335
- server.use(
336
- http.get('https://auth.example.com/userinfo', () => {
337
- return new HttpResponse(null, { status: 401 });
338
- }),
339
- );
340
-
341
- const verifier = new OidcVerifier({
342
- issuer: 'https://auth.example.com',
343
- audience: 'my-api',
344
- });
345
-
346
- const userInfo = await verifier.fetchUserInfo('invalid-token');
347
-
348
- expect(userInfo).toBeNull();
349
- });
350
- });
351
-
352
- describe('clearCache', () => {
353
- it('should clear cached discovery and jwks', async () => {
354
- let callCount = 0;
355
- server.use(
356
- http.get(
357
- 'https://auth.example.com/.well-known/openid-configuration',
358
- () => {
359
- callCount++;
360
- return HttpResponse.json(mockDiscovery);
361
- },
362
- ),
363
- );
364
-
365
- const verifier = new OidcVerifier({
366
- issuer: 'https://auth.example.com',
367
- audience: 'my-api',
368
- });
369
-
370
- await verifier.getDiscovery();
371
- expect(callCount).toBe(1);
372
-
373
- verifier.clearCache();
374
-
375
- await verifier.getDiscovery();
376
- expect(callCount).toBe(2);
377
- });
378
- });
379
-
380
- describe('cacheDiscovery option', () => {
381
- it('should not cache when cacheDiscovery is false', async () => {
382
- let callCount = 0;
383
- server.use(
384
- http.get(
385
- 'https://auth.example.com/.well-known/openid-configuration',
386
- () => {
387
- callCount++;
388
- return HttpResponse.json(mockDiscovery);
389
- },
390
- ),
391
- );
392
-
393
- const verifier = new OidcVerifier({
394
- issuer: 'https://auth.example.com',
395
- audience: 'my-api',
396
- cacheDiscovery: false,
397
- });
398
-
399
- await verifier.getDiscovery();
400
- await verifier.getDiscovery();
401
-
402
- expect(callCount).toBe(2);
403
- });
404
- });
405
- });
@@ -1,259 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { TokenManager, type TokenPayload } from '../tokenManager.ts';
3
-
4
- describe('TokenManager', () => {
5
- let tokenManager: TokenManager;
6
- let userPayload: TokenPayload;
7
-
8
- beforeEach(() => {
9
- tokenManager = new TokenManager({
10
- accessTokenSecret: 'test-access-secret',
11
- refreshTokenSecret: 'test-refresh-secret',
12
- accessTokenExpiresIn: '15m',
13
- refreshTokenExpiresIn: '7d',
14
- });
15
-
16
- userPayload = {
17
- userId: 'user123',
18
- email: 'test@example.com',
19
- role: 'admin',
20
- };
21
- });
22
-
23
- describe('token generation and verification workflow', () => {
24
- it('should generate and verify complete token pair', () => {
25
- const { accessToken, refreshToken } =
26
- tokenManager.generateTokenPair(userPayload);
27
-
28
- const accessDecoded = tokenManager.verifyAccessToken(accessToken);
29
- const refreshDecoded = tokenManager.verifyRefreshToken(refreshToken);
30
-
31
- expect(accessDecoded.userId).toBe(userPayload.userId);
32
- expect(accessDecoded.email).toBe(userPayload.email);
33
- expect(accessDecoded.role).toBe(userPayload.role);
34
- expect(refreshDecoded.userId).toBe(userPayload.userId);
35
- expect(refreshDecoded.email).toBe(userPayload.email);
36
- expect(refreshDecoded.role).toBe(userPayload.role);
37
- });
38
-
39
- it('should handle complete token refresh workflow', () => {
40
- const { refreshToken } = tokenManager.generateTokenPair(userPayload);
41
-
42
- const newAccessToken = tokenManager.refreshAccessToken(refreshToken);
43
- const newDecoded = tokenManager.verifyAccessToken(newAccessToken);
44
-
45
- expect(newDecoded.userId).toBe(userPayload.userId);
46
- expect(newDecoded.email).toBe(userPayload.email);
47
- expect(newDecoded.role).toBe(userPayload.role);
48
- expect(newDecoded.iat).toBeDefined();
49
- expect(newDecoded.exp).toBeDefined();
50
- });
51
-
52
- it('should maintain payload integrity through refresh cycle', () => {
53
- const originalPayload = {
54
- userId: 'user456',
55
- email: 'user@example.com',
56
- permissions: ['read', 'write'],
57
- metadata: { department: 'engineering' },
58
- };
59
-
60
- const { refreshToken } = tokenManager.generateTokenPair(originalPayload);
61
- const newAccessToken = tokenManager.refreshAccessToken(refreshToken);
62
- const decoded = tokenManager.verifyAccessToken(newAccessToken);
63
-
64
- expect(decoded.userId).toBe(originalPayload.userId);
65
- expect(decoded.email).toBe(originalPayload.email);
66
- expect(decoded.permissions).toEqual(originalPayload.permissions);
67
- expect(decoded.metadata).toEqual(originalPayload.metadata);
68
- });
69
- });
70
-
71
- describe('token expiration handling', () => {
72
- it('should correctly identify token expiration status', () => {
73
- const { accessToken } = tokenManager.generateTokenPair(userPayload);
74
-
75
- expect(tokenManager.isTokenExpired(accessToken)).toBe(false);
76
-
77
- const expiration = tokenManager.getTokenExpiration(accessToken);
78
- expect(expiration).toBeInstanceOf(Date);
79
- expect(expiration?.getTime()).toBeGreaterThan(Date.now());
80
- });
81
-
82
- it('should handle expired tokens correctly', async () => {
83
- vi.useFakeTimers();
84
- const shortLivedManager = new TokenManager({
85
- accessTokenSecret: 'test-access-secret',
86
- refreshTokenSecret: 'test-refresh-secret',
87
- accessTokenExpiresIn: '10m',
88
- });
89
-
90
- const { accessToken } = shortLivedManager.generateTokenPair(userPayload);
91
-
92
- vi.advanceTimersByTime(1000 * 60 * 16); // Fast-forward time to expire access token
93
-
94
- expect(tokenManager.isTokenExpired(accessToken)).toBe(true);
95
-
96
- vi.useRealTimers();
97
- });
98
-
99
- it('should decode tokens without verification', () => {
100
- const { accessToken } = tokenManager.generateTokenPair(userPayload);
101
-
102
- const decoded = tokenManager.decodeToken(accessToken);
103
-
104
- expect(decoded).not.toBeNull();
105
- expect(decoded?.userId).toBe(userPayload.userId);
106
- expect(decoded?.email).toBe(userPayload.email);
107
- });
108
- });
109
-
110
- describe('security validation', () => {
111
- it('should reject tokens with invalid signatures', () => {
112
- const wrongSecretManager = new TokenManager({
113
- accessTokenSecret: 'wrong-secret',
114
- refreshTokenSecret: 'test-refresh-secret',
115
- });
116
-
117
- const { accessToken } = wrongSecretManager.generateTokenPair(userPayload);
118
-
119
- expect(() => {
120
- tokenManager.verifyAccessToken(accessToken);
121
- }).toThrow('Invalid access token');
122
- });
123
-
124
- it('should reject malformed tokens', () => {
125
- expect(() => {
126
- tokenManager.verifyAccessToken('invalid-token');
127
- }).toThrow('Invalid access token');
128
-
129
- expect(() => {
130
- tokenManager.verifyRefreshToken('invalid-token');
131
- }).toThrow('Invalid refresh token');
132
-
133
- expect(() => {
134
- tokenManager.refreshAccessToken('invalid-token');
135
- }).toThrow('Invalid refresh token');
136
- });
137
-
138
- it('should handle cross-secret validation correctly', () => {
139
- const { accessToken, refreshToken } =
140
- tokenManager.generateTokenPair(userPayload);
141
-
142
- // Access token should not verify with refresh secret
143
- expect(() => {
144
- tokenManager.verifyRefreshToken(accessToken);
145
- }).toThrow('Invalid refresh token');
146
-
147
- // Refresh token should not verify with access secret
148
- expect(() => {
149
- tokenManager.verifyAccessToken(refreshToken);
150
- }).toThrow('Invalid access token');
151
- });
152
- });
153
-
154
- describe('configuration flexibility', () => {
155
- it('should support custom expiration times', () => {
156
- const customManager = new TokenManager({
157
- accessTokenSecret: 'test-access-secret',
158
- refreshTokenSecret: 'test-refresh-secret',
159
- accessTokenExpiresIn: '1h',
160
- refreshTokenExpiresIn: '30d',
161
- });
162
-
163
- const { accessToken, refreshToken } =
164
- customManager.generateTokenPair(userPayload);
165
-
166
- const accessDecoded = customManager.verifyAccessToken(accessToken);
167
- const refreshDecoded = customManager.verifyRefreshToken(refreshToken);
168
-
169
- const accessExpTime = (accessDecoded.exp - accessDecoded.iat) * 1000;
170
- const refreshExpTime = (refreshDecoded.exp - refreshDecoded.iat) * 1000;
171
-
172
- // Access token should be ~1 hour
173
- expect(accessExpTime).toBeGreaterThan(55 * 60 * 1000);
174
- expect(accessExpTime).toBeLessThan(65 * 60 * 1000);
175
-
176
- // Refresh token should be ~30 days
177
- expect(refreshExpTime).toBeGreaterThan(29 * 24 * 60 * 60 * 1000);
178
- expect(refreshExpTime).toBeLessThan(31 * 24 * 60 * 60 * 1000);
179
- });
180
-
181
- it('should use default expiration times', () => {
182
- const defaultManager = new TokenManager({
183
- accessTokenSecret: 'test-access-secret',
184
- refreshTokenSecret: 'test-refresh-secret',
185
- });
186
-
187
- const { accessToken, refreshToken } =
188
- defaultManager.generateTokenPair(userPayload);
189
-
190
- const accessDecoded = defaultManager.verifyAccessToken(accessToken);
191
- const refreshDecoded = defaultManager.verifyRefreshToken(refreshToken);
192
-
193
- const accessExpTime = (accessDecoded.exp - accessDecoded.iat) * 1000;
194
- const refreshExpTime = (refreshDecoded.exp - refreshDecoded.iat) * 1000;
195
-
196
- // Default access token should be ~15 minutes
197
- expect(accessExpTime).toBeGreaterThan(14 * 60 * 1000);
198
- expect(accessExpTime).toBeLessThan(16 * 60 * 1000);
199
-
200
- // Default refresh token should be ~7 days
201
- expect(refreshExpTime).toBeGreaterThan(6 * 24 * 60 * 60 * 1000);
202
- expect(refreshExpTime).toBeLessThan(8 * 24 * 60 * 60 * 1000);
203
- });
204
- });
205
-
206
- describe('edge cases and error handling', () => {
207
- it('should handle empty payload gracefully', () => {
208
- const emptyPayload = { userId: '' };
209
-
210
- const { accessToken, refreshToken } =
211
- tokenManager.generateTokenPair(emptyPayload);
212
-
213
- const accessDecoded = tokenManager.verifyAccessToken(accessToken);
214
- const refreshDecoded = tokenManager.verifyRefreshToken(refreshToken);
215
-
216
- expect(accessDecoded.userId).toBe('');
217
- expect(refreshDecoded.userId).toBe('');
218
- });
219
-
220
- it('should handle large payloads correctly', () => {
221
- const largePayload = {
222
- userId: 'user123',
223
- permissions: Array.from({ length: 100 }, (_, i) => `permission_${i}`),
224
- metadata: {
225
- department: 'engineering',
226
- team: 'platform',
227
- projects: Array.from({ length: 50 }, (_, i) => `project_${i}`),
228
- },
229
- };
230
-
231
- const { accessToken, refreshToken } =
232
- tokenManager.generateTokenPair(largePayload);
233
-
234
- const accessDecoded = tokenManager.verifyAccessToken(accessToken);
235
- const refreshDecoded = tokenManager.verifyRefreshToken(refreshToken);
236
-
237
- expect(accessDecoded.userId).toBe(largePayload.userId);
238
- expect(accessDecoded.permissions).toEqual(largePayload.permissions);
239
- expect(accessDecoded.metadata).toEqual(largePayload.metadata);
240
- expect(refreshDecoded.userId).toBe(largePayload.userId);
241
- expect(refreshDecoded.permissions).toEqual(largePayload.permissions);
242
- expect(refreshDecoded.metadata).toEqual(largePayload.metadata);
243
- });
244
-
245
- it('should return null for completely invalid tokens', () => {
246
- expect(tokenManager.decodeToken('')).toBeNull();
247
- expect(tokenManager.decodeToken('not.a.token')).toBeNull();
248
- expect(tokenManager.decodeToken('invalid-token')).toBeNull();
249
-
250
- expect(tokenManager.getTokenExpiration('')).toBeNull();
251
- expect(tokenManager.getTokenExpiration('not.a.token')).toBeNull();
252
- expect(tokenManager.getTokenExpiration('invalid-token')).toBeNull();
253
-
254
- expect(tokenManager.isTokenExpired('')).toBe(true);
255
- expect(tokenManager.isTokenExpired('not.a.token')).toBe(true);
256
- expect(tokenManager.isTokenExpired('invalid-token')).toBe(true);
257
- });
258
- });
259
- });
@@ -1,43 +0,0 @@
1
- import type { Cache } from '@geekmidas/cache';
2
- import type { TokenStorage } from './client.js';
3
-
4
- export class CacheTokenStorage implements TokenStorage {
5
- private cache: Cache;
6
- private accessTokenKey: string;
7
- private refreshTokenKey: string;
8
-
9
- constructor(
10
- cache: Cache,
11
- accessTokenKey: string = 'access_token',
12
- refreshTokenKey: string = 'refresh_token',
13
- ) {
14
- this.cache = cache;
15
- this.accessTokenKey = accessTokenKey;
16
- this.refreshTokenKey = refreshTokenKey;
17
- }
18
-
19
- async getAccessToken(): Promise<string | null> {
20
- const token = await this.cache.get<string>(this.accessTokenKey);
21
- return token ?? null;
22
- }
23
-
24
- async setAccessToken(token: string, ttl?: number): Promise<void> {
25
- await this.cache.set(this.accessTokenKey, token, ttl);
26
- }
27
-
28
- async getRefreshToken(): Promise<string | null> {
29
- const token = await this.cache.get<string>(this.refreshTokenKey);
30
- return token ?? null;
31
- }
32
-
33
- async setRefreshToken(token: string, ttl?: number): Promise<void> {
34
- await this.cache.set(this.refreshTokenKey, token, ttl);
35
- }
36
-
37
- async clearTokens(): Promise<void> {
38
- await Promise.all([
39
- this.cache.delete(this.accessTokenKey),
40
- this.cache.delete(this.refreshTokenKey),
41
- ]);
42
- }
43
- }