1
- import type { User } from '@workos-inc/node' ;
2
- import { data , redirect } from 'react-router' ;
3
- import { getSignInUrl , getSignUpUrl , signOut , switchToOrganization } from './auth.js' ;
1
+ import { User } from '@workos-inc/node' ;
2
+ import { getSignInUrl , getSignUpUrl , signOut , switchToOrganization , withAuth } from './auth.js' ;
4
3
import * as authorizationUrl from './get-authorization-url.js' ;
5
4
import * as session from './session.js' ;
5
+ import * as configModule from './config.js' ;
6
+ import { data , redirect , LoaderFunctionArgs } from 'react-router' ;
6
7
import { assertIsResponse } from './test-utils/test-helpers.js' ;
7
8
8
9
const terminateSession = jest . mocked ( session . terminateSession ) ;
9
10
const refreshSession = jest . mocked ( session . refreshSession ) ;
11
+ const getSessionFromCookie = jest . mocked ( session . getSessionFromCookie ) ;
12
+ const getClaimsFromAccessToken = jest . mocked ( session . getClaimsFromAccessToken ) ;
13
+ const getConfig = jest . mocked ( configModule . getConfig ) ;
10
14
11
15
jest . mock ( './session' , ( ) => ( {
12
16
terminateSession : jest . fn ( ) . mockResolvedValue ( new Response ( ) ) ,
13
17
refreshSession : jest . fn ( ) ,
18
+ getSessionFromCookie : jest . fn ( ) ,
19
+ getClaimsFromAccessToken : jest . fn ( ) ,
20
+ } ) ) ;
21
+
22
+ jest . mock ( './config' , ( ) => ( {
23
+ getConfig : jest . fn ( ) ,
14
24
} ) ) ;
15
25
16
26
// Mock redirect and data from react-router
@@ -35,7 +45,6 @@ jest.mock('react-router', () => {
35
45
describe ( 'auth' , ( ) => {
36
46
beforeEach ( ( ) => {
37
47
jest . spyOn ( authorizationUrl , 'getAuthorizationUrl' ) ;
38
- jest . clearAllMocks ( ) ;
39
48
} ) ;
40
49
41
50
describe ( 'getSignInUrl' , ( ) => {
@@ -284,4 +293,193 @@ describe('auth', () => {
284
293
} ) ;
285
294
} ) ;
286
295
} ) ;
296
+
297
+ describe ( 'withAuth' , ( ) => {
298
+ const createMockRequest = ( cookie ?: string ) => {
299
+ return {
300
+ request : new Request ( 'https://example.com' , {
301
+ headers : cookie ? { Cookie : cookie } : { } ,
302
+ } ) ,
303
+ } as LoaderFunctionArgs ;
304
+ } ;
305
+
306
+ beforeEach ( ( ) => {
307
+ jest . clearAllMocks ( ) ;
308
+ getConfig . mockReturnValue ( 'wos-session' ) ;
309
+ } ) ;
310
+
311
+ it ( 'should return user info when a valid session exists' , async ( ) => {
312
+ // Mock session with valid access token
313
+ const mockSession = {
314
+ accessToken : 'valid-access-token' ,
315
+ refreshToken : 'refresh-token' ,
316
+ user : {
317
+ id : 'user-1' ,
318
+
319
+ firstName : 'Test' ,
320
+ lastName : 'User' ,
321
+ emailVerified : true ,
322
+ profilePictureUrl : 'https://example.com/profile.jpg' ,
323
+ object : 'user' as const ,
324
+ createdAt : '2023-01-01T00:00:00Z' ,
325
+ updatedAt : '2023-01-01T00:00:00Z' ,
326
+ lastSignInAt : '2023-01-01T00:00:00Z' ,
327
+ externalId : null ,
328
+ } ,
329
+ impersonator : {
330
+
331
+ reason : 'testing' ,
332
+ } ,
333
+ headers : { } ,
334
+ } ;
335
+
336
+ // Mock claims from access token
337
+ const mockClaims = {
338
+ sessionId : 'session-123' ,
339
+ organizationId : 'org-456' ,
340
+ role : 'admin' ,
341
+ permissions : [ 'read' , 'write' ] ,
342
+ entitlements : [ 'feature-1' , 'feature-2' ] ,
343
+ exp : Date . now ( ) / 1000 + 3600 , // 1 hour from now
344
+ iss : 'https://api.workos.com' ,
345
+ } ;
346
+
347
+ getSessionFromCookie . mockResolvedValue ( mockSession ) ;
348
+ getClaimsFromAccessToken . mockReturnValue ( mockClaims ) ;
349
+
350
+ const result = await withAuth ( createMockRequest ( 'wos-session=valid-session-data' ) ) ;
351
+
352
+ // Verify called with correct params
353
+ expect ( getSessionFromCookie ) . toHaveBeenCalledWith ( 'wos-session=valid-session-data' ) ;
354
+ expect ( getClaimsFromAccessToken ) . toHaveBeenCalledWith ( 'valid-access-token' ) ;
355
+
356
+ // Check result contains expected user info
357
+ expect ( result ) . toEqual ( {
358
+ user : mockSession . user ,
359
+ sessionId : mockClaims . sessionId ,
360
+ organizationId : mockClaims . organizationId ,
361
+ role : mockClaims . role ,
362
+ permissions : mockClaims . permissions ,
363
+ entitlements : mockClaims . entitlements ,
364
+ impersonator : mockSession . impersonator ,
365
+ accessToken : mockSession . accessToken ,
366
+ } ) ;
367
+ } ) ;
368
+
369
+ it ( 'should handle expired access tokens' , async ( ) => {
370
+ // Mock session with expired access token
371
+ const mockSession = {
372
+ accessToken : 'expired-access-token' ,
373
+ refreshToken : 'refresh-token' ,
374
+ user : {
375
+ id : 'user-1' ,
376
+
377
+ firstName : 'Test' ,
378
+ lastName : 'User' ,
379
+ emailVerified : true ,
380
+ profilePictureUrl : 'https://example.com/profile.jpg' ,
381
+ object : 'user' as const ,
382
+ createdAt : '2023-01-01T00:00:00Z' ,
383
+ updatedAt : '2023-01-01T00:00:00Z' ,
384
+ lastSignInAt : '2023-01-01T00:00:00Z' ,
385
+ externalId : null ,
386
+ } ,
387
+ headers : { } ,
388
+ } ;
389
+
390
+ // Mock claims with expired token
391
+ const mockClaims = {
392
+ sessionId : 'session-123' ,
393
+ organizationId : 'org-456' ,
394
+ role : 'admin' ,
395
+ permissions : [ 'read' , 'write' ] ,
396
+ entitlements : [ 'feature-1' , 'feature-2' ] ,
397
+ exp : Date . now ( ) / 1000 - 3600 , // 1 hour ago (expired)
398
+ iss : 'https://api.workos.com' ,
399
+ } ;
400
+
401
+ getSessionFromCookie . mockResolvedValue ( mockSession ) ;
402
+ getClaimsFromAccessToken . mockReturnValue ( mockClaims ) ;
403
+
404
+ // Spy on console.warn
405
+ const consoleWarnSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ) ;
406
+
407
+ const result = await withAuth ( createMockRequest ( 'wos-session=expired-session-data' ) ) ;
408
+
409
+ // Should warn about expired token
410
+ expect ( consoleWarnSpy ) . toHaveBeenCalledWith ( 'Access token expired for user' ) ;
411
+
412
+ // Result should still contain user info
413
+ expect ( result ) . toEqual ( {
414
+ user : mockSession . user ,
415
+ sessionId : mockClaims . sessionId ,
416
+ organizationId : mockClaims . organizationId ,
417
+ role : mockClaims . role ,
418
+ permissions : mockClaims . permissions ,
419
+ entitlements : mockClaims . entitlements ,
420
+ impersonator : undefined ,
421
+ accessToken : mockSession . accessToken ,
422
+ } ) ;
423
+
424
+ consoleWarnSpy . mockRestore ( ) ;
425
+ } ) ;
426
+
427
+ it ( 'should return NoUserInfo when no session exists' , async ( ) => {
428
+ // Mock no session
429
+ getSessionFromCookie . mockResolvedValue ( null ) ;
430
+
431
+ const result = await withAuth ( createMockRequest ( ) ) ;
432
+
433
+ expect ( result ) . toEqual ( {
434
+ user : null ,
435
+ } ) ;
436
+
437
+ // getClaimsFromAccessToken should not be called
438
+ expect ( getClaimsFromAccessToken ) . not . toHaveBeenCalled ( ) ;
439
+ } ) ;
440
+
441
+ it ( 'should return NoUserInfo when session exists but has no access token' , async ( ) => {
442
+ // Mock session with no access token - we'll add a dummy accessToken that will be ignored
443
+ getSessionFromCookie . mockResolvedValue ( {
444
+ user : {
445
+ id : 'user-1' ,
446
+
447
+ firstName : 'Test' ,
448
+ lastName : 'User' ,
449
+ emailVerified : true ,
450
+ profilePictureUrl : 'https://example.com/profile.jpg' ,
451
+ object : 'user' as const ,
452
+ createdAt : '2023-01-01T00:00:00Z' ,
453
+ updatedAt : '2023-01-01T00:00:00Z' ,
454
+ lastSignInAt : '2023-01-01T00:00:00Z' ,
455
+ externalId : null ,
456
+ } ,
457
+ refreshToken : 'refresh-token' ,
458
+ headers : { } ,
459
+ accessToken : '' , // Empty string to meet type requirement but it will be treated as falsy
460
+ } ) ;
461
+
462
+ const result = await withAuth ( createMockRequest ( 'wos-session=invalid-session-data' ) ) ;
463
+
464
+ expect ( result ) . toEqual ( {
465
+ user : null ,
466
+ } ) ;
467
+
468
+ // getClaimsFromAccessToken should not be called
469
+ expect ( getClaimsFromAccessToken ) . not . toHaveBeenCalled ( ) ;
470
+ } ) ;
471
+
472
+ it ( 'should warn when no cookie header includes the cookie name' , async ( ) => {
473
+ // Spy on console.warn
474
+ const consoleWarnSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ) ;
475
+
476
+ getSessionFromCookie . mockResolvedValue ( null ) ;
477
+
478
+ await withAuth ( createMockRequest ( 'other-cookie=value' ) ) ;
479
+
480
+ expect ( consoleWarnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( 'No session cookie "wos-session" found.' ) ) ;
481
+
482
+ consoleWarnSpy . mockRestore ( ) ;
483
+ } ) ;
484
+ } ) ;
287
485
} ) ;
0 commit comments