Skip to content

[ID-3582][ID-3592][DO NOT MERGE] chore: sunet device code auth #203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,7 @@
#include "Immutable/Misc/ImtblLogging.h"


UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::Login(UObject* WorldContextObject, bool UseCachedSession)
{
UImtblConnectionAsyncActions* PassportInitBlueprintNode = NewObject<UImtblConnectionAsyncActions>();

PassportInitBlueprintNode->WorldContextObject = WorldContextObject;
PassportInitBlueprintNode->bUseCachedSession = UseCachedSession;
PassportInitBlueprintNode->bIsConnectImx = false;

return PassportInitBlueprintNode;
}

UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::ConnectImx(UObject* WorldContextObject, bool UseCachedSession)
{
UImtblConnectionAsyncActions* PassportInitBlueprintNode = NewObject<UImtblConnectionAsyncActions>();

PassportInitBlueprintNode->WorldContextObject = WorldContextObject;
PassportInitBlueprintNode->bUseCachedSession = UseCachedSession;
PassportInitBlueprintNode->bIsConnectImx = true;

return PassportInitBlueprintNode;
}

UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::LoginPKCE(UObject* WorldContextObject)
UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::Login(UObject* WorldContextObject)
{
UImtblConnectionAsyncActions* PassportInitBlueprintNode = NewObject<UImtblConnectionAsyncActions>();

Expand All @@ -40,7 +18,7 @@ UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::LoginPKCE(UObject* W
return PassportInitBlueprintNode;
}

UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::ConnectImxPKCE(UObject* WorldContextObject)
UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::ConnectImx(UObject* WorldContextObject)
{
UImtblConnectionAsyncActions* PassportInitBlueprintNode = NewObject<UImtblConnectionAsyncActions>();

Expand Down Expand Up @@ -74,13 +52,9 @@ void UImtblConnectionAsyncActions::DoConnect(TWeakObjectPtr<UImtblJSConnector> J
if (bIsPKCE)
{
#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC | PLATFORM_WINDOWS
Passport->ConnectPKCE(bIsConnectImx, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UImtblConnectionAsyncActions::OnConnect));
Passport->Connect(bIsConnectImx, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UImtblConnectionAsyncActions::OnConnect));
#endif
}
else
{
Passport->Connect(bIsConnectImx, bUseCachedSession, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UImtblConnectionAsyncActions::OnConnect));
}
}
else
{
Expand Down
11 changes: 0 additions & 11 deletions Source/Immutable/Private/Immutable/ImmutableDataTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,7 @@ FString FImmutablePassportInitData::ToJsonString() const
return OutString;
}

TOptional<FImmutablePassportInitDeviceFlowData> FImmutablePassportInitDeviceFlowData::FromJsonString(const FString& JsonObjectString)
{
FImmutablePassportInitDeviceFlowData PassportConnect;

if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonObjectString, &PassportConnect, 0, 0))
{
IMTBL_WARN("Could not parse response from JavaScript into the expected " "Passport connect format")
return TOptional<FImmutablePassportInitDeviceFlowData>();
}

return PassportConnect;
}

FString FImmutablePassportZkEvmRequestAccountsData::ToJsonString() const
{
Expand Down
156 changes: 17 additions & 139 deletions Source/Immutable/Private/Immutable/ImmutablePassport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,8 @@ void UImmutablePassport::Initialize(const FImtblPassportResponseDelegate& Respon
CallJS(ImmutablePassportAction::INIT, InitData.ToJsonString(), ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnInitializeResponse), false);
}

void UImmutablePassport::Connect(bool IsConnectImx, bool TryToRelogin, const FImtblPassportResponseDelegate& ResponseDelegate)
{
SetStateFlags(IPS_CONNECTING);
if (IsConnectImx)
{
SetStateFlags(IPS_IMX);
}
if (TryToRelogin)
{
CallJS(IsConnectImx ? ImmutablePassportAction::RECONNECT : ImmutablePassportAction::RELOGIN, TEXT(""), ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::ReinstateConnection));
}
else
{
Analytics->Track(IsConnectImx ? UImmutableAnalytics::EEventName::START_CONNECT_IMX : UImmutableAnalytics::EEventName::START_LOGIN);
CallJS(ImmutablePassportAction::INIT_DEVICE_FLOW, TEXT(""), ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnInitDeviceFlowResponse));
}
}

#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC | PLATFORM_WINDOWS
void UImmutablePassport::ConnectPKCE(bool IsConnectImx, const FImtblPassportResponseDelegate& ResponseDelegate)
void UImmutablePassport::Connect(bool IsConnectImx, const FImtblPassportResponseDelegate& ResponseDelegate)
{
SetStateFlags(IPS_CONNECTING | IPS_PKCE);

Expand All @@ -126,7 +108,7 @@ void UImmutablePassport::ConnectPKCE(bool IsConnectImx, const FImtblPassportResp
}
PKCEResponseDelegate = ResponseDelegate;
Analytics->Track(IsConnectImx ? UImmutableAnalytics::EEventName::START_CONNECT_IMX_PKCE : UImmutableAnalytics::EEventName::START_LOGIN_PKCE);
CallJS(ImmutablePassportAction::GetPKCEAuthUrl, TEXT(""), PKCEResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnGetPKCEAuthUrlResponse));
CallJS(ImmutablePassportAction::GetPKCEAuthUrl, TEXT(""), PKCEResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnGetAuthUrlResponse));
}
#endif

Expand Down Expand Up @@ -198,13 +180,7 @@ void UImmutablePassport::ZkEvmSignTypedDataV4(const FString& RequestJsonString,
CallJS(ImmutablePassportAction::ZkEvmSignTypedDataV4, RequestJsonString, ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnBridgeCallbackResponse));
}

void UImmutablePassport::ConfirmCode(const FString& DeviceCode, const float Interval, const FImtblPassportResponseDelegate& ResponseDelegate)
{
FImmutablePassportCodeConfirmRequestData Data{DeviceCode, Interval};
FString Action = IsStateFlagsSet(IPS_IMX) ? ImmutablePassportAction::CONNECT_CONFIRM_CODE : ImmutablePassportAction::LOGIN_CONFIRM_CODE;

CallJS(Action, UStructToJsonString(Data), ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConfirmCodeResponse));
}

void UImmutablePassport::GetIdToken(const FImtblPassportResponseDelegate& ResponseDelegate)
{
Expand Down Expand Up @@ -347,40 +323,7 @@ void UImmutablePassport::Setup(const TWeakObjectPtr<UImtblJSConnector> Connector
Analytics->Setup(Connector);
}

void UImmutablePassport::ReinstateConnection(FImtblJSResponse Response)
{
ResetStateFlags(IPS_CONNECTING);

if (auto ResponseDelegate = GetResponseDelegate(Response))
{
// currently, this response has to be called only for RELOGIN AND RECONNECT bridge routines
bool IsRelogin = Response.responseFor.Compare(ImmutablePassportAction::RELOGIN, ESearchCase::IgnoreCase) == 0;
const FString CallbackName = IsRelogin ? "Relogin" : "Reconnect";
UImmutableAnalytics::EEventName EventName = IsRelogin ? UImmutableAnalytics::EEventName::COMPLETE_RELOGIN : UImmutableAnalytics::EEventName::COMPLETE_RECONNECT;

if (Response.success)
{
SetStateFlags(IPS_CONNECTED);
ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{true, "", Response});
Analytics->Track(EventName, true);
}
else
{
Analytics->Track(EventName, false);
#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC
if (IsStateFlagsSet(IPS_PKCE))
{
PKCEResponseDelegate = ResponseDelegate.GetValue();
CallJS(ImmutablePassportAction::GetPKCEAuthUrl, TEXT(""), PKCEResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnGetPKCEAuthUrlResponse));
}
else
#endif
{
CallJS(ImmutablePassportAction::INIT_DEVICE_FLOW, TEXT(""), ResponseDelegate.GetValue(), FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnInitDeviceFlowResponse));
}
}
}
}

bool UImmutablePassport::CheckIsInitialized(const FString& Action, const FImtblPassportResponseDelegate& ResponseDelegate) const
{
Expand Down Expand Up @@ -436,36 +379,7 @@ void UImmutablePassport::OnInitializeResponse(FImtblJSResponse Response)
}
}

void UImmutablePassport::OnInitDeviceFlowResponse(FImtblJSResponse Response)
{
if (auto ResponseDelegate = GetResponseDelegate(Response))
{
const auto InitDeviceFlowData = JsonObjectToUStruct<FImmutablePassportInitDeviceFlowData>(Response.JsonObject);

if (!Response.success || !InitDeviceFlowData || !InitDeviceFlowData->code.Len())
{
FString Msg;

IMTBL_WARN("Login device flow initialization attempt failed.");
Response.Error.IsSet() ? Msg = Response.Error->ToString() : Msg = Response.JsonObject->GetStringField(TEXT("error"));
ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{false, Msg, Response});

return;
}
FString Err;

FPlatformProcess::LaunchURL(*InitDeviceFlowData->url, nullptr, &Err);
if (Err.Len())
{
FString Msg = "Failed to connect to Browser: " + Err;

IMTBL_ERR("%s", *Msg);
ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{false, Msg, Response});
return;
}
ConfirmCode(InitDeviceFlowData->deviceCode, InitDeviceFlowData->interval, ResponseDelegate.GetValue());
}
}

void UImmutablePassport::OnLogoutResponse(FImtblJSResponse Response)
{
Expand Down Expand Up @@ -521,30 +435,15 @@ void UImmutablePassport::OnLogoutResponse(FImtblJSResponse Response)

if (!Url.IsEmpty())
{
#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC | PLATFORM_WINDOWS
if (IsStateFlagsSet(IPS_PKCE))
{
OnHandleDeepLink.AddUObject(this, &UImmutablePassport::OnDeepLinkActivated);
OnHandleDeepLink.AddUObject(this, &UImmutablePassport::OnDeepLinkActivated);
#if PLATFORM_ANDROID
LaunchAndroidUrl(Url);
LaunchAndroidUrl(Url);
#elif PLATFORM_IOS
[[ImmutableIOS instance] launchUrl:TCHAR_TO_ANSI(*Url)];
[[ImmutableIOS instance] launchUrl:TCHAR_TO_ANSI(*Url)];
#elif PLATFORM_MAC
[[ImmutableMac instance] launchUrl:TCHAR_TO_ANSI(*Url) forRedirectUri:TCHAR_TO_ANSI(*InitData.logoutRedirectUri)];
#endif
#if PLATFORM_WINDOWS
Logout(Response);
#endif
}
else
{
#endif
Logout(Response);
Analytics->Track(UImmutableAnalytics::EEventName::COMPLETE_LOGOUT);
IMTBL_LOG("Logged out")
ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{ Response.success });
#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC | PLATFORM_WINDOWS
}
[[ImmutableMac instance] launchUrl:TCHAR_TO_ANSI(*Url) forRedirectUri:TCHAR_TO_ANSI(*InitData.logoutRedirectUri)];
#elif PLATFORM_WINDOWS
Logout(Response);
#endif
}
else
Expand All @@ -555,7 +454,7 @@ void UImmutablePassport::OnLogoutResponse(FImtblJSResponse Response)
}

#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC | PLATFORM_WINDOWS
void UImmutablePassport::OnGetPKCEAuthUrlResponse(FImtblJSResponse Response)
void UImmutablePassport::OnGetAuthUrlResponse(FImtblJSResponse Response)
{
if (PKCEResponseDelegate.IsBound())
{
Expand All @@ -573,7 +472,7 @@ void UImmutablePassport::OnGetPKCEAuthUrlResponse(FImtblJSResponse Response)

Msg = Response.JsonObject->GetStringField(TEXT("result")).Replace(TEXT(" "), TEXT("+"));
#if PLATFORM_ANDROID
OnPKCEDismissed = FImtblPassportOnPKCEDismissedDelegate::CreateUObject(this, &UImmutablePassport::HandleOnLoginPKCEDismissed);
OnPKCEDismissed = FImtblPassportOnPKCEDismissedDelegate::CreateUObject(this, &UImmutablePassport::HandleOnLoginDismissed);
LaunchAndroidUrl(Msg);
#elif PLATFORM_IOS
[[ImmutableIOS instance] launchUrl:TCHAR_TO_ANSI(*Msg)];
Expand All @@ -599,7 +498,7 @@ void UImmutablePassport::OnGetPKCEAuthUrlResponse(FImtblJSResponse Response)
}
}

void UImmutablePassport::OnConnectPKCEResponse(FImtblJSResponse Response)
void UImmutablePassport::OnConnectResponse(FImtblJSResponse Response)
{
if (PKCEResponseDelegate.IsBound())
{
Expand Down Expand Up @@ -632,29 +531,7 @@ void UImmutablePassport::OnConnectPKCEResponse(FImtblJSResponse Response)
ResetStateFlags(IPS_COMPLETING_PKCE);
}

void UImmutablePassport::OnConfirmCodeResponse(FImtblJSResponse Response)
{
if (auto ResponseDelegate = GetResponseDelegate(Response))
{
FString Msg;
FString TypeOfConnection = IsStateFlagsSet(IPS_IMX) ? TEXT("connect") : TEXT("login");
UImmutableAnalytics::EEventName EventName = IsStateFlagsSet(IPS_IMX) ? UImmutableAnalytics::EEventName::COMPLETE_CONNECT_IMX : UImmutableAnalytics::EEventName::COMPLETE_LOGIN;

ResetStateFlags(IPS_CONNECTING);
if (Response.success)
{
IMTBL_LOG("Code confirmed for %s operation.", *TypeOfConnection)
SetStateFlags(IPS_CONNECTED);
}
else
{
IMTBL_LOG("%s code not confirmed.", *TypeOfConnection)
Response.Error.IsSet() ? Msg = Response.Error->ToString() : Msg = Response.JsonObject->GetStringField(TEXT("error"));
}
Analytics->Track(EventName, Response.success);
ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{Response.success, Msg, Response});
}
}

void UImmutablePassport::OnBridgeCallbackResponse(FImtblJSResponse Response)
{
Expand Down Expand Up @@ -724,6 +601,7 @@ void UImmutablePassport::OnDeepLinkActivated(const FString& DeepLink)
FGraphEventRef GameThreadTask = FFunctionGraphTask::CreateAndDispatchWhenReady([this]()
{
Analytics->Track(UImmutableAnalytics::EEventName::COMPLETE_LOGOUT_PKCE);
IMTBL_LOG("Complete Logout PKCE")
PKCELogoutResponseDelegate.ExecuteIfBound(FImmutablePassportResult{true, "Logged out"});
PKCELogoutResponseDelegate = nullptr;
ResetStateFlags(IPS_CONNECTED | IPS_PKCE | IPS_IMX);
Expand All @@ -733,13 +611,13 @@ void UImmutablePassport::OnDeepLinkActivated(const FString& DeepLink)
}
else if (DeepLink.StartsWith(InitData.redirectUri))
{
CompleteLoginPKCEFlow(DeepLink);
CompleteLoginFlow(DeepLink);
}

PKCEData = nullptr;
}

void UImmutablePassport::CompleteLoginPKCEFlow(FString Url)
void UImmutablePassport::CompleteLoginFlow(FString Url)
{
// Required mainly for Android to detect when Chrome Custom tabs is dismissed
// See HandleOnLoginPKCEDismissed
Expand Down Expand Up @@ -780,10 +658,10 @@ void UImmutablePassport::CompleteLoginPKCEFlow(FString Url)
}
else
{
FImmutablePassportConnectPKCEData Data = FImmutablePassportConnectPKCEData{Code.GetValue(), State.GetValue()};
FImmutablePassportConnectData Data = FImmutablePassportConnectData{Code.GetValue(), State.GetValue()};

CallJS(IsStateFlagsSet(IPS_IMX) ? ImmutablePassportAction::CONNECT_PKCE : ImmutablePassportAction::LOGIN_PKCE, UStructToJsonString(Data), PKCEResponseDelegate,
FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConnectPKCEResponse));
FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConnectResponse));
}
}
#endif
Expand Down Expand Up @@ -812,7 +690,7 @@ void UImmutablePassport::HandleDeepLink(NSString* sDeepLink) const
}

#if PLATFORM_ANDROID
void UImmutablePassport::HandleOnLoginPKCEDismissed()
void UImmutablePassport::HandleOnLoginDismissed()
{
IMTBL_LOG("Handle On Login PKCE Dismissed");
OnPKCEDismissed = nullptr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,52 +19,29 @@ class IMMUTABLE_API UImtblConnectionAsyncActions : public UImtblBlueprintAsyncAc

public:
/**
* Log into Passport using Device Code Authorisation.
* Log into Passport
*
* @param WorldContextObject World context
* @param UseCachedSession Whether to use stored credentials for relogin
*
* @return A reference to the object represented by this node
*/
UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable")
static UImtblConnectionAsyncActions* Login(UObject* WorldContextObject, bool UseCachedSession = false);
static UImtblConnectionAsyncActions* Login(UObject* WorldContextObject);

/**
* Log into Passport using Device Code Authorisation, initialise the gamer's wallet and instantiate the IMX provider.
* Log into Passport, initialise the gamer's wallet and instantiate the IMX provider.
*
* @param WorldContextObject World context
* @param UseCachedSession Whether to use stored credentials for relogin
*
* @return A reference to the object represented by this node
*/
UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable")
static UImtblConnectionAsyncActions* ConnectImx(UObject* WorldContextObject, bool UseCachedSession = false);

/**
* Log into Passport using PKCE
*
* @param WorldContextObject World context
*
* @return A reference to the object represented by this node
*/
UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable")
static UImtblConnectionAsyncActions* LoginPKCE(UObject* WorldContextObject);

/**
* Log into Passport using PKCE, initialise the gamer's wallet and instantiate the IMX provider.
*
* @param WorldContextObject World context
*
* @return A reference to the object represented by this node
*/
UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable")
static UImtblConnectionAsyncActions* ConnectImxPKCE(UObject* WorldContextObject);
static UImtblConnectionAsyncActions* ConnectImx(UObject* WorldContextObject);


virtual void Activate() override;

private:
FImmutablePassportInitDeviceFlowData InitDeviceFlowData;

void DoConnect(TWeakObjectPtr<class UImtblJSConnector> JSConnector);
void OnConnect(FImmutablePassportResult Result);
Expand Down
Loading
Loading