Skip to content

Commit

Permalink
chore: oidc setup login/logout (#17746)
Browse files Browse the repository at this point in the history
* chore: install latest aiuth-client

* chore: fix tests

* chore: add oidc setup and silent login/logout

* chore: fix test

* chore: empty commit

* chore: change text and localize it

* chore: address comments and add wallet logout and login

* chore: conflicts

---------

Co-authored-by: Thisyahlen <[email protected]>
  • Loading branch information
thisyahlen-deriv and Thisyahlen authored Dec 16, 2024
1 parent b76a957 commit b0473ca
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 87 deletions.
71 changes: 56 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import React from 'react';

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import TradingHubLogout from '../tradinghub-logout';

jest.mock('@deriv/hooks', () => ({
...jest.requireActual('@deriv/hooks'),
useOauth2: jest.fn(({ handleLogout }) => ({
isOAuth2Enabled: true,
oAuthLogout: jest.fn(() => handleLogout && handleLogout()),
})),
}));
describe('TradingHubLogout', () => {
const mock_props: React.ComponentProps<typeof TradingHubLogout> = {
handleOnLogout: jest.fn(),
Expand All @@ -13,10 +22,10 @@ describe('TradingHubLogout', () => {
expect(screen.getByText('Log out')).toBeInTheDocument();
});

it('should invoke handleOnLogout when logout tab is clicked', () => {
it('should invoke handleOnLogout when logout tab is clicked', async () => {
render(<TradingHubLogout {...mock_props} />);
const el_tab = screen.getByTestId('dt_logout_tab');
userEvent.click(el_tab);
expect(mock_props.handleOnLogout).toBeCalledTimes(1);
await userEvent.click(el_tab);
expect(mock_props.handleOnLogout).toHaveBeenCalledTimes(1);
});
});
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"@deriv-com/quill-tokens": "2.0.4",
"@deriv-com/quill-ui": "1.24.2",
"@deriv-com/translations": "1.3.9",
"@deriv-com/auth-client": "1.3.3",
"@deriv-com/ui": "1.36.4",
"@deriv-com/utils": "^0.0.40",
"@deriv/account": "^1.0.0",
Expand Down
42 changes: 33 additions & 9 deletions packages/core/src/App/AppContent.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import React from 'react';

import { useRemoteConfig } from '@deriv/api';
import { useDevice } from '@deriv-com/ui';
import {
useGrowthbookGetFeatureValue,
useGrowthbookIsOn,
useLiveChat,
useOauth2,
useSilentLoginAndLogout,
} from '@deriv/hooks';
import { observer, useStore } from '@deriv/stores';
import { ThemeProvider } from '@deriv-com/quill-ui';
import { useTranslations } from '@deriv-com/translations';
import { useDevice } from '@deriv-com/ui';
import { browserSupportsWebAuthn } from '@simplewebauthn/browser';

import P2PIFrame from 'Modules/P2PIFrame';
import SmartTraderIFrame from 'Modules/SmartTraderIFrame';

import initDatadog from '../Utils/Datadog';
import initHotjar from '../Utils/Hotjar';

import ErrorBoundary from './Components/Elements/Errors/error-boundary.jsx';
import LandscapeBlocker from './Components/Elements/LandscapeBlocker';
import AppToastMessages from './Containers/app-toast-messages.jsx';
import AppContents from './Containers/Layout/app-contents.jsx';
import Footer from './Containers/Layout/footer.jsx';
import Header from './Containers/Layout/header';
import AppModals from './Containers/Modals';
import Routes from './Containers/Routes/routes.jsx';
import Devtools from './Devtools';
import LandscapeBlocker from './Components/Elements/LandscapeBlocker';
import initDatadog from '../Utils/Datadog';
import { ThemeProvider } from '@deriv-com/quill-ui';
import { useGrowthbookGetFeatureValue, useGrowthbookIsOn, useLiveChat, useOauth2 } from '@deriv/hooks';
import { useTranslations } from '@deriv-com/translations';
import initHotjar from '../Utils/Hotjar';

const AppContent: React.FC<{ passthrough: unknown }> = observer(({ passthrough }) => {
const store = useStore();
Expand All @@ -30,6 +40,7 @@ const AppContent: React.FC<{ passthrough: unknown }> = observer(({ passthrough }
landing_company_shortcode,
currency,
residence,
logout,
email,
setIsPasskeySupported,
account_settings,
Expand All @@ -43,7 +54,18 @@ const AppContent: React.FC<{ passthrough: unknown }> = observer(({ passthrough }
const { isMobile } = useDevice();
const { switchLanguage } = useTranslations();

const { isOAuth2Enabled } = useOauth2();
const { isOAuth2Enabled, oAuthLogout } = useOauth2({
handleLogout: async () => {
await logout();
},
});

useSilentLoginAndLogout({
is_client_store_initialized,
isOAuth2Enabled,
oAuthLogout,
});

const [isWebPasskeysFFEnabled, isGBLoaded] = useGrowthbookIsOn({
featureFlag: 'web_passkeys',
});
Expand Down Expand Up @@ -122,10 +144,12 @@ const AppContent: React.FC<{ passthrough: unknown }> = observer(({ passthrough }
}
}, [has_wallet, current_language, changeSelectedLanguage, is_dark_mode_on, setDarkMode]);

const isCallBackPage = window.location.pathname.includes('callback');

return (
<ThemeProvider theme={is_dark_mode_on ? 'dark' : 'light'}>
<LandscapeBlocker />
<Header />
{!isCallBackPage && <Header />}
<ErrorBoundary root_store={store}>
<AppContents>
{/* TODO: [trader-remove-client-base] */}
Expand Down
39 changes: 25 additions & 14 deletions packages/core/src/App/Components/Layout/Header/login-button.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';

import { Button } from '@deriv/components';
import { useOauth2 } from '@deriv/hooks';
import { redirectToLogin } from '@deriv/shared';
import { getLanguage, localize } from '@deriv/translations';
import { requestOidcAuthentication } from '@deriv-com/auth-client';

const LoginButton = ({ className }) => (
<Button
id='dt_login_button'
className={className}
has_effect
text={localize('Log in')}
onClick={() => {
window.LiveChatWidget?.call('hide');
redirectToLogin(false, getLanguage());
}}
tertiary
/>
);
const LoginButton = ({ className }) => {
const { isOAuth2Enabled } = useOauth2({});
return (
<Button
id='dt_login_button'
className={className}
has_effect
text={localize('Log in')}
onClick={async () => {
if (isOAuth2Enabled) {
await requestOidcAuthentication({
redirectCallbackUri: `${window.location.origin}/callback`,
});
}
window.LiveChatWidget?.call('hide');
redirectToLogin(false, getLanguage());
}}
tertiary
/>
);
};

LoginButton.propTypes = {
className: PropTypes.string,
Expand Down
17 changes: 14 additions & 3 deletions packages/core/src/App/Constants/routes-config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import React from 'react';
import { Redirect as RouterRedirect } from 'react-router-dom';
import { makeLazyLoader, routes, moduleLoader } from '@deriv/shared';

import { Loading } from '@deriv/components';
import { makeLazyLoader, moduleLoader, routes } from '@deriv/shared';
import { localize } from '@deriv/translations';

import Redirect from 'App/Containers/Redirect';
import RootComponent from 'App/Containers/RootComponent';
import Endpoint from 'Modules/Endpoint';

const CFDCompareAccounts = React.lazy(() =>
import(/* webpackChunkName: "cfd-compare-accounts" */ '@deriv/cfd/src/Containers/cfd-compare-accounts')
import CallbackPage from '../../Modules/Callback/CallbackPage.tsx';

const CFDCompareAccounts = React.lazy(
() => import(/* webpackChunkName: "cfd-compare-accounts" */ '@deriv/cfd/src/Containers/cfd-compare-accounts')
);

// Error Routes
Expand Down Expand Up @@ -356,6 +360,12 @@ const getModules = () => {
is_authenticated: false,
getTitle: () => localize("Trader's Hub"),
},
{
path: routes.callback_page,
component: CallbackPage,
is_authenticated: false,
getTitle: () => 'Callback',
},
];

return modules;
Expand All @@ -372,6 +382,7 @@ const initRoutesConfig = () => [
{ path: routes.index, component: RouterRedirect, getTitle: () => '', to: routes.traders_hub },
{ path: routes.endpoint, component: Endpoint, getTitle: () => 'Endpoint' }, // doesn't need localization as it's for internal use
{ path: routes.redirect, component: Redirect, getTitle: () => localize('Redirect') },
{ path: routes.callback_page, component: CallbackPage, getTitle: () => 'Callback' },
{
path: routes.complaints_policy,
component: lazyLoadComplaintsPolicy(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from 'react';
import { Analytics, TEvents } from '@deriv-com/analytics';
import Cookies from 'js-cookie';

import { Dialog, Icon, Text } from '@deriv/components';
import { useOauth2 } from '@deriv/hooks';
import { redirectToLogin } from '@deriv/shared';
import { observer, useStore } from '@deriv/stores';
import { getLanguage, localize, Localize } from '@deriv/translations';
import { useOauth2 } from '@deriv/hooks';
import { getLanguage, Localize, localize } from '@deriv/translations';
import { Analytics, TEvents } from '@deriv-com/analytics';
import { requestOidcAuthentication } from '@deriv-com/auth-client';

import './wallets-upgrade-logout-modal.scss';

const trackAnalyticsEvent = (
Expand All @@ -27,6 +30,19 @@ const WalletsUpgradeLogoutModal = observer(() => {
const { is_desktop } = ui;
const account_mode = is_virtual ? 'demo' : 'real';

const { oAuthLogout, isOAuth2Enabled } = useOauth2({
handleLogout: async () => {
await logout();
if (isOAuth2Enabled) {
await requestOidcAuthentication({
redirectCallbackUri: `${window.location.origin}/callback`,
});
} else {
redirectToLogin(false, getLanguage());
}
},
});

React.useEffect(() => {
trackAnalyticsEvent('open', account_mode);
}, [account_mode]);
Expand All @@ -37,20 +53,15 @@ const WalletsUpgradeLogoutModal = observer(() => {
expires: 0.5, // 12 hours expiration time
secure: true,
});

logout().then(() => {
redirectToLogin(false, getLanguage());
});
await oAuthLogout();
trackAnalyticsEvent('click_cta', account_mode);
};

const { oAuthLogout } = useOauth2({ handleLogout: onConfirmHandler });

return (
<Dialog
className='wallets-upgrade-logout-modal'
confirm_button_text={localize('Log out')}
onConfirm={oAuthLogout}
onConfirm={onConfirmHandler}
is_closed_on_confirm
is_visible
dismissable={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { moduleLoader } from '@deriv/shared';

import { useOauth2 } from '@deriv/hooks';
import { moduleLoader } from '@deriv/shared';
import { observer, useStore } from '@deriv/stores';

const AppStore = React.lazy(() =>
Expand Down
Loading

0 comments on commit b0473ca

Please sign in to comment.