import { TokenSet } from "openid-client" ; import { openidClient } from "./client" ; import { oAuth1Client } from "./client-legacy" ; import { useState } from "./state-handler" ; import { usePKCECodeVerifier } from "./pkce-handler" ; import { OAuthCallbackError } from "../../errors" ; import type { CallbackParamsType } from "openid-client" ; import type { Account , LoggerInstance , Profile } from "../../.." ; import type { OAuthChecks , OAuthConfig } from "../../../providers" ; import type { InternalOptions } from "../../../lib/types" ; import type { IncomingRequest , OutgoingResponse } from "../.." ; import type { Cookie } from "../cookie" ; export default async function oAuthCallback ( params ) { const { options , query , body , method , cookies } = params ; const { logger , provider } = options ; const errorMessage = body ?. error ?? query ?. error ; if ( errorMessage ) { const error = new Error ( errorMessage ) ; logger . error ( "OAUTH_CALLBACK_HANDLER_ERROR" , { error , error_description : query ?. error_description , body , providerId : provider . id , } ) ; throw error ; } if ( provider . version ?. startsWith ( "1." ) ) { try { const client = await oAuth1Client ( options ) ; const { oauth_token , oauth_verifier } = query ?? { } ; const tokens = await client . getOAuthAccessToken ( oauth_token as string , null , oauth_verifier ) ; let profile = await client . get ( ( provider as any ) . profileUrl , tokens . oauth_token , tokens . oauth_token_secret ) ; if ( typeof profile === "string" ) { profile = JSON . parse ( profile ) ; } return await getProfile ( { profile , tokens , provider , logger } ) ; } catch ( error ) { logger . error ( "OAUTH_V1_GET_ACCESS_TOKEN_ERROR" , error as Error ) ; throw error ; } } try { const client = await openidClient ( options ) ; let tokens ; const checks = { } ; const resCookies = [ ] ; const state = await useState ( cookies ?. [ options . cookies . state . name ] , options ) ; if ( state ) { checks . state = state . value ; resCookies . push ( state . cookie ) ; } const codeVerifier = cookies ?. [ options . cookies . pkceCodeVerifier . name ] ; const pkce = await usePKCECodeVerifier ( codeVerifier , options ) ; if ( pkce ) { checks . code_verifier = pkce . codeVerifier ; resCookies . push ( pkce . cookie ) ; } const params = { ... client . callbackParams ( { url : `http://n?${ new URLSearchParams ( query ) }` , body , method , } ) , ... provider . token ?. params , } ; if ( provider . token ?. request ) { const response = await provider . token . request ( { provider , params , checks , client , } ) ; tokens = new TokenSet ( response . tokens ) ; } else if ( provider . idToken ) { tokens = await client . callback ( provider . callbackUrl , params , checks ) ; } else { tokens = await client . oauthCallback ( provider . callbackUrl , params , checks ) ; } if ( Array . isArray ( tokens . scope ) ) { tokens . scope = tokens . scope . join ( " " ) ; } let profile ; if ( provider . userinfo ?. request ) { profile = await provider . userinfo . request ( { provider , tokens , client , } ) ; } else if ( provider . idToken ) { profile = tokens . claims ( ) ; } else { profile = await client . userinfo ( tokens , { params : provider . userinfo ?. params , } ) ; } const profileResult = await getProfile ( { profile , provider , tokens , logger , } ) ; return { ... profileResult , cookies : resCookies } ; } catch ( error ) { logger . error ( "OAUTH_CALLBACK_ERROR" , { error : error as Error , providerId : provider . id , } ) ; throw new OAuthCallbackError ( error as Error ) ; } } export interface GetProfileParams { profile : Profile ; tokens : TokenSet ; provider : OAuthConfig < any > ; logger : LoggerInstance ; } export interface GetProfileResult { profile : ReturnType < OAuthConfig [ "profile" ] > | null ; account : Omit < Account , "userId" > | null ; OAuthProfile : Profile ; } /** Returns profile, raw profile and auth provider details */ async function getProfile ( { profile : OAuthProfile , tokens , provider , logger , } ) { try { logger . debug ( "PROFILE_DATA" , { OAuthProfile } ) ; const profile = await provider . profile ( OAuthProfile , tokens ) ; profile . email = profile . email ?. toLowerCase ( ) ; return { profile , account : { provider : provider . id , type : provider . type , providerAccountId : profile . id . toString ( ) , ... tokens , } , OAuthProfile , } ; } catch ( error ) { logger . error ( "OAUTH_PARSE_PROFILE_ERROR" , { error : error as Error , OAuthProfile , } ) ; return { profile : null , account : null , OAuthProfile , } ; } }