@@ -58,6 +58,7 @@ ca/T0LLtgmbMmxSv/MmzIg==
5858 function getMockAuthorizationServer ( {
5959 tokenEndpointResponse,
6060 tokenEndpointErrorResponse,
61+ tokenEndpointFetchError,
6162 discoveryResponse,
6263 audience,
6364 nonce,
@@ -66,6 +67,7 @@ ca/T0LLtgmbMmxSv/MmzIg==
6667 } : {
6768 tokenEndpointResponse ?: oauth . TokenEndpointResponse | oauth . OAuth2Error ;
6869 tokenEndpointErrorResponse ?: oauth . OAuth2Error ;
70+ tokenEndpointFetchError ?: Error ;
6971 discoveryResponse ?: Response ;
7072 audience ?: string ;
7173 nonce ?: string ;
@@ -86,6 +88,10 @@ ca/T0LLtgmbMmxSv/MmzIg==
8688 }
8789
8890 if ( url . pathname === "/oauth/token" ) {
91+ if ( tokenEndpointFetchError ) {
92+ throw tokenEndpointFetchError ;
93+ }
94+
8995 const jwt = await new jose . SignJWT ( {
9096 sid : DEFAULT . sid ,
9197 auth_time : Date . now ( ) ,
@@ -3305,6 +3311,85 @@ ca/T0LLtgmbMmxSv/MmzIg==
33053311 expect ( sessionCookie ) . toBeUndefined ( ) ;
33063312 } ) ;
33073313
3314+ it ( "should be called with an error when there is an error performing the authorization code grant request" , async ( ) => {
3315+ const state = "transaction-state" ;
3316+ const code = "auth-code" ;
3317+
3318+ const mockOnCallback = vi
3319+ . fn ( )
3320+ . mockResolvedValue (
3321+ NextResponse . redirect ( new URL ( "/error-page" , DEFAULT . appBaseUrl ) )
3322+ ) ;
3323+
3324+ const secret = await generateSecret ( 32 ) ;
3325+ const transactionStore = new TransactionStore ( {
3326+ secret
3327+ } ) ;
3328+ const sessionStore = new StatelessSessionStore ( {
3329+ secret
3330+ } ) ;
3331+ const authClient = new AuthClient ( {
3332+ transactionStore,
3333+ sessionStore,
3334+
3335+ domain : DEFAULT . domain ,
3336+ clientId : DEFAULT . clientId ,
3337+ clientSecret : DEFAULT . clientSecret ,
3338+
3339+ secret,
3340+ appBaseUrl : DEFAULT . appBaseUrl ,
3341+
3342+ fetch : getMockAuthorizationServer ( {
3343+ tokenEndpointFetchError : new Error ( "Timeout error" )
3344+ } ) ,
3345+
3346+ onCallback : mockOnCallback
3347+ } ) ;
3348+
3349+ const url = new URL ( "/auth/callback" , DEFAULT . appBaseUrl ) ;
3350+ url . searchParams . set ( "code" , code ) ;
3351+ url . searchParams . set ( "state" , state ) ;
3352+
3353+ const headers = new Headers ( ) ;
3354+ const transactionState : TransactionState = {
3355+ nonce : "nonce-value" ,
3356+ maxAge : 3600 ,
3357+ codeVerifier : "code-verifier" ,
3358+ responseType : "code" ,
3359+ state : state ,
3360+ returnTo : "/dashboard"
3361+ } ;
3362+ const maxAge = 60 * 60 ; // 1 hour
3363+ const expiration = Math . floor ( Date . now ( ) / 1000 + maxAge ) ;
3364+ headers . set (
3365+ "cookie" ,
3366+ `__txn_${ state } =${ await encrypt ( transactionState , secret , expiration ) } `
3367+ ) ;
3368+ const request = new NextRequest ( url , {
3369+ method : "GET" ,
3370+ headers
3371+ } ) ;
3372+
3373+ // validate the new response redirect
3374+ const response = await authClient . handleCallback ( request ) ;
3375+ expect ( response . status ) . toEqual ( 307 ) ;
3376+ expect ( response . headers . get ( "Location" ) ) . not . toBeNull ( ) ;
3377+
3378+ const redirectUrl = new URL ( response . headers . get ( "Location" ) ! ) ;
3379+ expect ( redirectUrl . pathname ) . toEqual ( "/error-page" ) ;
3380+
3381+ expect ( mockOnCallback ) . toHaveBeenCalledWith (
3382+ expect . any ( Error ) ,
3383+ {
3384+ returnTo : transactionState . returnTo
3385+ } ,
3386+ null
3387+ ) ;
3388+ expect ( mockOnCallback . mock . calls [ 0 ] [ 0 ] . code ) . toEqual (
3389+ "authorization_code_grant_request_error"
3390+ ) ;
3391+ } ) ;
3392+
33083393 it ( "should be called with an error if there was an error during the code exchange" , async ( ) => {
33093394 const state = "transaction-state" ;
33103395 const code = "auth-code" ;
0 commit comments