@workos-inc/authkit-nextjs 3.0.0-beta.1 → 3.0.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 (106) hide show
  1. package/README.md +305 -102
  2. package/dist/esm/actions.js +35 -5
  3. package/dist/esm/actions.js.map +1 -1
  4. package/dist/esm/auth.js +71 -21
  5. package/dist/esm/auth.js.map +1 -1
  6. package/dist/esm/authkit-callback-route.js +90 -92
  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 +20 -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 +52 -0
  37. package/dist/esm/pkce.js.map +1 -0
  38. package/dist/esm/session.js +82 -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 +9 -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 +17 -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 +33 -34
  66. package/src/actions.spec.ts +91 -18
  67. package/src/actions.ts +44 -6
  68. package/src/auth.spec.ts +79 -29
  69. package/src/auth.ts +74 -42
  70. package/src/authkit-callback-route.spec.ts +372 -58
  71. package/src/authkit-callback-route.ts +121 -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 +63 -9
  83. package/src/cookie.ts +35 -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 +146 -0
  97. package/src/pkce.ts +59 -0
  98. package/src/session.spec.ts +87 -89
  99. package/src/session.ts +104 -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,3 +1,4 @@
1
+ import type { Mock, MockInstance } from 'vitest';
1
2
  import { NextRequest, NextResponse } from 'next/server';
2
3
  import { cookies, headers } from 'next/headers';
3
4
  import { redirect } from 'next/navigation';
@@ -7,15 +8,24 @@ import { getWorkOS } from './workos.js';
7
8
  import * as envVariables from './env-variables.js';
8
9
 
9
10
  import { jwtVerify } from 'jose';
11
+
12
+ // Helper to override env variable exports without triggering no-import-assign on the import binding
13
+ function setEnvVar(mod: Record<string, unknown>, key: string, value: unknown) {
14
+ Object.defineProperty(mod, key, { value, configurable: true });
15
+ }
10
16
  import { sealData } from 'iron-session';
11
17
  import { User } from '@workos-inc/node';
12
-
13
- jest.mock('jose', () => ({
14
- jwtVerify: jest.fn(),
15
- createRemoteJWKSet: jest.fn(),
16
- SignJWT: jest.requireActual('jose').SignJWT,
17
- decodeJwt: jest.requireActual('jose').decodeJwt,
18
- }));
18
+ import { getStateFromPKCECookieValue } from './pkce.js';
19
+
20
+ vi.mock('jose', async () => {
21
+ const actual = await vi.importActual<typeof import('jose')>('jose');
22
+ return {
23
+ jwtVerify: vi.fn(),
24
+ createRemoteJWKSet: vi.fn(),
25
+ SignJWT: actual.SignJWT,
26
+ decodeJwt: actual.decodeJwt,
27
+ };
28
+ });
19
29
 
20
30
  // logging is disabled by default, flip this to true to still have logs in the console
21
31
  const DEBUG = false;
@@ -42,20 +52,16 @@ describe('session.ts', () => {
42
52
  profilePictureUrl: null,
43
53
  firstName: null,
44
54
  lastName: null,
45
- lastSignInAt: null,
46
- locale: null,
47
- externalId: null,
48
- metadata: {},
49
55
  createdAt: '2024-01-01',
50
56
  updatedAt: '2024-01-01',
51
57
  } as User,
52
58
  };
53
59
 
54
- let consoleLogSpy: jest.SpyInstance;
60
+ let consoleLogSpy: MockInstance;
55
61
 
56
62
  beforeEach(async () => {
57
63
  // Clear all mocks between tests
58
- jest.clearAllMocks();
64
+ vi.clearAllMocks();
59
65
 
60
66
  // Reset the cookie store
61
67
  const nextCookies = await cookies();
@@ -67,9 +73,9 @@ describe('session.ts', () => {
67
73
  nextHeaders._reset();
68
74
  nextHeaders.set('x-workos-middleware', 'true');
69
75
 
70
- (jwtVerify as jest.Mock).mockReset();
76
+ (jwtVerify as Mock).mockReset();
71
77
 
72
- consoleLogSpy = jest.spyOn(console, 'log').mockImplementation((...args) => {
78
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation((...args) => {
73
79
  if (DEBUG) {
74
80
  console.info(...args);
75
81
  }
@@ -78,7 +84,7 @@ describe('session.ts', () => {
78
84
 
79
85
  afterEach(() => {
80
86
  consoleLogSpy.mockRestore();
81
- jest.resetModules();
87
+ vi.resetModules();
82
88
  });
83
89
 
84
90
  describe('withAuth', () => {
@@ -120,7 +126,7 @@ describe('session.ts', () => {
120
126
  await expect(async () => {
121
127
  await withAuth();
122
128
  }).rejects.toThrow(
123
- "You are calling 'withAuth' on https://example.com/ that isnt covered by the AuthKit middleware. Make sure it is running on all paths you are calling 'withAuth' from by updating your middleware config in 'middleware.(js|ts)'.",
129
+ /You are calling 'withAuth' on https:\/\/example\.com\/ that isn't covered by the AuthKit middleware/,
124
130
  );
125
131
  });
126
132
 
@@ -130,9 +136,7 @@ describe('session.ts', () => {
130
136
 
131
137
  await expect(async () => {
132
138
  await withAuth({ ensureSignedIn: true });
133
- }).rejects.toThrow(
134
- "You are calling 'withAuth' on a route that isn’t covered by the AuthKit middleware. Make sure it is running on all paths you are calling 'withAuth' from by updating your middleware config in 'middleware.(js|ts)'.",
135
- );
139
+ }).rejects.toThrow(/You are calling 'withAuth' on a route that isn't covered by the AuthKit middleware/);
136
140
  });
137
141
 
138
142
  it('should throw an error if the URL is not found in the headers', async () => {
@@ -150,14 +154,12 @@ describe('session.ts', () => {
150
154
 
151
155
  await withAuth({ ensureSignedIn: true });
152
156
 
153
- // URL-safe base64 encoding
154
- const pathname = encodeURIComponent(
155
- btoa(JSON.stringify({ returnPathname: '/protected?test=123' }))
156
- .replace(/\+/g, '-')
157
- .replace(/\//g, '_'),
158
- );
157
+ // The state is now sealed, se we need to unseal it
158
+ const redirectUrl = new URL((redirect as unknown as Mock).mock.calls[0][0]);
159
+ const sealedState = redirectUrl.searchParams.get('state')!;
160
+ const { returnPathname } = await getStateFromPKCECookieValue(sealedState);
159
161
 
160
- expect(redirect).toHaveBeenCalledWith(expect.stringContaining(pathname));
162
+ expect(returnPathname).toBe('/protected?test=123');
161
163
  });
162
164
  });
163
165
 
@@ -165,7 +167,7 @@ describe('session.ts', () => {
165
167
  it('should throw an error if the redirect URI is not set', async () => {
166
168
  const originalWorkosRedirectUri = envVariables.WORKOS_REDIRECT_URI;
167
169
 
168
- jest.replaceProperty(envVariables, 'WORKOS_REDIRECT_URI', '');
170
+ setEnvVar(envVariables, 'WORKOS_REDIRECT_URI', '');
169
171
 
170
172
  await expect(async () => {
171
173
  await updateSessionMiddleware(
@@ -180,13 +182,13 @@ describe('session.ts', () => {
180
182
  );
181
183
  }).rejects.toThrow('You must provide a redirect URI in the AuthKit middleware or in the environment variables.');
182
184
 
183
- jest.replaceProperty(envVariables, 'WORKOS_REDIRECT_URI', originalWorkosRedirectUri);
185
+ setEnvVar(envVariables, 'WORKOS_REDIRECT_URI', originalWorkosRedirectUri);
184
186
  });
185
187
 
186
188
  it('should throw an error if the cookie password is not set', async () => {
187
189
  const originalWorkosCookiePassword = envVariables.WORKOS_COOKIE_PASSWORD;
188
190
 
189
- jest.replaceProperty(envVariables, 'WORKOS_COOKIE_PASSWORD', '');
191
+ setEnvVar(envVariables, 'WORKOS_COOKIE_PASSWORD', '');
190
192
 
191
193
  await expect(async () => {
192
194
  await updateSessionMiddleware(
@@ -203,13 +205,13 @@ describe('session.ts', () => {
203
205
  'You must provide a valid cookie password that is at least 32 characters in the environment variables.',
204
206
  );
205
207
 
206
- jest.replaceProperty(envVariables, 'WORKOS_COOKIE_PASSWORD', originalWorkosCookiePassword);
208
+ setEnvVar(envVariables, 'WORKOS_COOKIE_PASSWORD', originalWorkosCookiePassword);
207
209
  });
208
210
 
209
211
  it('should throw an error if the cookie password is less than 32 characters', async () => {
210
212
  const originalWorkosCookiePassword = envVariables.WORKOS_COOKIE_PASSWORD;
211
213
 
212
- jest.replaceProperty(envVariables, 'WORKOS_COOKIE_PASSWORD', 'short');
214
+ setEnvVar(envVariables, 'WORKOS_COOKIE_PASSWORD', 'short');
213
215
 
214
216
  await expect(async () => {
215
217
  await updateSessionMiddleware(
@@ -226,7 +228,7 @@ describe('session.ts', () => {
226
228
  'You must provide a valid cookie password that is at least 32 characters in the environment variables.',
227
229
  );
228
230
 
229
- jest.replaceProperty(envVariables, 'WORKOS_COOKIE_PASSWORD', originalWorkosCookiePassword);
231
+ setEnvVar(envVariables, 'WORKOS_COOKIE_PASSWORD', originalWorkosCookiePassword);
230
232
  });
231
233
 
232
234
  it('should return early if there is no session', async () => {
@@ -247,7 +249,7 @@ describe('session.ts', () => {
247
249
  });
248
250
 
249
251
  it('should return 200 if the session is valid', async () => {
250
- jest.spyOn(console, 'log').mockImplementation(() => {});
252
+ vi.spyOn(console, 'log').mockImplementation(() => {});
251
253
 
252
254
  const nextCookies = await cookies();
253
255
  nextCookies.set(
@@ -255,7 +257,7 @@ describe('session.ts', () => {
255
257
  await sealData(mockSession, { password: process.env.WORKOS_COOKIE_PASSWORD as string }),
256
258
  );
257
259
 
258
- (jwtVerify as jest.Mock).mockImplementation(() => {
260
+ (jwtVerify as Mock).mockImplementation(() => {
259
261
  return true;
260
262
  });
261
263
 
@@ -278,11 +280,11 @@ describe('session.ts', () => {
278
280
  it('should attempt to refresh the session when the access token is invalid', async () => {
279
281
  mockSession.accessToken = await generateTestToken({}, true);
280
282
 
281
- (jwtVerify as jest.Mock).mockImplementation(() => {
283
+ (jwtVerify as Mock).mockImplementation(() => {
282
284
  throw new Error('Invalid token');
283
285
  });
284
286
 
285
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
287
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
286
288
  accessToken: await generateTestToken(),
287
289
  refreshToken: 'new-refresh-token',
288
290
  user: mockSession.user,
@@ -314,17 +316,15 @@ describe('session.ts', () => {
314
316
  });
315
317
 
316
318
  it('should delete the cookie when refreshing fails', async () => {
317
- jest.spyOn(console, 'log').mockImplementation(() => {});
319
+ vi.spyOn(console, 'log').mockImplementation(() => {});
318
320
 
319
321
  mockSession.accessToken = await generateTestToken({}, true);
320
322
 
321
- (jwtVerify as jest.Mock).mockImplementation(() => {
323
+ (jwtVerify as Mock).mockImplementation(() => {
322
324
  throw new Error('Invalid token');
323
325
  });
324
326
 
325
- jest
326
- .spyOn(workos.userManagement, 'authenticateWithRefreshToken')
327
- .mockRejectedValue(new Error('Failed to refresh'));
327
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(new Error('Failed to refresh'));
328
328
 
329
329
  const request = new NextRequest(new URL('http://example.com'));
330
330
 
@@ -360,7 +360,7 @@ describe('session.ts', () => {
360
360
 
361
361
  describe('middleware auth', () => {
362
362
  it('should redirect unauthenticated users on protected routes', async () => {
363
- jest.spyOn(console, 'log').mockImplementation(() => {});
363
+ vi.spyOn(console, 'log').mockImplementation(() => {});
364
364
 
365
365
  const request = new NextRequest(new URL('http://example.com/protected'));
366
366
  const result = await updateSessionMiddleware(
@@ -380,10 +380,7 @@ describe('session.ts', () => {
380
380
  );
381
381
  });
382
382
 
383
- it('should use Response if NextResponse.redirect is not available', async () => {
384
- const originalRedirect = NextResponse.redirect;
385
- (NextResponse as Partial<typeof NextResponse>).redirect = undefined;
386
-
383
+ it('should return a redirect response when middlewareAuth is enabled and user is not authenticated', async () => {
387
384
  const request = new NextRequest(new URL('http://example.com/protected'));
388
385
  const result = await updateSessionMiddleware(
389
386
  request,
@@ -396,10 +393,9 @@ describe('session.ts', () => {
396
393
  [],
397
394
  );
398
395
 
399
- expect(result).toBeInstanceOf(Response);
400
-
401
- // Restore the original redirect method
402
- (NextResponse as Partial<typeof NextResponse>).redirect = originalRedirect;
396
+ expect(result).toBeInstanceOf(NextResponse);
397
+ expect(result.status).toBe(307);
398
+ expect(result.headers.get('Location')).toContain('workos.com');
403
399
  });
404
400
 
405
401
  it('should automatically add the redirect URI to unauthenticatedPaths when middleware is enabled', async () => {
@@ -435,7 +431,7 @@ describe('session.ts', () => {
435
431
  expect(result.headers.get('Location')).toContain('screen_hint=sign-up');
436
432
  });
437
433
 
438
- it('should set the sign up paths in the headers', async () => {
434
+ it('should not leak sign-up paths header to the browser', async () => {
439
435
  const request = new NextRequest(new URL('http://example.com/protected-signup'));
440
436
  const result = await updateSessionMiddleware(
441
437
  request,
@@ -448,7 +444,8 @@ describe('session.ts', () => {
448
444
  ['/protected-signup'],
449
445
  );
450
446
 
451
- expect(result.headers.get('x-sign-up-paths')).toBe('/protected-signup');
447
+ // x-sign-up-paths is an internal header that should not leak to the browser
448
+ expect(result.headers.get('x-sign-up-paths')).toBeNull();
452
449
  });
453
450
 
454
451
  it('should allow logged out users on unauthenticated paths', async () => {
@@ -485,11 +482,11 @@ describe('session.ts', () => {
485
482
 
486
483
  it('should throw an error if the provided regex is invalid and a non-Error object is thrown', async () => {
487
484
  // Reset modules to ensure clean import state
488
- jest.resetModules();
485
+ vi.resetModules();
489
486
 
490
487
  // Import first, then spy
491
488
  const pathToRegexp = await import('path-to-regexp');
492
- const parseSpy = jest.spyOn(pathToRegexp, 'parse').mockImplementation(() => {
489
+ const parseSpy = vi.spyOn(pathToRegexp, 'parse').mockImplementation(() => {
493
490
  throw 'invalid regex';
494
491
  });
495
492
 
@@ -513,6 +510,9 @@ describe('session.ts', () => {
513
510
 
514
511
  // Verify the mock was called
515
512
  expect(parseSpy).toHaveBeenCalled();
513
+
514
+ // Restore the spy to prevent leaking to subsequent tests
515
+ parseSpy.mockRestore();
516
516
  });
517
517
 
518
518
  it('should default to the WORKOS_REDIRECT_URI environment variable if no redirect URI is provided', async () => {
@@ -532,17 +532,17 @@ describe('session.ts', () => {
532
532
  });
533
533
 
534
534
  it('should delete the cookie and redirect when refreshing fails', async () => {
535
- jest.spyOn(console, 'log').mockImplementation(() => {});
535
+ vi.spyOn(console, 'log').mockImplementation(() => {});
536
536
 
537
537
  mockSession.accessToken = await generateTestToken({}, true);
538
538
 
539
- (jwtVerify as jest.Mock).mockImplementation(() => {
539
+ (jwtVerify as Mock).mockImplementation(() => {
540
540
  throw new Error('Invalid token');
541
541
  });
542
542
 
543
- jest
544
- .spyOn(workos.userManagement, 'authenticateWithRefreshToken')
545
- .mockRejectedValue(new Error('Failed to refresh'));
543
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(
544
+ new Error('Failed to refresh'),
545
+ );
546
546
 
547
547
  const request = new NextRequest(new URL('http://example.com'));
548
548
 
@@ -643,12 +643,12 @@ describe('session.ts', () => {
643
643
  mockSession.accessToken = await generateTestToken({}, true);
644
644
 
645
645
  // Mock token verification to fail
646
- (jwtVerify as jest.Mock).mockImplementation(() => {
646
+ (jwtVerify as Mock).mockImplementation(() => {
647
647
  throw new Error('Invalid token');
648
648
  });
649
649
 
650
650
  // Mock successful refresh
651
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
651
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
652
652
  accessToken: await generateTestToken(),
653
653
  refreshToken: 'new-refresh-token',
654
654
  user: mockSession.user,
@@ -676,12 +676,12 @@ describe('session.ts', () => {
676
676
  mockSession.accessToken = await generateTestToken({}, true);
677
677
 
678
678
  // Mock token verification to fail
679
- (jwtVerify as jest.Mock).mockImplementation(() => {
679
+ (jwtVerify as Mock).mockImplementation(() => {
680
680
  throw new Error('Invalid token');
681
681
  });
682
682
 
683
683
  // Mock refresh failure
684
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(new Error('Refresh failed'));
684
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(new Error('Refresh failed'));
685
685
 
686
686
  const request = new NextRequest(new URL('http://example.com/protected'));
687
687
  request.cookies.set(
@@ -703,15 +703,15 @@ describe('session.ts', () => {
703
703
  mockSession.accessToken = await generateTestToken({}, true);
704
704
 
705
705
  // Mock token verification to fail
706
- (jwtVerify as jest.Mock).mockImplementation(() => {
706
+ (jwtVerify as Mock).mockImplementation(() => {
707
707
  throw new Error('Invalid token');
708
708
  });
709
709
 
710
710
  const newAccessToken = await generateTestToken();
711
- const mockSuccessCallback = jest.fn();
711
+ const mockSuccessCallback = vi.fn();
712
712
 
713
713
  // Mock successful refresh
714
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
714
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
715
715
  accessToken: newAccessToken,
716
716
  refreshToken: 'new-refresh-token',
717
717
  user: mockSession.user,
@@ -742,15 +742,15 @@ describe('session.ts', () => {
742
742
  mockSession.accessToken = await generateTestToken({}, true);
743
743
 
744
744
  // Mock token verification to fail
745
- (jwtVerify as jest.Mock).mockImplementation(() => {
745
+ (jwtVerify as Mock).mockImplementation(() => {
746
746
  throw new Error('Invalid token');
747
747
  });
748
748
 
749
749
  const mockError = new Error('Refresh failed');
750
- const mockErrorCallback = jest.fn();
750
+ const mockErrorCallback = vi.fn();
751
751
 
752
752
  // Mock refresh failure
753
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(mockError);
753
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(mockError);
754
754
 
755
755
  const request = new NextRequest(new URL('http://example.com/protected'));
756
756
  request.cookies.set(
@@ -773,15 +773,15 @@ describe('session.ts', () => {
773
773
 
774
774
  describe('refreshSession', () => {
775
775
  it('should refresh session successfully', async () => {
776
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
776
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
777
777
  accessToken: await generateTestToken(),
778
778
  refreshToken: 'new-refresh-token',
779
779
  user: mockSession.user,
780
780
  });
781
781
 
782
- jest
783
- .spyOn(workos.userManagement, 'getJwksUrl')
784
- .mockReturnValue('https://api.workos.com/sso/jwks/client_1234567890');
782
+ vi.spyOn(workos.userManagement, 'getJwksUrl').mockReturnValue(
783
+ 'https://api.workos.com/sso/jwks/client_1234567890',
784
+ );
785
785
 
786
786
  const nextCookies = await cookies();
787
787
  nextCookies.set(
@@ -817,15 +817,15 @@ describe('session.ts', () => {
817
817
  await sealData(mockSession, { password: process.env.WORKOS_COOKIE_PASSWORD as string }),
818
818
  );
819
819
 
820
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
820
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
821
821
  accessToken: await generateTestToken({ org_id: 'org_456' }),
822
822
  refreshToken: 'new-refresh-token',
823
823
  user: mockSession.user,
824
824
  });
825
825
 
826
- jest
827
- .spyOn(workos.userManagement, 'getJwksUrl')
828
- .mockReturnValue('https://api.workos.com/sso/jwks/client_1234567890');
826
+ vi.spyOn(workos.userManagement, 'getJwksUrl').mockReturnValue(
827
+ 'https://api.workos.com/sso/jwks/client_1234567890',
828
+ );
829
829
 
830
830
  const result = await refreshSession({ organizationId: 'org_456' });
831
831
 
@@ -844,8 +844,8 @@ describe('session.ts', () => {
844
844
  'wos-session',
845
845
  await sealData(mockSessionWithValidJWT, { password: process.env.WORKOS_COOKIE_PASSWORD as string }),
846
846
  );
847
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue('fail');
848
- expect(refreshSession({ ensureSignedIn: false })).rejects.toThrow('Failed to refresh session: fail');
847
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue('fail');
848
+ await expect(refreshSession({ ensureSignedIn: false })).rejects.toThrow('Failed to refresh session: fail');
849
849
  });
850
850
 
851
851
  it('throws if authenticateWithRefreshToken fails with error', async () => {
@@ -859,7 +859,7 @@ describe('session.ts', () => {
859
859
  'wos-session',
860
860
  await sealData(mockSessionWithValidJWT, { password: process.env.WORKOS_COOKIE_PASSWORD as string }),
861
861
  );
862
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(new Error('error'));
862
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(new Error('error'));
863
863
  await expect(refreshSession()).rejects.toThrow('Failed to refresh session: error');
864
864
  });
865
865
  });
@@ -869,7 +869,7 @@ describe('session.ts', () => {
869
869
  const nextCookies = await cookies();
870
870
  // @ts-expect-error - _reset is part of the mock
871
871
  nextCookies._reset();
872
- jest.clearAllMocks();
872
+ vi.clearAllMocks();
873
873
  });
874
874
 
875
875
  it('should return all token claims when accessToken is provided', async () => {
@@ -963,7 +963,7 @@ describe('session.ts', () => {
963
963
 
964
964
  describe('eager auth functionality', () => {
965
965
  beforeEach(() => {
966
- jest.clearAllMocks();
966
+ vi.clearAllMocks();
967
967
  });
968
968
 
969
969
  describe('isInitialDocumentRequest', () => {
@@ -1057,12 +1057,12 @@ describe('session.ts', () => {
1057
1057
  // Setup invalid session that needs refresh
1058
1058
  mockSession.accessToken = await generateTestToken({}, true);
1059
1059
 
1060
- (jwtVerify as jest.Mock).mockImplementation(() => {
1060
+ (jwtVerify as Mock).mockImplementation(() => {
1061
1061
  throw new Error('Invalid token');
1062
1062
  });
1063
1063
 
1064
1064
  const newAccessToken = await generateTestToken();
1065
- jest.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
1065
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockResolvedValue({
1066
1066
  accessToken: newAccessToken,
1067
1067
  refreshToken: 'new-refresh-token',
1068
1068
  user: mockSession.user,
@@ -1088,13 +1088,11 @@ describe('session.ts', () => {
1088
1088
  // Setup invalid session
1089
1089
  mockSession.accessToken = await generateTestToken({}, true);
1090
1090
 
1091
- (jwtVerify as jest.Mock).mockImplementation(() => {
1091
+ (jwtVerify as Mock).mockImplementation(() => {
1092
1092
  throw new Error('Invalid token');
1093
1093
  });
1094
1094
 
1095
- jest
1096
- .spyOn(workos.userManagement, 'authenticateWithRefreshToken')
1097
- .mockRejectedValue(new Error('Refresh failed'));
1095
+ vi.spyOn(workos.userManagement, 'authenticateWithRefreshToken').mockRejectedValue(new Error('Refresh failed'));
1098
1096
 
1099
1097
  const request = new NextRequest(new URL('http://example.com/page'));
1100
1098
  request.headers.set('accept', 'text/html');