Files
UnrealEngine/Engine/Plugins/Online/OnlineServicesEOS/Source/Private/Online/AuthEOS.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

923 lines
36 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Online/AuthEOS.h"
#include "Algo/Find.h"
#include "Algo/RemoveIf.h"
#include "Algo/Transform.h"
#include "Containers/StaticArray.h"
#include "EOSShared.h"
#include "IEOSSDKManager.h"
#include "Online/AuthErrors.h"
#include "Online/EpicAccountIdResolver.h"
#include "Online/EpicProductUserIdResolver.h"
#include "Online/OnlineErrorEpicCommon.h"
#include "Online/OnlineIdEOS.h"
#include "Online/OnlineServicesEOS.h"
#include "Online/OnlineUtils.h"
#include "Online/OnlineUtilsCommon.h"
#include "eos_auth.h"
#include "eos_connect.h"
#include "eos_userinfo.h"
namespace UE::Online {
struct FAuthEOSLoginConfig
{
TArray<FString> DefaultScopes;
bool bAutoLinkAccount = true;
};
namespace Meta {
BEGIN_ONLINE_STRUCT_META(FAuthEOSLoginConfig)
ONLINE_STRUCT_FIELD(FAuthEOSLoginConfig, DefaultScopes),
ONLINE_STRUCT_FIELD(FAuthEOSLoginConfig, bAutoLinkAccount)
END_ONLINE_STRUCT_META()
/* Meta*/ }
namespace
{
#define UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME TEXT("AccountInfoEOS")
#define UE_ONLINE_AUTH_EOS_CONTINUANCE_DATA_KEY_NAME TEXT("ContinuanceToken")
#define UE_ONLINE_AUTH_EOS_SELECTED_ACCOUNT_ID_KEY_NAME TEXT("SelectedAccountId")
/* anonymous */ }
namespace ELinkAccountTag
{
const FName InternalAccount = TEXT("InternalAccount");
}
FAuthEOS::FAuthEOS(FOnlineServicesCommon& InServices)
: Super(InServices)
{
}
void FAuthEOS::Initialize()
{
Super::Initialize();
UserInfoHandle = EOS_Platform_GetUserInfoInterface(*GetServices<FOnlineServicesEpicCommon>().GetEOSPlatformHandle());
check(UserInfoHandle != nullptr);
}
TOnlineAsyncOpHandle<FAuthLogin> FAuthEOS::Login(FAuthLogin::Params&& Params)
{
TOnlineAsyncOpRef<FAuthLogin> Op = GetOp<FAuthLogin>(MoveTemp(Params));
// Step 1: Set up operation data.
Op->Then([this](TOnlineAsyncOp<FAuthLogin>& InAsyncOp)
{
const FAuthLogin::Params& Params = InAsyncOp.GetParams();
// Check that user is valid.
if (!Params.PlatformUserId.IsValid())
{
InAsyncOp.SetError(Errors::InvalidParams());
return;
}
TSharedPtr<FAccountInfoEOS> AccountInfoEOS = AccountInfoRegistryEOS.Find(Params.PlatformUserId);
if (AccountInfoEOS)
{
InAsyncOp.SetError(Errors::Auth::AlreadyLoggedIn());
return;
}
AccountInfoEOS = MakeShared<FAccountInfoEOS>();
AccountInfoEOS->PlatformUserId = Params.PlatformUserId;
AccountInfoEOS->LoginStatus = ELoginStatus::NotLoggedIn;
// New login attempt - Clear the continuance token for the last login attempt for the user.
if (FUserScopedData* UserData = GetUserScopedData(Params.PlatformUserId))
{
UserData->LastLoginContinuationId = FLoginContinuationId();
}
// Set user auth data on operation.
InAsyncOp.Data.Set<TSharedRef<FAccountInfoEOS>>(UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME, AccountInfoEOS.ToSharedRef());
})
// Step 2: Login EAS.
.Then([this](TOnlineAsyncOp<FAuthLogin>& InAsyncOp)
{
TPromise<void> Promise;
TFuture<void> Future = Promise.GetFuture();
const FAuthLogin::Params& Params = InAsyncOp.GetParams();
FAuthEOSLoginConfig AuthEOSLoginConfig;
LoadConfig(AuthEOSLoginConfig, TEXT("Login"));
FAuthLoginEASImpl::Params LoginParams;
LoginParams.PlatformUserId = Params.PlatformUserId;
LoginParams.CredentialsType = Params.CredentialsType;
LoginParams.CredentialsId = Params.CredentialsId;
LoginParams.CredentialsToken = Params.CredentialsToken;
LoginParams.Scopes = !Params.Scopes.IsEmpty() ? Params.Scopes : AuthEOSLoginConfig.DefaultScopes;
LoginParams.bAutoLinkAccount = AuthEOSLoginConfig.bAutoLinkAccount;
LoginEASImpl(LoginParams)
.Next([this, Promise = MoveTemp(Promise), WeakOp = InAsyncOp.AsWeak()](TDefaultErrorResult<FAuthLoginEASImpl>&& LoginResult) mutable -> void
{
if (TSharedPtr<TOnlineAsyncOp<FAuthLogin>> Op = WeakOp.Pin())
{
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(*Op, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
if (LoginResult.IsError())
{
UE_LOG(LogOnlineServices, Warning, TEXT("[FAuthEOS::Login] Failure: LoginEASImpl %s"), *LoginResult.GetErrorValue().GetLogString());
Op->SetError(MoveTemp(LoginResult.GetErrorValue()));
}
else
{
// Cache EpicAccountId on successful EAS login.
AccountInfoEOS->EpicAccountId = LoginResult.GetOkValue().EpicAccountId;
}
}
Promise.EmplaceValue();
});
return Future;
})
// Step 3: Fetch external auth credentials for connect login.
.Then([this](TOnlineAsyncOp<FAuthLogin>& InAsyncOp)
{
const FAuthLogin::Params& Params = InAsyncOp.GetParams();
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(InAsyncOp, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
TPromise<FAuthLoginConnectImpl::Params> Promise;
TFuture<FAuthLoginConnectImpl::Params> Future = Promise.GetFuture();
TDefaultErrorResult<FAuthGetExternalAuthTokenImpl> AuthTokenResult = GetExternalAuthTokenImpl(FAuthGetExternalAuthTokenImpl::Params{AccountInfoEOS->EpicAccountId});
if (AuthTokenResult.IsError())
{
UE_LOG(LogOnlineServices, Warning, TEXT("[FAuthEOS::Login] Failure: GetExternalAuthTokenImpl %s"), *AuthTokenResult.GetErrorValue().GetLogString());
// Failed to acquire token - logout EAS.
LogoutEASImpl(FAuthLogoutEASImpl::Params{ AccountInfoEOS->EpicAccountId })
.Next([AsyncOp = InAsyncOp.AsShared(), Error = MoveTemp(AuthTokenResult.GetErrorValue()), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLogoutEASImpl>&&) mutable -> void
{
AsyncOp->SetError(MoveTemp(Error));
Promise.EmplaceValue(FAuthLoginConnectImpl::Params{});
});
return Future;
}
Promise.EmplaceValue(FAuthLoginConnectImpl::Params{Params.PlatformUserId, MoveTemp(AuthTokenResult.GetOkValue().Token)});
return Future;
})
// Step 4: Attempt connect login. On connect login failure handle logout of EAS.
.Then([this](TOnlineAsyncOp<FAuthLogin>& InAsyncOp, FAuthLoginConnectImpl::Params&& LoginConnectParams)
{
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(InAsyncOp, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
TPromise<void> Promise;
TFuture<void> Future = Promise.GetFuture();
// Attempt connect login.
LoginConnectImpl(LoginConnectParams)
.Next([this, AccountInfoEOS, WeakOp = InAsyncOp.AsWeak(), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLoginConnectImpl>&& LoginResult) mutable -> void
{
if (TSharedPtr<TOnlineAsyncOp<FAuthLogin>> AsyncOp = WeakOp.Pin())
{
if (LoginResult.IsError())
{
UE_LOG(LogOnlineServices, Warning, TEXT("[FAuthEOS::Login] Failure: LoginConnectImpl %s"), *LoginResult.GetErrorValue().GetLogString());
LogoutEASImpl(FAuthLogoutEASImpl::Params{ AccountInfoEOS->EpicAccountId })
.Next([AsyncOp, Error = MoveTemp(LoginResult.GetErrorValue()), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLogoutEASImpl>&&) mutable -> void
{
AsyncOp->SetError(MoveTemp(Error));
Promise.EmplaceValue();
});
}
else
{
// Successful login.
AccountInfoEOS->ProductUserId = LoginResult.GetOkValue().ProductUserId;
Promise.EmplaceValue();
}
}
else
{
Promise.EmplaceValue();
}
});
return Future;
})
// Step 5: Fetch dependent data.
.Then([this](TOnlineAsyncOp<FAuthLogin>& InAsyncOp)
{
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(InAsyncOp, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
// Get display name
EOS_UserInfo_CopyBestDisplayNameOptions Options = {};
Options.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_USERINFO_COPYBESTDISPLAYNAME_API_LATEST, 1);
Options.LocalUserId = AccountInfoEOS->EpicAccountId;
Options.TargetUserId = AccountInfoEOS->EpicAccountId;
EOS_UserInfo_BestDisplayName* BestDisplayName;
EOS_EResult CopyBestDisplayNameResult = EOS_UserInfo_CopyBestDisplayName(UserInfoHandle, &Options, &BestDisplayName);
if (CopyBestDisplayNameResult == EOS_EResult::EOS_UserInfo_BestDisplayNameIndeterminate)
{
EOS_UserInfo_CopyBestDisplayNameWithPlatformOptions WithPlatformOptions = {};
WithPlatformOptions.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_USERINFO_COPYBESTDISPLAYNAMEWITHPLATFORM_API_LATEST, 1);
WithPlatformOptions.LocalUserId = AccountInfoEOS->EpicAccountId;
WithPlatformOptions.TargetUserId = AccountInfoEOS->EpicAccountId;
WithPlatformOptions.TargetPlatformType = EOS_OPT_Epic;
CopyBestDisplayNameResult = EOS_UserInfo_CopyBestDisplayNameWithPlatform(UserInfoHandle, &WithPlatformOptions, &BestDisplayName);
}
if (CopyBestDisplayNameResult == EOS_EResult::EOS_Success)
{
AccountInfoEOS->Attributes.Emplace(AccountAttributeData::DisplayName, *GetBestDisplayNameStr(*BestDisplayName));
EOS_UserInfo_BestDisplayName_Release(BestDisplayName);
}
else
{
FOnlineError CopyUserInfoError(Errors::FromEOSResult(CopyBestDisplayNameResult));
UE_LOG(LogOnlineServices, Warning, TEXT("[FAuthEOS::Login] Failure: EOS_UserInfo_CopyBestDisplayName %s"), *CopyUserInfoError.GetLogString());
TPromise<void> Promise;
TFuture<void> Future = Promise.GetFuture();
LogoutEASImpl(FAuthLogoutEASImpl::Params{ AccountInfoEOS->EpicAccountId })
.Next([this, AccountInfoEOS, WeakOp = InAsyncOp.AsWeak(), Error = MoveTemp(CopyUserInfoError), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLogoutEASImpl>&&) mutable -> void
{
if (TSharedPtr<TOnlineAsyncOp<FAuthLogin>> AsyncOp = WeakOp.Pin())
{
LogoutConnectImpl(FAuthLogoutConnectImpl::Params{ AccountInfoEOS->ProductUserId })
.Next([AsyncOp, Error = MoveTemp(Error), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLogoutConnectImpl>&&) mutable -> void
{
AsyncOp->SetError(MoveTemp(Error));
Promise.EmplaceValue();
});
}
else
{
Promise.EmplaceValue();
}
});
return Future;
}
return MakeFulfilledPromise<void>().GetFuture();
})
// Step 6: bookkeeping and notifications.
.Then([this](TOnlineAsyncOp<FAuthLogin>& InAsyncOp)
{
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(InAsyncOp, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
AccountInfoEOS->LoginStatus = ELoginStatus::LoggedIn;
AccountInfoEOS->AccountId = CreateAccountId(AccountInfoEOS->EpicAccountId, AccountInfoEOS->ProductUserId);
AccountInfoRegistryEOS.Register(AccountInfoEOS);
UE_LOG(LogOnlineServices, Log, TEXT("[FAuthEOS::Login] Successfully logged in as [%s]"), *ToLogString(AccountInfoEOS->AccountId));
OnAuthLoginStatusChangedEvent.Broadcast(FAuthLoginStatusChanged{ AccountInfoEOS, AccountInfoEOS->LoginStatus });
InAsyncOp.SetResult(FAuthLogin::Result{AccountInfoEOS});
})
.Enqueue(GetSerialQueue());
return Op->GetHandle();
}
TOnlineAsyncOpHandle<FAuthLinkAccount> FAuthEOS::LinkAccount(FAuthLinkAccount::Params&& Params)
{
TOnlineAsyncOpRef<FAuthLinkAccount> Op = GetOp<FAuthLinkAccount>(MoveTemp(Params));
// Step 1: Set up operation data.
Op->Then([this](TOnlineAsyncOp<FAuthLinkAccount>& InAsyncOp)
{
const FAuthLinkAccount::Params& Params = InAsyncOp.GetParams();
// Check that user is valid.
if (!Params.PlatformUserId.IsValid())
{
InAsyncOp.SetError(Errors::InvalidParams());
return;
}
// Check that user scoped data exists for user.
FUserScopedData* UserData = FAuthEOSGS::GetUserScopedData(Params.PlatformUserId);
if (UserData == nullptr)
{
InAsyncOp.SetError(Errors::InvalidParams());
return;
}
// Make sure continuation exists.
const FLoginContinuationData* ContinuanceData = Algo::FindByPredicate(UserData->LoginContinuations, [&Params](const FLoginContinuationData& Continuation)->bool { return Continuation.ContinuationId == Params.ContinuationId; });
if (ContinuanceData == nullptr)
{
InAsyncOp.SetError(Errors::InvalidParams());
return;
}
InAsyncOp.Data.Set<FLoginContinuationData>(UE_ONLINE_AUTH_EOS_CONTINUANCE_DATA_KEY_NAME, *ContinuanceData);
})
// Step 2: Call link account.
.Then([this](TOnlineAsyncOp<FAuthLinkAccount>& InAsyncOp, TPromise<const EOS_Auth_LinkAccountCallbackInfo*>&& Promise)
{
const FAuthLinkAccount::Params& Params = InAsyncOp.GetParams();
const FLoginContinuationData& LoginContinuationData = GetOpDataChecked<FLoginContinuationData>(InAsyncOp, UE_ONLINE_AUTH_EOS_CONTINUANCE_DATA_KEY_NAME);
TSharedPtr<FAccountInfoEOS> AccountInfoEOS = AccountInfoRegistryEOS.Find(Params.PlatformUserId);
EOS_Auth_LinkAccountOptions LinkAccountOptions = {};
LinkAccountOptions.ApiVersion = 1;
LinkAccountOptions.ContinuanceToken = LoginContinuationData.ContinuanceToken;
LinkAccountOptions.LinkAccountFlags = (!Params.Tags.Contains(ELinkAccountTag::InternalAccount)) ? LoginContinuationData.LinkAccountFlags : EOS_ELinkAccountFlags::EOS_LA_NoFlags;
LinkAccountOptions.LocalUserId = AccountInfoEOS ? AccountInfoEOS->EpicAccountId : nullptr;
UE_EOS_CHECK_API_MISMATCH(EOS_AUTH_LINKACCOUNT_API_LATEST, 1);
EOS_Async(EOS_Auth_LinkAccount, AuthHandle, LinkAccountOptions, MoveTemp(Promise));
})
// Step 3: Handle link account result.
.Then([this](TOnlineAsyncOp<FAuthLinkAccount>& InAsyncOp, const EOS_Auth_LinkAccountCallbackInfo* Data)
{
UE_LOG(LogOnlineServices, Verbose, TEXT("[FAuthEOS::LinkAccount] EOS_Auth_LinkAccount Result: [%s]"), *LexToString(Data->ResultCode));
if (Data->ResultCode == EOS_EResult::EOS_Success)
{
InAsyncOp.Data.Set<EOS_EpicAccountId>(UE_ONLINE_AUTH_EOS_SELECTED_ACCOUNT_ID_KEY_NAME, Data->SelectedAccountId);
}
else
{
InAsyncOp.SetError(Errors::FromEOSResult(Data->ResultCode));
}
})
// Step 4: Link account success handling / user info setup.
.Then([this](TOnlineAsyncOp<FAuthLinkAccount>& InAsyncOp)
{
const FAuthLinkAccount::Params& Params = InAsyncOp.GetParams();
// Remove continuance token.
FUserScopedData* UserData = FAuthEOSGS::GetUserScopedData(Params.PlatformUserId);
check(UserData);
UserData->LastLoginContinuationId = FLoginContinuationId();
UserData->LoginContinuations.SetNum(Algo::RemoveIf(UserData->LoginContinuations, [&Params](const FLoginContinuationData& Continuation)
{
return Continuation.ContinuationId == Params.ContinuationId;
}));
// Create or fetch FAccountInfoEOS for user.
const EOS_EpicAccountId SelectedUserAccount = GetOpDataChecked<EOS_EpicAccountId>(InAsyncOp, UE_ONLINE_AUTH_EOS_SELECTED_ACCOUNT_ID_KEY_NAME);
TSharedPtr<FAccountInfoEOS> AccountInfoEOS = AccountInfoRegistryEOS.Find(Params.PlatformUserId);
const bool bWasLoggedIn = AccountInfoEOS && AccountInfoEOS->EpicAccountId == SelectedUserAccount;
const bool bChoseDifferentAccount = bWasLoggedIn && AccountInfoEOS->EpicAccountId != SelectedUserAccount;
// Notify logout of previous account.
if (AccountInfoEOS && bChoseDifferentAccount)
{
AccountInfoEOS->LoginStatus = ELoginStatus::NotLoggedIn;
OnAuthLoginStatusChangedEvent.Broadcast(FAuthLoginStatusChanged{ AccountInfoEOS.ToSharedRef(), AccountInfoEOS->LoginStatus });
AccountInfoRegistryEOS.Unregister(AccountInfoEOS->AccountId);
AccountInfoEOS = nullptr;
}
// Setup user account if user is not already logged in.
if (AccountInfoEOS == nullptr)
{
AccountInfoEOS = MakeShared<FAccountInfoEOS>();
AccountInfoEOS->PlatformUserId = Params.PlatformUserId;
AccountInfoEOS->EpicAccountId = SelectedUserAccount;
AccountInfoEOS->LoginStatus = ELoginStatus::NotLoggedIn;
}
// Set user auth data on operation.
InAsyncOp.Data.Set<TSharedRef<FAccountInfoEOS>>(UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME, AccountInfoEOS.ToSharedRef());
})
// Step 5: Fetch external auth credentials for connect login.
.Then([this](TOnlineAsyncOp<FAuthLinkAccount>& InAsyncOp)
{
const FAuthLinkAccount::Params& Params = InAsyncOp.GetParams();
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(InAsyncOp, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
TPromise<FAuthLoginConnectImpl::Params> Promise;
TFuture<FAuthLoginConnectImpl::Params> Future = Promise.GetFuture();
if (AccountInfoEOS->LoginStatus == ELoginStatus::NotLoggedIn)
{
TDefaultErrorResult<FAuthGetExternalAuthTokenImpl> AuthTokenResult = GetExternalAuthTokenImpl(FAuthGetExternalAuthTokenImpl::Params{AccountInfoEOS->EpicAccountId});
if (AuthTokenResult.IsError())
{
UE_LOG(LogOnlineServices, Warning, TEXT("[FAuthEOS::LinkAccount] Failure: GetExternalAuthTokenImpl %s"), *AuthTokenResult.GetErrorValue().GetLogString());
// Failed to acquire token - logout EAS.
LogoutEASImpl(FAuthLogoutEASImpl::Params{ AccountInfoEOS->EpicAccountId })
.Next([AsyncOp = InAsyncOp.AsShared(), Error = MoveTemp(AuthTokenResult.GetErrorValue()), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLogoutEASImpl>&&) mutable -> void
{
AsyncOp->SetError(MoveTemp(Error));
Promise.EmplaceValue(FAuthLoginConnectImpl::Params{});
});
return Future;
}
Promise.EmplaceValue(FAuthLoginConnectImpl::Params{Params.PlatformUserId, MoveTemp(AuthTokenResult.GetOkValue().Token)});
}
else
{
// No connect login is needed - user is already logged in.
Promise.EmplaceValue(FAuthLoginConnectImpl::Params{});
}
return Future;
})
// Step 6: Attempt connect login. On connect login failure handle logout of EAS.
.Then([this](TOnlineAsyncOp<FAuthLinkAccount>& InAsyncOp, FAuthLoginConnectImpl::Params&& LoginConnectParams)
{
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(InAsyncOp, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
TPromise<void> Promise;
TFuture<void> Future = Promise.GetFuture();
if (AccountInfoEOS->LoginStatus == ELoginStatus::NotLoggedIn)
{
// Attempt connect login.
LoginConnectImpl(LoginConnectParams)
.Next([this, AccountInfoEOS, WeakOp = InAsyncOp.AsWeak(), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLoginConnectImpl>&& LoginResult) mutable -> void
{
if (TSharedPtr<TOnlineAsyncOp<FAuthLinkAccount>> AsyncOp = WeakOp.Pin())
{
if (LoginResult.IsError())
{
UE_LOG(LogOnlineServices, Warning, TEXT("[FAuthEOS::LinkAccount] Failure: LoginConnectImpl %s"), *LoginResult.GetErrorValue().GetLogString());
LogoutEASImpl(FAuthLogoutEASImpl::Params{ AccountInfoEOS->EpicAccountId })
.Next([AsyncOp, Error = MoveTemp(LoginResult.GetErrorValue()), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLogoutEASImpl>&&) mutable -> void
{
AsyncOp->SetError(MoveTemp(Error));
Promise.EmplaceValue();
});
}
else
{
// Successful login.
AccountInfoEOS->ProductUserId = LoginResult.GetOkValue().ProductUserId;
Promise.EmplaceValue();
}
}
else
{
Promise.EmplaceValue();
}
});
}
else
{
// No connect login is needed - user is already logged in.
Promise.EmplaceValue();
}
return Future;
})
// Step 7: Fetch dependent data.
.Then([this](TOnlineAsyncOp<FAuthLinkAccount>& InAsyncOp)
{
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(InAsyncOp, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
if (AccountInfoEOS->LoginStatus == ELoginStatus::NotLoggedIn)
{
// Get display name
EOS_UserInfo_CopyBestDisplayNameOptions Options = {};
Options.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_USERINFO_COPYBESTDISPLAYNAME_API_LATEST, 1);
Options.LocalUserId = AccountInfoEOS->EpicAccountId;
Options.TargetUserId = AccountInfoEOS->EpicAccountId;
EOS_UserInfo_BestDisplayName* BestDisplayName;
EOS_EResult CopyBestDisplayNameResult = EOS_UserInfo_CopyBestDisplayName(UserInfoHandle, &Options, &BestDisplayName);
if (CopyBestDisplayNameResult == EOS_EResult::EOS_UserInfo_BestDisplayNameIndeterminate)
{
EOS_UserInfo_CopyBestDisplayNameWithPlatformOptions WithPlatformOptions = {};
WithPlatformOptions.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_USERINFO_COPYBESTDISPLAYNAMEWITHPLATFORM_API_LATEST, 1);
WithPlatformOptions.LocalUserId = AccountInfoEOS->EpicAccountId;
WithPlatformOptions.TargetUserId = AccountInfoEOS->EpicAccountId;
WithPlatformOptions.TargetPlatformType = EOS_OPT_Epic;
CopyBestDisplayNameResult = EOS_UserInfo_CopyBestDisplayNameWithPlatform(UserInfoHandle, &WithPlatformOptions, &BestDisplayName);
}
if (CopyBestDisplayNameResult == EOS_EResult::EOS_Success)
{
AccountInfoEOS->Attributes.Emplace(AccountAttributeData::DisplayName, *GetBestDisplayNameStr(*BestDisplayName));
EOS_UserInfo_BestDisplayName_Release(BestDisplayName);
}
else
{
FOnlineError CopyUserInfoError(Errors::FromEOSResult(CopyBestDisplayNameResult));
UE_LOG(LogOnlineServices, Warning, TEXT("[FAuthEOS::LinkAccount] Failure: EOS_UserInfo_CopyBestDisplayName %s"), *CopyUserInfoError.GetLogString());
TPromise<void> Promise;
TFuture<void> Future = Promise.GetFuture();
LogoutEASImpl(FAuthLogoutEASImpl::Params{ AccountInfoEOS->EpicAccountId })
.Next([this, AccountInfoEOS, WeakOp = InAsyncOp.AsWeak(), Error = MoveTemp(CopyUserInfoError), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLogoutEASImpl>&&) mutable -> void
{
if (TSharedPtr<TOnlineAsyncOp<FAuthLinkAccount>> AsyncOp = WeakOp.Pin())
{
LogoutConnectImpl(FAuthLogoutConnectImpl::Params{ AccountInfoEOS->ProductUserId })
.Next([AsyncOp, Error = MoveTemp(Error), Promise = MoveTemp(Promise)](TDefaultErrorResult<FAuthLogoutConnectImpl>&&) mutable -> void
{
AsyncOp->SetError(MoveTemp(Error));
Promise.EmplaceValue();
});
}
else
{
Promise.EmplaceValue();
}
});
return Future;
}
}
return MakeFulfilledPromise<void>().GetFuture();
})
// Step 8: bookkeeping and notifications.
.Then([this](TOnlineAsyncOp<FAuthLinkAccount>& InAsyncOp)
{
const TSharedRef<FAccountInfoEOS>& AccountInfoEOS = GetOpDataChecked<TSharedRef<FAccountInfoEOS>>(InAsyncOp, UE_ONLINE_AUTH_EOS_ACCOUNT_INFO_KEY_NAME);
const bool bUserWasLoggedIn = AccountInfoEOS->LoginStatus == ELoginStatus::LoggedIn;
if (bUserWasLoggedIn)
{
UE_LOG(LogOnlineServices, Log, TEXT("[FAuthEOS::LinkAccount] Successfully linked account. AccountId: %s"), *ToLogString(AccountInfoEOS->AccountId));
}
else
{
AccountInfoEOS->LoginStatus = ELoginStatus::LoggedIn;
AccountInfoEOS->AccountId = CreateAccountId(AccountInfoEOS->EpicAccountId, AccountInfoEOS->ProductUserId);
AccountInfoRegistryEOS.Register(AccountInfoEOS);
UE_LOG(LogOnlineServices, Log, TEXT("[FAuthEOS::LinkAccount] Successfully logged in. AccountId: %s"), *ToLogString(AccountInfoEOS->AccountId));
OnAuthLoginStatusChangedEvent.Broadcast(FAuthLoginStatusChanged{ AccountInfoEOS, AccountInfoEOS->LoginStatus });
}
InAsyncOp.SetResult(FAuthLinkAccount::Result{AccountInfoEOS});
})
.Enqueue(GetSerialQueue());
return Op->GetHandle();
}
TOnlineAsyncOpHandle<FAuthQueryExternalServerAuthTicket> FAuthEOS::QueryExternalServerAuthTicket(FAuthQueryExternalServerAuthTicket::Params&& Params)
{
TOnlineAsyncOpRef<FAuthQueryExternalServerAuthTicket> Op = GetJoinableOp<FAuthQueryExternalServerAuthTicket>(MoveTemp(Params));
if (!Op->IsReady())
{
Op->Then([this](TOnlineAsyncOp<FAuthQueryExternalServerAuthTicket>& InAsyncOp)
{
const FAuthQueryExternalServerAuthTicket::Params& Params = InAsyncOp.GetParams();
TSharedPtr<FAccountInfoEOS> AccountInfoEOS = AccountInfoRegistryEOS.Find(Params.LocalAccountId);
if (!AccountInfoEOS)
{
InAsyncOp.SetError(Errors::InvalidParams());
return;
}
EOS_Auth_CopyUserAuthTokenOptions CopyUserAuthTokenOptions = {};
CopyUserAuthTokenOptions.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_AUTH_COPYUSERAUTHTOKEN_API_LATEST, 1);
EOS_Auth_Token* AuthToken = nullptr;
EOS_EResult Result = EOS_Auth_CopyUserAuthToken(AuthHandle, &CopyUserAuthTokenOptions, AccountInfoEOS->EpicAccountId, &AuthToken);
if (Result == EOS_EResult::EOS_Success)
{
ON_SCOPE_EXIT
{
EOS_Auth_Token_Release(AuthToken);
};
FExternalServerAuthTicket ExternalServerAuthTicket;
ExternalServerAuthTicket.Type = ExternalLoginType::Epic;
ExternalServerAuthTicket.Data = UTF8_TO_TCHAR(AuthToken->AccessToken);
InAsyncOp.SetResult(FAuthQueryExternalServerAuthTicket::Result{ MoveTemp(ExternalServerAuthTicket) });
}
else
{
InAsyncOp.SetError(Errors::FromEOSResult(Result));
}
})
.Enqueue(GetSerialQueue());
}
return Op->GetHandle();
}
TOnlineAsyncOpHandle<FAuthQueryExternalAuthToken> FAuthEOS::QueryExternalAuthToken(FAuthQueryExternalAuthToken::Params&& Params)
{
TOnlineAsyncOpRef<FAuthQueryExternalAuthToken> Op = GetJoinableOp<FAuthQueryExternalAuthToken>(MoveTemp(Params));
if (!Op->IsReady())
{
Op->Then([this](TOnlineAsyncOp<FAuthQueryExternalAuthToken>& InAsyncOp)
{
const FAuthQueryExternalAuthToken::Params& Params = InAsyncOp.GetParams();
TSharedPtr<FAccountInfoEOS> AccountInfoEOS = AccountInfoRegistryEOS.Find(Params.LocalAccountId);
if (!AccountInfoEOS)
{
InAsyncOp.SetError(Errors::InvalidParams());
return;
}
// The primary external auth method is an id token.
if (Params.Method == EExternalAuthTokenMethod::Primary)
{
TDefaultErrorResult<FAuthGetExternalAuthTokenImpl> AuthTokenResult = GetExternalAuthTokenImpl(FAuthGetExternalAuthTokenImpl::Params{ AccountInfoEOS->EpicAccountId });
if (AuthTokenResult.IsError())
{
UE_LOG(LogOnlineServices, Warning, TEXT("[FAuthEOS::QueryExternalAuthToken] Failure: GetExternalAuthTokenImpl %s"), *AuthTokenResult.GetErrorValue().GetLogString());
InAsyncOp.SetError(MoveTemp(AuthTokenResult.GetErrorValue()));
return;
}
InAsyncOp.SetResult(FAuthQueryExternalAuthToken::Result{ MoveTemp(AuthTokenResult.GetOkValue().Token) });
}
// The secondary external auth method is an EAS refresh token.
else if (Params.Method == EExternalAuthTokenMethod::Secondary)
{
EOS_Auth_CopyUserAuthTokenOptions CopyUserAuthTokenOptions = {};
CopyUserAuthTokenOptions.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_AUTH_COPYUSERAUTHTOKEN_API_LATEST, 1);
EOS_Auth_Token* AuthToken = nullptr;
EOS_EResult Result = EOS_Auth_CopyUserAuthToken(AuthHandle, &CopyUserAuthTokenOptions, AccountInfoEOS->EpicAccountId, &AuthToken);
if (Result == EOS_EResult::EOS_Success)
{
ON_SCOPE_EXIT
{
EOS_Auth_Token_Release(AuthToken);
};
FExternalAuthToken ExternalAuthToken;
ExternalAuthToken.Type = ExternalLoginType::Epic;
ExternalAuthToken.Data = UTF8_TO_TCHAR(AuthToken->RefreshToken);
InAsyncOp.SetResult(FAuthQueryExternalAuthToken::Result{ MoveTemp(ExternalAuthToken) });
}
else
{
InAsyncOp.SetError(Errors::FromEOSResult(Result));
}
}
else
{
InAsyncOp.SetError(Errors::InvalidParams());
}
})
.Enqueue(GetSerialQueue());
}
return Op->GetHandle();
}
TOnlineResult<FAuthGetLinkAccountContinuationId> FAuthEOS::GetLinkAccountContinuationId(FAuthGetLinkAccountContinuationId::Params&& Params) const
{
if (!Params.PlatformUserId.IsValid())
{
return TOnlineResult<FAuthGetLinkAccountContinuationId>(Errors::InvalidUser());
}
const FUserScopedData* UserData = GetUserScopedData(Params.PlatformUserId);
if (UserData == nullptr)
{
UE_LOG(LogOnlineServices, Verbose, TEXT("[%hs]: Failed to find user scoped data. PlatformUserId: %s."), __FUNCTION__, *ToLogString(Params.PlatformUserId));
return TOnlineResult<FAuthGetLinkAccountContinuationId>(Errors::NotFound());
}
if (!UserData->LastLoginContinuationId.IsValid())
{
UE_LOG(LogOnlineServices, Verbose, TEXT("[%hs]: Failed to find valid login continuation. PlatformUserId: %s."), __FUNCTION__, *ToLogString(Params.PlatformUserId));
return TOnlineResult<FAuthGetLinkAccountContinuationId>(Errors::NotFound());
}
UE_LOG(LogOnlineServices, Verbose, TEXT("[%hs]: Found continuation id. PlatformUserId: %s, ContinuationId: %s."), __FUNCTION__, *ToLogString(Params.PlatformUserId), *ToLogString(UserData->LastLoginContinuationId));
return TOnlineResult<FAuthGetLinkAccountContinuationId>(FAuthGetLinkAccountContinuationId::Result{UserData->LastLoginContinuationId});
}
using FEpicAccountIdStrBuffer = TStaticArray<char, EOS_EPICACCOUNTID_MAX_LENGTH + 1>;
TFuture<TArray<FAccountId>> FAuthEOS::ResolveAccountIds(const FAccountId& LocalAccountId, const TArray<EOS_EpicAccountId>& InEpicAccountIds)
{
// Search for all the account id's
TArray<FAccountId> AccountIdHandles;
AccountIdHandles.Reserve(InEpicAccountIds.Num());
TArray<EOS_EpicAccountId> MissingEpicAccountIds;
MissingEpicAccountIds.Reserve(InEpicAccountIds.Num());
for (const EOS_EpicAccountId EpicAccountId : InEpicAccountIds)
{
if (!EOS_EpicAccountId_IsValid(EpicAccountId))
{
return MakeFulfilledPromise<TArray<FAccountId>>().GetFuture();
}
FAccountId Found = FindAccountId(EpicAccountId);
if (!Found.IsValid())
{
MissingEpicAccountIds.Emplace(EpicAccountId);
}
AccountIdHandles.Emplace(MoveTemp(Found));
}
if (MissingEpicAccountIds.IsEmpty())
{
// We have them all, so we can just return
return MakeFulfilledPromise<TArray<FAccountId>>(MoveTemp(AccountIdHandles)).GetFuture();
}
// If we failed to find all the handles, we need to query, which requires a valid LocalAccountId
// Note this is unavailable on Dedicated Servers as well, unlike EOS_Connect_QueryProductUserIdMappings
if (!FOnlineAccountIdRegistryEOS::ValidateOnlineId(LocalAccountId))
{
checkNoEntry();
return MakeFulfilledPromise<TArray<FAccountId>>().GetFuture();
}
TPromise<TArray<FAccountId>> Promise;
TFuture<TArray<FAccountId>> Future = Promise.GetFuture();
TArray<FEpicAccountIdStrBuffer> EpicAccountIdStrsToQuery;
EpicAccountIdStrsToQuery.Reserve(MissingEpicAccountIds.Num());
for (const EOS_EpicAccountId EpicAccountId : MissingEpicAccountIds)
{
FEpicAccountIdStrBuffer& EpicAccountIdStr = EpicAccountIdStrsToQuery.Emplace_GetRef();
int32_t BufferSize = sizeof(EpicAccountIdStr);
if (!EOS_EpicAccountId_IsValid(EpicAccountId) ||
EOS_EpicAccountId_ToString(EpicAccountId, EpicAccountIdStr.GetData(), &BufferSize) != EOS_EResult::EOS_Success)
{
checkNoEntry();
return MakeFulfilledPromise<TArray<FAccountId>>().GetFuture();
}
}
TArray<const char*> EpicAccountIdStrPtrs;
Algo::Transform(EpicAccountIdStrsToQuery, EpicAccountIdStrPtrs, [](const FEpicAccountIdStrBuffer& Str) { return &Str[0]; });
EOS_Connect_QueryExternalAccountMappingsOptions Options = {};
Options.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_CONNECT_QUERYEXTERNALACCOUNTMAPPINGS_API_LATEST, 1);
Options.LocalUserId = GetProductUserIdChecked(LocalAccountId);
Options.AccountIdType = EOS_EExternalAccountType::EOS_EAT_EPIC;
Options.ExternalAccountIds = (const char**)EpicAccountIdStrPtrs.GetData();
Options.ExternalAccountIdCount = EpicAccountIdStrPtrs.Num();
TSharedPtr<IEOSFastTickLock> EOSSDKFastTickLock = GetServices<FOnlineServicesEpicCommon>().GetEOSPlatformHandle()->GetFastTickLock();
EOS_Async(EOS_Connect_QueryExternalAccountMappings, ConnectHandle, Options,
[this, WeakThis = AsWeak(), InEpicAccountIds, Promise = MoveTemp(Promise), EOSSDKFastTickLock = MoveTemp(EOSSDKFastTickLock)](const EOS_Connect_QueryExternalAccountMappingsCallbackInfo* Data) mutable -> void
{
TArray<FAccountId> AccountIds;
if (const TSharedPtr<IAuth> StrongThis = WeakThis.Pin())
{
AccountIds.Reserve(InEpicAccountIds.Num());
if (Data->ResultCode == EOS_EResult::EOS_Success)
{
EOS_Connect_GetExternalAccountMappingsOptions Options = {};
Options.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_CONNECT_GETEXTERNALACCOUNTMAPPING_API_LATEST, 1);
Options.LocalUserId = Data->LocalUserId;
Options.AccountIdType = EOS_EExternalAccountType::EOS_EAT_EPIC;
for (const EOS_EpicAccountId EpicAccountId : InEpicAccountIds)
{
FAccountId AccountId = FindAccountId(EpicAccountId);
if (!AccountId.IsValid())
{
FEpicAccountIdStrBuffer EpicAccountIdStr;
int32_t BufferSize = sizeof(EpicAccountIdStr);
verify(EOS_EpicAccountId_ToString(EpicAccountId, EpicAccountIdStr.GetData(), &BufferSize) == EOS_EResult::EOS_Success);
Options.TargetExternalUserId = &EpicAccountIdStr[0];
const EOS_ProductUserId ProductUserId = EOS_Connect_GetExternalAccountMapping(ConnectHandle, &Options);
AccountId = CreateAccountId(EpicAccountId, ProductUserId);
}
AccountIds.Emplace(MoveTemp(AccountId));
}
}
else
{
UE_LOG(LogOnlineServices, Warning, TEXT("ResolveAccountId failed to query external mapping Result=[%s]"), *LexToString(Data->ResultCode));
}
}
Promise.SetValue(MoveTemp(AccountIds));
});
return Future;
}
TFuture<TArray<FAccountId>> FAuthEOS::ResolveAccountIds(const FAccountId& LocalAccountId, const TArray<EOS_ProductUserId>& InProductUserIds)
{
// Search for all the account id's
TArray<FAccountId> AccountIdHandles;
AccountIdHandles.Reserve(InProductUserIds.Num());
TArray<EOS_ProductUserId> MissingProductUserIds;
MissingProductUserIds.Reserve(InProductUserIds.Num());
for (const EOS_ProductUserId ProductUserId : InProductUserIds)
{
if (!EOS_ProductUserId_IsValid(ProductUserId))
{
return MakeFulfilledPromise<TArray<FAccountId>>().GetFuture();
}
FAccountId Found = FindAccountId(ProductUserId);
if (!Found.IsValid())
{
MissingProductUserIds.Emplace(ProductUserId);
}
AccountIdHandles.Emplace(MoveTemp(Found));
}
if (MissingProductUserIds.IsEmpty())
{
// We have them all, so we can just return
return MakeFulfilledPromise<TArray<FAccountId>>(MoveTemp(AccountIdHandles)).GetFuture();
}
// If we failed to find all the handles, we need to query, which requires a valid LocalAccountId
if (!(IsRunningDedicatedServer() || FOnlineAccountIdRegistryEOS::ValidateOnlineId(LocalAccountId)))
{
checkNoEntry();
return MakeFulfilledPromise<TArray<FAccountId>>().GetFuture();
}
TPromise<TArray<FAccountId>> Promise;
TFuture<TArray<FAccountId>> Future = Promise.GetFuture();
EOS_Connect_QueryProductUserIdMappingsOptions Options = {};
Options.ApiVersion = 2;
UE_EOS_CHECK_API_MISMATCH(EOS_CONNECT_QUERYPRODUCTUSERIDMAPPINGS_API_LATEST, 2);
Options.LocalUserId = IsRunningDedicatedServer() ? nullptr : GetProductUserIdChecked(LocalAccountId);
Options.ProductUserIds = MissingProductUserIds.GetData();
Options.ProductUserIdCount = MissingProductUserIds.Num();
TSharedPtr<IEOSFastTickLock> EOSSDKFastTickLock = GetServices<FOnlineServicesEpicCommon>().GetEOSPlatformHandle()->GetFastTickLock();
EOS_Async(EOS_Connect_QueryProductUserIdMappings, ConnectHandle, Options,
[this, WeakThis = AsWeak(), InProductUserIds, Promise = MoveTemp(Promise), EOSSDKFastTickLock = MoveTemp(EOSSDKFastTickLock)](const EOS_Connect_QueryProductUserIdMappingsCallbackInfo* Data) mutable -> void
{
TArray<FAccountId> AccountIds;
if (const TSharedPtr<IAuth> StrongThis = WeakThis.Pin())
{
if (Data->ResultCode == EOS_EResult::EOS_Success)
{
EOS_Connect_GetProductUserIdMappingOptions Options = {};
Options.ApiVersion = 1;
UE_EOS_CHECK_API_MISMATCH(EOS_CONNECT_GETPRODUCTUSERIDMAPPING_API_LATEST, 1);
Options.LocalUserId = Data->LocalUserId;
Options.AccountIdType = EOS_EExternalAccountType::EOS_EAT_EPIC;
for (const EOS_ProductUserId ProductUserId : InProductUserIds)
{
FAccountId AccountId = FindAccountId(ProductUserId);
if (!AccountId.IsValid())
{
Options.TargetProductUserId = ProductUserId;
FEpicAccountIdStrBuffer EpicAccountIdStr;
int32_t BufferLength = sizeof(EpicAccountIdStr);
EOS_EpicAccountId EpicAccountId = nullptr;
const EOS_EResult Result = EOS_Connect_GetProductUserIdMapping(ConnectHandle, &Options, EpicAccountIdStr.GetData(), &BufferLength);
if (Result == EOS_EResult::EOS_Success)
{
EpicAccountId = EOS_EpicAccountId_FromString(EpicAccountIdStr.GetData());
check(EOS_EpicAccountId_IsValid(EpicAccountId));
}
AccountId = CreateAccountId(EpicAccountId, ProductUserId);
}
AccountIds.Emplace(MoveTemp(AccountId));
}
}
else
{
UE_LOG(LogOnlineServices, Warning, TEXT("ResolveAccountId failed to query external mapping Result=[%s]"), *LexToString(Data->ResultCode));
}
}
Promise.SetValue(MoveTemp(AccountIds));
});
return Future;
}
FAccountId FAuthEOS::CreateAccountId(const EOS_EpicAccountId EpicAccountId, const EOS_ProductUserId ProductUserId)
{
return FOnlineAccountIdRegistryEOS::Get().FindOrAddAccountId(EpicAccountId, ProductUserId);
}
FAccountId FAuthEOS::FindAccountId(const EOS_ProductUserId ProductUserId)
{
return UE::Online::FindAccountId(Services.GetServicesProvider(), ProductUserId);
}
FAccountId FAuthEOS::FindAccountId(const EOS_EpicAccountId EpicAccountId)
{
return UE::Online::FindAccountId(Services.GetServicesProvider(), EpicAccountId);
}
/* UE::Online */ }