Skip to content

Commit 86a2b34

Browse files
authored
Merge pull request #420 from ameerul-deriv/FEWP-366-migrate-p2p-standalone-to-oidc
Ameerul / FEWP-366 Migrate P2P standalone to OIDC
2 parents 833bf4c + a82042e commit 86a2b34

File tree

12 files changed

+207
-44
lines changed

12 files changed

+207
-44
lines changed

.github/workflows/build-and-deploy-test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444

4545
- name: Get cached dependencies
4646
id: cache-npm
47-
uses: actions/cache/restore@e12d46a63a90f2fae62d114769bbf2a179198b5c
47+
uses: actions/cache@v3
4848
with:
4949
path: node_modules
5050
key: npm-${{ hashFiles('./package-lock.json') }}

package-lock.json

+44-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@datadog/browser-rum": "^5.11.0",
2121
"@deriv-com/analytics": "^1.27.0",
2222
"@deriv-com/api-hooks": "^1.7.1",
23-
"@deriv-com/auth-client": "1.0.27",
23+
"@deriv-com/auth-client": "1.4.3",
2424
"@deriv-com/translations": "^1.3.9",
2525
"@deriv-com/ui": "^1.35.6",
2626
"@deriv-com/utils": "^0.0.39",

public/front-channel.html

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Deriv</title>
5+
<meta charset="utf-8" />
6+
<script>
7+
try {
8+
// we check if we can access the top.location, if we can't it will throw an error
9+
// and this means this front-channel is not rendered on the same window as the application which is logging out
10+
let isSameOrigin = top.location.hostname === self.location.hostname;
11+
} catch (err) {
12+
localStorage.removeItem('client.accounts');
13+
localStorage.removeItem('active_loginid');
14+
localStorage.removeItem('authToken');
15+
}
16+
</script>
17+
</head>
18+
19+
<body></body>
20+
</html>

src/App.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Loader, useDevice } from '@deriv-com/ui';
1010
import { URLConstants } from '@deriv-com/utils';
1111
import useGrowthbookGetFeatureValue from './hooks/custom-hooks/useGrowthbookGetFeatureValue';
1212
import useOAuth2Enabled from './hooks/custom-hooks/useOAuth2Enabled';
13+
import { getCurrentRoute } from './utils';
1314

1415
const { VITE_CROWDIN_BRANCH_NAME, VITE_PROJECT_NAME, VITE_TRANSLATIONS_CDN_URL } = process.env;
1516
const i18nInstance = initializeI18n({
@@ -26,19 +27,22 @@ const App = () => {
2627
const { initialise: initDatadog } = useDatadog();
2728
const { isDesktop } = useDevice();
2829
const { initialise: initDerivAnalytics } = useDerivAnalytics();
30+
const isCallbackPage = getCurrentRoute() === 'callback';
2931

3032
initTrackJS();
3133
initDerivAnalytics();
3234
initDatadog();
33-
onRenderAuthCheck();
3435

3536
useEffect(() => {
3637
if (isGBLoaded && ShouldRedirectToDerivApp) {
3738
const NODE_ENV = process.env.VITE_NODE_ENV;
3839
const APP_URL = NODE_ENV === 'production' ? URLConstants.derivAppProduction : URLConstants.derivAppStaging;
3940
window.location.href = `${APP_URL}/cashier/p2p`;
41+
} else if (isGBLoaded) {
42+
onRenderAuthCheck();
4043
}
41-
}, [isGBLoaded, ShouldRedirectToDerivApp]);
44+
}, [isGBLoaded, ShouldRedirectToDerivApp, onRenderAuthCheck]);
45+
4246
return (
4347
<BrowserRouter>
4448
<ErrorBoundary>
@@ -52,9 +56,9 @@ const App = () => {
5256
}
5357
>
5458
{!isOAuth2Enabled && <DerivIframe />}
55-
<AppHeader />
59+
{!isCallbackPage && <AppHeader />}
5660
<AppContent />
57-
{isDesktop && <AppFooter />}
61+
{isDesktop && !isCallbackPage && <AppFooter />}
5862
</Suspense>
5963
</TranslationProvider>
6064
</QueryParamProvider>

src/components/AppHeader/AppHeader.tsx

+11-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { api, useGrowthbookGetFeatureValue, useOAuth } from '@/hooks';
44
import { Chat, getCurrentRoute } from '@/utils';
55
import { StandaloneCircleUserRegularIcon } from '@deriv/quill-icons';
66
import { useAuthData } from '@deriv-com/api-hooks';
7+
import { requestOidcAuthentication } from '@deriv-com/auth-client';
78
import { useTranslations } from '@deriv-com/translations';
89
import { Button, Header, Text, Tooltip, useDevice, Wrapper } from '@deriv-com/ui';
910
import { LocalStorageUtils } from '@deriv-com/utils';
@@ -29,7 +30,7 @@ const AppHeader = () => {
2930
useEffect(() => {
3031
document.documentElement.dir = instance.dir((currentLang || 'en').toLowerCase());
3132
}, [currentLang, instance]);
32-
const { oAuthLogout } = useOAuth();
33+
const { isOAuth2Enabled, oAuthLogout } = useOAuth();
3334
const [isNotificationServiceEnabled] = useGrowthbookGetFeatureValue({
3435
featureFlag: 'new_notifications_service_enabled',
3536
});
@@ -75,7 +76,15 @@ const AppHeader = () => {
7576
<Button
7677
className='w-36'
7778
color='primary-light'
78-
onClick={() => window.open(oauthUrl, '_self')}
79+
onClick={async () => {
80+
if (isOAuth2Enabled) {
81+
await requestOidcAuthentication({
82+
redirectCallbackUri: `${window.location.origin}/callback`,
83+
});
84+
} else {
85+
window.open(oauthUrl, '_self');
86+
}
87+
}}
7988
variant='ghost'
8089
>
8190
<Text weight='bold'>{localize('Log in')}</Text>

src/components/AppHeader/__tests__/AppHeader.spec.tsx

+16-1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ jest.mock('@/hooks', () => ({
9292
},
9393
}));
9494

95+
jest.mock('@deriv-com/auth-client', () => ({
96+
OAuth2Logout: jest.fn(({ WSLogoutAndRedirect }) => {
97+
const mockIframe = document.createElement('iframe');
98+
mockIframe.id = 'logout-iframe';
99+
document.body.appendChild(mockIframe);
100+
setTimeout(() => {
101+
const event = new MessageEvent('message', { data: 'logout_complete' });
102+
window.dispatchEvent(event);
103+
}, 100);
104+
WSLogoutAndRedirect();
105+
}),
106+
useIsOAuth2Enabled: jest.fn().mockReturnValue(false),
107+
useOAuth2: jest.fn().mockReturnValue({ isOAuth2Enabled: false }),
108+
}));
109+
95110
describe('<AppHeader/>', () => {
96111
window.open = jest.fn();
97112

@@ -134,7 +149,7 @@ describe('<AppHeader/>', () => {
134149
});
135150

136151
Object.defineProperty(document, 'domain', {
137-
value: 'example.com',
152+
value: 'example.com/endpoint',
138153
writable: true,
139154
});
140155

src/hooks/custom-hooks/useOAuth.ts

+47-15
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@ import { useCallback } from 'react';
22
import Cookies from 'js-cookie';
33
import { getOauthUrl } from '@/constants';
44
import { getCurrentRoute, removeCookies } from '@/utils';
5+
import { isSafariBrowser } from '@/utils/browser';
56
import { useAuthData } from '@deriv-com/api-hooks';
6-
import { TOAuth2EnabledAppList, useOAuth2 } from '@deriv-com/auth-client';
7+
import {
8+
OAuth2Logout,
9+
requestOidcAuthentication,
10+
TOAuth2EnabledAppList,
11+
useIsOAuth2Enabled,
12+
} from '@deriv-com/auth-client';
713
import useGrowthbookGetFeatureValue from './useGrowthbookGetFeatureValue';
814

915
type UseOAuthReturn = {
16+
isOAuth2Enabled: boolean;
1017
oAuthLogout: () => void;
1118
onRenderAuthCheck: () => void;
1219
};
@@ -20,29 +27,51 @@ const useOAuth = (): UseOAuthReturn => {
2027
featureFlag: 'hydra_be',
2128
}) as unknown as [TOAuth2EnabledAppList, boolean];
2229

23-
const oAuthGrowthbookConfig = {
24-
OAuth2EnabledApps,
25-
OAuth2EnabledAppsInitialised,
26-
};
30+
const isOAuth2Enabled = useIsOAuth2Enabled(OAuth2EnabledApps, OAuth2EnabledAppsInitialised);
2731

2832
const { logout } = useAuthData();
2933
const { error, isAuthorized, isAuthorizing } = useAuthData();
3034
const isEndpointPage = getCurrentRoute() === 'endpoint';
35+
const isCallbackPage = getCurrentRoute() === 'callback';
3136
const isRedirectPage = getCurrentRoute() === 'redirect';
3237
const oauthUrl = getOauthUrl();
3338
const authTokenLocalStorage = localStorage.getItem('authToken');
3439

3540
const WSLogoutAndRedirect = async () => {
3641
await logout();
3742
removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
38-
window.open(oauthUrl, '_self');
43+
if (!isOAuth2Enabled) {
44+
window.open(oauthUrl, '_self');
45+
}
3946
};
40-
const { OAuth2Logout: oAuthLogout } = useOAuth2(oAuthGrowthbookConfig, WSLogoutAndRedirect);
47+
const handleLogout = async () => {
48+
await OAuth2Logout({
49+
postLogoutRedirectUri: window.location.origin,
50+
redirectCallbackUri: `${window.location.origin}/callback`,
51+
WSLogoutAndRedirect,
52+
});
53+
};
54+
55+
const redirectToAuth = async () => {
56+
if (isOAuth2Enabled) {
57+
await requestOidcAuthentication({
58+
redirectCallbackUri: `${window.location.origin}/callback`,
59+
});
60+
} else {
61+
window.open(oauthUrl, '_self');
62+
}
63+
};
64+
65+
const hasAuthToken = localStorage.getItem('authToken');
66+
const loggedState = Cookies.get('logged_state');
4167

42-
const onRenderAuthCheck = useCallback(() => {
43-
if (!isEndpointPage) {
44-
if (error?.code === 'InvalidToken') {
45-
oAuthLogout();
68+
const onRenderAuthCheck = useCallback(async () => {
69+
if (!isEndpointPage && !isCallbackPage) {
70+
// NOTE: we only do single logout using logged_state cookie checks only in Safari
71+
// because front channels do not work in Safari, front channels (front-channel.html) would already help us automatically log out
72+
const shouldSingleLogoutWithLoggedState = hasAuthToken && loggedState === 'false' && isSafariBrowser();
73+
if ((shouldSingleLogoutWithLoggedState && isOAuth2Enabled) || error?.code === 'InvalidToken') {
74+
await handleLogout();
4675
} else if (isRedirectPage) {
4776
const params = new URLSearchParams(location.search);
4877
const from = params.get('from');
@@ -55,21 +84,24 @@ const useOAuth = (): UseOAuthReturn => {
5584
window.location.href = window.location.origin;
5685
}
5786
} else if (!isAuthorized && !isAuthorizing && !authTokenLocalStorage) {
58-
window.open(oauthUrl, '_self');
87+
await redirectToAuth();
5988
}
6089
}
90+
// eslint-disable-next-line react-hooks/exhaustive-deps
6191
}, [
6292
isEndpointPage,
93+
isCallbackPage,
94+
hasAuthToken,
95+
loggedState,
96+
isOAuth2Enabled,
6397
error?.code,
6498
isRedirectPage,
6599
isAuthorized,
66100
isAuthorizing,
67101
authTokenLocalStorage,
68-
oAuthLogout,
69-
oauthUrl,
70102
]);
71103

72-
return { oAuthLogout, onRenderAuthCheck };
104+
return { isOAuth2Enabled, oAuthLogout: handleLogout, onRenderAuthCheck };
73105
};
74106

75107
export default useOAuth;

0 commit comments

Comments
 (0)