@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.
- package/CHANGELOG.md +12 -0
- package/dist/{cacheTokenStorage-Ca05vf3T.mjs → cacheTokenStorage-16NE8Cd9.mjs} +3 -3
- package/dist/cacheTokenStorage-16NE8Cd9.mjs.map +1 -0
- package/dist/{cacheTokenStorage-BzBRJfd-.d.mts → cacheTokenStorage-B7sOoysu.d.cts} +8 -6
- package/dist/cacheTokenStorage-B7sOoysu.d.cts.map +1 -0
- package/dist/{cacheTokenStorage-CvptztkW.cjs → cacheTokenStorage-CEpMniTi.cjs} +3 -3
- package/dist/cacheTokenStorage-CEpMniTi.cjs.map +1 -0
- package/dist/{cacheTokenStorage-yNUpDD8W.d.cts → cacheTokenStorage-_tq4Kmhc.d.mts} +8 -6
- package/dist/cacheTokenStorage-_tq4Kmhc.d.mts.map +1 -0
- package/dist/cacheTokenStorage.cjs +1 -1
- package/dist/cacheTokenStorage.d.cts +1 -1
- package/dist/cacheTokenStorage.d.mts +1 -1
- package/dist/cacheTokenStorage.mjs +1 -1
- package/dist/{client-DaiUm0UZ.cjs → client-BPl_mMvb.cjs} +14 -8
- package/dist/client-BPl_mMvb.cjs.map +1 -0
- package/dist/{client-DjNZS6vZ.mjs → client-eidYNJSH.mjs} +14 -8
- package/dist/client-eidYNJSH.mjs.map +1 -0
- package/dist/client.cjs +2 -2
- package/dist/client.d.cts +1 -1
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +2 -2
- package/dist/hono/jwt.cjs +1 -1
- package/dist/hono/jwt.cjs.map +1 -1
- package/dist/hono/jwt.d.cts +2 -1
- package/dist/hono/jwt.d.cts.map +1 -0
- package/dist/hono/jwt.d.mts +2 -1
- package/dist/hono/jwt.d.mts.map +1 -0
- package/dist/hono/jwt.mjs +1 -1
- package/dist/hono/jwt.mjs.map +1 -1
- package/dist/hono/oidc.cjs +1 -1
- package/dist/hono/oidc.cjs.map +1 -1
- package/dist/hono/oidc.d.cts +3 -2
- package/dist/hono/oidc.d.cts.map +1 -0
- package/dist/hono/oidc.d.mts +3 -2
- package/dist/hono/oidc.d.mts.map +1 -0
- package/dist/hono/oidc.mjs +1 -1
- package/dist/hono/oidc.mjs.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/{jwt-DF9kFbmh.d.cts → jwt-3eIuY2dg.d.cts} +2 -1
- package/dist/jwt-3eIuY2dg.d.cts.map +1 -0
- package/dist/jwt-Bo_eACEi.cjs.map +1 -1
- package/dist/{jwt-DH8CJ2xC.d.mts → jwt-BqLlRkw-.d.mts} +2 -1
- package/dist/jwt-BqLlRkw-.d.mts.map +1 -0
- package/dist/jwt-D88cPJyT.mjs.map +1 -1
- package/dist/jwt.d.cts +1 -1
- package/dist/jwt.d.mts +1 -1
- package/dist/lambda/jwt.cjs +2 -2
- package/dist/lambda/jwt.cjs.map +1 -1
- package/dist/lambda/jwt.d.cts +2 -1
- package/dist/lambda/jwt.d.cts.map +1 -0
- package/dist/lambda/jwt.d.mts +2 -1
- package/dist/lambda/jwt.d.mts.map +1 -0
- package/dist/lambda/jwt.mjs +2 -2
- package/dist/lambda/jwt.mjs.map +1 -1
- package/dist/lambda/oidc.cjs +2 -2
- package/dist/lambda/oidc.cjs.map +1 -1
- package/dist/lambda/oidc.d.cts +3 -2
- package/dist/lambda/oidc.d.cts.map +1 -0
- package/dist/lambda/oidc.d.mts +3 -2
- package/dist/lambda/oidc.d.mts.map +1 -0
- package/dist/lambda/oidc.mjs +2 -2
- package/dist/lambda/oidc.mjs.map +1 -1
- package/dist/oidc-C0KyYaAK.cjs.map +1 -1
- package/dist/{oidc-CKOGLxjV.d.mts → oidc-ChA17wLB.d.cts} +3 -2
- package/dist/oidc-ChA17wLB.d.cts.map +1 -0
- package/dist/{oidc-DDLIB5gb.d.cts → oidc-DTRgBBl6.d.mts} +3 -2
- package/dist/oidc-DTRgBBl6.d.mts.map +1 -0
- package/dist/oidc-KA30gMlx.mjs.map +1 -1
- package/dist/oidc.d.cts +2 -2
- package/dist/oidc.d.mts +2 -2
- package/dist/server.cjs +2 -2
- package/dist/server.d.cts +2 -2
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +2 -2
- package/dist/{tokenManager--Q93eNYr.d.mts → tokenManager-CL3yE06D.d.cts} +2 -1
- package/dist/tokenManager-CL3yE06D.d.cts.map +1 -0
- package/dist/{tokenManager-BVXxfHRa.mjs → tokenManager-Ci2Tgxp8.mjs} +5 -3
- package/dist/tokenManager-Ci2Tgxp8.mjs.map +1 -0
- package/dist/{tokenManager-EPmwgai2.cjs → tokenManager-CvEMgxOp.cjs} +5 -3
- package/dist/tokenManager-CvEMgxOp.cjs.map +1 -0
- package/dist/{tokenManager-H6-lWEah.d.cts → tokenManager-RrwjL0y-.d.mts} +2 -1
- package/dist/tokenManager-RrwjL0y-.d.mts.map +1 -0
- package/dist/tokenManager.cjs +1 -1
- package/dist/tokenManager.d.cts +1 -1
- package/dist/tokenManager.d.mts +1 -1
- package/dist/tokenManager.mjs +1 -1
- package/package.json +3 -3
- package/src/__tests__/cacheTokenStorage.spec.ts +148 -148
- package/src/__tests__/client.spec.ts +339 -339
- package/src/__tests__/hono-jwt.spec.ts +219 -219
- package/src/__tests__/hono-oidc.spec.ts +560 -560
- package/src/__tests__/jwt.spec.ts +150 -127
- package/src/__tests__/lambda-jwt.spec.ts +205 -205
- package/src/__tests__/lambda-oidc.spec.ts +580 -579
- package/src/__tests__/oidc.spec.ts +365 -365
- package/src/__tests__/tokenManager.spec.ts +254 -254
- package/src/cacheTokenStorage.ts +32 -32
- package/src/client.ts +199 -190
- package/src/hono/jwt.ts +111 -111
- package/src/hono/oidc.ts +131 -131
- package/src/index.ts +2 -2
- package/src/jwt.ts +57 -57
- package/src/lambda/jwt.ts +170 -170
- package/src/lambda/oidc.ts +165 -165
- package/src/oidc.ts +121 -121
- package/src/server.ts +6 -7
- package/src/tokenManager.ts +89 -87
- package/tsconfig.json +10 -0
- package/dist/cacheTokenStorage-Ca05vf3T.mjs.map +0 -1
- package/dist/cacheTokenStorage-CvptztkW.cjs.map +0 -1
- package/dist/client-DaiUm0UZ.cjs.map +0 -1
- package/dist/client-DjNZS6vZ.mjs.map +0 -1
- package/dist/tokenManager-BVXxfHRa.mjs.map +0 -1
- package/dist/tokenManager-EPmwgai2.cjs.map +0 -1
|
@@ -1,465 +1,465 @@
|
|
|
1
1
|
import { InMemoryCache } from '@geekmidas/cache/memory';
|
|
2
|
-
import {
|
|
2
|
+
import { HttpResponse, http } from 'msw';
|
|
3
3
|
import { setupServer } from 'msw/node';
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
afterAll,
|
|
6
|
+
afterEach,
|
|
7
|
+
beforeAll,
|
|
8
|
+
beforeEach,
|
|
9
|
+
describe,
|
|
10
|
+
expect,
|
|
11
|
+
it,
|
|
12
12
|
} from 'vitest';
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
CacheTokenStorage,
|
|
15
|
+
LocalStorageTokenStorage,
|
|
16
|
+
MemoryTokenStorage,
|
|
17
|
+
TokenClient,
|
|
18
|
+
type TokenStorage,
|
|
19
19
|
} from '../client.ts';
|
|
20
20
|
|
|
21
21
|
// Mock localStorage for browser environment simulation
|
|
22
22
|
const mockLocalStorage = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
getItem: (key: string) => {
|
|
24
|
+
return mockLocalStorage._storage[key] || null;
|
|
25
|
+
},
|
|
26
|
+
setItem: (key: string, value: string) => {
|
|
27
|
+
mockLocalStorage._storage[key] = value;
|
|
28
|
+
},
|
|
29
|
+
removeItem: (key: string) => {
|
|
30
|
+
delete mockLocalStorage._storage[key];
|
|
31
|
+
},
|
|
32
|
+
_storage: {} as Record<string, string>,
|
|
33
|
+
_reset: () => {
|
|
34
|
+
mockLocalStorage._storage = {};
|
|
35
|
+
},
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
Object.defineProperty(global, 'localStorage', {
|
|
39
|
-
|
|
39
|
+
value: mockLocalStorage,
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
// Setup MSW server for API testing
|
|
43
43
|
const server = setupServer(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
http.post('https://auth.toolbox.dev/auth/refresh', () => {
|
|
45
|
+
return HttpResponse.json({
|
|
46
|
+
accessToken: 'new-access-token',
|
|
47
|
+
refreshToken: 'new-refresh-token',
|
|
48
|
+
});
|
|
49
|
+
}),
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
describe('LocalStorageTokenStorage', () => {
|
|
53
|
-
|
|
53
|
+
let storage: LocalStorageTokenStorage;
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
mockLocalStorage._reset();
|
|
57
|
+
storage = new LocalStorageTokenStorage();
|
|
58
|
+
});
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
describe('token lifecycle', () => {
|
|
61
|
+
it('should return null when tokens do not exist', () => {
|
|
62
|
+
expect(storage.getAccessToken()).toBeNull();
|
|
63
|
+
expect(storage.getRefreshToken()).toBeNull();
|
|
64
|
+
});
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
it('should clear both tokens', () => {
|
|
67
|
+
storage.setAccessToken('access-token');
|
|
68
|
+
storage.setRefreshToken('refresh-token');
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
storage.clearTokens();
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
expect(storage.getAccessToken()).toBeNull();
|
|
73
|
+
expect(storage.getRefreshToken()).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
describe('MemoryTokenStorage', () => {
|
|
79
|
-
|
|
79
|
+
let storage: MemoryTokenStorage;
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
storage = new MemoryTokenStorage();
|
|
83
|
+
});
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
describe('token lifecycle', () => {
|
|
86
|
+
it('should store and retrieve tokens in memory', () => {
|
|
87
|
+
storage.setAccessToken('memory-access-token');
|
|
88
|
+
storage.setRefreshToken('memory-refresh-token');
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
expect(storage.getAccessToken()).toBe('memory-access-token');
|
|
91
|
+
expect(storage.getRefreshToken()).toBe('memory-refresh-token');
|
|
92
|
+
});
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
it('should return null for unset tokens', () => {
|
|
95
|
+
expect(storage.getAccessToken()).toBeNull();
|
|
96
|
+
expect(storage.getRefreshToken()).toBeNull();
|
|
97
|
+
});
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
it('should clear all tokens', () => {
|
|
100
|
+
storage.setAccessToken('access-token');
|
|
101
|
+
storage.setRefreshToken('refresh-token');
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
storage.clearTokens();
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
expect(storage.getAccessToken()).toBeNull();
|
|
106
|
+
expect(storage.getRefreshToken()).toBeNull();
|
|
107
|
+
});
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
it('should maintain separate instances', () => {
|
|
110
|
+
const storage1 = new MemoryTokenStorage();
|
|
111
|
+
const storage2 = new MemoryTokenStorage();
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
storage1.setAccessToken('token1');
|
|
114
|
+
storage2.setAccessToken('token2');
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
expect(storage1.getAccessToken()).toBe('token1');
|
|
117
|
+
expect(storage2.getAccessToken()).toBe('token2');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
describe('CacheTokenStorage', () => {
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
let storage: CacheTokenStorage;
|
|
124
|
+
let cache: InMemoryCache<string>;
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
cache = new InMemoryCache<string>();
|
|
128
|
+
storage = new CacheTokenStorage(cache);
|
|
129
|
+
});
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
describe('token lifecycle with cache', () => {
|
|
132
|
+
it('should store and retrieve tokens via cache', async () => {
|
|
133
|
+
await storage.setAccessToken('cache-access-token');
|
|
134
|
+
await storage.setRefreshToken('cache-refresh-token');
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
const accessToken = await storage.getAccessToken();
|
|
137
|
+
const refreshToken = await storage.getRefreshToken();
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
expect(accessToken).toBe('cache-access-token');
|
|
140
|
+
expect(refreshToken).toBe('cache-refresh-token');
|
|
141
|
+
});
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
it('should handle TTL for tokens', async () => {
|
|
144
|
+
await storage.setAccessToken('ttl-access-token', 3600);
|
|
145
|
+
await storage.setRefreshToken('ttl-refresh-token', 7200);
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
const accessToken = await storage.getAccessToken();
|
|
148
|
+
const refreshToken = await storage.getRefreshToken();
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
expect(accessToken).toBe('ttl-access-token');
|
|
151
|
+
expect(refreshToken).toBe('ttl-refresh-token');
|
|
152
|
+
});
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
it('should clear both tokens from cache', async () => {
|
|
155
|
+
await storage.setAccessToken('access-token');
|
|
156
|
+
await storage.setRefreshToken('refresh-token');
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
await storage.clearTokens();
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
const accessToken = await storage.getAccessToken();
|
|
161
|
+
const refreshToken = await storage.getRefreshToken();
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
expect(accessToken).toBeNull();
|
|
164
|
+
expect(refreshToken).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
describe('TokenClient', () => {
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
let client: TokenClient;
|
|
171
|
+
let storage: TokenStorage;
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
beforeAll(() => server.listen());
|
|
174
|
+
afterEach(() => server.resetHandlers());
|
|
175
|
+
afterAll(() => server.close());
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
beforeEach(() => {
|
|
178
|
+
storage = new MemoryTokenStorage();
|
|
179
|
+
client = new TokenClient({
|
|
180
|
+
storage,
|
|
181
|
+
refreshEndpoint: 'https://auth.toolbox.dev/auth/refresh',
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
describe('token management', () => {
|
|
186
|
+
it('should store and retrieve tokens', async () => {
|
|
187
|
+
await client.setTokens('access-token', 'refresh-token');
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
const accessToken = await client.getAccessToken();
|
|
190
|
+
const refreshToken = await client.getRefreshToken();
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
expect(accessToken).toBe('access-token');
|
|
193
|
+
expect(refreshToken).toBe('refresh-token');
|
|
194
|
+
});
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
it('should store tokens with TTL', async () => {
|
|
197
|
+
await client.setTokens('access-token', 'refresh-token', 900, 7200);
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
const accessToken = await client.getAccessToken();
|
|
200
|
+
const refreshToken = await client.getRefreshToken();
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
expect(accessToken).toBe('access-token');
|
|
203
|
+
expect(refreshToken).toBe('refresh-token');
|
|
204
|
+
});
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
it('should store only access token when refresh not provided', async () => {
|
|
207
|
+
await client.setTokens('access-only-token');
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
const accessToken = await client.getAccessToken();
|
|
210
|
+
const refreshToken = await client.getRefreshToken();
|
|
211
211
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
expect(accessToken).toBe('access-only-token');
|
|
213
|
+
expect(refreshToken).toBeNull();
|
|
214
|
+
});
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
it('should clear all tokens', async () => {
|
|
217
|
+
await client.setTokens('access-token', 'refresh-token');
|
|
218
|
+
await client.clearTokens();
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
const accessToken = await client.getAccessToken();
|
|
221
|
+
const refreshToken = await client.getRefreshToken();
|
|
222
222
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
expect(accessToken).toBeNull();
|
|
224
|
+
expect(refreshToken).toBeNull();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
228
|
+
describe('token validation', () => {
|
|
229
|
+
it('should identify valid tokens correctly', () => {
|
|
230
|
+
const validPayload = { exp: Math.floor(Date.now() / 1000) + 3600 };
|
|
231
|
+
const validToken = `header.${btoa(JSON.stringify(validPayload))}.signature`;
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
expect(client.isTokenExpired(validToken)).toBe(false);
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
const expiration = client.getTokenExpiration(validToken);
|
|
236
|
+
expect(expiration).toBeInstanceOf(Date);
|
|
237
|
+
expect(expiration?.getTime()).toBe(validPayload.exp * 1000);
|
|
238
|
+
});
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
it('should identify expired tokens correctly', () => {
|
|
241
|
+
const expiredPayload = { exp: Math.floor(Date.now() / 1000) - 3600 };
|
|
242
|
+
const expiredToken = `header.${btoa(JSON.stringify(expiredPayload))}.signature`;
|
|
243
243
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
244
|
+
expect(client.isTokenExpired(expiredToken)).toBe(true);
|
|
245
|
+
|
|
246
|
+
const expiration = client.getTokenExpiration(expiredToken);
|
|
247
|
+
expect(expiration).toBeInstanceOf(Date);
|
|
248
|
+
expect(expiration?.getTime()).toBe(expiredPayload.exp * 1000);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should handle malformed tokens gracefully', () => {
|
|
252
|
+
expect(client.isTokenExpired('malformed-token')).toBe(true);
|
|
253
|
+
expect(client.isTokenExpired('')).toBe(true);
|
|
254
|
+
expect(client.isTokenExpired('not.a.token')).toBe(true);
|
|
255
|
+
|
|
256
|
+
expect(client.getTokenExpiration('malformed-token')).toBeNull();
|
|
257
|
+
expect(client.getTokenExpiration('')).toBeNull();
|
|
258
|
+
expect(client.getTokenExpiration('not.a.token')).toBeNull();
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('API integration with MSW', () => {
|
|
263
|
+
it('should refresh tokens via API successfully', async () => {
|
|
264
|
+
await storage.setRefreshToken('valid-refresh-token');
|
|
265
|
+
|
|
266
|
+
const success = await client.refreshTokens();
|
|
267
|
+
|
|
268
|
+
expect(success).toBe(true);
|
|
269
|
+
expect(await storage.getAccessToken()).toBe('new-access-token');
|
|
270
|
+
expect(await storage.getRefreshToken()).toBe('new-refresh-token');
|
|
271
|
+
});
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
it('should handle API errors gracefully', async () => {
|
|
274
|
+
server.use(
|
|
275
|
+
http.post('https://auth.toolbox.dev/auth/refresh', () => {
|
|
276
|
+
return new HttpResponse(null, { status: 401 });
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
await storage.setRefreshToken('invalid-refresh-token');
|
|
281
281
|
|
|
282
|
-
|
|
282
|
+
const success = await client.refreshTokens();
|
|
283
283
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
284
|
+
expect(success).toBe(false);
|
|
285
|
+
expect(await storage.getAccessToken()).toBeNull();
|
|
286
|
+
expect(await storage.getRefreshToken()).toBeNull();
|
|
287
|
+
});
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
289
|
+
it('should handle network errors gracefully', async () => {
|
|
290
|
+
server.use(
|
|
291
|
+
http.post('https://auth.toolbox.dev/auth/refresh', () => {
|
|
292
|
+
return HttpResponse.error();
|
|
293
|
+
}),
|
|
294
|
+
);
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
await storage.setRefreshToken('valid-refresh-token');
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
const success = await client.refreshTokens();
|
|
299
299
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
300
|
+
expect(success).toBe(false);
|
|
301
|
+
expect(await storage.getAccessToken()).toBeNull();
|
|
302
|
+
expect(await storage.getRefreshToken()).toBeNull();
|
|
303
|
+
});
|
|
304
304
|
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
it('should fail when no refresh token available', async () => {
|
|
306
|
+
const success = await client.refreshTokens();
|
|
307
307
|
|
|
308
|
-
|
|
309
|
-
|
|
308
|
+
expect(success).toBe(false);
|
|
309
|
+
});
|
|
310
310
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
311
|
+
it('should fail when refresh endpoint not configured', async () => {
|
|
312
|
+
const clientWithoutEndpoint = new TokenClient({ storage });
|
|
313
|
+
await storage.setRefreshToken('valid-refresh-token');
|
|
314
314
|
|
|
315
|
-
|
|
315
|
+
const success = await clientWithoutEndpoint.refreshTokens();
|
|
316
316
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
317
|
+
expect(success).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
321
|
+
describe('callback handling', () => {
|
|
322
|
+
it('should call onTokenRefresh callback on success', async () => {
|
|
323
|
+
let callbackData: any = null;
|
|
324
|
+
const clientWithCallback = new TokenClient({
|
|
325
|
+
storage,
|
|
326
|
+
refreshEndpoint: 'https://auth.toolbox.dev/auth/refresh',
|
|
327
|
+
onTokenRefresh: (data) => {
|
|
328
|
+
callbackData = data;
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
331
|
|
|
332
|
-
|
|
332
|
+
await storage.setRefreshToken('valid-refresh-token');
|
|
333
333
|
|
|
334
|
-
|
|
334
|
+
await clientWithCallback.refreshTokens();
|
|
335
335
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
expect(callbackData).toEqual({
|
|
337
|
+
accessToken: 'new-access-token',
|
|
338
|
+
refreshToken: 'new-refresh-token',
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
342
|
+
it('should call onTokenExpired callback on failure', async () => {
|
|
343
|
+
let callbackCalled = false;
|
|
344
|
+
const clientWithCallback = new TokenClient({
|
|
345
|
+
storage,
|
|
346
|
+
refreshEndpoint: 'https://auth.toolbox.dev/auth/refresh',
|
|
347
|
+
onTokenExpired: () => {
|
|
348
|
+
callbackCalled = true;
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
351
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
352
|
+
server.use(
|
|
353
|
+
http.post('https://auth.toolbox.dev/auth/refresh', () => {
|
|
354
|
+
return new HttpResponse(null, { status: 401 });
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
357
|
|
|
358
|
-
|
|
358
|
+
await storage.setRefreshToken('invalid-refresh-token');
|
|
359
359
|
|
|
360
|
-
|
|
360
|
+
await clientWithCallback.refreshTokens();
|
|
361
361
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
362
|
+
expect(callbackCalled).toBe(true);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
365
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
366
|
+
describe('getValidAccessToken workflow', () => {
|
|
367
|
+
it('should return valid token without refresh', async () => {
|
|
368
|
+
const validPayload = { exp: Math.floor(Date.now() / 1000) + 3600 };
|
|
369
|
+
const validToken = `header.${btoa(JSON.stringify(validPayload))}.signature`;
|
|
370
370
|
|
|
371
|
-
|
|
371
|
+
await storage.setAccessToken(validToken);
|
|
372
372
|
|
|
373
|
-
|
|
373
|
+
const token = await client.getValidAccessToken();
|
|
374
374
|
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
expect(token).toBe(validToken);
|
|
376
|
+
});
|
|
377
377
|
|
|
378
|
-
|
|
379
|
-
|
|
378
|
+
it('should return null when no token available', async () => {
|
|
379
|
+
const token = await client.getValidAccessToken();
|
|
380
380
|
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
expect(token).toBeNull();
|
|
382
|
+
});
|
|
383
383
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
384
|
+
it('should refresh expired token automatically', async () => {
|
|
385
|
+
const expiredPayload = { exp: Math.floor(Date.now() / 1000) - 3600 };
|
|
386
|
+
const expiredToken = `header.${btoa(JSON.stringify(expiredPayload))}.signature`;
|
|
387
387
|
|
|
388
|
-
|
|
389
|
-
|
|
388
|
+
await storage.setAccessToken(expiredToken);
|
|
389
|
+
await storage.setRefreshToken('valid-refresh-token');
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
const token = await client.getValidAccessToken();
|
|
392
392
|
|
|
393
|
-
|
|
394
|
-
|
|
393
|
+
expect(token).toBe('new-access-token');
|
|
394
|
+
});
|
|
395
395
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
396
|
+
it('should return null when refresh fails', async () => {
|
|
397
|
+
server.use(
|
|
398
|
+
http.post('https://auth.toolbox.dev/auth/refresh', () => {
|
|
399
|
+
return new HttpResponse(null, { status: 401 });
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
402
|
|
|
403
|
-
|
|
404
|
-
|
|
403
|
+
const expiredPayload = { exp: Math.floor(Date.now() / 1000) - 3600 };
|
|
404
|
+
const expiredToken = `header.${btoa(JSON.stringify(expiredPayload))}.signature`;
|
|
405
405
|
|
|
406
|
-
|
|
407
|
-
|
|
406
|
+
await storage.setAccessToken(expiredToken);
|
|
407
|
+
await storage.setRefreshToken('invalid-refresh-token');
|
|
408
408
|
|
|
409
|
-
|
|
409
|
+
const token = await client.getValidAccessToken();
|
|
410
410
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
411
|
+
expect(token).toBeNull();
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
414
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
415
|
+
describe('authorization headers', () => {
|
|
416
|
+
it('should create headers with current token', async () => {
|
|
417
|
+
await storage.setAccessToken('current-token');
|
|
418
418
|
|
|
419
|
-
|
|
419
|
+
const headers = await client.createAuthHeaders();
|
|
420
420
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
421
|
+
expect(headers).toEqual({
|
|
422
|
+
Authorization: 'Bearer current-token',
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
425
|
|
|
426
|
-
|
|
427
|
-
|
|
426
|
+
it('should return empty headers when no token', async () => {
|
|
427
|
+
const headers = await client.createAuthHeaders();
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
expect(headers).toEqual({});
|
|
430
|
+
});
|
|
431
431
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
432
|
+
it('should create headers with valid token', async () => {
|
|
433
|
+
const validPayload = { exp: Math.floor(Date.now() / 1000) + 3600 };
|
|
434
|
+
const validToken = `header.${btoa(JSON.stringify(validPayload))}.signature`;
|
|
435
435
|
|
|
436
|
-
|
|
436
|
+
await storage.setAccessToken(validToken);
|
|
437
437
|
|
|
438
|
-
|
|
438
|
+
const headers = await client.createValidAuthHeaders();
|
|
439
439
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
440
|
+
expect(headers).toEqual({
|
|
441
|
+
Authorization: `Bearer ${validToken}`,
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
444
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
445
|
+
it('should create headers with refreshed token', async () => {
|
|
446
|
+
const expiredPayload = { exp: Math.floor(Date.now() / 1000) - 3600 };
|
|
447
|
+
const expiredToken = `header.${btoa(JSON.stringify(expiredPayload))}.signature`;
|
|
448
448
|
|
|
449
|
-
|
|
450
|
-
|
|
449
|
+
await storage.setAccessToken(expiredToken);
|
|
450
|
+
await storage.setRefreshToken('valid-refresh-token');
|
|
451
451
|
|
|
452
|
-
|
|
452
|
+
const headers = await client.createValidAuthHeaders();
|
|
453
453
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
454
|
+
expect(headers).toEqual({
|
|
455
|
+
Authorization: 'Bearer new-access-token',
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
458
|
|
|
459
|
-
|
|
460
|
-
|
|
459
|
+
it('should return empty headers when no valid token available', async () => {
|
|
460
|
+
const headers = await client.createValidAuthHeaders();
|
|
461
461
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
462
|
+
expect(headers).toEqual({});
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
465
|
});
|