diff --git a/package-lock.json b/package-lock.json index 5d70b939..267ea675 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "@deriv-com/analytics": "^1.22.1", - "@deriv-com/auth-client": "^1.0.29", + "@deriv-com/auth-client": "^1.2.15", "@deriv-com/quill-ui": "^1.16.21", "@deriv/deriv-api": "^1.0.11", "@deriv/ui": "^0.8.0", @@ -2599,12 +2599,16 @@ } }, "node_modules/@deriv-com/auth-client": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@deriv-com/auth-client/-/auth-client-1.0.29.tgz", - "integrity": "sha512-4iBxaSKM9hu6qVHGUr4MHtik59j1ZIuvveRnSqAbqQgcQGzKfSLRwGJ53P/5lVByh9fIIyICpuxbTgaaYA4eXg==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@deriv-com/auth-client/-/auth-client-1.2.15.tgz", + "integrity": "sha512-tplrAeklKg/o1EZZXd6FSowvfwKzebzyUPLk0S/HN/tGeCDljCu8jo2aMXeJGzX9G338686nPYq2EnfeCnVgUg==", "dependencies": { - "@deriv-com/utils": "^0.0.37", + "@deriv-com/utils": "^0.0.42", + "js-cookie": "3.0.5", "oidc-client-ts": "^3.1.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "^4.27.3" } }, "node_modules/@deriv-com/quill-tokens": { @@ -2688,9 +2692,9 @@ } }, "node_modules/@deriv-com/utils": { - "version": "0.0.37", - "resolved": "https://registry.npmjs.org/@deriv-com/utils/-/utils-0.0.37.tgz", - "integrity": "sha512-+ngUvT+OqwblBoqkHcsbLtljjwOGIjjMpo5xLS5fwyhtNvBe8Rcq+140QV1j0xq9vlm2kmcowEKIVBq33imFmg==" + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@deriv-com/utils/-/utils-0.0.42.tgz", + "integrity": "sha512-4JhTpg0sQWCq94RSMGpuT/09bYSV8yO3WdunM2R84qxWNitRH/i4k/xfdleRVzX+xLSmMmJWlkbD6NAlP8U5eg==" }, "node_modules/@deriv/api-types": { "version": "1.0.292", @@ -6472,9 +6476,9 @@ } }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz", + "integrity": "sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 0bb7d5a6..f3d57b41 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dependencies": { "@deriv-com/quill-ui": "^1.16.21", "@deriv-com/analytics": "^1.22.1", - "@deriv-com/auth-client": "^1.0.29", + "@deriv-com/auth-client": "^1.2.15", "@deriv/deriv-api": "^1.0.11", "@radix-ui/react-tooltip": "^1.0.7", "@react-spring/web": "^9.7.3", diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index 7bbd9594..28a1efb9 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -12,6 +12,7 @@ import useDeviceType from '@site/src/hooks/useDeviceType'; import { IUserNavbarItemProps } from './item.types'; import styles from './UserNavbarItem.module.scss'; +import { useHandleLogin } from '@site/src/hooks/useHandleLogin'; interface IActionProps { handleClick: () => void; @@ -60,6 +61,10 @@ const DashboardActions: React.FC = ({ handleClick, isDesktop }) => }; const SignedInActions: React.FC = ({ handleClick, isDesktop }) => { + const { handleLogin } = useHandleLogin({ + onClickLogin: handleClick, + }); + const signedInButtonClasses = clsx('navbar__item', styles.UserNavbarItem, styles.SignedInButton); return ( @@ -67,7 +72,7 @@ const SignedInActions: React.FC = ({ handleClick, isDesktop }) => - diff --git a/src/features/Callback/CallbackPage.tsx b/src/features/Callback/CallbackPage.tsx new file mode 100644 index 00000000..2c88aadb --- /dev/null +++ b/src/features/Callback/CallbackPage.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Callback } from '@deriv-com/auth-client'; +import { transformAccountsFromResponseBody } from '@site/src/utils'; +import useAuthContext from '@site/src/hooks/useAuthContext'; + +const CallbackPage = () => { + const { updateLoginAccounts } = useAuthContext(); + + return ( + { + const accounts = transformAccountsFromResponseBody(tokens); + + updateLoginAccounts(accounts); + + window.location.href = '/'; + }} + /> + ); +}; + +export default CallbackPage; diff --git a/src/features/Callback/index.ts b/src/features/Callback/index.ts new file mode 100644 index 00000000..1c7f3911 --- /dev/null +++ b/src/features/Callback/index.ts @@ -0,0 +1,3 @@ +import CallbackPage from './CallbackPage'; + +export default CallbackPage; diff --git a/src/features/Login/Login.tsx b/src/features/Login/Login.tsx index ff119d36..7e9fc2b7 100644 --- a/src/features/Login/Login.tsx +++ b/src/features/Login/Login.tsx @@ -6,15 +6,18 @@ import Footer from '@site/src/components/Footer'; import Translate from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import { useHandleLogin } from '@site/src/hooks/useHandleLogin'; + export const Login = () => { const { getUrl } = useLoginUrl(); const { i18n: { currentLocale }, } = useDocusaurusContext(); - const handleClick = () => { - window.location.assign(getUrl(currentLocale)); - }; + const { handleLogin } = useHandleLogin({ + onClickLogin: () => window.location.assign(getUrl(currentLocale)), + }); + return (
@@ -26,7 +29,7 @@ export const Login = () => {
-
diff --git a/src/hooks/useHandleLogin/index.tsx b/src/hooks/useHandleLogin/index.tsx new file mode 100644 index 00000000..96ffee13 --- /dev/null +++ b/src/hooks/useHandleLogin/index.tsx @@ -0,0 +1,37 @@ +import useGrowthbookGetFeatureValue from '../useGrowthbookGetFeatureValue/'; +import { + requestOidcAuthentication, + TOAuth2EnabledAppList, + useIsOAuth2Enabled, +} from '@deriv-com/auth-client'; + +/** + * Handles the new login flow for the user using OIDC. + * + * If the user is not logged in and OAuth2 is enabled, it will redirect the user to the + * OAuth2 authorization page from the OIDC config endpoint. If OAuth2 is not enabled it will + * redirect the user to the legacy oauth url coming from the onClickLogin callback. + * + * @param {Object} props - The props object. + * @param {Function} props.onClickLogin - The callback to be called when the user is logged in. + * @returns {Object} - An object with the `handleLogin` function. + */ +export const useHandleLogin = ({ onClickLogin }: { onClickLogin: () => void }) => { + const [OAuth2EnabledApps, OAuth2EnabledAppsInitialised] = + useGrowthbookGetFeatureValue({ + featureFlag: 'hydra_be', + }); + + const isOAuth2Enabled = useIsOAuth2Enabled(OAuth2EnabledApps, OAuth2EnabledAppsInitialised); + + const handleLogin = async () => { + if (isOAuth2Enabled) { + await requestOidcAuthentication({ + redirectCallbackUri: `${window.location.origin}/callback`, + }); + } + onClickLogin(); + }; + + return { handleLogin }; +}; diff --git a/src/pages/callback.tsx b/src/pages/callback.tsx new file mode 100644 index 00000000..93448376 --- /dev/null +++ b/src/pages/callback.tsx @@ -0,0 +1,21 @@ +import React, { useEffect } from 'react'; +import Layout from '@theme/Layout'; +import CallbackPage from '../features/Callback'; + +const Callback = () => { + useEffect(() => { + const navbar = document.getElementsByClassName('navbar navbar--fixed-top')[0] as HTMLElement; + if (navbar) { + navbar.style.display = 'none'; + } + }, []); + return ( + +
+ +
+
+ ); +}; + +export default Callback; diff --git a/src/utils/__tests__/utils.test.ts b/src/utils/__tests__/utils.test.ts index 366f8e61..da21e587 100644 --- a/src/utils/__tests__/utils.test.ts +++ b/src/utils/__tests__/utils.test.ts @@ -7,6 +7,7 @@ const { isHost, getServerConfig, getCurrencyObject, + transformAccountsFromResponseBody, } = utils; describe('Get an object with currency data', () => { @@ -106,6 +107,37 @@ describe('Get Is Browser', () => { }); }); +describe('Transform Accounts from Response Body', () => { + it('Should return an array of account objects', () => { + const inputData = { + acct1: 'CR111111', + cur1: 'USD', + token1: 'a1-123123123', + acct2: 'CR222222', + cur2: 'ETH', + token2: 'a1-34343434', + }; + + const expectedOutput = [ + { + currency: 'USD', + name: 'CR111111', + token: 'a1-123123123', + }, + { + currency: 'ETH', + name: 'CR222222', + token: 'a1-34343434', + }, + ]; + + const output = transformAccountsFromResponseBody(inputData); + + expect(output.length).toBe(expectedOutput.length); + expect(output).toStrictEqual(expectedOutput); + }); +}); + describe('Get Accounts from Search Params', () => { it('Should return user accounts', () => { const test_search_params = diff --git a/src/utils/index.ts b/src/utils/index.ts index 827bb4dc..7ec6e46a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -106,6 +106,30 @@ export const getIsBrowser = () => { return typeof window !== 'undefined'; }; +/** + * @description This function takes the response data from the accounts endpoint and transforms it into an array of objects + * @param {object} data - The response data from the accounts endpoint + * @returns {array} An array of objects with the shape of { currency: string, name: string, token: string } + */ +export const transformAccountsFromResponseBody = (data) => { + const result = []; + const keys = Object.keys(data); + + for (let i = 1; i <= keys.length / 3; i++) { + const groupedObject = { + currency: data[`cur${i}`], + name: data[`acct${i}`], + token: data[`token${i}`], + }; + + if (groupedObject.currency && groupedObject.name && groupedObject.token) { + result.push(groupedObject); + } + } + + return result; +}; + /** * @description based on the received query params after successful login, generates the array of user's accounts * @param searchParams the query params in the auth path when user does the login successfully @@ -126,10 +150,7 @@ export const getAccountsFromSearchParams = (searchParams: string) => { const queryIndex = index + 1; // we should check each account in the search params, this is some kind of validation for the URL search params - if ( - params.has(`acct${queryIndex}`) && - params.has(`token${queryIndex}`) - ) { + if (params.has(`acct${queryIndex}`) && params.has(`token${queryIndex}`)) { accounts.push({ name: params.get(`acct${queryIndex}`), token: params.get(`token${queryIndex}`),