From bd25379713bc36bdff385fb2cf4cae3d008c8ba3 Mon Sep 17 00:00:00 2001
From: adrienne-deriv <103016120+adrienne-deriv@users.noreply.github.com>
Date: Fri, 7 Mar 2025 16:33:17 +0800
Subject: [PATCH] Adrienne / Fix SSO triggering multiple times due to useeffect
dependencies (#18114)
* feat: store redirect metadata for traders hub from os-redirect
* Merge branch 'master' of github.com:deriv-com/deriv-app
* chore: refactored sso and slo conditions
* chore: remove debouncing in sso and slo handler
* chore: resolve test cases
---
packages/core/src/App/AppContent.tsx | 1 -
.../RootComponent/root-component.jsx | 12 ++-
...ec.ts => useSilentLoginAndLogout.spec.tsx} | 78 ++++++++++++-------
packages/hooks/src/useSilentLoginAndLogout.ts | 37 ++++-----
4 files changed, 73 insertions(+), 55 deletions(-)
rename packages/hooks/src/__tests__/{useSilentLoginAndLogout.spec.ts => useSilentLoginAndLogout.spec.tsx} (65%)
diff --git a/packages/core/src/App/AppContent.tsx b/packages/core/src/App/AppContent.tsx
index 85d5ebcb9683..d876b23cf2b4 100644
--- a/packages/core/src/App/AppContent.tsx
+++ b/packages/core/src/App/AppContent.tsx
@@ -73,7 +73,6 @@ const AppContent: React.FC<{ passthrough: unknown }> = observer(({ passthrough }
is_client_store_initialized,
isOAuth2Enabled,
oAuthLogout,
- prevent_single_login,
});
const [isWebPasskeysFFEnabled, isGBLoaded] = useGrowthbookIsOn({
diff --git a/packages/core/src/App/Containers/RootComponent/root-component.jsx b/packages/core/src/App/Containers/RootComponent/root-component.jsx
index b16740822c5d..784b911c0a02 100644
--- a/packages/core/src/App/Containers/RootComponent/root-component.jsx
+++ b/packages/core/src/App/Containers/RootComponent/root-component.jsx
@@ -25,7 +25,14 @@ const RootComponent = observer(props => {
setIsWalletsOnboardingTourGuideVisible,
notification_messages_ui,
} = ui;
- const { has_wallet, logout, prevent_redirect_to_hub, is_client_store_initialized, setPreventSingleLogin } = client;
+ const {
+ has_wallet,
+ logout,
+ prevent_redirect_to_hub,
+ is_client_store_initialized,
+ prevent_single_login,
+ setPreventSingleLogin,
+ } = client;
const { oAuthLogout } = useOauth2({ handleLogout: logout });
@@ -56,7 +63,7 @@ const RootComponent = observer(props => {
}
const shouldStayInDerivApp = !isHubRedirectionEnabled || !has_wallet || prevent_redirect_to_hub;
- if (isHubRedirectionLoaded && is_client_store_initialized && shouldStayInDerivApp) {
+ if (prevent_single_login && isHubRedirectionLoaded && is_client_store_initialized && shouldStayInDerivApp) {
setPreventSingleLogin(false);
}
}, [
@@ -64,6 +71,7 @@ const RootComponent = observer(props => {
isHubRedirectionEnabled,
has_wallet,
prevent_redirect_to_hub,
+ prevent_single_login,
is_client_store_initialized,
]);
diff --git a/packages/hooks/src/__tests__/useSilentLoginAndLogout.spec.ts b/packages/hooks/src/__tests__/useSilentLoginAndLogout.spec.tsx
similarity index 65%
rename from packages/hooks/src/__tests__/useSilentLoginAndLogout.spec.ts
rename to packages/hooks/src/__tests__/useSilentLoginAndLogout.spec.tsx
index 8c39bc695d85..cae3f7924e0f 100644
--- a/packages/hooks/src/__tests__/useSilentLoginAndLogout.spec.ts
+++ b/packages/hooks/src/__tests__/useSilentLoginAndLogout.spec.tsx
@@ -1,9 +1,11 @@
+import React from 'react';
import Cookies from 'js-cookie';
import { requestOidcAuthentication } from '@deriv-com/auth-client';
import { renderHook } from '@testing-library/react-hooks';
import useSilentLoginAndLogout from '../useSilentLoginAndLogout';
+import { mockStore, StoreProvider } from '@deriv/stores';
jest.mock('js-cookie', () => ({
get: jest.fn(),
@@ -15,6 +17,12 @@ jest.mock('@deriv-com/auth-client', () => ({
describe('useSilentLoginAndLogout', () => {
const mockOAuthLogout = jest.fn();
+ const mockStoreData = mockStore({
+ client: { prevent_single_login: false },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+ {children}
+ );
beforeEach(() => {
jest.clearAllMocks();
@@ -42,12 +50,14 @@ describe('useSilentLoginAndLogout', () => {
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(JSON.stringify({}));
- renderHook(() =>
- useSilentLoginAndLogout({
- is_client_store_initialized: true,
- isOAuth2Enabled: true,
- oAuthLogout: mockOAuthLogout,
- })
+ renderHook(
+ () =>
+ useSilentLoginAndLogout({
+ is_client_store_initialized: true,
+ isOAuth2Enabled: true,
+ oAuthLogout: mockOAuthLogout,
+ }),
+ { wrapper }
);
expect(requestOidcAuthentication).toHaveBeenCalledWith({
@@ -61,12 +71,14 @@ describe('useSilentLoginAndLogout', () => {
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(JSON.stringify({}));
- renderHook(() =>
- useSilentLoginAndLogout({
- is_client_store_initialized: true,
- isOAuth2Enabled: true,
- oAuthLogout: mockOAuthLogout,
- })
+ renderHook(
+ () =>
+ useSilentLoginAndLogout({
+ is_client_store_initialized: true,
+ isOAuth2Enabled: true,
+ oAuthLogout: mockOAuthLogout,
+ }),
+ { wrapper }
);
expect(requestOidcAuthentication).not.toHaveBeenCalled();
@@ -83,12 +95,14 @@ describe('useSilentLoginAndLogout', () => {
value: { pathname: '/callback' },
});
- renderHook(() =>
- useSilentLoginAndLogout({
- is_client_store_initialized: true,
- isOAuth2Enabled: true,
- oAuthLogout: mockOAuthLogout,
- })
+ renderHook(
+ () =>
+ useSilentLoginAndLogout({
+ is_client_store_initialized: true,
+ isOAuth2Enabled: true,
+ oAuthLogout: mockOAuthLogout,
+ }),
+ { wrapper }
);
expect(requestOidcAuthentication).not.toHaveBeenCalled();
@@ -100,12 +114,14 @@ describe('useSilentLoginAndLogout', () => {
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(JSON.stringify({ account1: {}, account2: {} }));
- renderHook(() =>
- useSilentLoginAndLogout({
- is_client_store_initialized: true,
- isOAuth2Enabled: true,
- oAuthLogout: mockOAuthLogout,
- })
+ renderHook(
+ () =>
+ useSilentLoginAndLogout({
+ is_client_store_initialized: true,
+ isOAuth2Enabled: true,
+ oAuthLogout: mockOAuthLogout,
+ }),
+ { wrapper }
);
expect(requestOidcAuthentication).not.toHaveBeenCalled();
@@ -117,12 +133,14 @@ describe('useSilentLoginAndLogout', () => {
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(JSON.stringify({ account1: {}, account2: {} }));
- renderHook(() =>
- useSilentLoginAndLogout({
- is_client_store_initialized: true,
- isOAuth2Enabled: true,
- oAuthLogout: mockOAuthLogout,
- })
+ renderHook(
+ () =>
+ useSilentLoginAndLogout({
+ is_client_store_initialized: true,
+ isOAuth2Enabled: true,
+ oAuthLogout: mockOAuthLogout,
+ }),
+ { wrapper }
);
expect(requestOidcAuthentication).not.toHaveBeenCalled();
diff --git a/packages/hooks/src/useSilentLoginAndLogout.ts b/packages/hooks/src/useSilentLoginAndLogout.ts
index 591887f2f321..bd99776f29c9 100644
--- a/packages/hooks/src/useSilentLoginAndLogout.ts
+++ b/packages/hooks/src/useSilentLoginAndLogout.ts
@@ -1,8 +1,8 @@
-import { useEffect } from 'react';
+import { useEffect, useRef } from 'react';
import Cookies from 'js-cookie';
import { requestOidcAuthentication } from '@deriv-com/auth-client';
-
+import { useStore } from '@deriv/stores';
/**
* Handles silent login and single logout logic for OAuth2.
*
@@ -16,22 +16,26 @@ const useSilentLoginAndLogout = ({
is_client_store_initialized,
isOAuth2Enabled,
oAuthLogout,
- prevent_single_login,
}: {
is_client_store_initialized: boolean;
isOAuth2Enabled: boolean;
oAuthLogout: () => Promise;
- prevent_single_login?: boolean;
}) => {
const loggedState = Cookies.get('logged_state');
+ const { client } = useStore();
const clientAccounts = JSON.parse(localStorage.getItem('client.accounts') || '{}');
const isClientAccountsPopulated = Object.keys(clientAccounts).length > 0;
const isSilentLoginExcluded =
window.location.pathname.includes('callback') || window.location.pathname.includes('endpoint');
+ // state to manage and ensure OIDC callback functions are invoked once only
+ const isAuthenticating = useRef(false);
+ const isLoggingOut = useRef(false);
+ const { prevent_single_login } = client;
+
useEffect(() => {
- if (prevent_single_login) return;
+ if (prevent_single_login || !isOAuth2Enabled || !is_client_store_initialized || isSilentLoginExcluded) return;
// NOTE: Remove this logic once social signup is intergated with OIDC
const params = new URLSearchParams(window.location.search);
const isUsingLegacyFlow = params.has('token1') && params.has('acct1');
@@ -39,29 +43,19 @@ const useSilentLoginAndLogout = ({
return;
}
- if (
- !isUsingLegacyFlow &&
- loggedState === 'true' &&
- !isClientAccountsPopulated &&
- isOAuth2Enabled &&
- is_client_store_initialized &&
- !isSilentLoginExcluded
- ) {
+ if (!isUsingLegacyFlow && loggedState === 'true' && !isClientAccountsPopulated) {
// Perform silent login
+ if (isAuthenticating.current) return;
+ isAuthenticating.current = true;
requestOidcAuthentication({
redirectCallbackUri: `${window.location.origin}/callback`,
});
}
- if (
- !isUsingLegacyFlow &&
- loggedState === 'false' &&
- is_client_store_initialized &&
- isOAuth2Enabled &&
- isClientAccountsPopulated &&
- !window.location.pathname.includes('callback')
- ) {
+ if (!isUsingLegacyFlow && loggedState === 'false' && isClientAccountsPopulated) {
// Perform single logout
+ if (isLoggingOut.current) return;
+ isLoggingOut.current = true;
oAuthLogout();
}
}, [
@@ -69,7 +63,6 @@ const useSilentLoginAndLogout = ({
isClientAccountsPopulated,
is_client_store_initialized,
isOAuth2Enabled,
- oAuthLogout,
isSilentLoginExcluded,
prevent_single_login,
]);