From db952262e7296e29e2f6923c6abdfd263900939d Mon Sep 17 00:00:00 2001 From: Adrienne Rio Date: Fri, 7 Mar 2025 15:09:19 +0800 Subject: [PATCH 1/3] chore: remove debouncing in sso and slo handler --- packages/core/src/App/AppContent.tsx | 1 - .../RootComponent/root-component.jsx | 12 +++++- packages/hooks/src/useSilentLoginAndLogout.ts | 37 ++++++++----------- 3 files changed, 25 insertions(+), 25 deletions(-) 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/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, ]); From 90a24b4506712fde6ba8d048f45fcd6bc6f3a54e Mon Sep 17 00:00:00 2001 From: Adrienne Rio Date: Fri, 7 Mar 2025 15:14:39 +0800 Subject: [PATCH 2/3] From a2b15ccb40c50cb0d8a6a436121f1ff63636ae23 Mon Sep 17 00:00:00 2001 From: Adrienne Rio Date: Fri, 7 Mar 2025 15:20:49 +0800 Subject: [PATCH 3/3] chore: resolve test cases --- ...ec.ts => useSilentLoginAndLogout.spec.tsx} | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) rename packages/hooks/src/__tests__/{useSilentLoginAndLogout.spec.ts => useSilentLoginAndLogout.spec.tsx} (65%) 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();