import { AccountNotLinkedError } from "../errors" ; import { fromDate } from "./utils" ; import { randomBytes , randomUUID } from "crypto" ; import { InternalOptions } from "../../lib/types" ; import { AdapterSession , AdapterUser } from "../../adapters" ; import { JWT } from "../../jwt" ; import { Account , User } from "../.." ; import { SessionToken } from "./cookie" ; /**
 * This function handles the complex flow of signing users in, and either creating,
 * linking (or not linking) accounts depending on if the user is currently logged
 * in, if they have account already and the authentication mechanism they are using.
 *
 * It prevents insecure behaviour, such as linking OAuth accounts unless a user is
 * signed in and authenticated with an existing valid account.
 *
 * All verification (e.g. OAuth flows or email address verificaiton flows) are
 * done prior to this handler being called to avoid additonal complexity in this
 * handler.
 */ export default async function callbackHandler ( params ) { const { sessionToken , profile , account , options } = params ; if ( ! account ?. providerAccountId || ! account . type ) throw new Error ( "Missing or invalid provider account" ) ; if ( ! [ "email" , "oauth" ] . includes ( account . type ) ) throw new Error ( "Provider not supported" ) ; const { adapter , jwt , events , session : { strategy : sessionStrategy } , } = options ; if ( ! adapter ) { return { user : profile , account , session : { } } ; } const { createUser , updateUser , getUser , getUserByAccount , getUserByEmail , linkAccount , createSession , getSessionAndUser , deleteSession , } = adapter ; let session = null ; let user = null ; let isNewUser = false ; const useJwtSession = sessionStrategy === "jwt" ; if ( sessionToken ) { if ( useJwtSession ) { try { session = await jwt . decode ( { ... jwt , token : sessionToken } ) ; if ( session && "sub" in session && session . sub ) { user = await getUser ( session . sub ) ; } } catch { } } else { const userAndSession = await getSessionAndUser ( sessionToken ) ; if ( userAndSession ) { session = userAndSession . session ; user = userAndSession . user ; } } } if ( account . type === "email" ) { const userByEmail = profile . email ? await getUserByEmail ( profile . email ) : null ; if ( userByEmail ) { if ( user ?. id !== userByEmail . id && ! useJwtSession && sessionToken ) { await deleteSession ( sessionToken ) ; } user = await updateUser ( { id : userByEmail . id , emailVerified : new Date ( ) } ) ; await events . updateUser ?. ( { user } ) ; } else { const newUser = { ... profile , emailVerified : new Date ( ) } ; delete ( newUser as Omit < AdapterUser , "id" > ) . id ; user = await createUser ( newUser ) ; await events . createUser ?. ( { user } ) ; isNewUser = true ; } session = useJwtSession ? { } : await createSession ( { sessionToken : generateSessionToken ( ) , userId : user . id , expires : fromDate ( options . session . maxAge ) , } ) ; return { session , user , isNewUser } ; } else if ( account . type === "oauth" ) { const userByAccount = await getUserByAccount ( { providerAccountId : account . providerAccountId , provider : account . provider , } ) ; if ( userByAccount ) { if ( user ) { if ( userByAccount . id === user . id ) { return { session , user , isNewUser } ; } throw new AccountNotLinkedError ( "The account is already associated with another user" ) ; } session = useJwtSession ? { } : await createSession ( { sessionToken : generateSessionToken ( ) , userId : userByAccount . id , expires : fromDate ( options . session . maxAge ) , } ) ; return { session , user : userByAccount , isNewUser } ; } else { if ( user ) { await linkAccount ( { ... account , userId : user . id } ) ; await events . linkAccount ?. ( { user , account } ) ; return { session , user , isNewUser } ; } const userByEmail = profile . email ? await getUserByEmail ( profile . email ) : null ; if ( userByEmail ) { throw new AccountNotLinkedError ( "Another account already exists with the same e-mail address" ) ; } const newUser = { ... profile , emailVerified : null } ; delete ( newUser as Omit < AdapterUser , "id" > ) . id ; user = await createUser ( newUser ) ; await events . createUser ?. ( { user } ) ; await linkAccount ( { ... account , userId : user . id } ) ; await events . linkAccount ?. ( { user , account } ) ; session = useJwtSession ? { } : await createSession ( { sessionToken : generateSessionToken ( ) , userId : user . id , expires : fromDate ( options . session . maxAge ) , } ) ; return { session , user , isNewUser : true } ; } } } function generateSessionToken ( ) { return randomUUID ?. ( ) ?? randomBytes ( 32 ) . toString ( "hex" ) ; }