import oAuthCallback from "../lib/oauth/callback" ; import callbackHandler from "../lib/callback-handler" ; import { hashToken } from "../lib/utils" ; import type { InternalOptions } from "../../lib/types" ; import type { IncomingRequest , OutgoingResponse } from ".." ; import type { Cookie , SessionStore } from "../lib/cookie" ; import type { User } from "../.." ; /** Handle callbacks from login services */ export default async function callback ( params ) { const { options , query , body , method , headers , sessionStore } = params ; const { provider , adapter , url , callbackUrl , pages , jwt , events , callbacks , session : { strategy : sessionStrategy , maxAge : sessionMaxAge } , logger , } = options ; const cookies = [ ] ; const useJwtSession = sessionStrategy === "jwt" ; if ( provider . type === "oauth" ) { try { const { profile , account , OAuthProfile , cookies : oauthCookies , } = await oAuthCallback ( { query , body , method , options , cookies : params . cookies , } ) ; if ( oauthCookies ) cookies . push ( ... oauthCookies ) ; try { logger . debug ( "OAUTH_CALLBACK_RESPONSE" , { profile , account , OAuthProfile , } ) ; if ( ! profile ) { return { redirect : `${ url }/signin` , cookies } ; } let userOrProfile = profile ; if ( adapter ) { const { getUserByAccount } = adapter ; const userByAccount = await getUserByAccount ( { providerAccountId : account . providerAccountId , provider : provider . id , } ) ; if ( userByAccount ) userOrProfile = userByAccount ; } try { const isAllowed = await callbacks . signIn ( { user : userOrProfile , account , profile : OAuthProfile , } ) ; if ( ! isAllowed ) { return { redirect : `${ url }/error?error=AccessDenied` , cookies } ; } else if ( typeof isAllowed === "string" ) { return { redirect : isAllowed , cookies } ; } } catch ( error ) { return { redirect : `${ url }/error?error=${ encodeURIComponent ( ( error as Error ) . message ) }` , cookies , } ; } const { user , session , isNewUser } = await callbackHandler ( { sessionToken : sessionStore . value , profile , account , options , } ) ; if ( useJwtSession ) { const defaultToken = { name : user . name , email : user . email , picture : user . image , sub : user . id ?. toString ( ) , } ; const token = await callbacks . jwt ( { token : defaultToken , user , account , profile : OAuthProfile , isNewUser , } ) ; const newToken = await jwt . encode ( { ... jwt , token } ) ; const cookieExpires = new Date ( ) ; cookieExpires . setTime ( cookieExpires . getTime ( ) + sessionMaxAge * 1000 ) ; const sessionCookies = sessionStore . chunk ( newToken , { expires : cookieExpires , } ) ; cookies . push ( ... sessionCookies ) ; } else { cookies . push ( { name : options . cookies . sessionToken . name , value : session . sessionToken , options : { ... options . cookies . sessionToken . options , expires : session . expires , } , } ) ; } await events . signIn ?. ( { user , account , profile , isNewUser } ) ; if ( isNewUser && pages . newUser ) { return { redirect : `${ pages . newUser }${ pages . newUser . includes ( "?" ) ? "&" : "?" }callbackUrl=${ encodeURIComponent ( callbackUrl ) }` , cookies , } ; } return { redirect : callbackUrl , cookies } ; } catch ( error ) { if ( ( error as Error ) . name === "AccountNotLinkedError" ) { return { redirect : `${ url }/error?error=OAuthAccountNotLinked` , cookies , } ; } else if ( ( error as Error ) . name === "CreateUserError" ) { return { redirect : `${ url }/error?error=OAuthCreateAccount` , cookies } ; } logger . error ( "OAUTH_CALLBACK_HANDLER_ERROR" , error as Error ) ; return { redirect : `${ url }/error?error=Callback` , cookies } ; } } catch ( error ) { if ( ( error as Error ) . name === "OAuthCallbackError" ) { logger . error ( "CALLBACK_OAUTH_ERROR" , error as Error ) ; return { redirect : `${ url }/error?error=OAuthCallback` , cookies } ; } logger . error ( "OAUTH_CALLBACK_ERROR" , error as Error ) ; return { redirect : `${ url }/error?error=Callback` , cookies } ; } } else if ( provider . type === "email" ) { try { const { useVerificationToken , getUserByEmail } = adapter ! ; const token = query ?. token ; const identifier = query ?. email ; const invite = await useVerificationToken ?. ( { identifier , token : hashToken ( token , options ) , } ) ; const invalidInvite = ! invite || invite . expires . valueOf ( ) < Date . now ( ) ; if ( invalidInvite ) { return { redirect : `${ url }/error?error=Verification` , cookies } ; } const profile = ( identifier ? await getUserByEmail ( identifier ) : null ) ?? { email : identifier , } ; type { import ( "src" ) . Account } const account = { providerAccountId : profile . email , type : "email" , provider : provider . id , } ; try { const signInCallbackResponse = await callbacks . signIn ( { user : profile , account , email : { email : identifier } , } ) ; if ( ! signInCallbackResponse ) { return { redirect : `${ url }/error?error=AccessDenied` , cookies } ; } else if ( typeof signInCallbackResponse === "string" ) { return { redirect : signInCallbackResponse , cookies } ; } } catch ( error ) { return { redirect : `${ url }/error?error=${ encodeURIComponent ( ( error as Error ) . message ) }` , cookies , } ; } const { user , session , isNewUser } = await callbackHandler ( { sessionToken : sessionStore . value , profile , account , options , } ) ; if ( useJwtSession ) { const defaultToken = { name : user . name , email : user . email , picture : user . image , sub : user . id ?. toString ( ) , } ; const token = await callbacks . jwt ( { token : defaultToken , user , account , isNewUser , } ) ; const newToken = await jwt . encode ( { ... jwt , token } ) ; const cookieExpires = new Date ( ) ; cookieExpires . setTime ( cookieExpires . getTime ( ) + sessionMaxAge * 1000 ) ; const sessionCookies = sessionStore . chunk ( newToken , { expires : cookieExpires , } ) ; cookies . push ( ... sessionCookies ) ; } else { cookies . push ( { name : options . cookies . sessionToken . name , value : session . sessionToken , options : { ... options . cookies . sessionToken . options , expires : session . expires , } , } ) ; } await events . signIn ?. ( { user , account , isNewUser } ) ; if ( isNewUser && pages . newUser ) { return { redirect : `${ pages . newUser }${ pages . newUser . includes ( "?" ) ? "&" : "?" }callbackUrl=${ encodeURIComponent ( callbackUrl ) }` , cookies , } ; } return { redirect : callbackUrl , cookies } ; } catch ( error ) { if ( ( error as Error ) . name === "CreateUserError" ) { return { redirect : `${ url }/error?error=EmailCreateAccount` , cookies } ; } logger . error ( "CALLBACK_EMAIL_ERROR" , error as Error ) ; return { redirect : `${ url }/error?error=Callback` , cookies } ; } } else if ( provider . type === "credentials" && method === "POST" ) { const credentials = body ; let user ; try { user = ( await provider . authorize ( credentials , { query , body , headers , method , } ) ) as User ; if ( ! user ) { return { status : 401 , redirect : `${ url }/error?${ new URLSearchParams ( { error : "CredentialsSignin" , provider : provider . id , } ) }` , cookies , } ; } } catch ( error ) { return { redirect : `${ url }/error?error=${ encodeURIComponent ( ( error as Error ) . message ) }` , cookies , } ; } type { import ( "src" ) . Account } const account = { providerAccountId : user . id , type : "credentials" , provider : provider . id , } ; try { const isAllowed = await callbacks . signIn ( { user , account , credentials , } ) ; if ( ! isAllowed ) { return { status : 403 , redirect : `${ url }/error?error=AccessDenied` , cookies , } ; } else if ( typeof isAllowed === "string" ) { return { redirect : isAllowed , cookies } ; } } catch ( error ) { return { redirect : `${ url }/error?error=${ encodeURIComponent ( ( error as Error ) . message ) }` , cookies , } ; } const defaultToken = { name : user . name , email : user . email , picture : user . image , sub : user . id ?. toString ( ) , } ; const token = await callbacks . jwt ( { token : defaultToken , user , account , isNewUser : false , } ) ; const newToken = await jwt . encode ( { ... jwt , token } ) ; const cookieExpires = new Date ( ) ; cookieExpires . setTime ( cookieExpires . getTime ( ) + sessionMaxAge * 1000 ) ; const sessionCookies = sessionStore . chunk ( newToken , { expires : cookieExpires , } ) ; cookies . push ( ... sessionCookies ) ; await events . signIn ?. ( { user , account } ) ; return { redirect : callbackUrl , cookies } ; } return { status : 500 , body : `Error: Callback for provider type ${ provider . type } not supported` , cookies , } ; }