From bfe84fdec505c9b9470d5cd4b943ececcbf50fd5 Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Fri, 11 Oct 2024 13:28:12 +0800 Subject: [PATCH 1/9] feat: implement oidc for login --- package-lock.json | 20 +++++ package.json | 1 + .../UserNavbarItem/item.desktop.tsx | 46 +++++++++++- src/pages/index.tsx | 23 ------ src/pages/login/callback/index.tsx | 75 +++++++++++++++++++ 5 files changed, 140 insertions(+), 25 deletions(-) delete mode 100644 src/pages/index.tsx create mode 100644 src/pages/login/callback/index.tsx diff --git a/package-lock.json b/package-lock.json index c8a0a2103..f093c3687 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "identity-obj-proxy": "^3.0.0", "install": "^0.13.0", "moment": "^2.29.4", + "oidc-client-ts": "^3.1.0", "prism-react-renderer": "^2.3.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -17050,6 +17051,14 @@ "node": ">=4.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -20988,6 +20997,17 @@ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" }, + "node_modules/oidc-client-ts": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.1.0.tgz", + "integrity": "sha512-IDopEXjiwjkmJLYZo6BTlvwOtnlSniWZkKZoXforC/oLZHC9wkIxd25Kwtmo5yKFMMVcsp3JY6bhcNJqdYk8+g==", + "dependencies": { + "jwt-decode": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", diff --git a/package.json b/package.json index 2223ead7b..b4e4b0564 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "identity-obj-proxy": "^3.0.0", "install": "^0.13.0", "moment": "^2.29.4", + "oidc-client-ts": "^3.1.0", "prism-react-renderer": "^2.3.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index cfd5f8db0..62fa49267 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -3,15 +3,57 @@ import clsx from 'clsx'; import React, { useState } from 'react'; import AccountSwitcher from '../AccountSwitcher'; import { IUserNavbarItemProps } from './item.types'; +import { UserManager, WebStorageStateStore } from 'oidc-client-ts'; import styles from './UserNavbarItem.module.scss'; import SearchButton from '../SearchButton'; import Translate from '@docusaurus/Translate'; const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) => { const [toggle_search, setToggleSearch] = useState(false); + const serverUrl = localStorage.getItem('config.server_url'); - const handleClick = () => { - location.assign(authUrl); + const handleClick = async () => { + // location.assign(authUrl); + + const oidc = `https://${serverUrl}/.well-known/openid-configuration`; + + try { + const response = await fetch(oidc); + const data = await response.json(); + + const endpoints = { + authorization_endpoint: data.authorization_endpoint, + token_endpoint: data.token_endpoint, + userinfo_endpoint: data.userinfo_endpoint, + end_session_endpoint: data.end_session_endpoint, + }; + + localStorage.setItem('config.oidc_endpoints', JSON.stringify(endpoints)); + + console.log(data); + + const userManager = new UserManager({ + authority: data.issuer, + client_id: '1011', + redirect_uri: 'http://localhost:3000/login/callback', + response_type: 'code', + scope: 'openid', + stateStore: new WebStorageStateStore({ store: window.localStorage }), + post_logout_redirect_uri: data.end_session_endpoint, + client_secret: '1011_secret', + metadata: { + issuer: data.issuer, + authorization_endpoint: data.authorization_endpoint, + token_endpoint: data.token_endpoint, + userinfo_endpoint: data.userinfo_endpoint, + end_session_endpoint: data.end_session_endpoint, + }, + }); + + await userManager.signinRedirect(); + } catch (error) { + console.log(error); + } }; const logInButtonClasses = clsx( diff --git a/src/pages/index.tsx b/src/pages/index.tsx deleted file mode 100644 index 64a0b8717..000000000 --- a/src/pages/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import Layout from '@theme/Layout'; -import Head from '@docusaurus/Head'; -import HomepageFeatures from '@site/src/features/Home'; - -export default function Home(): JSX.Element { - return ( - - - Deriv API | Customise your trading app - - - - - ); -} diff --git a/src/pages/login/callback/index.tsx b/src/pages/login/callback/index.tsx new file mode 100644 index 000000000..8aa26ae80 --- /dev/null +++ b/src/pages/login/callback/index.tsx @@ -0,0 +1,75 @@ +import React, { useEffect } from 'react'; +import Layout from '@theme/Layout'; + +export default function LoginCallback(): JSX.Element { + const [error, setError] = React.useState(null); + const [error_description, setErrorDescription] = React.useState(null); + + const urlParams = new URLSearchParams(window.location.search); + + const code = urlParams.get('code'); + const state = urlParams.get('state'); + + console.log(code); + const oidc_endpoints = localStorage.getItem('config.oidc_endpoints'); + + const token_endpoint = JSON.parse(oidc_endpoints).token_endpoint; + + useEffect(() => { + const navbar = document.querySelector('.navbar.navbar--fixed-top') as HTMLElement; + if (navbar) { + navbar.style.display = 'none'; + } + + const exchangeToken = async () => { + try { + const oidc_key = `oidc.${state}`; + + const oidc_data = localStorage.getItem(oidc_key); + const code_verifier = oidc_data ? JSON.parse(oidc_data).code_verifier : null; + + console.log(code_verifier); + + if (!code_verifier) return; + + const response = await fetch(token_endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + }, + body: new URLSearchParams({ + grant_type: 'authorization_code', + redirect_uri: 'http://localhost:3000/login/callback', + code: code, + code_verifier: code_verifier, + client_id: '1011', + }).toString(), + }); + + const data = await response.json(); + if (response.ok) { + console.log('Token exchange successful', data); + // Handle the access token here (e.g., save it or use it in further API calls) + } else { + console.error('Error exchanging token:', data); + setError(data.error); + setErrorDescription(data.error_description); + } + } catch (error) { + console.error('Token exchange failed:', error); + } + }; + + exchangeToken(); + }, [code, state, token_endpoint]); + + return ( + + {error &&

{error}

} + {error_description && ( +

{error_description}

+ )} +
+ ); +} From e3bddf167784bb44e1a997f23e94150f985b415d Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Fri, 11 Oct 2024 13:30:24 +0800 Subject: [PATCH 2/9] chore: remove logs --- src/components/UserNavbarItem/item.desktop.tsx | 3 --- src/pages/login/callback/index.tsx | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index 62fa49267..83cdd755b 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -30,8 +30,6 @@ const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) localStorage.setItem('config.oidc_endpoints', JSON.stringify(endpoints)); - console.log(data); - const userManager = new UserManager({ authority: data.issuer, client_id: '1011', @@ -40,7 +38,6 @@ const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) scope: 'openid', stateStore: new WebStorageStateStore({ store: window.localStorage }), post_logout_redirect_uri: data.end_session_endpoint, - client_secret: '1011_secret', metadata: { issuer: data.issuer, authorization_endpoint: data.authorization_endpoint, diff --git a/src/pages/login/callback/index.tsx b/src/pages/login/callback/index.tsx index 8aa26ae80..c8c986b3b 100644 --- a/src/pages/login/callback/index.tsx +++ b/src/pages/login/callback/index.tsx @@ -10,7 +10,6 @@ export default function LoginCallback(): JSX.Element { const code = urlParams.get('code'); const state = urlParams.get('state'); - console.log(code); const oidc_endpoints = localStorage.getItem('config.oidc_endpoints'); const token_endpoint = JSON.parse(oidc_endpoints).token_endpoint; @@ -28,8 +27,6 @@ export default function LoginCallback(): JSX.Element { const oidc_data = localStorage.getItem(oidc_key); const code_verifier = oidc_data ? JSON.parse(oidc_data).code_verifier : null; - console.log(code_verifier); - if (!code_verifier) return; const response = await fetch(token_endpoint, { From e5e5c6837b6070b7f35485c39dc2d60eaf98dd1a Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Fri, 11 Oct 2024 14:19:29 +0800 Subject: [PATCH 3/9] chore: add logic --- src/pages/login/callback/index.tsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/pages/login/callback/index.tsx b/src/pages/login/callback/index.tsx index c8c986b3b..0cb31f88f 100644 --- a/src/pages/login/callback/index.tsx +++ b/src/pages/login/callback/index.tsx @@ -14,6 +14,30 @@ export default function LoginCallback(): JSX.Element { const token_endpoint = JSON.parse(oidc_endpoints).token_endpoint; + const fetchLegacyTokens = async (access_token: string) => { + try { + const legacyTokenResponse = await fetch('https://qa101.deriv.dev/oauth2/legacy/token', { + method: 'POST', + headers: { + Authorization: `Bearer ${access_token}`, + 'Content-Type': 'application/json', + }, + }); + + const legacyData = await legacyTokenResponse.json(); + if (legacyTokenResponse.ok) { + console.log('Legacy token fetch successful', legacyData); + // You can store or handle the legacy tokens as needed here + } else { + console.error('Error fetching legacy tokens:', legacyData); + setError(legacyData.error); + setErrorDescription(legacyData.error_description); + } + } catch (error) { + console.error('Failed to fetch legacy tokens:', error); + } + }; + useEffect(() => { const navbar = document.querySelector('.navbar.navbar--fixed-top') as HTMLElement; if (navbar) { @@ -48,6 +72,9 @@ export default function LoginCallback(): JSX.Element { if (response.ok) { console.log('Token exchange successful', data); // Handle the access token here (e.g., save it or use it in further API calls) + localStorage.setItem('id_token', data.id_token); + + await fetchLegacyTokens(data.access_token); } else { console.error('Error exchanging token:', data); setError(data.error); From 8d10387490faf01bf3f763f2bbcf750fff62b58a Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Fri, 11 Oct 2024 16:03:37 +0800 Subject: [PATCH 4/9] fix: build --- package-lock.json | 22 +++++-- package.json | 1 + .../UserNavbarItem/item.desktop.tsx | 2 +- .../callback => features/Callback}/index.tsx | 62 +++++++++---------- src/pages/callback.tsx | 14 +++++ 5 files changed, 64 insertions(+), 37 deletions(-) rename src/{pages/login/callback => features/Callback}/index.tsx (64%) create mode 100644 src/pages/callback.tsx diff --git a/package-lock.json b/package-lock.json index f093c3687..0f36f7665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.2", "@radix-ui/react-tabs": "^1.0.2", "@textea/json-viewer": "^3.4.1", + "axios": "^1.7.7", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", "clsx": "^1.2.1", "docusaurus-plugin-sass": "^0.2.2", @@ -8344,8 +8345,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -8406,6 +8406,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -9477,7 +9487,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -10660,7 +10669,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -12650,7 +12658,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -22370,6 +22377,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", diff --git a/package.json b/package.json index b4e4b0564..3934389f6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.2", "@radix-ui/react-tabs": "^1.0.2", "@textea/json-viewer": "^3.4.1", + "axios": "^1.7.7", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", "clsx": "^1.2.1", "docusaurus-plugin-sass": "^0.2.2", diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index 83cdd755b..b38b6b150 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -33,7 +33,7 @@ const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) const userManager = new UserManager({ authority: data.issuer, client_id: '1011', - redirect_uri: 'http://localhost:3000/login/callback', + redirect_uri: 'http://localhost:3000/callback', response_type: 'code', scope: 'openid', stateStore: new WebStorageStateStore({ store: window.localStorage }), diff --git a/src/pages/login/callback/index.tsx b/src/features/Callback/index.tsx similarity index 64% rename from src/pages/login/callback/index.tsx rename to src/features/Callback/index.tsx index 0cb31f88f..21d9a2919 100644 --- a/src/pages/login/callback/index.tsx +++ b/src/features/Callback/index.tsx @@ -1,11 +1,11 @@ import React, { useEffect } from 'react'; -import Layout from '@theme/Layout'; +import axios from 'axios'; -export default function LoginCallback(): JSX.Element { +export default function CallbackComponent() { const [error, setError] = React.useState(null); const [error_description, setErrorDescription] = React.useState(null); - const urlParams = new URLSearchParams(window.location.search); + const urlParams = new URLSearchParams(window?.location?.search); const code = urlParams.get('code'); const state = urlParams.get('state'); @@ -14,30 +14,6 @@ export default function LoginCallback(): JSX.Element { const token_endpoint = JSON.parse(oidc_endpoints).token_endpoint; - const fetchLegacyTokens = async (access_token: string) => { - try { - const legacyTokenResponse = await fetch('https://qa101.deriv.dev/oauth2/legacy/token', { - method: 'POST', - headers: { - Authorization: `Bearer ${access_token}`, - 'Content-Type': 'application/json', - }, - }); - - const legacyData = await legacyTokenResponse.json(); - if (legacyTokenResponse.ok) { - console.log('Legacy token fetch successful', legacyData); - // You can store or handle the legacy tokens as needed here - } else { - console.error('Error fetching legacy tokens:', legacyData); - setError(legacyData.error); - setErrorDescription(legacyData.error_description); - } - } catch (error) { - console.error('Failed to fetch legacy tokens:', error); - } - }; - useEffect(() => { const navbar = document.querySelector('.navbar.navbar--fixed-top') as HTMLElement; if (navbar) { @@ -61,7 +37,7 @@ export default function LoginCallback(): JSX.Element { }, body: new URLSearchParams({ grant_type: 'authorization_code', - redirect_uri: 'http://localhost:3000/login/callback', + redirect_uri: 'http://localhost:3000/callback', code: code, code_verifier: code_verifier, client_id: '1011', @@ -74,7 +50,31 @@ export default function LoginCallback(): JSX.Element { // Handle the access token here (e.g., save it or use it in further API calls) localStorage.setItem('id_token', data.id_token); - await fetchLegacyTokens(data.access_token); + try { + const response = await axios.post( + 'https://qa101.deriv.dev/oauth2/legacy/tokens', + {}, + { + headers: { + Authorization: `Bearer ${data.access_token}`, + 'Content-Type': 'application/json', + }, + }, + ); + + const legacyData = response.data; + console.log('Legacy token fetch successful', legacyData); + // You can store or handle the legacy tokens as needed here + } catch (error) { + if (error.response) { + const legacyData = error.response.data; + console.error('Error fetching legacy tokens:', legacyData); + setError(legacyData.error); + setErrorDescription(legacyData.error_description); + } else { + console.error('Failed to fetch legacy tokens:', error); + } + } } else { console.error('Error exchanging token:', data); setError(data.error); @@ -89,11 +89,11 @@ export default function LoginCallback(): JSX.Element { }, [code, state, token_endpoint]); return ( - + <> {error &&

{error}

} {error_description && (

{error_description}

)} -
+ ); } diff --git a/src/pages/callback.tsx b/src/pages/callback.tsx new file mode 100644 index 000000000..b752db0da --- /dev/null +++ b/src/pages/callback.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Layout from '@theme/Layout'; +import BrowserOnly from '@docusaurus/BrowserOnly'; +import CallbackComponent from '../features/Callback'; + +export default function LoginCallback(): JSX.Element { + return ( + +
+ {() => } +
+
+ ); +} From 95bf7d274295b2a9e6af3fc407344cf9953022c2 Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Mon, 14 Oct 2024 16:05:20 +0800 Subject: [PATCH 5/9] chore: change hostname --- .vscode/settings.json | 4 +++- src/components/UserNavbarItem/item.desktop.tsx | 3 ++- src/features/Callback/index.tsx | 7 +++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c167a488..02db64b75 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { - "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true } diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index b38b6b150..40e925757 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -33,7 +33,8 @@ const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) const userManager = new UserManager({ authority: data.issuer, client_id: '1011', - redirect_uri: 'http://localhost:3000/callback', + redirect_uri: + 'http://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', response_type: 'code', scope: 'openid', stateStore: new WebStorageStateStore({ store: window.localStorage }), diff --git a/src/features/Callback/index.tsx b/src/features/Callback/index.tsx index 21d9a2919..d183a42e1 100644 --- a/src/features/Callback/index.tsx +++ b/src/features/Callback/index.tsx @@ -16,9 +16,7 @@ export default function CallbackComponent() { useEffect(() => { const navbar = document.querySelector('.navbar.navbar--fixed-top') as HTMLElement; - if (navbar) { - navbar.style.display = 'none'; - } + if (navbar) navbar.style.display = 'none'; const exchangeToken = async () => { try { @@ -37,7 +35,8 @@ export default function CallbackComponent() { }, body: new URLSearchParams({ grant_type: 'authorization_code', - redirect_uri: 'http://localhost:3000/callback', + redirect_uri: + 'http://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', code: code, code_verifier: code_verifier, client_id: '1011', From f828b73901b6f0e8bb94efdf4203327fbe3b3618 Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Mon, 14 Oct 2024 16:12:16 +0800 Subject: [PATCH 6/9] chore: change hostname --- src/components/UserNavbarItem/item.desktop.tsx | 2 +- src/features/Callback/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index 40e925757..44af58982 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -34,7 +34,7 @@ const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) authority: data.issuer, client_id: '1011', redirect_uri: - 'http://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', + 'https://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', response_type: 'code', scope: 'openid', stateStore: new WebStorageStateStore({ store: window.localStorage }), diff --git a/src/features/Callback/index.tsx b/src/features/Callback/index.tsx index d183a42e1..7ca3d59d0 100644 --- a/src/features/Callback/index.tsx +++ b/src/features/Callback/index.tsx @@ -36,7 +36,7 @@ export default function CallbackComponent() { body: new URLSearchParams({ grant_type: 'authorization_code', redirect_uri: - 'http://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', + 'https://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', code: code, code_verifier: code_verifier, client_id: '1011', From febc538e45212d4508098abeedcf5bf00173b7f9 Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Mon, 14 Oct 2024 16:25:40 +0800 Subject: [PATCH 7/9] chore: change appid --- src/components/UserNavbarItem/item.desktop.tsx | 3 ++- src/features/Callback/index.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index 44af58982..32a219335 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -16,6 +16,7 @@ const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) // location.assign(authUrl); const oidc = `https://${serverUrl}/.well-known/openid-configuration`; + const appid = localStorage.getItem('config.app_id'); try { const response = await fetch(oidc); @@ -32,7 +33,7 @@ const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) const userManager = new UserManager({ authority: data.issuer, - client_id: '1011', + client_id: appid, redirect_uri: 'https://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', response_type: 'code', diff --git a/src/features/Callback/index.tsx b/src/features/Callback/index.tsx index 7ca3d59d0..147ac127d 100644 --- a/src/features/Callback/index.tsx +++ b/src/features/Callback/index.tsx @@ -24,6 +24,7 @@ export default function CallbackComponent() { const oidc_data = localStorage.getItem(oidc_key); const code_verifier = oidc_data ? JSON.parse(oidc_data).code_verifier : null; + const appId = localStorage.getItem('config.app_id'); if (!code_verifier) return; @@ -39,7 +40,7 @@ export default function CallbackComponent() { 'https://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', code: code, code_verifier: code_verifier, - client_id: '1011', + client_id: appId, }).toString(), }); From 91de0174740832e7ebe352a51c3612cc07e9ff6e Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Mon, 14 Oct 2024 17:27:30 +0800 Subject: [PATCH 8/9] chore: add logic to not hardcode the origin --- src/components/UserNavbarItem/item.desktop.tsx | 3 +-- src/features/Callback/index.tsx | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index 32a219335..bbe065acb 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -34,8 +34,7 @@ const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) const userManager = new UserManager({ authority: data.issuer, client_id: appid, - redirect_uri: - 'https://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', + redirect_uri: `${window.location.origin}/callback`, response_type: 'code', scope: 'openid', stateStore: new WebStorageStateStore({ store: window.localStorage }), diff --git a/src/features/Callback/index.tsx b/src/features/Callback/index.tsx index 147ac127d..6748b745b 100644 --- a/src/features/Callback/index.tsx +++ b/src/features/Callback/index.tsx @@ -1,7 +1,10 @@ import React, { useEffect } from 'react'; import axios from 'axios'; +import { getAccountsFromSearchParams } from '@site/src/utils'; +import useAuthContext from '@site/src/hooks/useAuthContext'; export default function CallbackComponent() { + const { updateLoginAccounts } = useAuthContext(); const [error, setError] = React.useState(null); const [error_description, setErrorDescription] = React.useState(null); @@ -36,8 +39,7 @@ export default function CallbackComponent() { }, body: new URLSearchParams({ grant_type: 'authorization_code', - redirect_uri: - 'https://deriv-api-docs-git-fork-thisyahlen-deriv-thisyahlen-oidc.binary.sx/callback', + redirect_uri: `${window.location.origin}/callback`, code: code, code_verifier: code_verifier, client_id: appId, @@ -64,6 +66,9 @@ export default function CallbackComponent() { const legacyData = response.data; console.log('Legacy token fetch successful', legacyData); + const accounts = getAccountsFromSearchParams(legacyData); + updateLoginAccounts(accounts); + window.location.href = '/dashboard'; // You can store or handle the legacy tokens as needed here } catch (error) { if (error.response) { From 1abbdc6fcbeb850257dde0a99a21d35faace5765 Mon Sep 17 00:00:00 2001 From: Thisyahlen Date: Tue, 15 Oct 2024 18:05:02 +0800 Subject: [PATCH 9/9] chore: use dashboard as the callback page --- package-lock.json | 11 +-- package.json | 2 +- .../UserNavbarItem/item.desktop.tsx | 44 ++--------- src/features/Callback/index.tsx | 4 +- src/features/dashboard/index.tsx | 76 ++++++++++++++++++- 5 files changed, 87 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f36f7665..60f3017f0 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.27", + "@deriv-com/auth-client": "^1.0.28", "@deriv/deriv-api": "^1.0.11", "@deriv/quill-icons": "^1.22.10", "@deriv/ui": "^0.8.0", @@ -2598,11 +2598,12 @@ } }, "node_modules/@deriv-com/auth-client": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@deriv-com/auth-client/-/auth-client-1.0.27.tgz", - "integrity": "sha512-8nZVJeljFnu/zAKiXvye5P9yK5CFHwcs2gWi7YGmHx7nVBL1BnaxMNbc/2JuQAD/oKpHh81hNOLADBslyJBIeA==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@deriv-com/auth-client/-/auth-client-1.0.28.tgz", + "integrity": "sha512-mT3C4wdD5USxeEPiOC3UXw2v+bDN7lv9xfz90JqdidxTzfrhsxZKiCuWModq1fo3ATKGPCW1tI5Ag0HEZu08qQ==", "dependencies": { - "@deriv-com/utils": "^0.0.33" + "@deriv-com/utils": "^0.0.33", + "oidc-client-ts": "^3.1.0" } }, "node_modules/@deriv-com/utils": { diff --git a/package.json b/package.json index 3934389f6..6128df4b6 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@deriv-com/analytics": "^1.22.1", - "@deriv-com/auth-client": "^1.0.27", + "@deriv-com/auth-client": "^1.0.28", "@deriv/deriv-api": "^1.0.11", "@deriv/quill-icons": "^1.22.10", "@deriv/ui": "^0.8.0", diff --git a/src/components/UserNavbarItem/item.desktop.tsx b/src/components/UserNavbarItem/item.desktop.tsx index bbe065acb..8d83565c2 100644 --- a/src/components/UserNavbarItem/item.desktop.tsx +++ b/src/components/UserNavbarItem/item.desktop.tsx @@ -3,55 +3,21 @@ import clsx from 'clsx'; import React, { useState } from 'react'; import AccountSwitcher from '../AccountSwitcher'; import { IUserNavbarItemProps } from './item.types'; -import { UserManager, WebStorageStateStore } from 'oidc-client-ts'; +import { requestOidcAuthentication } from '@deriv-com/auth-client'; import styles from './UserNavbarItem.module.scss'; import SearchButton from '../SearchButton'; import Translate from '@docusaurus/Translate'; const UserNavbarDesktopItem = ({ authUrl, is_logged_in }: IUserNavbarItemProps) => { const [toggle_search, setToggleSearch] = useState(false); - const serverUrl = localStorage.getItem('config.server_url'); const handleClick = async () => { // location.assign(authUrl); + const app_id = localStorage.getItem('config.app_id'); + const redirect_uri = `${window.location.origin}/dashboard`; + const post_logout_redirect_uri = `${window.location.origin}/`; - const oidc = `https://${serverUrl}/.well-known/openid-configuration`; - const appid = localStorage.getItem('config.app_id'); - - try { - const response = await fetch(oidc); - const data = await response.json(); - - const endpoints = { - authorization_endpoint: data.authorization_endpoint, - token_endpoint: data.token_endpoint, - userinfo_endpoint: data.userinfo_endpoint, - end_session_endpoint: data.end_session_endpoint, - }; - - localStorage.setItem('config.oidc_endpoints', JSON.stringify(endpoints)); - - const userManager = new UserManager({ - authority: data.issuer, - client_id: appid, - redirect_uri: `${window.location.origin}/callback`, - response_type: 'code', - scope: 'openid', - stateStore: new WebStorageStateStore({ store: window.localStorage }), - post_logout_redirect_uri: data.end_session_endpoint, - metadata: { - issuer: data.issuer, - authorization_endpoint: data.authorization_endpoint, - token_endpoint: data.token_endpoint, - userinfo_endpoint: data.userinfo_endpoint, - end_session_endpoint: data.end_session_endpoint, - }, - }); - - await userManager.signinRedirect(); - } catch (error) { - console.log(error); - } + await requestOidcAuthentication(app_id, redirect_uri, post_logout_redirect_uri); }; const logInButtonClasses = clsx( diff --git a/src/features/Callback/index.tsx b/src/features/Callback/index.tsx index 6748b745b..7162e9de6 100644 --- a/src/features/Callback/index.tsx +++ b/src/features/Callback/index.tsx @@ -48,7 +48,6 @@ export default function CallbackComponent() { const data = await response.json(); if (response.ok) { - console.log('Token exchange successful', data); // Handle the access token here (e.g., save it or use it in further API calls) localStorage.setItem('id_token', data.id_token); @@ -65,7 +64,6 @@ export default function CallbackComponent() { ); const legacyData = response.data; - console.log('Legacy token fetch successful', legacyData); const accounts = getAccountsFromSearchParams(legacyData); updateLoginAccounts(accounts); window.location.href = '/dashboard'; @@ -91,7 +89,7 @@ export default function CallbackComponent() { }; exchangeToken(); - }, [code, state, token_endpoint]); + }, [code, state, token_endpoint, updateLoginAccounts]); return ( <> diff --git a/src/features/dashboard/index.tsx b/src/features/dashboard/index.tsx index 637831f54..d821b6a1f 100644 --- a/src/features/dashboard/index.tsx +++ b/src/features/dashboard/index.tsx @@ -3,17 +3,89 @@ import { Login } from '../Auth/Login/Login'; import useAuthContext from '@site/src/hooks/useAuthContext'; import DashboardTabs from './components/Tabs'; import useAppManager from '@site/src/hooks/useAppManager'; +import { getAccountsFromSearchParams } from '@site/src/utils'; +import axios from 'axios'; export const AppManager = () => { - const { is_logged_in } = useAuthContext(); + const { is_logged_in, updateLoginAccounts } = useAuthContext(); const { setIsDashboard, is_dashboard } = useAppManager(); useEffect(() => { setIsDashboard(true); + const exchangeToken = async () => { + try { + const urlParams = new URLSearchParams(window?.location?.search); + const oidc_endpoints = localStorage.getItem('config.oidc_endpoints') || '{}'; + + const token_endpoint = JSON.parse(oidc_endpoints).token_endpoint || ''; + + const code = urlParams.get('code'); + const state = urlParams.get('state'); + const oidc_key = `oidc.${state}`; + + const oidc_data = localStorage.getItem(oidc_key); + const code_verifier = oidc_data ? JSON.parse(oidc_data).code_verifier : null; + const appId = localStorage.getItem('config.app_id'); + + if (!code_verifier) return; + + const response = await fetch(token_endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + }, + body: new URLSearchParams({ + grant_type: 'authorization_code', + redirect_uri: `${window.location.origin}/dashboard`, + code: code, + code_verifier: code_verifier, + client_id: appId, + }).toString(), + }); + + const data = await response.json(); + if (response.ok) { + // Handle the access token here (e.g., save it or use it in further API calls) + localStorage.setItem('id_token', data.id_token); + + try { + const response = await axios.post( + 'https://qa101.deriv.dev/oauth2/legacy/tokens', + {}, + { + headers: { + Authorization: `Bearer ${data.access_token}`, + 'Content-Type': 'application/json', + }, + }, + ); + + const legacyData = response.data; + const accounts = getAccountsFromSearchParams(legacyData); + updateLoginAccounts(accounts); + window.history.replaceState({}, document.title, '/dashboard'); + } catch (error) { + if (error.response) { + const legacyData = error.response.data; + console.error('Error fetching legacy tokens:', legacyData); + } else { + console.error('Failed to fetch legacy tokens:', error); + } + } + } else { + console.error('Error exchanging token:', data); + } + } catch (error) { + console.error('Token exchange failed:', error); + } + }; + + exchangeToken(); return () => { setIsDashboard(false); }; - }, [setIsDashboard]); + }, [setIsDashboard, updateLoginAccounts]); return {is_logged_in ? : }; };