@geekmidas/auth 0.1.0 → 1.0.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 (117) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{cacheTokenStorage-Ca05vf3T.mjs → cacheTokenStorage-16NE8Cd9.mjs} +3 -3
  3. package/dist/cacheTokenStorage-16NE8Cd9.mjs.map +1 -0
  4. package/dist/{cacheTokenStorage-BzBRJfd-.d.mts → cacheTokenStorage-B7sOoysu.d.cts} +8 -6
  5. package/dist/cacheTokenStorage-B7sOoysu.d.cts.map +1 -0
  6. package/dist/{cacheTokenStorage-CvptztkW.cjs → cacheTokenStorage-CEpMniTi.cjs} +3 -3
  7. package/dist/cacheTokenStorage-CEpMniTi.cjs.map +1 -0
  8. package/dist/{cacheTokenStorage-yNUpDD8W.d.cts → cacheTokenStorage-_tq4Kmhc.d.mts} +8 -6
  9. package/dist/cacheTokenStorage-_tq4Kmhc.d.mts.map +1 -0
  10. package/dist/cacheTokenStorage.cjs +1 -1
  11. package/dist/cacheTokenStorage.d.cts +1 -1
  12. package/dist/cacheTokenStorage.d.mts +1 -1
  13. package/dist/cacheTokenStorage.mjs +1 -1
  14. package/dist/{client-DaiUm0UZ.cjs → client-BPl_mMvb.cjs} +14 -8
  15. package/dist/client-BPl_mMvb.cjs.map +1 -0
  16. package/dist/{client-DjNZS6vZ.mjs → client-eidYNJSH.mjs} +14 -8
  17. package/dist/client-eidYNJSH.mjs.map +1 -0
  18. package/dist/client.cjs +2 -2
  19. package/dist/client.d.cts +1 -1
  20. package/dist/client.d.mts +1 -1
  21. package/dist/client.mjs +2 -2
  22. package/dist/hono/jwt.cjs +1 -1
  23. package/dist/hono/jwt.cjs.map +1 -1
  24. package/dist/hono/jwt.d.cts +2 -1
  25. package/dist/hono/jwt.d.cts.map +1 -0
  26. package/dist/hono/jwt.d.mts +2 -1
  27. package/dist/hono/jwt.d.mts.map +1 -0
  28. package/dist/hono/jwt.mjs +1 -1
  29. package/dist/hono/jwt.mjs.map +1 -1
  30. package/dist/hono/oidc.cjs +1 -1
  31. package/dist/hono/oidc.cjs.map +1 -1
  32. package/dist/hono/oidc.d.cts +3 -2
  33. package/dist/hono/oidc.d.cts.map +1 -0
  34. package/dist/hono/oidc.d.mts +3 -2
  35. package/dist/hono/oidc.d.mts.map +1 -0
  36. package/dist/hono/oidc.mjs +1 -1
  37. package/dist/hono/oidc.mjs.map +1 -1
  38. package/dist/index.cjs +3 -3
  39. package/dist/index.d.cts +2 -2
  40. package/dist/index.d.mts +2 -2
  41. package/dist/index.mjs +3 -3
  42. package/dist/{jwt-DF9kFbmh.d.cts → jwt-3eIuY2dg.d.cts} +2 -1
  43. package/dist/jwt-3eIuY2dg.d.cts.map +1 -0
  44. package/dist/jwt-Bo_eACEi.cjs.map +1 -1
  45. package/dist/{jwt-DH8CJ2xC.d.mts → jwt-BqLlRkw-.d.mts} +2 -1
  46. package/dist/jwt-BqLlRkw-.d.mts.map +1 -0
  47. package/dist/jwt-D88cPJyT.mjs.map +1 -1
  48. package/dist/jwt.d.cts +1 -1
  49. package/dist/jwt.d.mts +1 -1
  50. package/dist/lambda/jwt.cjs +2 -2
  51. package/dist/lambda/jwt.cjs.map +1 -1
  52. package/dist/lambda/jwt.d.cts +2 -1
  53. package/dist/lambda/jwt.d.cts.map +1 -0
  54. package/dist/lambda/jwt.d.mts +2 -1
  55. package/dist/lambda/jwt.d.mts.map +1 -0
  56. package/dist/lambda/jwt.mjs +2 -2
  57. package/dist/lambda/jwt.mjs.map +1 -1
  58. package/dist/lambda/oidc.cjs +2 -2
  59. package/dist/lambda/oidc.cjs.map +1 -1
  60. package/dist/lambda/oidc.d.cts +3 -2
  61. package/dist/lambda/oidc.d.cts.map +1 -0
  62. package/dist/lambda/oidc.d.mts +3 -2
  63. package/dist/lambda/oidc.d.mts.map +1 -0
  64. package/dist/lambda/oidc.mjs +2 -2
  65. package/dist/lambda/oidc.mjs.map +1 -1
  66. package/dist/oidc-C0KyYaAK.cjs.map +1 -1
  67. package/dist/{oidc-CKOGLxjV.d.mts → oidc-ChA17wLB.d.cts} +3 -2
  68. package/dist/oidc-ChA17wLB.d.cts.map +1 -0
  69. package/dist/{oidc-DDLIB5gb.d.cts → oidc-DTRgBBl6.d.mts} +3 -2
  70. package/dist/oidc-DTRgBBl6.d.mts.map +1 -0
  71. package/dist/oidc-KA30gMlx.mjs.map +1 -1
  72. package/dist/oidc.d.cts +2 -2
  73. package/dist/oidc.d.mts +2 -2
  74. package/dist/server.cjs +2 -2
  75. package/dist/server.d.cts +2 -2
  76. package/dist/server.d.mts +2 -2
  77. package/dist/server.mjs +2 -2
  78. package/dist/{tokenManager--Q93eNYr.d.mts → tokenManager-CL3yE06D.d.cts} +2 -1
  79. package/dist/tokenManager-CL3yE06D.d.cts.map +1 -0
  80. package/dist/{tokenManager-BVXxfHRa.mjs → tokenManager-Ci2Tgxp8.mjs} +5 -3
  81. package/dist/tokenManager-Ci2Tgxp8.mjs.map +1 -0
  82. package/dist/{tokenManager-EPmwgai2.cjs → tokenManager-CvEMgxOp.cjs} +5 -3
  83. package/dist/tokenManager-CvEMgxOp.cjs.map +1 -0
  84. package/dist/{tokenManager-H6-lWEah.d.cts → tokenManager-RrwjL0y-.d.mts} +2 -1
  85. package/dist/tokenManager-RrwjL0y-.d.mts.map +1 -0
  86. package/dist/tokenManager.cjs +1 -1
  87. package/dist/tokenManager.d.cts +1 -1
  88. package/dist/tokenManager.d.mts +1 -1
  89. package/dist/tokenManager.mjs +1 -1
  90. package/package.json +3 -3
  91. package/src/__tests__/cacheTokenStorage.spec.ts +148 -148
  92. package/src/__tests__/client.spec.ts +339 -339
  93. package/src/__tests__/hono-jwt.spec.ts +219 -219
  94. package/src/__tests__/hono-oidc.spec.ts +560 -560
  95. package/src/__tests__/jwt.spec.ts +150 -127
  96. package/src/__tests__/lambda-jwt.spec.ts +205 -205
  97. package/src/__tests__/lambda-oidc.spec.ts +580 -579
  98. package/src/__tests__/oidc.spec.ts +365 -365
  99. package/src/__tests__/tokenManager.spec.ts +254 -254
  100. package/src/cacheTokenStorage.ts +32 -32
  101. package/src/client.ts +199 -190
  102. package/src/hono/jwt.ts +111 -111
  103. package/src/hono/oidc.ts +131 -131
  104. package/src/index.ts +2 -2
  105. package/src/jwt.ts +57 -57
  106. package/src/lambda/jwt.ts +170 -170
  107. package/src/lambda/oidc.ts +165 -165
  108. package/src/oidc.ts +121 -121
  109. package/src/server.ts +6 -7
  110. package/src/tokenManager.ts +89 -87
  111. package/tsconfig.json +10 -0
  112. package/dist/cacheTokenStorage-Ca05vf3T.mjs.map +0 -1
  113. package/dist/cacheTokenStorage-CvptztkW.cjs.map +0 -1
  114. package/dist/client-DaiUm0UZ.cjs.map +0 -1
  115. package/dist/client-DjNZS6vZ.mjs.map +0 -1
  116. package/dist/tokenManager-BVXxfHRa.mjs.map +0 -1
  117. package/dist/tokenManager-EPmwgai2.cjs.map +0 -1
@@ -2,258 +2,258 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import { TokenManager, type TokenPayload } from '../tokenManager.ts';
3
3
 
4
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
- });
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
259
  });
@@ -2,42 +2,42 @@ import type { Cache } from '@geekmidas/cache';
2
2
  import type { TokenStorage } from './client.js';
3
3
 
4
4
  export class CacheTokenStorage implements TokenStorage {
5
- private cache: Cache<string>;
6
- private accessTokenKey: string;
7
- private refreshTokenKey: string;
5
+ private cache: Cache;
6
+ private accessTokenKey: string;
7
+ private refreshTokenKey: string;
8
8
 
9
- constructor(
10
- cache: Cache<string>,
11
- accessTokenKey: string = 'access_token',
12
- refreshTokenKey: string = 'refresh_token',
13
- ) {
14
- this.cache = cache;
15
- this.accessTokenKey = accessTokenKey;
16
- this.refreshTokenKey = refreshTokenKey;
17
- }
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
18
 
19
- async getAccessToken(): Promise<string | null> {
20
- const token = await this.cache.get(this.accessTokenKey);
21
- return token || null;
22
- }
19
+ async getAccessToken(): Promise<string | null> {
20
+ const token = await this.cache.get<string>(this.accessTokenKey);
21
+ return token ?? null;
22
+ }
23
23
 
24
- async setAccessToken(token: string, ttl?: number): Promise<void> {
25
- await this.cache.set(this.accessTokenKey, token, ttl);
26
- }
24
+ async setAccessToken(token: string, ttl?: number): Promise<void> {
25
+ await this.cache.set(this.accessTokenKey, token, ttl);
26
+ }
27
27
 
28
- async getRefreshToken(): Promise<string | null> {
29
- const token = await this.cache.get(this.refreshTokenKey);
30
- return token || null;
31
- }
28
+ async getRefreshToken(): Promise<string | null> {
29
+ const token = await this.cache.get<string>(this.refreshTokenKey);
30
+ return token ?? null;
31
+ }
32
32
 
33
- async setRefreshToken(token: string, ttl?: number): Promise<void> {
34
- await this.cache.set(this.refreshTokenKey, token, ttl);
35
- }
33
+ async setRefreshToken(token: string, ttl?: number): Promise<void> {
34
+ await this.cache.set(this.refreshTokenKey, token, ttl);
35
+ }
36
36
 
37
- async clearTokens(): Promise<void> {
38
- await Promise.all([
39
- this.cache.delete(this.accessTokenKey),
40
- this.cache.delete(this.refreshTokenKey),
41
- ]);
42
- }
37
+ async clearTokens(): Promise<void> {
38
+ await Promise.all([
39
+ this.cache.delete(this.accessTokenKey),
40
+ this.cache.delete(this.refreshTokenKey),
41
+ ]);
42
+ }
43
43
  }