@workos-inc/authkit-nextjs 3.0.0-beta.1 → 3.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 (106) hide show
  1. package/README.md +276 -102
  2. package/dist/esm/actions.js +35 -4
  3. package/dist/esm/actions.js.map +1 -1
  4. package/dist/esm/auth.js +51 -20
  5. package/dist/esm/auth.js.map +1 -1
  6. package/dist/esm/authkit-callback-route.js +82 -93
  7. package/dist/esm/authkit-callback-route.js.map +1 -1
  8. package/dist/esm/components/authkit-provider.js +36 -15
  9. package/dist/esm/components/authkit-provider.js.map +1 -1
  10. package/dist/esm/components/impersonation.js +17 -15
  11. package/dist/esm/components/impersonation.js.map +1 -1
  12. package/dist/esm/components/min-max-button.js +1 -1
  13. package/dist/esm/components/min-max-button.js.map +1 -1
  14. package/dist/esm/components/tokenStore.js +28 -19
  15. package/dist/esm/components/tokenStore.js.map +1 -1
  16. package/dist/esm/components/useAccessToken.js +1 -1
  17. package/dist/esm/components/useAccessToken.js.map +1 -1
  18. package/dist/esm/components/useTokenClaims.js +1 -1
  19. package/dist/esm/components/useTokenClaims.js.map +1 -1
  20. package/dist/esm/cookie.js +16 -5
  21. package/dist/esm/cookie.js.map +1 -1
  22. package/dist/esm/env-variables.js +6 -6
  23. package/dist/esm/env-variables.js.map +1 -1
  24. package/dist/esm/errors.js +36 -0
  25. package/dist/esm/errors.js.map +1 -0
  26. package/dist/esm/get-authorization-url.js +51 -12
  27. package/dist/esm/get-authorization-url.js.map +1 -1
  28. package/dist/esm/index.js +5 -2
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/interfaces.js +7 -1
  31. package/dist/esm/interfaces.js.map +1 -1
  32. package/dist/esm/middleware-helpers.js +102 -0
  33. package/dist/esm/middleware-helpers.js.map +1 -0
  34. package/dist/esm/middleware.js +3 -1
  35. package/dist/esm/middleware.js.map +1 -1
  36. package/dist/esm/pkce.js +38 -0
  37. package/dist/esm/pkce.js.map +1 -0
  38. package/dist/esm/session.js +73 -35
  39. package/dist/esm/session.js.map +1 -1
  40. package/dist/esm/test-helpers.js +1 -1
  41. package/dist/esm/test-helpers.js.map +1 -1
  42. package/dist/esm/types/actions.d.ts +34 -5
  43. package/dist/esm/types/auth.d.ts +7 -15
  44. package/dist/esm/types/components/authkit-provider.d.ts +6 -2
  45. package/dist/esm/types/components/impersonation.d.ts +2 -1
  46. package/dist/esm/types/cookie.d.ts +8 -0
  47. package/dist/esm/types/env-variables.d.ts +2 -1
  48. package/dist/esm/types/errors.d.ts +15 -0
  49. package/dist/esm/types/get-authorization-url.d.ts +2 -2
  50. package/dist/esm/types/index.d.ts +5 -2
  51. package/dist/esm/types/interfaces.d.ts +12 -0
  52. package/dist/esm/types/jwt.d.ts +9 -9
  53. package/dist/esm/types/middleware-helpers.d.ts +27 -0
  54. package/dist/esm/types/middleware.d.ts +3 -1
  55. package/dist/esm/types/pkce.d.ts +12 -0
  56. package/dist/esm/types/session.d.ts +1 -1
  57. package/dist/esm/types/utils.d.ts +5 -0
  58. package/dist/esm/types/validate-api-key.d.ts +1 -0
  59. package/dist/esm/types/workos.d.ts +1 -1
  60. package/dist/esm/utils.js +10 -2
  61. package/dist/esm/utils.js.map +1 -1
  62. package/dist/esm/validate-api-key.js +16 -0
  63. package/dist/esm/validate-api-key.js.map +1 -0
  64. package/dist/esm/workos.js +1 -1
  65. package/package.json +32 -34
  66. package/src/actions.spec.ts +94 -17
  67. package/src/actions.ts +44 -5
  68. package/src/auth.spec.ts +60 -29
  69. package/src/auth.ts +55 -41
  70. package/src/authkit-callback-route.spec.ts +310 -58
  71. package/src/authkit-callback-route.ts +106 -103
  72. package/src/components/authkit-provider.spec.tsx +264 -70
  73. package/src/components/authkit-provider.tsx +40 -15
  74. package/src/components/button.spec.tsx +4 -6
  75. package/src/components/impersonation.spec.tsx +152 -35
  76. package/src/components/impersonation.tsx +37 -30
  77. package/src/components/min-max-button.spec.tsx +2 -1
  78. package/src/components/tokenStore.spec.ts +59 -44
  79. package/src/components/tokenStore.ts +11 -3
  80. package/src/components/useAccessToken.spec.tsx +82 -83
  81. package/src/components/useTokenClaims.spec.tsx +23 -22
  82. package/src/cookie.spec.ts +14 -9
  83. package/src/cookie.ts +29 -0
  84. package/src/env-variables.ts +2 -0
  85. package/src/errors.spec.ts +108 -0
  86. package/src/errors.ts +46 -0
  87. package/src/get-authorization-url.spec.ts +170 -15
  88. package/src/get-authorization-url.ts +69 -23
  89. package/src/index.ts +20 -2
  90. package/src/interfaces.ts +15 -0
  91. package/src/jwt.ts +9 -9
  92. package/src/middleware-helpers.spec.ts +238 -0
  93. package/src/middleware-helpers.ts +134 -0
  94. package/src/middleware.spec.ts +25 -0
  95. package/src/middleware.ts +4 -1
  96. package/src/pkce.spec.ts +125 -0
  97. package/src/pkce.ts +42 -0
  98. package/src/session.spec.ts +87 -89
  99. package/src/session.ts +91 -27
  100. package/src/test-helpers.ts +1 -1
  101. package/src/utils.spec.ts +14 -31
  102. package/src/utils.ts +9 -0
  103. package/src/validate-api-key.spec.ts +111 -0
  104. package/src/validate-api-key.ts +19 -0
  105. package/src/workos.spec.ts +2 -2
  106. package/src/workos.ts +1 -1
@@ -1,6 +1,14 @@
1
1
  import { getAccessTokenAction, refreshAccessTokenAction } from '../actions.js';
2
+ import type { RefreshAccessTokenActionResult } from '../actions.js';
2
3
  import { decodeJwt } from '../jwt.js';
3
4
 
5
+ function unwrapRefreshResult(result: RefreshAccessTokenActionResult): string | undefined {
6
+ if (result.error) {
7
+ throw new Error(result.error);
8
+ }
9
+ return result.accessToken;
10
+ }
11
+
4
12
  interface TokenState {
5
13
  token: string | undefined;
6
14
  loading: boolean;
@@ -323,7 +331,7 @@ export class TokenStore {
323
331
 
324
332
  if (!silent) {
325
333
  // Manual refresh - always force refresh
326
- token = await refreshAccessTokenAction();
334
+ token = unwrapRefreshResult(await refreshAccessTokenAction());
327
335
  } else {
328
336
  // Silent refresh - only fetch from server if we don't have a local token
329
337
  if (!previousToken) {
@@ -342,14 +350,14 @@ export class TokenStore {
342
350
 
343
351
  // If the token from server is expiring, refresh it
344
352
  if (!token || (tokenData && tokenData.isExpiring)) {
345
- const refreshedToken = await refreshAccessTokenAction();
353
+ const refreshedToken = unwrapRefreshResult(await refreshAccessTokenAction());
346
354
  if (refreshedToken) {
347
355
  token = refreshedToken;
348
356
  }
349
357
  }
350
358
  } else {
351
359
  // We have a local token that needs refreshing (already checked by getAccessTokenSilently)
352
- token = await refreshAccessTokenAction();
360
+ token = unwrapRefreshResult(await refreshAccessTokenAction());
353
361
  }
354
362
  }
355
363
 
@@ -1,3 +1,4 @@
1
+ import type { Mock } from 'vitest';
1
2
  import '@testing-library/jest-dom';
2
3
  import { act, render, waitFor } from '@testing-library/react';
3
4
  import React from 'react';
@@ -6,41 +7,37 @@ import { useAuth } from './authkit-provider.js';
6
7
  import { useAccessToken } from './useAccessToken.js';
7
8
  import { tokenStore } from './tokenStore.js';
8
9
 
9
- jest.mock('../actions.js', () => ({
10
- getAccessTokenAction: jest.fn(),
11
- refreshAccessTokenAction: jest.fn(),
10
+ vi.mock('../actions.js', () => ({
11
+ getAccessTokenAction: vi.fn(),
12
+ refreshAccessTokenAction: vi.fn(),
12
13
  }));
13
14
 
14
- jest.mock('./authkit-provider.js', () => {
15
- const originalModule = jest.requireActual('./authkit-provider.js');
15
+ vi.mock('./authkit-provider.js', async () => {
16
+ const originalModule = await vi.importActual<typeof import('./authkit-provider.js')>('./authkit-provider.js');
16
17
  return {
17
18
  ...originalModule,
18
- useAuth: jest.fn(),
19
+ useAuth: vi.fn(),
19
20
  };
20
21
  });
21
22
 
22
23
  describe('useAccessToken', () => {
23
24
  beforeEach(() => {
24
25
  tokenStore.reset();
25
- jest.resetAllMocks();
26
- jest.useFakeTimers();
26
+ vi.resetAllMocks();
27
+ vi.useFakeTimers({ shouldAdvanceTime: true });
27
28
 
28
- // Reset mock implementations to avoid test interference
29
- (getAccessTokenAction as jest.Mock).mockReset();
30
- (refreshAccessTokenAction as jest.Mock).mockReset();
31
-
32
- (useAuth as jest.Mock).mockImplementation(() => ({
29
+ (useAuth as Mock).mockImplementation(() => ({
33
30
  user: { id: 'user_123' },
34
31
  sessionId: 'session_123',
35
- refreshAuth: jest.fn().mockResolvedValue({}),
32
+ refreshAuth: vi.fn().mockResolvedValue({}),
36
33
  }));
37
34
  });
38
35
 
39
36
  afterEach(() => {
40
- jest.clearAllTimers();
41
- jest.useRealTimers();
37
+ vi.clearAllTimers();
38
+ vi.useRealTimers();
42
39
  tokenStore.reset();
43
- jest.clearAllMocks();
40
+ vi.clearAllMocks();
44
41
  });
45
42
 
46
43
  const TestComponent = () => {
@@ -60,7 +57,7 @@ describe('useAccessToken', () => {
60
57
  it('should fetch an access token on mount and show loading state initially', async () => {
61
58
  const mockToken =
62
59
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
63
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(mockToken);
60
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(mockToken);
64
61
 
65
62
  const { getByTestId } = render(<TestComponent />);
66
63
 
@@ -92,8 +89,8 @@ describe('useAccessToken', () => {
92
89
  const refreshedToken =
93
90
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
94
91
 
95
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(expiringToken);
96
- (refreshAccessTokenAction as jest.Mock).mockResolvedValueOnce(refreshedToken);
92
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(expiringToken);
93
+ (refreshAccessTokenAction as Mock).mockResolvedValueOnce({ accessToken: refreshedToken });
97
94
 
98
95
  const { getByTestId } = render(<TestComponent />);
99
96
 
@@ -115,8 +112,8 @@ describe('useAccessToken', () => {
115
112
  const refreshedToken =
116
113
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWZyZXNoZWQiLCJzaWQiOiJzZXNzaW9uXzEyMyIsImV4cCI6OTk5OTk5OTk5OX0.mock-signature-2';
117
114
 
118
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(initialToken);
119
- (refreshAccessTokenAction as jest.Mock).mockResolvedValueOnce(refreshedToken);
115
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(initialToken);
116
+ (refreshAccessTokenAction as Mock).mockResolvedValueOnce({ accessToken: refreshedToken });
120
117
 
121
118
  const { getByTestId } = render(<TestComponent />);
122
119
 
@@ -141,10 +138,10 @@ describe('useAccessToken', () => {
141
138
  });
142
139
 
143
140
  it('should handle the not loggged in state', async () => {
144
- (useAuth as jest.Mock).mockImplementation(() => ({
141
+ (useAuth as Mock).mockImplementation(() => ({
145
142
  user: undefined,
146
143
  sessionId: undefined,
147
- refreshAuth: jest.fn().mockResolvedValue({}),
144
+ refreshAuth: vi.fn().mockResolvedValue({}),
148
145
  }));
149
146
 
150
147
  const { getByTestId } = render(<TestComponent />);
@@ -157,7 +154,7 @@ describe('useAccessToken', () => {
157
154
 
158
155
  it('should handle errors during token fetch', async () => {
159
156
  const error = new Error('Failed to fetch token');
160
- (getAccessTokenAction as jest.Mock).mockRejectedValueOnce(error);
157
+ (getAccessTokenAction as Mock).mockRejectedValueOnce(error);
161
158
 
162
159
  const { getByTestId } = render(<TestComponent />);
163
160
 
@@ -174,10 +171,12 @@ describe('useAccessToken', () => {
174
171
  it('should handle errors during manual refresh', async () => {
175
172
  const initialToken =
176
173
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
177
- const error = new Error('Failed to refresh token');
178
174
 
179
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(initialToken);
180
- (refreshAccessTokenAction as jest.Mock).mockRejectedValueOnce(error);
175
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(initialToken);
176
+ (refreshAccessTokenAction as Mock).mockResolvedValueOnce({
177
+ accessToken: undefined,
178
+ error: 'Failed to refresh token',
179
+ });
181
180
 
182
181
  const { getByTestId } = render(<TestComponent />);
183
182
 
@@ -200,13 +199,13 @@ describe('useAccessToken', () => {
200
199
  it('should reset token state when user is undefined', async () => {
201
200
  const mockToken =
202
201
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
203
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(mockToken);
202
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(mockToken);
204
203
 
205
204
  // First render with user
206
- (useAuth as jest.Mock).mockImplementation(() => ({
205
+ (useAuth as Mock).mockImplementation(() => ({
207
206
  user: { id: 'user_123' },
208
207
  sessionId: 'session_123',
209
- refreshAuth: jest.fn().mockResolvedValue({}),
208
+ refreshAuth: vi.fn().mockResolvedValue({}),
210
209
  }));
211
210
 
212
211
  const { getByTestId, rerender } = render(<TestComponent />);
@@ -215,10 +214,10 @@ describe('useAccessToken', () => {
215
214
  expect(getByTestId('token')).toHaveTextContent(mockToken);
216
215
  });
217
216
 
218
- (useAuth as jest.Mock).mockImplementation(() => ({
217
+ (useAuth as Mock).mockImplementation(() => ({
219
218
  user: undefined,
220
219
  sessionId: undefined,
221
- refreshAuth: jest.fn().mockResolvedValue({}),
220
+ refreshAuth: vi.fn().mockResolvedValue({}),
222
221
  }));
223
222
 
224
223
  rerender(<TestComponent />);
@@ -230,7 +229,7 @@ describe('useAccessToken', () => {
230
229
 
231
230
  it('should handle invalid tokens gracefully', async () => {
232
231
  const invalidToken = 'invalid-token';
233
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(invalidToken);
232
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(invalidToken);
234
233
 
235
234
  const { getByTestId } = render(<TestComponent />);
236
235
 
@@ -246,7 +245,7 @@ describe('useAccessToken', () => {
246
245
  const mockToken =
247
246
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
248
247
 
249
- (getAccessTokenAction as jest.Mock).mockRejectedValueOnce(error).mockResolvedValueOnce(mockToken);
248
+ (getAccessTokenAction as Mock).mockRejectedValueOnce(error).mockResolvedValueOnce(mockToken);
250
249
 
251
250
  const { getByTestId } = render(<TestComponent />);
252
251
 
@@ -257,7 +256,7 @@ describe('useAccessToken', () => {
257
256
  });
258
257
 
259
258
  act(() => {
260
- jest.advanceTimersByTime(5 * 60 * 1000); // RETRY_DELAY
259
+ vi.advanceTimersByTime(5 * 60 * 1000); // RETRY_DELAY
261
260
  });
262
261
 
263
262
  // Loading should remain false during retry
@@ -281,10 +280,12 @@ describe('useAccessToken', () => {
281
280
  iat: currentTimeInSeconds - 35,
282
281
  };
283
282
  const expiringToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(payload))}.mock-signature`;
284
- const error = new Error('Failed to refresh token');
285
283
 
286
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(expiringToken);
287
- (refreshAccessTokenAction as jest.Mock).mockRejectedValueOnce(error);
284
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(expiringToken);
285
+ (refreshAccessTokenAction as Mock).mockResolvedValueOnce({
286
+ accessToken: undefined,
287
+ error: 'Failed to refresh token',
288
+ });
288
289
 
289
290
  const { getByTestId } = render(<TestComponent />);
290
291
 
@@ -301,7 +302,7 @@ describe('useAccessToken', () => {
301
302
 
302
303
  it('should handle token with an invalid payload format', async () => {
303
304
  const badPayloadToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.invalidpayload.mock-signature';
304
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(badPayloadToken);
305
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(badPayloadToken);
305
306
 
306
307
  const { getByTestId } = render(<TestComponent />);
307
308
 
@@ -313,7 +314,7 @@ describe('useAccessToken', () => {
313
314
  });
314
315
 
315
316
  it('should immediately try to update token when token is undefined', async () => {
316
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(undefined).mockResolvedValueOnce(undefined);
317
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(undefined).mockResolvedValueOnce(undefined);
317
318
 
318
319
  const { getByTestId } = render(<TestComponent />);
319
320
 
@@ -327,17 +328,17 @@ describe('useAccessToken', () => {
327
328
 
328
329
  it('should react to sessionId changes', async () => {
329
330
  // Clear any previous mocks to ensure clean state
330
- jest.clearAllMocks();
331
+ vi.clearAllMocks();
331
332
 
332
333
  const token1 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSIsImV4cCI6OTk5OTk5OTk5OX0.mock-signature-1';
333
334
  const token2 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSIsImV4cCI6OTk5OTk5OTk5OX0.mock-signature-2';
334
335
 
335
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(token1).mockResolvedValueOnce(token2);
336
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(token1).mockResolvedValueOnce(token2);
336
337
 
337
- (useAuth as jest.Mock).mockImplementation(() => ({
338
+ (useAuth as Mock).mockImplementation(() => ({
338
339
  user: { id: 'user1' },
339
340
  sessionId: 'session1',
340
- refreshAuth: jest.fn().mockResolvedValue({}),
341
+ refreshAuth: vi.fn().mockResolvedValue({}),
341
342
  }));
342
343
 
343
344
  const { rerender } = render(<TestComponent />);
@@ -346,10 +347,10 @@ describe('useAccessToken', () => {
346
347
  expect(getAccessTokenAction).toHaveBeenCalledTimes(1);
347
348
  });
348
349
 
349
- (useAuth as jest.Mock).mockImplementation(() => ({
350
+ (useAuth as Mock).mockImplementation(() => ({
350
351
  user: { id: 'user1' }, // Same user ID
351
352
  sessionId: 'session2',
352
- refreshAuth: jest.fn().mockResolvedValue({}),
353
+ refreshAuth: vi.fn().mockResolvedValue({}),
353
354
  }));
354
355
 
355
356
  rerender(<TestComponent />);
@@ -360,8 +361,8 @@ describe('useAccessToken', () => {
360
361
  });
361
362
 
362
363
  it('should prevent concurrent token fetches via updateToken', async () => {
363
- jest.clearAllMocks();
364
- (getAccessTokenAction as jest.Mock).mockReset();
364
+ vi.clearAllMocks();
365
+ (getAccessTokenAction as Mock).mockReset();
365
366
 
366
367
  const mockToken =
367
368
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
@@ -374,7 +375,7 @@ describe('useAccessToken', () => {
374
375
  }, 0);
375
376
  });
376
377
 
377
- (getAccessTokenAction as jest.Mock).mockImplementation(() => {
378
+ (getAccessTokenAction as Mock).mockImplementation(() => {
378
379
  fetchCalls++;
379
380
  return tokenPromise;
380
381
  });
@@ -397,7 +398,7 @@ describe('useAccessToken', () => {
397
398
  });
398
399
 
399
400
  it('should prevent concurrent manual refresh operations', async () => {
400
- jest.clearAllMocks();
401
+ vi.clearAllMocks();
401
402
 
402
403
  let refreshCalls = 0;
403
404
 
@@ -412,12 +413,12 @@ describe('useAccessToken', () => {
412
413
  setTimeout(() => resolve(refreshedToken), 10);
413
414
  });
414
415
 
415
- (refreshAccessTokenAction as jest.Mock).mockImplementation(() => {
416
+ (refreshAccessTokenAction as Mock).mockImplementation(() => {
416
417
  refreshCalls++;
417
- return refreshPromise;
418
+ return refreshPromise.then((t) => ({ accessToken: t }));
418
419
  });
419
420
 
420
- (getAccessTokenAction as jest.Mock).mockImplementation(() => {
421
+ (getAccessTokenAction as Mock).mockImplementation(() => {
421
422
  return Promise.resolve(mockToken);
422
423
  });
423
424
 
@@ -446,7 +447,7 @@ describe('useAccessToken', () => {
446
447
 
447
448
  it('should handle non-Error objects thrown during token fetch', async () => {
448
449
  // Simulate a string error being thrown
449
- (getAccessTokenAction as jest.Mock).mockImplementation(() => {
450
+ (getAccessTokenAction as Mock).mockImplementation(() => {
450
451
  throw 'String error message';
451
452
  });
452
453
 
@@ -461,13 +462,13 @@ describe('useAccessToken', () => {
461
462
 
462
463
  it('should show loading state immediately on first render when user exists but no token', () => {
463
464
  // Mock user with no token initially
464
- (useAuth as jest.Mock).mockImplementation(() => ({
465
+ (useAuth as Mock).mockImplementation(() => ({
465
466
  user: { id: 'user_123' },
466
467
  sessionId: 'session_123',
467
- refreshAuth: jest.fn().mockResolvedValue({}),
468
+ refreshAuth: vi.fn().mockResolvedValue({}),
468
469
  }));
469
470
 
470
- (getAccessTokenAction as jest.Mock).mockImplementation(
471
+ (getAccessTokenAction as Mock).mockImplementation(
471
472
  () => new Promise((resolve) => setTimeout(() => resolve('token'), 100)),
472
473
  );
473
474
 
@@ -482,12 +483,12 @@ describe('useAccessToken', () => {
482
483
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJleGlzdGluZyIsInNpZCI6InNlc3Npb24xMjMiLCJleHAiOjk5OTk5OTk5OTl9.existing';
483
484
 
484
485
  await act(async () => {
485
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(existingToken);
486
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(existingToken);
486
487
  await tokenStore.getAccessTokenSilently();
487
488
  });
488
489
 
489
490
  // Reset the mock to track new calls
490
- (getAccessTokenAction as jest.Mock).mockClear();
491
+ (getAccessTokenAction as Mock).mockClear();
491
492
 
492
493
  const { getByTestId } = render(<TestComponent />);
493
494
 
@@ -511,8 +512,8 @@ describe('useAccessToken', () => {
511
512
  resolveRefreshPromise = resolve;
512
513
  });
513
514
 
514
- (refreshAccessTokenAction as jest.Mock).mockReturnValue(refreshPromise);
515
- (getAccessTokenAction as jest.Mock).mockResolvedValue(initialToken);
515
+ (refreshAccessTokenAction as Mock).mockReturnValue(refreshPromise.then((t) => ({ accessToken: t })));
516
+ (getAccessTokenAction as Mock).mockResolvedValue(initialToken);
516
517
 
517
518
  const { getByTestId } = render(<TestComponent />);
518
519
 
@@ -541,7 +542,7 @@ describe('useAccessToken', () => {
541
542
  it('should clear refresh timeout on unmount', async () => {
542
543
  const mockToken =
543
544
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
544
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(mockToken);
545
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(mockToken);
545
546
 
546
547
  const { getByTestId, unmount } = render(<TestComponent />);
547
548
 
@@ -555,7 +556,7 @@ describe('useAccessToken', () => {
555
556
  it('should handle edge cases when token data is null', async () => {
556
557
  // Create a token that resembles a JWT but with a null payload
557
558
  const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.bnVsbA==.mock-signature'; // "null" in base64
558
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(token);
559
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(token);
559
560
 
560
561
  const { getByTestId } = render(<TestComponent />);
561
562
 
@@ -570,7 +571,7 @@ describe('useAccessToken', () => {
570
571
  it('should handle errors with string messages instead of Error objects', async () => {
571
572
  const error = 'String error message';
572
573
  const errorObj = new Error(error);
573
- (getAccessTokenAction as jest.Mock).mockRejectedValueOnce(errorObj);
574
+ (getAccessTokenAction as Mock).mockRejectedValueOnce(errorObj);
574
575
 
575
576
  const { getByTestId } = render(<TestComponent />);
576
577
 
@@ -585,11 +586,9 @@ describe('useAccessToken', () => {
585
586
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
586
587
  const stringError = 'String error directly'; // Not wrapped in Error object
587
588
 
588
- (getAccessTokenAction as jest.Mock).mockResolvedValueOnce(initialToken);
589
- // Mock refreshAccessTokenAction to reject with a string, not an Error object
590
- (refreshAccessTokenAction as jest.Mock).mockImplementation(() => {
591
- return Promise.reject(stringError); // Directly reject with string
592
- });
589
+ (getAccessTokenAction as Mock).mockResolvedValueOnce(initialToken);
590
+ // Mock refreshAccessTokenAction to return an error result (as server actions do)
591
+ (refreshAccessTokenAction as Mock).mockResolvedValueOnce({ accessToken: undefined, error: stringError });
593
592
 
594
593
  const { getByTestId } = render(<TestComponent />);
595
594
 
@@ -613,12 +612,12 @@ describe('useAccessToken', () => {
613
612
  const token =
614
613
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
615
614
 
616
- (getAccessTokenAction as jest.Mock).mockResolvedValue(token);
615
+ (getAccessTokenAction as Mock).mockResolvedValue(token);
617
616
 
618
- (useAuth as jest.Mock).mockImplementation(() => ({
617
+ (useAuth as Mock).mockImplementation(() => ({
619
618
  user: { id: 'user_123' },
620
619
  sessionId: 'session_123',
621
- refreshAuth: jest.fn().mockResolvedValue({}),
620
+ refreshAuth: vi.fn().mockResolvedValue({}),
622
621
  }));
623
622
 
624
623
  const { getByTestId, rerender } = render(<TestComponent />);
@@ -628,10 +627,10 @@ describe('useAccessToken', () => {
628
627
  expect(getAccessTokenAction).toHaveBeenCalledTimes(1);
629
628
  });
630
629
 
631
- (useAuth as jest.Mock).mockImplementation(() => ({
630
+ (useAuth as Mock).mockImplementation(() => ({
632
631
  user: { id: 'user_456' }, // Different user
633
632
  sessionId: 'session_123', // Same session
634
- refreshAuth: jest.fn().mockResolvedValue({}),
633
+ refreshAuth: vi.fn().mockResolvedValue({}),
635
634
  }));
636
635
 
637
636
  rerender(<TestComponent />);
@@ -642,10 +641,10 @@ describe('useAccessToken', () => {
642
641
  });
643
642
 
644
643
  it('should handle getAccessToken when user is not authenticated', async () => {
645
- (useAuth as jest.Mock).mockImplementation(() => ({
644
+ (useAuth as Mock).mockImplementation(() => ({
646
645
  user: null,
647
646
  sessionId: undefined,
648
- refreshAuth: jest.fn().mockResolvedValue({}),
647
+ refreshAuth: vi.fn().mockResolvedValue({}),
649
648
  }));
650
649
 
651
650
  const TestComponentWithGetAccessToken = () => {
@@ -670,11 +669,11 @@ describe('useAccessToken', () => {
670
669
  const mockToken =
671
670
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature';
672
671
 
673
- (getAccessTokenAction as jest.Mock).mockResolvedValue(mockToken);
674
- (useAuth as jest.Mock).mockImplementation(() => ({
672
+ (getAccessTokenAction as Mock).mockResolvedValue(mockToken);
673
+ (useAuth as Mock).mockImplementation(() => ({
675
674
  user: { id: 'user_123' },
676
675
  sessionId: 'session_123',
677
- refreshAuth: jest.fn().mockResolvedValue({}),
676
+ refreshAuth: vi.fn().mockResolvedValue({}),
678
677
  }));
679
678
 
680
679
  const TestComponentWithGetAccessToken = () => {
@@ -696,7 +695,7 @@ describe('useAccessToken', () => {
696
695
 
697
696
  // Advance timers to trigger getAccessToken call
698
697
  act(() => {
699
- jest.advanceTimersByTime(100);
698
+ vi.advanceTimersByTime(100);
700
699
  });
701
700
 
702
701
  await waitFor(() => {
@@ -705,10 +704,10 @@ describe('useAccessToken', () => {
705
704
  });
706
705
 
707
706
  it('should handle manual refresh when user is not authenticated', async () => {
708
- (useAuth as jest.Mock).mockImplementation(() => ({
707
+ (useAuth as Mock).mockImplementation(() => ({
709
708
  user: null,
710
709
  sessionId: undefined,
711
- refreshAuth: jest.fn().mockResolvedValue({}),
710
+ refreshAuth: vi.fn().mockResolvedValue({}),
712
711
  }));
713
712
 
714
713
  const TestComponentWithRefresh = () => {
@@ -1,27 +1,28 @@
1
+ import type { Mock } from 'vitest';
1
2
  import '@testing-library/jest-dom';
2
3
  import { render, waitFor } from '@testing-library/react';
3
4
  import React from 'react';
4
5
  import { useAuth } from './authkit-provider.js';
5
6
 
6
- jest.mock('../actions.js', () => ({
7
- getAccessTokenAction: jest.fn(),
8
- refreshAccessTokenAction: jest.fn(),
7
+ vi.mock('../actions.js', () => ({
8
+ getAccessTokenAction: vi.fn(),
9
+ refreshAccessTokenAction: vi.fn(),
9
10
  }));
10
11
 
11
- jest.mock('./authkit-provider.js', () => {
12
- const originalModule = jest.requireActual('./authkit-provider.js');
12
+ vi.mock('./authkit-provider.js', async () => {
13
+ const originalModule = await vi.importActual<typeof import('./authkit-provider.js')>('./authkit-provider.js');
13
14
  return {
14
15
  ...originalModule,
15
- useAuth: jest.fn(),
16
+ useAuth: vi.fn(),
16
17
  };
17
18
  });
18
19
 
19
- jest.mock('./useAccessToken.js', () => ({
20
- useAccessToken: jest.fn(() => ({ accessToken: undefined })),
20
+ vi.mock('./useAccessToken.js', () => ({
21
+ useAccessToken: vi.fn(() => ({ accessToken: undefined })),
21
22
  }));
22
23
 
23
- jest.mock('jose', () => ({
24
- decodeJwt: jest.fn((token: string) => {
24
+ vi.mock('jose', () => ({
25
+ decodeJwt: vi.fn((token: string) => {
25
26
  if (token === 'malformed-token' || token === 'throw-error-token') {
26
27
  throw new Error('Invalid JWT');
27
28
  }
@@ -42,21 +43,21 @@ import { useTokenClaims } from './useTokenClaims.js';
42
43
 
43
44
  describe('useTokenClaims', () => {
44
45
  beforeEach(() => {
45
- jest.clearAllMocks();
46
- jest.useFakeTimers();
46
+ vi.clearAllMocks();
47
+ vi.useFakeTimers({ shouldAdvanceTime: true });
47
48
 
48
- (useAuth as jest.Mock).mockImplementation(() => ({
49
+ (useAuth as Mock).mockImplementation(() => ({
49
50
  user: { id: 'user_123' },
50
51
  sessionId: 'session_123',
51
- refreshAuth: jest.fn().mockResolvedValue({}),
52
+ refreshAuth: vi.fn().mockResolvedValue({}),
52
53
  }));
53
54
 
54
55
  // Reset useAccessToken mock to default
55
- (useAccessToken as jest.Mock).mockReturnValue({ accessToken: undefined });
56
+ (useAccessToken as Mock).mockReturnValue({ accessToken: undefined });
56
57
  });
57
58
 
58
59
  afterEach(() => {
59
- jest.useRealTimers();
60
+ vi.useRealTimers();
60
61
  });
61
62
 
62
63
  const TokenClaimsTestComponent = () => {
@@ -69,7 +70,7 @@ describe('useTokenClaims', () => {
69
70
  };
70
71
 
71
72
  it('should return empty object when no access token is available', async () => {
72
- (useAccessToken as jest.Mock).mockReturnValue({ accessToken: undefined });
73
+ (useAccessToken as Mock).mockReturnValue({ accessToken: undefined });
73
74
 
74
75
  const { getByTestId } = render(<TokenClaimsTestComponent />);
75
76
 
@@ -101,7 +102,7 @@ describe('useTokenClaims', () => {
101
102
  };
102
103
  const token = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(payload))}.mock-signature`;
103
104
 
104
- (useAccessToken as jest.Mock).mockReturnValue({ accessToken: token });
105
+ (useAccessToken as Mock).mockReturnValue({ accessToken: token });
105
106
 
106
107
  const { getByTestId } = render(<TokenClaimsTestComponent />);
107
108
 
@@ -129,7 +130,7 @@ describe('useTokenClaims', () => {
129
130
  };
130
131
  const token = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(payload))}.mock-signature`;
131
132
 
132
- (useAccessToken as jest.Mock).mockReturnValue({ accessToken: token });
133
+ (useAccessToken as Mock).mockReturnValue({ accessToken: token });
133
134
 
134
135
  const { getByTestId } = render(<TokenClaimsTestComponent />);
135
136
 
@@ -147,7 +148,7 @@ describe('useTokenClaims', () => {
147
148
  };
148
149
  const token = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(payload))}.mock-signature`;
149
150
 
150
- (useAccessToken as jest.Mock).mockReturnValue({ accessToken: token });
151
+ (useAccessToken as Mock).mockReturnValue({ accessToken: token });
151
152
 
152
153
  const { getByTestId } = render(<TokenClaimsTestComponent />);
153
154
 
@@ -175,7 +176,7 @@ describe('useTokenClaims', () => {
175
176
  };
176
177
  const token = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(payload))}.mock-signature`;
177
178
 
178
- (useAccessToken as jest.Mock).mockReturnValue({ accessToken: token });
179
+ (useAccessToken as Mock).mockReturnValue({ accessToken: token });
179
180
 
180
181
  const { getByTestId } = render(<TokenClaimsTestComponent />);
181
182
 
@@ -185,7 +186,7 @@ describe('useTokenClaims', () => {
185
186
  });
186
187
 
187
188
  it('should return empty object when decodeJwt throws an error', async () => {
188
- (useAccessToken as jest.Mock).mockReturnValue({ accessToken: 'malformed-token' });
189
+ (useAccessToken as Mock).mockReturnValue({ accessToken: 'malformed-token' });
189
190
 
190
191
  const { getByTestId } = render(<TokenClaimsTestComponent />);
191
192
 
@@ -1,14 +1,13 @@
1
- import { describe, it, expect } from '@jest/globals';
2
-
3
- // Mock at the top of the file
4
- jest.mock('./env-variables');
5
-
6
1
  describe('cookie.ts', () => {
7
2
  beforeEach(() => {
8
3
  // Clear all mocks before each test
9
- jest.clearAllMocks();
10
- // Reset modules
11
- jest.resetModules();
4
+ vi.clearAllMocks();
5
+ // Reset modules to ensure fresh imports
6
+ vi.resetModules();
7
+ // Re-mock env-variables with a fresh copy each time
8
+ vi.doMock('./env-variables', async (importOriginal) => {
9
+ return { ...(await importOriginal<typeof import('./env-variables')>()) };
10
+ });
12
11
  });
13
12
 
14
13
  describe('getCookieOptions', () => {
@@ -147,11 +146,17 @@ describe('cookie.ts', () => {
147
146
  });
148
147
 
149
148
  describe('getJwtCookie', () => {
149
+ const originalEnv = process.env;
150
+
150
151
  beforeEach(() => {
151
- // Reset NODE_ENV for each test
152
+ process.env = { ...originalEnv };
152
153
  delete process.env.NODE_ENV;
153
154
  });
154
155
 
156
+ afterEach(() => {
157
+ process.env = originalEnv;
158
+ });
159
+
155
160
  it('should create JWT cookie with Secure flag for HTTPS URLs', async () => {
156
161
  const { getJwtCookie } = await import('./cookie');
157
162