From 012a25f2ab7a0865d35e5b226d4d55727ec4daf5 Mon Sep 17 00:00:00 2001 From: Danh Date: Thu, 14 Sep 2023 16:12:55 +0700 Subject: [PATCH 01/21] set up c c s --- src/constants/index.ts | 4 + src/pages/App.tsx | 7 + src/pages/Oauth/Consent.tsx | 27 +++ src/pages/Oauth/Login.tsx | 173 ++++++++++++++++++ src/pages/Oauth/Logout.tsx | 27 +++ .../components/AuthForm/AuthFormField.tsx | 28 +++ .../AuthForm/AuthFormFieldMessage.tsx | 20 ++ src/pages/Oauth/components/AuthForm/index.tsx | 124 +++++++++++++ src/pages/Oauth/constants/index.ts | 4 + src/pages/Oauth/styled.tsx | 57 ++++++ src/pages/Oauth/utils.ts | 67 +++++++ .../components/TruesightFooter.tsx | 7 +- src/utils/transaction.ts | 13 ++ 13 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 src/pages/Oauth/Consent.tsx create mode 100644 src/pages/Oauth/Login.tsx create mode 100644 src/pages/Oauth/Logout.tsx create mode 100644 src/pages/Oauth/components/AuthForm/AuthFormField.tsx create mode 100644 src/pages/Oauth/components/AuthForm/AuthFormFieldMessage.tsx create mode 100644 src/pages/Oauth/components/AuthForm/index.tsx create mode 100644 src/pages/Oauth/constants/index.ts create mode 100644 src/pages/Oauth/styled.tsx create mode 100644 src/pages/Oauth/utils.ts diff --git a/src/constants/index.ts b/src/constants/index.ts index 7a61112a2e..4806321cd4 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -251,6 +251,10 @@ export const APP_PATHS = { PROFILE_MANAGE: '/manage', ELASTIC_LEGACY: '/elastic-legacy', VERIFY_AUTH: '/auth', + + IAM_LOGIN: '/login', + IAM_LOGOUT: '/logout', + IAM_CONSENT: '/consent', } as const export const TERM_FILES_PATH = { diff --git a/src/pages/App.tsx b/src/pages/App.tsx index acb2fe4ee2..a92dd039b1 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -43,6 +43,10 @@ import { isAddressString, isSupportLimitOrder, shortenAddress } from 'utils' import ElasticLegacyNotice from './ElasticLegacy/ElasticLegacyNotice' import VerifyAuth from './Verify/VerifyAuth' +const Login = lazy(() => import('./Oauth/Login')) +const Logout = lazy(() => import('./Oauth/Logout')) +const Consent = lazy(() => import('./Oauth/Consent')) + // test page for swap only through elastic const ElasticSwap = lazy(() => import('./ElasticSwap')) const SwapV2 = lazy(() => import('./SwapV2')) @@ -430,6 +434,9 @@ export default function App() { } /> } /> + } /> + } /> + } /> } /> diff --git a/src/pages/Oauth/Consent.tsx b/src/pages/Oauth/Consent.tsx new file mode 100644 index 0000000000..af418b7292 --- /dev/null +++ b/src/pages/Oauth/Consent.tsx @@ -0,0 +1,27 @@ +import KyberOauth2 from '@kybernetwork/oauth2' +import { useEffect } from 'react' + +import { ENV_KEY } from 'constants/env' +import useParsedQueryString from 'hooks/useParsedQueryString' +import { PageContainer } from 'pages/Oauth/styled' + +function Page() { + const { consent_challenge } = useParsedQueryString<{ consent_challenge: string }>() + + useEffect(() => { + if (!consent_challenge) return + KyberOauth2.initialize({ mode: ENV_KEY }) + KyberOauth2.oauthUi + .getFlowConsent(consent_challenge) + .then(data => { + console.debug('resp consent', data) + }) + .catch(err => { + console.debug('err consent', err) + }) + }, [consent_challenge]) + + return +} + +export default Page diff --git a/src/pages/Oauth/Login.tsx b/src/pages/Oauth/Login.tsx new file mode 100644 index 0000000000..405542416f --- /dev/null +++ b/src/pages/Oauth/Login.tsx @@ -0,0 +1,173 @@ +import KyberOauth2, { LoginFlow, LoginMethod } from '@kybernetwork/oauth2' +import { useCallback, useEffect, useRef, useState } from 'react' + +import Loader from 'components/Loader' +import { didUserReject } from 'constants/connectors/utils' +import { ENV_KEY } from 'constants/env' +import { useActiveWeb3React, useWeb3React } from 'hooks' +import useParsedQueryString from 'hooks/useParsedQueryString' +import { Col, Container, KyberLogo, TextDesc } from 'pages/Oauth/styled' +import getShortenAddress from 'utils/getShortenAddress' +import { queryStringToObject } from 'utils/string' +import { formatSignature } from 'utils/transaction' + +import AuthForm from './components/AuthForm' +import { BUTTON_IDS } from './constants/index' +import { createSignMessage, getSupportLoginMethods } from './utils' + +const getErrorMsg = (error: any) => { + const data = error?.response?.data + const isExpired = data?.error?.id === 'self_service_flow_expired' + if (isExpired) + return ( + + Time to sign-in is Expired, please{' '} + go back and try again. + + ) + return data?.ui?.messages?.[0]?.text || data?.error?.reason || data?.error?.message || error?.message || error + '' +} + +function Login() { + const { account: address, chainId } = useActiveWeb3React() + const { library: provider } = useWeb3React() + + const [processingSignEth, setProcessingSign] = useState(false) + const [authFormConfig, setAuthFormConfig] = useState() + const [error, setError] = useState('') + const [autoLogin, setAutoLogin] = useState(false) // not waiting for click btn + + const { wallet_address } = useParsedQueryString<{ wallet_address: string }>() + + const loginMethods = getSupportLoginMethods(authFormConfig) + const isSignInEth = loginMethods.includes(LoginMethod.ETH) + const isMismatchEthAddress = + !loginMethods.includes(LoginMethod.GOOGLE) && + isSignInEth && + wallet_address && + address && + wallet_address?.toLowerCase() !== address?.toLowerCase() + + const connectingWallet = useRef(false) + + const signInWithEth = useCallback(async () => { + try { + const siweConfig = authFormConfig?.oauth_client?.metadata?.siwe_config + if (isMismatchEthAddress || !siweConfig || connectingWallet.current || !provider || !address || !chainId) { + return + } + setProcessingSign(true) + const { ui, challenge, issued_at } = authFormConfig + connectingWallet.current = true + const csrf = ui.nodes.find(e => e.attributes.name === 'csrf_token')?.attributes?.value ?? '' + const message = createSignMessage({ + address, + chainId, + nonce: challenge, + issuedAt: issued_at, + ...siweConfig, + }) + + const signature = await provider.getSigner().signMessage(message) + const resp = await KyberOauth2.oauthUi.loginEthereum({ + address, + signature: formatSignature(signature), + csrf, + chainId, + }) + + if (resp) { + connectingWallet.current = false + setProcessingSign(false) + } + } catch (error: any) { + if (!didUserReject(error)) { + setError(getErrorMsg(error)) + } + console.error('signInWithEthereum err', error) + connectingWallet.current = false + setProcessingSign(false) + } + }, [address, provider, authFormConfig, chainId, isMismatchEthAddress]) + + useEffect(() => { + const getFlowLogin = async () => { + try { + KyberOauth2.initialize({ mode: ENV_KEY }) + const loginFlow = await KyberOauth2.oauthUi.getFlowLogin() + if (!loginFlow) return + setAuthFormConfig(loginFlow) + + const { client_id } = loginFlow.oauth_client + const loginMethods = getSupportLoginMethods(loginFlow) + + let autoLogin = false + const isIncludeGoogle = loginMethods.includes(LoginMethod.GOOGLE) + if (loginMethods.length === 1) { + if (loginMethods.includes(LoginMethod.ANONYMOUS)) { + throw new Error('Not found login method for this app') + } + if (isIncludeGoogle) { + autoLogin = true + } + } + // todo + if (loginMethods.includes(LoginMethod.ETH) && !isIncludeGoogle) { + setTimeout(() => document.getElementById(BUTTON_IDS.LOGIN_ETH)?.click(), 200) + } + setAutoLogin(autoLogin) + KyberOauth2.initialize({ clientId: client_id, mode: ENV_KEY }) + + if (autoLogin) setTimeout(() => document.getElementById(BUTTON_IDS.LOGIN_GOOGLE)?.click(), 200) + } catch (error: any) { + const { error_description } = queryStringToObject(window.location.search) + setError(error_description || getErrorMsg(error)) + } + } + getFlowLogin() + }, []) + + const appName = authFormConfig?.oauth_client?.client_name || authFormConfig?.oauth_client?.client_id + + const renderEthMsg = () => + isMismatchEthAddress ? ( + + Your address is mismatched. The expected address is {getShortenAddress(wallet_address)}, but the address + provided is {getShortenAddress(address)}. Please change your wallet address accordingly. + + ) : ( + address && ( + + To get started, please sign-in to verify your ownership of this wallet address {getShortenAddress(address)} + + ) + ) + + return ( + + + + {error ? ( + {error} + ) : autoLogin ? ( + + Checking data ... + + ) : isSignInEth && address ? ( + renderEthMsg() + ) : ( + appName && Please sign in to continue with {appName} + )} + + + + ) +} + +export default Login diff --git a/src/pages/Oauth/Logout.tsx b/src/pages/Oauth/Logout.tsx new file mode 100644 index 0000000000..b131e543d8 --- /dev/null +++ b/src/pages/Oauth/Logout.tsx @@ -0,0 +1,27 @@ +import KyberOauth2 from '@kybernetwork/oauth2' +import { useEffect } from 'react' + +import { ENV_KEY } from 'constants/env' +import useParsedQueryString from 'hooks/useParsedQueryString' +import { PageContainer } from 'pages/Oauth/styled' + +function Logout() { + const { logout_challenge } = useParsedQueryString<{ logout_challenge: string }>() + + useEffect(() => { + if (!logout_challenge) return + KyberOauth2.initialize({ mode: ENV_KEY }) + KyberOauth2.oauthUi + .acceptLogout(logout_challenge) + .then(data => { + console.debug('logout resp', data) + }) + .catch(err => { + console.debug('err logout', err) + }) + }, [logout_challenge]) + + return +} + +export default Logout diff --git a/src/pages/Oauth/components/AuthForm/AuthFormField.tsx b/src/pages/Oauth/components/AuthForm/AuthFormField.tsx new file mode 100644 index 0000000000..a4ab45b476 --- /dev/null +++ b/src/pages/Oauth/components/AuthForm/AuthFormField.tsx @@ -0,0 +1,28 @@ +import { LoginFlowUiNode } from '@kybernetwork/oauth2' +import React from 'react' + +import { ButtonOutlined, ButtonPrimary } from 'components/Button' + +import { BUTTON_IDS } from '../../constants/index' + +interface AuthFormFieldProps extends React.InputHTMLAttributes { + field: LoginFlowUiNode + outline?: boolean +} + +const AuthFormField: React.FC = ({ field, outline }) => { + const attributes = field.attributes + if (field.group === 'oidc') { + const props = { + height: '36px', + id: BUTTON_IDS.LOGIN_GOOGLE, + type: 'submit', + value: attributes.value, + name: attributes.name, + children: <>Sign-In with Google, + } + return React.createElement(outline ? ButtonOutlined : ButtonPrimary, props) + } + return null +} +export default AuthFormField diff --git a/src/pages/Oauth/components/AuthForm/AuthFormFieldMessage.tsx b/src/pages/Oauth/components/AuthForm/AuthFormFieldMessage.tsx new file mode 100644 index 0000000000..40eeac866a --- /dev/null +++ b/src/pages/Oauth/components/AuthForm/AuthFormFieldMessage.tsx @@ -0,0 +1,20 @@ +import { Text } from 'rebass' + +import useTheme from 'hooks/useTheme' + +const AuthFormFieldMessage: React.FC<{ messages?: { type: string; text: string }[] }> = ({ messages }) => { + const theme = useTheme() + if (!messages?.length) return null + + const messageList: JSX.Element[] = messages.map((value, index) => { + return ( + + {value.text} + + ) + }) + + return
{messageList}
+} + +export default AuthFormFieldMessage diff --git a/src/pages/Oauth/components/AuthForm/index.tsx b/src/pages/Oauth/components/AuthForm/index.tsx new file mode 100644 index 0000000000..4198aa4478 --- /dev/null +++ b/src/pages/Oauth/components/AuthForm/index.tsx @@ -0,0 +1,124 @@ +import { LoginFlow, LoginFlowUiNode, LoginMethod } from '@kybernetwork/oauth2' +import React from 'react' +import { Flex, Text } from 'rebass' +import styled from 'styled-components' + +import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import Wallet from 'components/Icons/Wallet' +import Loader from 'components/Loader' +import { useActiveWeb3React } from 'hooks' +import useParsedQueryString from 'hooks/useParsedQueryString' +import { useWalletModalToggle } from 'state/application/hooks' + +import { BUTTON_IDS } from '../../constants/index' +import { getSupportLoginMethods, navigateToUrl } from '../../utils' +import AuthFormField from './AuthFormField' +import AuthFormFieldMessage from './AuthFormFieldMessage' + +const Form = styled.form` + display: flex; + flex-direction: column; + align-items: center; + gap: 14px; +` + +interface AuthFormProps extends React.FormHTMLAttributes { + formConfig: LoginFlow | undefined + autoLogin: boolean + processingSignEth: boolean + signInWithEth: () => void + disableEth: boolean +} + +const Splash = () =>
+ +const AuthForm: React.FC = ({ + children, + formConfig, + autoLogin, + signInWithEth, + processingSignEth, + disableEth, + ...otherProps +}) => { + const { back_uri } = useParsedQueryString<{ back_uri: string }>() + + const loginMethods = getSupportLoginMethods(formConfig) + const showEth = loginMethods.includes(LoginMethod.ETH) && !autoLogin + const hasGoogle = loginMethods.includes(LoginMethod.GOOGLE) + const { account } = useActiveWeb3React() + const toggleWalletModal = useWalletModalToggle() + + const onClickEth = (e: React.MouseEvent) => { + e.preventDefault() + !account ? toggleWalletModal() : signInWithEth() + } + + const renderBtnEth = () => ( + + {processingSignEth ? ( + <> +   Signing In + + ) : ( + <> + +   Sign-In with Wallet + + )} + + ) + + if (!formConfig) return null + const { ui } = formConfig + + const showBtnCancel = !hasGoogle && back_uri && !processingSignEth + const hasBothEthAndGoogle = hasGoogle && showEth + return ( +
+ + {showEth && + (showBtnCancel ? ( + + {showBtnCancel && ( + navigateToUrl(back_uri)} + height={'36px'} + > + Cancel + + )} + {renderBtnEth()} + + ) : ( + renderBtnEth() + ))} + {hasBothEthAndGoogle && ( +
+ or +
+ )} + {hasGoogle && + ui?.nodes?.map((field: LoginFlowUiNode, index: number) => ( + + ))} + + ) +} +export default AuthForm diff --git a/src/pages/Oauth/constants/index.ts b/src/pages/Oauth/constants/index.ts new file mode 100644 index 0000000000..dade1a84e5 --- /dev/null +++ b/src/pages/Oauth/constants/index.ts @@ -0,0 +1,4 @@ +export const BUTTON_IDS = { + LOGIN_GOOGLE: 'btnLoginGoogle', + LOGIN_ETH: 'btnLoginEth', +} diff --git a/src/pages/Oauth/styled.tsx b/src/pages/Oauth/styled.tsx new file mode 100644 index 0000000000..ab1a513232 --- /dev/null +++ b/src/pages/Oauth/styled.tsx @@ -0,0 +1,57 @@ +import styled from 'styled-components' + +import backgroundImage from 'assets/images/truesight-v2/landing-page/background-gradient.png' +import Loader from 'components/Loader' +import { useIsDarkMode } from 'state/user/hooks' + +export const Container = styled.div` + flex: 1; + justify-content: center; + padding: 20px 0; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + background-image: url(${backgroundImage}); + background-size: 100%; + background-repeat: repeat-y; +` + +export const Col = styled.div` + gap: 30px; + display: flex; + flex-direction: column; + align-items: center; + ${({ theme }) => theme.mediaWidth.upToSmall` + gap: 16px; + `}; +` +export const TextDesc = styled.div` + font-size: 20px; + line-height: 24px; + color: ${({ theme }) => theme.subText}; + ${({ theme }) => theme.mediaWidth.upToSmall` + font-size: 16px; + line-height: 20px; + `}; +` + +export const KyberLogo = () => { + const iseDark = useIsDarkMode() + return ( + loading-icon + ) +} + +export function PageContainer({ msg }: { msg: string }) { + return ( + + + + + {msg} + + + + ) +} diff --git a/src/pages/Oauth/utils.ts b/src/pages/Oauth/utils.ts new file mode 100644 index 0000000000..00d67528c1 --- /dev/null +++ b/src/pages/Oauth/utils.ts @@ -0,0 +1,67 @@ +import { LoginFlow } from '@kybernetwork/oauth2' + +export const getSupportLoginMethods = (loginFlow: LoginFlow | undefined) => { + return loginFlow?.oauth_client?.metadata?.allowed_login_methods ?? [] +} + +const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap.com/, /https:\/\/(.+)\.kyberengineering.io/] +const isValidRedirectURL = (url: string | undefined) => { + try { + if (!url) return false + const newUrl = new URL(url) // valid url + if ( + url.endsWith('.js') || + newUrl.pathname.endsWith('.js') || + !whiteListDomains.some(regex => newUrl.origin.match(regex)) + ) { + return false + } + return newUrl.protocol === 'http:' || newUrl.protocol === 'https:' + } catch (error) { + return false + } +} + +export const navigateToUrl = (url: string | undefined) => { + if (url && isValidRedirectURL(url)) window.location.href = url +} + +type MessageParams = { + domain: string + uri: string + address: string + version: string + nonce: string + chainId: number + issuedAt: string + statement: string +} + +// message follow eip https://eips.ethereum.org/EIPS/eip-4361 +export const createSignMessage = ({ + domain, + uri, + address, + version, + nonce, + chainId, + issuedAt, + statement, +}: MessageParams): string => { + let prefix = [`${domain} wants you to sign in with your Ethereum account:`, address].join('\n') + + prefix = [prefix, statement].join('\n\n') + if (statement) { + prefix += '\n' + } + + const suffix = [ + `URI: ${uri}`, + `Version: ${version}`, + `Chain ID: ` + chainId, + `Nonce: ${nonce}`, + `Issued At: ${issuedAt}`, + ].join('\n') + + return [prefix, suffix].join('\n') +} diff --git a/src/pages/TrueSightV2/components/TruesightFooter.tsx b/src/pages/TrueSightV2/components/TruesightFooter.tsx index 8557d9b152..1f2566c7fa 100644 --- a/src/pages/TrueSightV2/components/TruesightFooter.tsx +++ b/src/pages/TrueSightV2/components/TruesightFooter.tsx @@ -41,7 +41,12 @@ export default function TruesightFooter() { const toggle = useWalletModalToggle() const { account } = useActiveWeb3React() const location = useLocation() - if (account || location.pathname.startsWith(APP_PATHS.MY_EARNINGS)) { + if ( + account || + [APP_PATHS.MY_EARNINGS, APP_PATHS.IAM_LOGIN, APP_PATHS.IAM_CONSENT, APP_PATHS.IAM_LOGOUT].some(path => + location.pathname.startsWith(path), + ) + ) { return null } diff --git a/src/utils/transaction.ts b/src/utils/transaction.ts index 1a2e35a0ed..51d866cbec 100644 --- a/src/utils/transaction.ts +++ b/src/utils/transaction.ts @@ -1,3 +1,5 @@ +import { ethers } from 'ethers' + import { GROUP_TRANSACTION_BY_TYPE, TRANSACTION_GROUP, @@ -22,3 +24,14 @@ export const getTransactionStatus = (transaction: TransactionDetails) => { error: !pending && transaction?.receipt?.status !== 1, } } + +// todo danh update limit order use this function +export const formatSignature = (rawSignature: string) => { + const bytes = ethers.utils.arrayify(rawSignature) + const lastByte = bytes[64] + if (lastByte === 0 || lastByte === 1) { + // to support hardware wallet https://ethereum.stackexchange.com/a/113727 + bytes[64] += 27 + } + return ethers.utils.hexlify(bytes) +} From ef52f2aef38a6e3ec22a51c0621fb2ebda5391a3 Mon Sep 17 00:00:00 2001 From: Danh Date: Mon, 18 Sep 2023 10:34:51 +0700 Subject: [PATCH 02/21] css --- src/pages/Oauth/components/AuthForm/index.tsx | 6 ++++-- src/pages/Oauth/styled.tsx | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/Oauth/components/AuthForm/index.tsx b/src/pages/Oauth/components/AuthForm/index.tsx index 4198aa4478..b12e85dbd9 100644 --- a/src/pages/Oauth/components/AuthForm/index.tsx +++ b/src/pages/Oauth/components/AuthForm/index.tsx @@ -1,5 +1,6 @@ import { LoginFlow, LoginFlowUiNode, LoginMethod } from '@kybernetwork/oauth2' import React from 'react' +import { isMobile } from 'react-device-detect' import { Flex, Text } from 'rebass' import styled from 'styled-components' @@ -65,7 +66,8 @@ const AuthForm: React.FC = ({ > {processingSignEth ? ( <> -   Signing In + +   Signing In ) : ( <> @@ -79,7 +81,7 @@ const AuthForm: React.FC = ({ if (!formConfig) return null const { ui } = formConfig - const showBtnCancel = !hasGoogle && back_uri && !processingSignEth + const showBtnCancel = !isMobile && !hasGoogle && back_uri && !processingSignEth const hasBothEthAndGoogle = hasGoogle && showEth return (
theme.subText}; + text-align: center; ${({ theme }) => theme.mediaWidth.upToSmall` font-size: 16px; line-height: 20px; From 824e7224cd373ea1da7835f7dd82bda669091985 Mon Sep 17 00:00:00 2001 From: Danh Date: Mon, 18 Sep 2023 10:38:27 +0700 Subject: [PATCH 03/21] css --- src/pages/Oauth/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Oauth/Login.tsx b/src/pages/Oauth/Login.tsx index 405542416f..9a830afabf 100644 --- a/src/pages/Oauth/Login.tsx +++ b/src/pages/Oauth/Login.tsx @@ -151,7 +151,7 @@ function Login() { {error} ) : autoLogin ? ( - Checking data ... + Checking data ... ) : isSignInEth && address ? ( renderEthMsg() From 3a2a06e7a5787ac5b566a3d70285c3ba92928c13 Mon Sep 17 00:00:00 2001 From: Danh Date: Mon, 18 Sep 2023 11:08:18 +0700 Subject: [PATCH 04/21] update latest code --- src/pages/Oauth/Login.tsx | 10 ++++++---- src/pages/Oauth/utils.ts | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/Oauth/Login.tsx b/src/pages/Oauth/Login.tsx index 9a830afabf..53a2ac6e9e 100644 --- a/src/pages/Oauth/Login.tsx +++ b/src/pages/Oauth/Login.tsx @@ -13,18 +13,20 @@ import { formatSignature } from 'utils/transaction' import AuthForm from './components/AuthForm' import { BUTTON_IDS } from './constants/index' -import { createSignMessage, getSupportLoginMethods } from './utils' +import { createSignMessage, getSupportLoginMethods, isValidRedirectURL } from './utils' const getErrorMsg = (error: any) => { const data = error?.response?.data const isExpired = data?.error?.id === 'self_service_flow_expired' - if (isExpired) + const backUri = queryStringToObject(window.location.search)?.back_uri + '' + if (isExpired && isValidRedirectURL(backUri)) { return ( - Time to sign-in is Expired, please{' '} - go back and try again. + Time to sign-in is Expired, please go back and try again. ) + } + return data?.ui?.messages?.[0]?.text || data?.error?.reason || data?.error?.message || error?.message || error + '' } diff --git a/src/pages/Oauth/utils.ts b/src/pages/Oauth/utils.ts index 00d67528c1..1d5bcdfaba 100644 --- a/src/pages/Oauth/utils.ts +++ b/src/pages/Oauth/utils.ts @@ -5,7 +5,7 @@ export const getSupportLoginMethods = (loginFlow: LoginFlow | undefined) => { } const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap.com/, /https:\/\/(.+)\.kyberengineering.io/] -const isValidRedirectURL = (url: string | undefined) => { +export const isValidRedirectURL = (url: string | undefined) => { try { if (!url) return false const newUrl = new URL(url) // valid url From 198fae61dedce997ec2b5f4083323b8aef8f2783 Mon Sep 17 00:00:00 2001 From: Danh Date: Mon, 18 Sep 2023 14:10:34 +0700 Subject: [PATCH 05/21] update auto sign in eth --- .../AuthForm/AuthFormField.tsx | 6 +- .../AuthForm/AuthFormFieldMessage.tsx | 20 ++-- src/pages/Oauth/AuthForm/ButtonEth.tsx | 40 +++++++ .../Oauth/{components => }/AuthForm/index.tsx | 101 +++++++----------- src/pages/Oauth/Login.tsx | 49 ++++----- src/pages/Oauth/constants/index.ts | 4 - 6 files changed, 119 insertions(+), 101 deletions(-) rename src/pages/Oauth/{components => }/AuthForm/AuthFormField.tsx (87%) rename src/pages/Oauth/{components => }/AuthForm/AuthFormFieldMessage.tsx (50%) create mode 100644 src/pages/Oauth/AuthForm/ButtonEth.tsx rename src/pages/Oauth/{components => }/AuthForm/index.tsx (52%) delete mode 100644 src/pages/Oauth/constants/index.ts diff --git a/src/pages/Oauth/components/AuthForm/AuthFormField.tsx b/src/pages/Oauth/AuthForm/AuthFormField.tsx similarity index 87% rename from src/pages/Oauth/components/AuthForm/AuthFormField.tsx rename to src/pages/Oauth/AuthForm/AuthFormField.tsx index a4ab45b476..864841c4e1 100644 --- a/src/pages/Oauth/components/AuthForm/AuthFormField.tsx +++ b/src/pages/Oauth/AuthForm/AuthFormField.tsx @@ -3,8 +3,6 @@ import React from 'react' import { ButtonOutlined, ButtonPrimary } from 'components/Button' -import { BUTTON_IDS } from '../../constants/index' - interface AuthFormFieldProps extends React.InputHTMLAttributes { field: LoginFlowUiNode outline?: boolean @@ -15,7 +13,7 @@ const AuthFormField: React.FC = ({ field, outline }) => { if (field.group === 'oidc') { const props = { height: '36px', - id: BUTTON_IDS.LOGIN_GOOGLE, + id: 'btnLoginGoogle', type: 'submit', value: attributes.value, name: attributes.name, @@ -23,6 +21,6 @@ const AuthFormField: React.FC = ({ field, outline }) => { } return React.createElement(outline ? ButtonOutlined : ButtonPrimary, props) } - return null + return null // need to update when support more sign in method } export default AuthFormField diff --git a/src/pages/Oauth/components/AuthForm/AuthFormFieldMessage.tsx b/src/pages/Oauth/AuthForm/AuthFormFieldMessage.tsx similarity index 50% rename from src/pages/Oauth/components/AuthForm/AuthFormFieldMessage.tsx rename to src/pages/Oauth/AuthForm/AuthFormFieldMessage.tsx index 40eeac866a..fe979c286b 100644 --- a/src/pages/Oauth/components/AuthForm/AuthFormFieldMessage.tsx +++ b/src/pages/Oauth/AuthForm/AuthFormFieldMessage.tsx @@ -6,15 +6,17 @@ const AuthFormFieldMessage: React.FC<{ messages?: { type: string; text: string } const theme = useTheme() if (!messages?.length) return null - const messageList: JSX.Element[] = messages.map((value, index) => { - return ( - - {value.text} - - ) - }) - - return
{messageList}
+ return ( +
+ {messages.map((value, index) => { + return ( + + {value.text} + + ) + })} +
+ ) } export default AuthFormFieldMessage diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx new file mode 100644 index 0000000000..ced990f59a --- /dev/null +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -0,0 +1,40 @@ +import { Text } from 'rebass' + +import { ButtonPrimary } from 'components/Button' +import Wallet from 'components/Icons/Wallet' +import Loader from 'components/Loader' + +const ButtonEth = ({ + loading, + disabled, + onClick, +}: { + disabled: boolean + loading: boolean + onClick: (e: React.MouseEvent) => void +}) => { + return ( + + {loading ? ( + <> + +   Signing In + + ) : ( + <> + +   Sign-In with Wallet + + )} + + ) +} + +export default ButtonEth diff --git a/src/pages/Oauth/components/AuthForm/index.tsx b/src/pages/Oauth/AuthForm/index.tsx similarity index 52% rename from src/pages/Oauth/components/AuthForm/index.tsx rename to src/pages/Oauth/AuthForm/index.tsx index b12e85dbd9..8edc27f117 100644 --- a/src/pages/Oauth/components/AuthForm/index.tsx +++ b/src/pages/Oauth/AuthForm/index.tsx @@ -1,18 +1,19 @@ import { LoginFlow, LoginFlowUiNode, LoginMethod } from '@kybernetwork/oauth2' -import React from 'react' +import React, { useCallback, useEffect, useRef } from 'react' import { isMobile } from 'react-device-detect' -import { Flex, Text } from 'rebass' +import { Flex } from 'rebass' import styled from 'styled-components' -import { ButtonOutlined, ButtonPrimary } from 'components/Button' -import Wallet from 'components/Icons/Wallet' -import Loader from 'components/Loader' +import { ButtonOutlined } from 'components/Button' import { useActiveWeb3React } from 'hooks' import useParsedQueryString from 'hooks/useParsedQueryString' +import useTheme from 'hooks/useTheme' +import { useEagerConnect } from 'hooks/web3/useEagerConnect' +import ButtonEth from 'pages/Oauth/AuthForm/ButtonEth' +import { FlowStatus } from 'pages/Oauth/Login' import { useWalletModalToggle } from 'state/application/hooks' -import { BUTTON_IDS } from '../../constants/index' -import { getSupportLoginMethods, navigateToUrl } from '../../utils' +import { getSupportLoginMethods, navigateToUrl } from '../utils' import AuthFormField from './AuthFormField' import AuthFormFieldMessage from './AuthFormFieldMessage' @@ -25,59 +26,49 @@ const Form = styled.form` interface AuthFormProps extends React.FormHTMLAttributes { formConfig: LoginFlow | undefined - autoLogin: boolean processingSignEth: boolean signInWithEth: () => void disableEth: boolean + flowStatus: FlowStatus } const Splash = () =>
const AuthForm: React.FC = ({ - children, formConfig, - autoLogin, signInWithEth, + flowStatus, processingSignEth, disableEth, - ...otherProps }) => { const { back_uri } = useParsedQueryString<{ back_uri: string }>() + const theme = useTheme() + const { autoLoginMethod, flowReady } = flowStatus const loginMethods = getSupportLoginMethods(formConfig) - const showEth = loginMethods.includes(LoginMethod.ETH) && !autoLogin + const showEth = loginMethods.includes(LoginMethod.ETH) && autoLoginMethod !== LoginMethod.GOOGLE const hasGoogle = loginMethods.includes(LoginMethod.GOOGLE) const { account } = useActiveWeb3React() const toggleWalletModal = useWalletModalToggle() - const onClickEth = (e: React.MouseEvent) => { - e.preventDefault() - !account ? toggleWalletModal() : signInWithEth() - } - - const renderBtnEth = () => ( - - {processingSignEth ? ( - <> - -   Signing In - - ) : ( - <> - -   Sign-In with Wallet - - )} - + const onClickEth = useCallback( + (e?: React.MouseEvent) => { + e?.preventDefault() + !account ? toggleWalletModal() : signInWithEth() + }, + [toggleWalletModal, signInWithEth, account], ) + const isInit = useRef(false) + const triedEager = useEagerConnect() + const tried = triedEager.current + useEffect(() => { + if (tried && !isInit.current && flowReady && autoLoginMethod === LoginMethod.ETH) { + onClickEth() + isInit.current = true + } + }, [flowReady, autoLoginMethod, onClickEth, tried]) + if (!formConfig) return null const { ui } = formConfig @@ -88,31 +79,21 @@ const AuthForm: React.FC = ({ encType="application/x-www-form-urlencoded" action={ui.action} method={ui.method} - style={{ opacity: autoLogin ? 0 : 1 }} - {...otherProps} - className="login-form" + style={{ opacity: autoLoginMethod === LoginMethod.GOOGLE ? 0 : 1 }} > - {showEth && - (showBtnCancel ? ( - - {showBtnCancel && ( - navigateToUrl(back_uri)} - height={'36px'} - > - Cancel - - )} - {renderBtnEth()} - - ) : ( - renderBtnEth() - ))} + {showEth && ( + + {showBtnCancel && ( + navigateToUrl(back_uri)} height={'36px'}> + Cancel + + )} + + + )} {hasBothEthAndGoogle && ( -
+
or
)} diff --git a/src/pages/Oauth/Login.tsx b/src/pages/Oauth/Login.tsx index 53a2ac6e9e..ae555f8960 100644 --- a/src/pages/Oauth/Login.tsx +++ b/src/pages/Oauth/Login.tsx @@ -11,8 +11,7 @@ import getShortenAddress from 'utils/getShortenAddress' import { queryStringToObject } from 'utils/string' import { formatSignature } from 'utils/transaction' -import AuthForm from './components/AuthForm' -import { BUTTON_IDS } from './constants/index' +import AuthForm from './AuthForm' import { createSignMessage, getSupportLoginMethods, isValidRedirectURL } from './utils' const getErrorMsg = (error: any) => { @@ -30,14 +29,19 @@ const getErrorMsg = (error: any) => { return data?.ui?.messages?.[0]?.text || data?.error?.reason || data?.error?.message || error?.message || error + '' } -function Login() { - const { account: address, chainId } = useActiveWeb3React() +export type FlowStatus = { + flowReady: boolean + autoLoginMethod: LoginMethod | undefined // not waiting for click btn +} + +export function Login() { + const { account, chainId } = useActiveWeb3React() const { library: provider } = useWeb3React() const [processingSignEth, setProcessingSign] = useState(false) const [authFormConfig, setAuthFormConfig] = useState() const [error, setError] = useState('') - const [autoLogin, setAutoLogin] = useState(false) // not waiting for click btn + const [flowStatus, setFlowStatus] = useState({ flowReady: false, autoLoginMethod: undefined }) const { wallet_address } = useParsedQueryString<{ wallet_address: string }>() @@ -47,15 +51,15 @@ function Login() { !loginMethods.includes(LoginMethod.GOOGLE) && isSignInEth && wallet_address && - address && - wallet_address?.toLowerCase() !== address?.toLowerCase() + account && + wallet_address?.toLowerCase() !== account?.toLowerCase() const connectingWallet = useRef(false) const signInWithEth = useCallback(async () => { try { const siweConfig = authFormConfig?.oauth_client?.metadata?.siwe_config - if (isMismatchEthAddress || !siweConfig || connectingWallet.current || !provider || !address || !chainId) { + if (isMismatchEthAddress || !siweConfig || connectingWallet.current || !provider || !account || !chainId) { return } setProcessingSign(true) @@ -63,7 +67,7 @@ function Login() { connectingWallet.current = true const csrf = ui.nodes.find(e => e.attributes.name === 'csrf_token')?.attributes?.value ?? '' const message = createSignMessage({ - address, + address: account, chainId, nonce: challenge, issuedAt: issued_at, @@ -72,7 +76,7 @@ function Login() { const signature = await provider.getSigner().signMessage(message) const resp = await KyberOauth2.oauthUi.loginEthereum({ - address, + address: account, signature: formatSignature(signature), csrf, chainId, @@ -90,7 +94,7 @@ function Login() { connectingWallet.current = false setProcessingSign(false) } - }, [address, provider, authFormConfig, chainId, isMismatchEthAddress]) + }, [account, provider, authFormConfig, chainId, isMismatchEthAddress]) useEffect(() => { const getFlowLogin = async () => { @@ -103,24 +107,21 @@ function Login() { const { client_id } = loginFlow.oauth_client const loginMethods = getSupportLoginMethods(loginFlow) - let autoLogin = false + let autoLoginMethod: LoginMethod | undefined const isIncludeGoogle = loginMethods.includes(LoginMethod.GOOGLE) if (loginMethods.length === 1) { if (loginMethods.includes(LoginMethod.ANONYMOUS)) { throw new Error('Not found login method for this app') } if (isIncludeGoogle) { - autoLogin = true + autoLoginMethod = LoginMethod.GOOGLE } } - // todo if (loginMethods.includes(LoginMethod.ETH) && !isIncludeGoogle) { - setTimeout(() => document.getElementById(BUTTON_IDS.LOGIN_ETH)?.click(), 200) + autoLoginMethod = LoginMethod.ETH } - setAutoLogin(autoLogin) KyberOauth2.initialize({ clientId: client_id, mode: ENV_KEY }) - - if (autoLogin) setTimeout(() => document.getElementById(BUTTON_IDS.LOGIN_GOOGLE)?.click(), 200) + setFlowStatus({ flowReady: true, autoLoginMethod }) } catch (error: any) { const { error_description } = queryStringToObject(window.location.search) setError(error_description || getErrorMsg(error)) @@ -135,12 +136,12 @@ function Login() { isMismatchEthAddress ? ( Your address is mismatched. The expected address is {getShortenAddress(wallet_address)}, but the address - provided is {getShortenAddress(address)}. Please change your wallet address accordingly. + provided is {getShortenAddress(account)}. Please change your wallet address accordingly. ) : ( - address && ( + account && ( - To get started, please sign-in to verify your ownership of this wallet address {getShortenAddress(address)} + To get started, please sign-in to verify your ownership of this wallet address {getShortenAddress(account)} ) ) @@ -151,18 +152,18 @@ function Login() { {error ? ( {error} - ) : autoLogin ? ( + ) : flowStatus.autoLoginMethod === LoginMethod.GOOGLE ? ( Checking data ... - ) : isSignInEth && address ? ( + ) : isSignInEth && account ? ( renderEthMsg() ) : ( appName && Please sign in to continue with {appName} )} Date: Mon, 18 Sep 2023 14:53:14 +0700 Subject: [PATCH 06/21] auto login gg --- src/pages/Oauth/AuthForm/AuthFormField.tsx | 24 ++++++++-- src/pages/Oauth/AuthForm/ButtonEth.tsx | 23 ++++++++- src/pages/Oauth/AuthForm/index.tsx | 55 ++++++++++++---------- 3 files changed, 70 insertions(+), 32 deletions(-) diff --git a/src/pages/Oauth/AuthForm/AuthFormField.tsx b/src/pages/Oauth/AuthForm/AuthFormField.tsx index 864841c4e1..3ad2e5af62 100644 --- a/src/pages/Oauth/AuthForm/AuthFormField.tsx +++ b/src/pages/Oauth/AuthForm/AuthFormField.tsx @@ -1,22 +1,36 @@ -import { LoginFlowUiNode } from '@kybernetwork/oauth2' -import React from 'react' +import { LoginFlowUiNode, LoginMethod } from '@kybernetwork/oauth2' +import React, { useCallback, useRef } from 'react' import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import { useAutoSignIn } from 'pages/Oauth/AuthForm' +import { FlowStatus } from 'pages/Oauth/Login' interface AuthFormFieldProps extends React.InputHTMLAttributes { field: LoginFlowUiNode - outline?: boolean + outline: boolean + flowStatus: FlowStatus } -const AuthFormField: React.FC = ({ field, outline }) => { +const AuthFormField: React.FC = ({ field, outline, flowStatus }) => { const attributes = field.attributes - if (field.group === 'oidc') { + const ref = useRef(null) + const isGoogleBtn = field.group === 'oidc' + + const onClick = useCallback(() => { + if (!isGoogleBtn) return + ref.current?.click?.() + }, [isGoogleBtn]) + + useAutoSignIn({ onClick, flowStatus, method: LoginMethod.GOOGLE }) + + if (isGoogleBtn) { const props = { height: '36px', id: 'btnLoginGoogle', type: 'submit', value: attributes.value, name: attributes.name, + ref, children: <>Sign-In with Google, } return React.createElement(outline ? ButtonOutlined : ButtonPrimary, props) diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index ced990f59a..ce994fe25e 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -1,18 +1,39 @@ +import { LoginMethod } from '@kybernetwork/oauth2' +import { useCallback } from 'react' import { Text } from 'rebass' import { ButtonPrimary } from 'components/Button' import Wallet from 'components/Icons/Wallet' import Loader from 'components/Loader' +import { useActiveWeb3React } from 'hooks' +import { useAutoSignIn } from 'pages/Oauth/AuthForm' +import { FlowStatus } from 'pages/Oauth/Login' +import { useWalletModalToggle } from 'state/application/hooks' const ButtonEth = ({ loading, disabled, onClick, + flowStatus, }: { disabled: boolean loading: boolean - onClick: (e: React.MouseEvent) => void + onClick: () => void + flowStatus: FlowStatus }) => { + const toggleWalletModal = useWalletModalToggle() + const { account } = useActiveWeb3React() + + const onClickEth = useCallback( + (e?: React.MouseEvent) => { + e?.preventDefault?.() + !account ? toggleWalletModal() : onClick() + }, + [toggleWalletModal, onClick, account], + ) + + useAutoSignIn({ onClick: onClickEth, flowStatus, method: LoginMethod.ETH }) + return ( { const Splash = () =>
+export const useAutoSignIn = ({ + onClick, + method, + flowStatus: { flowReady, autoLoginMethod }, +}: { + onClick: (e?: React.MouseEvent) => void + method: LoginMethod + flowStatus: FlowStatus +}) => { + const autoSelect = useRef(false) + const { current: tried } = useEagerConnect() + useEffect(() => { + if (autoSelect.current || !flowReady || autoLoginMethod !== method) return + if ((tried && autoLoginMethod === LoginMethod.ETH) || autoLoginMethod === LoginMethod.GOOGLE) { + autoSelect.current = true + onClick() + } + }, [flowReady, autoLoginMethod, onClick, tried, method]) +} + const AuthForm: React.FC = ({ formConfig, signInWithEth, @@ -43,31 +61,11 @@ const AuthForm: React.FC = ({ }) => { const { back_uri } = useParsedQueryString<{ back_uri: string }>() const theme = useTheme() - const { autoLoginMethod, flowReady } = flowStatus + const { autoLoginMethod } = flowStatus const loginMethods = getSupportLoginMethods(formConfig) const showEth = loginMethods.includes(LoginMethod.ETH) && autoLoginMethod !== LoginMethod.GOOGLE const hasGoogle = loginMethods.includes(LoginMethod.GOOGLE) - const { account } = useActiveWeb3React() - const toggleWalletModal = useWalletModalToggle() - - const onClickEth = useCallback( - (e?: React.MouseEvent) => { - e?.preventDefault() - !account ? toggleWalletModal() : signInWithEth() - }, - [toggleWalletModal, signInWithEth, account], - ) - - const isInit = useRef(false) - const triedEager = useEagerConnect() - const tried = triedEager.current - useEffect(() => { - if (tried && !isInit.current && flowReady && autoLoginMethod === LoginMethod.ETH) { - onClickEth() - isInit.current = true - } - }, [flowReady, autoLoginMethod, onClickEth, tried]) if (!formConfig) return null const { ui } = formConfig @@ -89,7 +87,12 @@ const AuthForm: React.FC = ({ Cancel )} - + )} {hasBothEthAndGoogle && ( @@ -99,7 +102,7 @@ const AuthForm: React.FC = ({ )} {hasGoogle && ui?.nodes?.map((field: LoginFlowUiNode, index: number) => ( - + ))} ) From 7da344c587e13271e486cc9a1e73bc2bba9a1302 Mon Sep 17 00:00:00 2001 From: Danh Date: Mon, 18 Sep 2023 14:57:16 +0700 Subject: [PATCH 07/21] refactor --- src/pages/Oauth/AuthForm/AuthFormField.tsx | 2 +- src/pages/Oauth/AuthForm/ButtonEth.tsx | 2 +- src/pages/Oauth/AuthForm/index.tsx | 23 +----------------- src/pages/Oauth/AuthForm/useAutoSignIn.tsx | 27 ++++++++++++++++++++++ 4 files changed, 30 insertions(+), 24 deletions(-) create mode 100644 src/pages/Oauth/AuthForm/useAutoSignIn.tsx diff --git a/src/pages/Oauth/AuthForm/AuthFormField.tsx b/src/pages/Oauth/AuthForm/AuthFormField.tsx index 3ad2e5af62..8392cb986e 100644 --- a/src/pages/Oauth/AuthForm/AuthFormField.tsx +++ b/src/pages/Oauth/AuthForm/AuthFormField.tsx @@ -2,7 +2,7 @@ import { LoginFlowUiNode, LoginMethod } from '@kybernetwork/oauth2' import React, { useCallback, useRef } from 'react' import { ButtonOutlined, ButtonPrimary } from 'components/Button' -import { useAutoSignIn } from 'pages/Oauth/AuthForm' +import useAutoSignIn from 'pages/Oauth/AuthForm/useAutoSignIn' import { FlowStatus } from 'pages/Oauth/Login' interface AuthFormFieldProps extends React.InputHTMLAttributes { diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index ce994fe25e..49e5054fe8 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -6,7 +6,7 @@ import { ButtonPrimary } from 'components/Button' import Wallet from 'components/Icons/Wallet' import Loader from 'components/Loader' import { useActiveWeb3React } from 'hooks' -import { useAutoSignIn } from 'pages/Oauth/AuthForm' +import useAutoSignIn from 'pages/Oauth/AuthForm/useAutoSignIn' import { FlowStatus } from 'pages/Oauth/Login' import { useWalletModalToggle } from 'state/application/hooks' diff --git a/src/pages/Oauth/AuthForm/index.tsx b/src/pages/Oauth/AuthForm/index.tsx index bb43772833..e0ec139795 100644 --- a/src/pages/Oauth/AuthForm/index.tsx +++ b/src/pages/Oauth/AuthForm/index.tsx @@ -1,5 +1,5 @@ import { LoginFlow, LoginFlowUiNode, LoginMethod } from '@kybernetwork/oauth2' -import React, { useEffect, useRef } from 'react' +import React from 'react' import { isMobile } from 'react-device-detect' import { Flex } from 'rebass' import styled from 'styled-components' @@ -7,7 +7,6 @@ import styled from 'styled-components' import { ButtonOutlined } from 'components/Button' import useParsedQueryString from 'hooks/useParsedQueryString' import useTheme from 'hooks/useTheme' -import { useEagerConnect } from 'hooks/web3/useEagerConnect' import ButtonEth from 'pages/Oauth/AuthForm/ButtonEth' import { FlowStatus } from 'pages/Oauth/Login' @@ -32,26 +31,6 @@ interface AuthFormProps extends React.FormHTMLAttributes { const Splash = () =>
-export const useAutoSignIn = ({ - onClick, - method, - flowStatus: { flowReady, autoLoginMethod }, -}: { - onClick: (e?: React.MouseEvent) => void - method: LoginMethod - flowStatus: FlowStatus -}) => { - const autoSelect = useRef(false) - const { current: tried } = useEagerConnect() - useEffect(() => { - if (autoSelect.current || !flowReady || autoLoginMethod !== method) return - if ((tried && autoLoginMethod === LoginMethod.ETH) || autoLoginMethod === LoginMethod.GOOGLE) { - autoSelect.current = true - onClick() - } - }, [flowReady, autoLoginMethod, onClick, tried, method]) -} - const AuthForm: React.FC = ({ formConfig, signInWithEth, diff --git a/src/pages/Oauth/AuthForm/useAutoSignIn.tsx b/src/pages/Oauth/AuthForm/useAutoSignIn.tsx new file mode 100644 index 0000000000..25c825ab2e --- /dev/null +++ b/src/pages/Oauth/AuthForm/useAutoSignIn.tsx @@ -0,0 +1,27 @@ +import { LoginMethod } from '@kybernetwork/oauth2' +import { useEffect, useRef } from 'react' + +import { useEagerConnect } from 'hooks/web3/useEagerConnect' +import { FlowStatus } from 'pages/Oauth/Login' + +const useAutoSignIn = ({ + onClick, + method, + flowStatus: { flowReady, autoLoginMethod }, +}: { + onClick: (e?: React.MouseEvent) => void + method: LoginMethod + flowStatus: FlowStatus +}) => { + const autoSelect = useRef(false) + const { current: triedEager } = useEagerConnect() + useEffect(() => { + if (autoSelect.current || !flowReady || autoLoginMethod !== method) return + if ((triedEager && autoLoginMethod === LoginMethod.ETH) || autoLoginMethod === LoginMethod.GOOGLE) { + autoSelect.current = true + onClick() + } + }, [flowReady, autoLoginMethod, onClick, triedEager, method]) +} + +export default useAutoSignIn From 6732c9c068656d7fa3d4ccd74b2f75eff555ba8e Mon Sep 17 00:00:00 2001 From: Danh Date: Fri, 22 Sep 2023 16:08:02 +0700 Subject: [PATCH 08/21] prevent default --- src/pages/Oauth/AuthForm/index.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/Oauth/AuthForm/index.tsx b/src/pages/Oauth/AuthForm/index.tsx index e0ec139795..cfa90c9bac 100644 --- a/src/pages/Oauth/AuthForm/index.tsx +++ b/src/pages/Oauth/AuthForm/index.tsx @@ -62,7 +62,14 @@ const AuthForm: React.FC = ({ {showEth && ( {showBtnCancel && ( - navigateToUrl(back_uri)} height={'36px'}> + { + e.preventDefault() + navigateToUrl(back_uri) + }} + height={'36px'} + > Cancel )} From a2b674d7c070f5019fc5bc05b3039580abba11df Mon Sep 17 00:00:00 2001 From: Danh Date: Sun, 24 Sep 2023 14:16:59 +0700 Subject: [PATCH 09/21] refactor s s c s --- src/pages/Oauth/AuthForm/AuthFormField.tsx | 40 ----------- ...rmFieldMessage.tsx => AuthFormMessage.tsx} | 0 src/pages/Oauth/AuthForm/ButtonEth.tsx | 60 ++++++++++------ src/pages/Oauth/AuthForm/ButtonGoogle.tsx | 36 ++++++++++ src/pages/Oauth/AuthForm/index.tsx | 70 ++++++------------- src/pages/Oauth/Login.tsx | 25 ++++--- src/pages/Oauth/{utils.ts => helpers.ts} | 0 src/pages/Oauth/styled.tsx | 6 +- 8 files changed, 112 insertions(+), 125 deletions(-) delete mode 100644 src/pages/Oauth/AuthForm/AuthFormField.tsx rename src/pages/Oauth/AuthForm/{AuthFormFieldMessage.tsx => AuthFormMessage.tsx} (100%) create mode 100644 src/pages/Oauth/AuthForm/ButtonGoogle.tsx rename src/pages/Oauth/{utils.ts => helpers.ts} (100%) diff --git a/src/pages/Oauth/AuthForm/AuthFormField.tsx b/src/pages/Oauth/AuthForm/AuthFormField.tsx deleted file mode 100644 index 8392cb986e..0000000000 --- a/src/pages/Oauth/AuthForm/AuthFormField.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { LoginFlowUiNode, LoginMethod } from '@kybernetwork/oauth2' -import React, { useCallback, useRef } from 'react' - -import { ButtonOutlined, ButtonPrimary } from 'components/Button' -import useAutoSignIn from 'pages/Oauth/AuthForm/useAutoSignIn' -import { FlowStatus } from 'pages/Oauth/Login' - -interface AuthFormFieldProps extends React.InputHTMLAttributes { - field: LoginFlowUiNode - outline: boolean - flowStatus: FlowStatus -} - -const AuthFormField: React.FC = ({ field, outline, flowStatus }) => { - const attributes = field.attributes - const ref = useRef(null) - const isGoogleBtn = field.group === 'oidc' - - const onClick = useCallback(() => { - if (!isGoogleBtn) return - ref.current?.click?.() - }, [isGoogleBtn]) - - useAutoSignIn({ onClick, flowStatus, method: LoginMethod.GOOGLE }) - - if (isGoogleBtn) { - const props = { - height: '36px', - id: 'btnLoginGoogle', - type: 'submit', - value: attributes.value, - name: attributes.name, - ref, - children: <>Sign-In with Google, - } - return React.createElement(outline ? ButtonOutlined : ButtonPrimary, props) - } - return null // need to update when support more sign in method -} -export default AuthFormField diff --git a/src/pages/Oauth/AuthForm/AuthFormFieldMessage.tsx b/src/pages/Oauth/AuthForm/AuthFormMessage.tsx similarity index 100% rename from src/pages/Oauth/AuthForm/AuthFormFieldMessage.tsx rename to src/pages/Oauth/AuthForm/AuthFormMessage.tsx diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index 49e5054fe8..d07e56e0bd 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -1,8 +1,8 @@ import { LoginMethod } from '@kybernetwork/oauth2' import { useCallback } from 'react' -import { Text } from 'rebass' +import { Flex, Text } from 'rebass' -import { ButtonPrimary } from 'components/Button' +import { ButtonOutlined, ButtonPrimary } from 'components/Button' import Wallet from 'components/Icons/Wallet' import Loader from 'components/Loader' import { useActiveWeb3React } from 'hooks' @@ -15,11 +15,15 @@ const ButtonEth = ({ disabled, onClick, flowStatus, + showBtnCancel, + onClickCancel, }: { disabled: boolean loading: boolean onClick: () => void + onClickCancel: () => void flowStatus: FlowStatus + showBtnCancel: boolean }) => { const toggleWalletModal = useWalletModalToggle() const { account } = useActiveWeb3React() @@ -35,26 +39,40 @@ const ButtonEth = ({ useAutoSignIn({ onClick: onClickEth, flowStatus, method: LoginMethod.ETH }) return ( - - {loading ? ( - <> - -   Signing In - - ) : ( - <> - -   Sign-In with Wallet - + + {showBtnCancel && ( + { + e.preventDefault() + onClickCancel() + }} + height={'36px'} + > + Cancel + )} - + + {loading ? ( + <> + +   Signing In + + ) : ( + <> + +   Sign-In with Wallet + + )} + + ) } diff --git a/src/pages/Oauth/AuthForm/ButtonGoogle.tsx b/src/pages/Oauth/AuthForm/ButtonGoogle.tsx new file mode 100644 index 0000000000..4989c785f6 --- /dev/null +++ b/src/pages/Oauth/AuthForm/ButtonGoogle.tsx @@ -0,0 +1,36 @@ +import { LoginMethod } from '@kybernetwork/oauth2' +import React, { useCallback, useRef } from 'react' + +import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import useAutoSignIn from 'pages/Oauth/AuthForm/useAutoSignIn' +import { FlowStatus } from 'pages/Oauth/Login' + +interface Props { + outline: boolean + flowStatus: FlowStatus +} + +const ButtonGoogle: React.FC = ({ outline, flowStatus }) => { + const ref = useRef(null) + const { autoLoginMethod } = flowStatus + const isAutoLogin = autoLoginMethod === LoginMethod.GOOGLE + + const onClick = useCallback(() => { + ref.current?.click?.() + }, []) + + useAutoSignIn({ onClick, flowStatus, method: LoginMethod.GOOGLE }) + + const props = { + height: '36px', + id: 'btnLoginGoogle', + type: 'submit', + value: 'google', + name: 'provider', + ref, + children: <>Sign-In with Google, + style: isAutoLogin ? { opacity: 0 } : undefined, + } + return React.createElement(outline ? ButtonOutlined : ButtonPrimary, props) +} +export default ButtonGoogle diff --git a/src/pages/Oauth/AuthForm/index.tsx b/src/pages/Oauth/AuthForm/index.tsx index cfa90c9bac..989b896b7a 100644 --- a/src/pages/Oauth/AuthForm/index.tsx +++ b/src/pages/Oauth/AuthForm/index.tsx @@ -1,18 +1,16 @@ -import { LoginFlow, LoginFlowUiNode, LoginMethod } from '@kybernetwork/oauth2' +import { LoginFlow, LoginMethod } from '@kybernetwork/oauth2' import React from 'react' import { isMobile } from 'react-device-detect' -import { Flex } from 'rebass' import styled from 'styled-components' -import { ButtonOutlined } from 'components/Button' import useParsedQueryString from 'hooks/useParsedQueryString' import useTheme from 'hooks/useTheme' import ButtonEth from 'pages/Oauth/AuthForm/ButtonEth' +import ButtonGoogle from 'pages/Oauth/AuthForm/ButtonGoogle' import { FlowStatus } from 'pages/Oauth/Login' -import { getSupportLoginMethods, navigateToUrl } from '../utils' -import AuthFormField from './AuthFormField' -import AuthFormFieldMessage from './AuthFormFieldMessage' +import { getSupportLoginMethods, navigateToUrl } from '../helpers' +import AuthFormFieldMessage from './AuthFormMessage' const Form = styled.form` display: flex; @@ -23,7 +21,6 @@ const Form = styled.form` interface AuthFormProps extends React.FormHTMLAttributes { formConfig: LoginFlow | undefined - processingSignEth: boolean signInWithEth: () => void disableEth: boolean flowStatus: FlowStatus @@ -31,65 +28,38 @@ interface AuthFormProps extends React.FormHTMLAttributes { const Splash = () =>
-const AuthForm: React.FC = ({ - formConfig, - signInWithEth, - flowStatus, - processingSignEth, - disableEth, -}) => { +const AuthForm: React.FC = ({ formConfig, signInWithEth, flowStatus, disableEth }) => { const { back_uri } = useParsedQueryString<{ back_uri: string }>() const theme = useTheme() - const { autoLoginMethod } = flowStatus + if (!formConfig) return null + const { autoLoginMethod, processingSignIn } = flowStatus + const { ui } = formConfig const loginMethods = getSupportLoginMethods(formConfig) + const showEth = loginMethods.includes(LoginMethod.ETH) && autoLoginMethod !== LoginMethod.GOOGLE const hasGoogle = loginMethods.includes(LoginMethod.GOOGLE) - - if (!formConfig) return null - const { ui } = formConfig - - const showBtnCancel = !isMobile && !hasGoogle && back_uri && !processingSignEth + const showBtnCancel = !isMobile && !hasGoogle && back_uri && !processingSignIn const hasBothEthAndGoogle = hasGoogle && showEth return ( -
+ {showEth && ( - - {showBtnCancel && ( - { - e.preventDefault() - navigateToUrl(back_uri) - }} - height={'36px'} - > - Cancel - - )} - - + navigateToUrl(back_uri)} + showBtnCancel={!!showBtnCancel} + onClick={signInWithEth} + disabled={processingSignIn || disableEth} + loading={processingSignIn} + flowStatus={flowStatus} + /> )} {hasBothEthAndGoogle && (
or
)} - {hasGoogle && - ui?.nodes?.map((field: LoginFlowUiNode, index: number) => ( - - ))} + {hasGoogle && } ) } diff --git a/src/pages/Oauth/Login.tsx b/src/pages/Oauth/Login.tsx index ae555f8960..3b789809de 100644 --- a/src/pages/Oauth/Login.tsx +++ b/src/pages/Oauth/Login.tsx @@ -6,13 +6,13 @@ import { didUserReject } from 'constants/connectors/utils' import { ENV_KEY } from 'constants/env' import { useActiveWeb3React, useWeb3React } from 'hooks' import useParsedQueryString from 'hooks/useParsedQueryString' -import { Col, Container, KyberLogo, TextDesc } from 'pages/Oauth/styled' +import { Container, Content, KyberLogo, TextDesc } from 'pages/Oauth/styled' import getShortenAddress from 'utils/getShortenAddress' import { queryStringToObject } from 'utils/string' import { formatSignature } from 'utils/transaction' import AuthForm from './AuthForm' -import { createSignMessage, getSupportLoginMethods, isValidRedirectURL } from './utils' +import { createSignMessage, getSupportLoginMethods, isValidRedirectURL } from './helpers' const getErrorMsg = (error: any) => { const data = error?.response?.data @@ -30,6 +30,7 @@ const getErrorMsg = (error: any) => { } export type FlowStatus = { + processingSignIn: boolean flowReady: boolean autoLoginMethod: LoginMethod | undefined // not waiting for click btn } @@ -38,10 +39,13 @@ export function Login() { const { account, chainId } = useActiveWeb3React() const { library: provider } = useWeb3React() - const [processingSignEth, setProcessingSign] = useState(false) const [authFormConfig, setAuthFormConfig] = useState() const [error, setError] = useState('') - const [flowStatus, setFlowStatus] = useState({ flowReady: false, autoLoginMethod: undefined }) + const [flowStatus, setFlowStatus] = useState({ + flowReady: false, + autoLoginMethod: undefined, + processingSignIn: false, + }) const { wallet_address } = useParsedQueryString<{ wallet_address: string }>() @@ -62,7 +66,7 @@ export function Login() { if (isMismatchEthAddress || !siweConfig || connectingWallet.current || !provider || !account || !chainId) { return } - setProcessingSign(true) + setFlowStatus(v => ({ ...v, processingSignIn: true })) const { ui, challenge, issued_at } = authFormConfig connectingWallet.current = true const csrf = ui.nodes.find(e => e.attributes.name === 'csrf_token')?.attributes?.value ?? '' @@ -84,7 +88,7 @@ export function Login() { if (resp) { connectingWallet.current = false - setProcessingSign(false) + setFlowStatus(v => ({ ...v, processingSignIn: false })) } } catch (error: any) { if (!didUserReject(error)) { @@ -92,7 +96,7 @@ export function Login() { } console.error('signInWithEthereum err', error) connectingWallet.current = false - setProcessingSign(false) + setFlowStatus(v => ({ ...v, processingSignIn: false })) } }, [account, provider, authFormConfig, chainId, isMismatchEthAddress]) @@ -121,7 +125,7 @@ export function Login() { autoLoginMethod = LoginMethod.ETH } KyberOauth2.initialize({ clientId: client_id, mode: ENV_KEY }) - setFlowStatus({ flowReady: true, autoLoginMethod }) + setFlowStatus(v => ({ ...v, flowReady: true, autoLoginMethod })) } catch (error: any) { const { error_description } = queryStringToObject(window.location.search) setError(error_description || getErrorMsg(error)) @@ -148,7 +152,7 @@ export function Login() { return ( - + {error ? ( {error} @@ -165,10 +169,9 @@ export function Login() { formConfig={authFormConfig} flowStatus={flowStatus} signInWithEth={signInWithEth} - processingSignEth={processingSignEth} disableEth={!!isMismatchEthAddress} /> - + ) } diff --git a/src/pages/Oauth/utils.ts b/src/pages/Oauth/helpers.ts similarity index 100% rename from src/pages/Oauth/utils.ts rename to src/pages/Oauth/helpers.ts diff --git a/src/pages/Oauth/styled.tsx b/src/pages/Oauth/styled.tsx index 89ea46fa80..5c5dc8d41a 100644 --- a/src/pages/Oauth/styled.tsx +++ b/src/pages/Oauth/styled.tsx @@ -17,7 +17,7 @@ export const Container = styled.div` background-repeat: repeat-y; ` -export const Col = styled.div` +export const Content = styled.div` gap: 30px; display: flex; flex-direction: column; @@ -47,12 +47,12 @@ export const KyberLogo = () => { export function PageContainer({ msg }: { msg: string }) { return ( - + {msg} - + ) } From 54350dec5a5381cec877f57794a6f372feca5ba8 Mon Sep 17 00:00:00 2001 From: Danh Date: Mon, 25 Sep 2023 11:19:02 +0700 Subject: [PATCH 10/21] update regex --- src/pages/Oauth/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Oauth/helpers.ts b/src/pages/Oauth/helpers.ts index 1d5bcdfaba..c868714ac3 100644 --- a/src/pages/Oauth/helpers.ts +++ b/src/pages/Oauth/helpers.ts @@ -4,7 +4,7 @@ export const getSupportLoginMethods = (loginFlow: LoginFlow | undefined) => { return loginFlow?.oauth_client?.metadata?.allowed_login_methods ?? [] } -const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap.com/, /https:\/\/(.+)\.kyberengineering.io/] +const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap\.com$/, /https:\/\/(.+)\.kyberengineering\.io$/] export const isValidRedirectURL = (url: string | undefined) => { try { if (!url) return false From 1002f0239410fdabed332d61c1b7462212f044e3 Mon Sep 17 00:00:00 2001 From: Danh Date: Tue, 26 Sep 2023 23:54:55 +0700 Subject: [PATCH 11/21] rename id s --- src/pages/Oauth/AuthForm/ButtonEth.tsx | 2 +- src/pages/Oauth/Login.tsx | 11 +++-------- src/pages/Oauth/helpers.ts | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index d07e56e0bd..434330f01d 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -56,7 +56,7 @@ const ButtonEth = ({ width={'230px'} height={'36px'} className="login-btn" - id={'btnLoginGoogle'} + id={'btnLoginEth'} onClick={onClick} disabled={disabled} > diff --git a/src/pages/Oauth/Login.tsx b/src/pages/Oauth/Login.tsx index 3b789809de..7a82a6d9d0 100644 --- a/src/pages/Oauth/Login.tsx +++ b/src/pages/Oauth/Login.tsx @@ -12,18 +12,13 @@ import { queryStringToObject } from 'utils/string' import { formatSignature } from 'utils/transaction' import AuthForm from './AuthForm' -import { createSignMessage, getSupportLoginMethods, isValidRedirectURL } from './helpers' +import { createSignMessage, getSupportLoginMethods } from './helpers' const getErrorMsg = (error: any) => { const data = error?.response?.data const isExpired = data?.error?.id === 'self_service_flow_expired' - const backUri = queryStringToObject(window.location.search)?.back_uri + '' - if (isExpired && isValidRedirectURL(backUri)) { - return ( - - Time to sign-in is Expired, please go back and try again. - - ) + if (isExpired) { + return Time to sign-in is Expired, please go back and try again. } return data?.ui?.messages?.[0]?.text || data?.error?.reason || data?.error?.message || error?.message || error + '' diff --git a/src/pages/Oauth/helpers.ts b/src/pages/Oauth/helpers.ts index c868714ac3..472533ce1f 100644 --- a/src/pages/Oauth/helpers.ts +++ b/src/pages/Oauth/helpers.ts @@ -5,7 +5,7 @@ export const getSupportLoginMethods = (loginFlow: LoginFlow | undefined) => { } const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap\.com$/, /https:\/\/(.+)\.kyberengineering\.io$/] -export const isValidRedirectURL = (url: string | undefined) => { +const isValidRedirectURL = (url: string | undefined) => { try { if (!url) return false const newUrl = new URL(url) // valid url From 68c46918f0807d3d6d8b99634ddfff43c42a4626 Mon Sep 17 00:00:00 2001 From: Danh Date: Tue, 3 Oct 2023 15:07:35 +0700 Subject: [PATCH 12/21] add trans --- src/pages/Oauth/Consent.tsx | 12 +++++++++++- src/pages/Oauth/Login.tsx | 7 ++++++- src/pages/Oauth/Logout.tsx | 12 +++++++++++- src/pages/Oauth/styled.tsx | 3 ++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/pages/Oauth/Consent.tsx b/src/pages/Oauth/Consent.tsx index af418b7292..e6ae77a562 100644 --- a/src/pages/Oauth/Consent.tsx +++ b/src/pages/Oauth/Consent.tsx @@ -1,6 +1,8 @@ import KyberOauth2 from '@kybernetwork/oauth2' +import { Trans } from '@lingui/macro' import { useEffect } from 'react' +import Dots from 'components/Dots' import { ENV_KEY } from 'constants/env' import useParsedQueryString from 'hooks/useParsedQueryString' import { PageContainer } from 'pages/Oauth/styled' @@ -21,7 +23,15 @@ function Page() { }) }, [consent_challenge]) - return + return ( + + Checking data + + } + /> + ) } export default Page diff --git a/src/pages/Oauth/Login.tsx b/src/pages/Oauth/Login.tsx index 7a82a6d9d0..ed807e4f62 100644 --- a/src/pages/Oauth/Login.tsx +++ b/src/pages/Oauth/Login.tsx @@ -1,4 +1,5 @@ import KyberOauth2, { LoginFlow, LoginMethod } from '@kybernetwork/oauth2' +import { Trans } from '@lingui/macro' import { useCallback, useEffect, useRef, useState } from 'react' import Loader from 'components/Loader' @@ -18,7 +19,11 @@ const getErrorMsg = (error: any) => { const data = error?.response?.data const isExpired = data?.error?.id === 'self_service_flow_expired' if (isExpired) { - return Time to sign-in is Expired, please go back and try again. + return ( + + Time to sign-in is Expired, please go back and try again. + + ) } return data?.ui?.messages?.[0]?.text || data?.error?.reason || data?.error?.message || error?.message || error + '' diff --git a/src/pages/Oauth/Logout.tsx b/src/pages/Oauth/Logout.tsx index b131e543d8..c26c0d0138 100644 --- a/src/pages/Oauth/Logout.tsx +++ b/src/pages/Oauth/Logout.tsx @@ -1,6 +1,8 @@ import KyberOauth2 from '@kybernetwork/oauth2' +import { Trans } from '@lingui/macro' import { useEffect } from 'react' +import Dots from 'components/Dots' import { ENV_KEY } from 'constants/env' import useParsedQueryString from 'hooks/useParsedQueryString' import { PageContainer } from 'pages/Oauth/styled' @@ -21,7 +23,15 @@ function Logout() { }) }, [logout_challenge]) - return + return ( + + Logging out + + } + /> + ) } export default Logout diff --git a/src/pages/Oauth/styled.tsx b/src/pages/Oauth/styled.tsx index 5c5dc8d41a..10d20def76 100644 --- a/src/pages/Oauth/styled.tsx +++ b/src/pages/Oauth/styled.tsx @@ -1,3 +1,4 @@ +import { ReactNode } from 'react' import styled from 'styled-components' import backgroundImage from 'assets/images/truesight-v2/landing-page/background-gradient.png' @@ -44,7 +45,7 @@ export const KyberLogo = () => { ) } -export function PageContainer({ msg }: { msg: string }) { +export function PageContainer({ msg }: { msg: ReactNode }) { return ( From 3860b7686ad095628668fe95fa484b9c07af9f6a Mon Sep 17 00:00:00 2001 From: Danh Date: Tue, 3 Oct 2023 16:48:48 +0700 Subject: [PATCH 13/21] update trans --- src/pages/Oauth/AuthForm/ButtonEth.tsx | 9 +++++++-- src/pages/Oauth/AuthForm/ButtonGoogle.tsx | 3 ++- src/pages/Oauth/AuthForm/index.tsx | 5 +++-- src/pages/Oauth/Consent.tsx | 4 ++-- src/pages/Oauth/Logout.tsx | 4 ++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index 434330f01d..fe0a39e319 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -1,4 +1,5 @@ import { LoginMethod } from '@kybernetwork/oauth2' +import { Trans } from '@lingui/macro' import { useCallback } from 'react' import { Flex, Text } from 'rebass' @@ -63,12 +64,16 @@ const ButtonEth = ({ {loading ? ( <> -   Signing In +  {' '} + + {' '} + Signing In + ) : ( <> -   Sign-In with Wallet +   Sign-In with Wallet )}
diff --git a/src/pages/Oauth/AuthForm/ButtonGoogle.tsx b/src/pages/Oauth/AuthForm/ButtonGoogle.tsx index 4989c785f6..34e1896d94 100644 --- a/src/pages/Oauth/AuthForm/ButtonGoogle.tsx +++ b/src/pages/Oauth/AuthForm/ButtonGoogle.tsx @@ -1,4 +1,5 @@ import { LoginMethod } from '@kybernetwork/oauth2' +import { Trans } from '@lingui/macro' import React, { useCallback, useRef } from 'react' import { ButtonOutlined, ButtonPrimary } from 'components/Button' @@ -28,7 +29,7 @@ const ButtonGoogle: React.FC = ({ outline, flowStatus }) => { value: 'google', name: 'provider', ref, - children: <>Sign-In with Google, + children: Sign-In with Google, style: isAutoLogin ? { opacity: 0 } : undefined, } return React.createElement(outline ? ButtonOutlined : ButtonPrimary, props) diff --git a/src/pages/Oauth/AuthForm/index.tsx b/src/pages/Oauth/AuthForm/index.tsx index 989b896b7a..4689f9ec22 100644 --- a/src/pages/Oauth/AuthForm/index.tsx +++ b/src/pages/Oauth/AuthForm/index.tsx @@ -1,6 +1,7 @@ import { LoginFlow, LoginMethod } from '@kybernetwork/oauth2' import React from 'react' import { isMobile } from 'react-device-detect' +import { Flex } from 'rebass' import styled from 'styled-components' import useParsedQueryString from 'hooks/useParsedQueryString' @@ -55,9 +56,9 @@ const AuthForm: React.FC = ({ formConfig, signInWithEth, flowStat /> )} {hasBothEthAndGoogle && ( -
+ or -
+ )} {hasGoogle && } diff --git a/src/pages/Oauth/Consent.tsx b/src/pages/Oauth/Consent.tsx index e6ae77a562..75f6a304e2 100644 --- a/src/pages/Oauth/Consent.tsx +++ b/src/pages/Oauth/Consent.tsx @@ -16,10 +16,10 @@ function Page() { KyberOauth2.oauthUi .getFlowConsent(consent_challenge) .then(data => { - console.debug('resp consent', data) + console.debug('Oauth resp consent', data) }) .catch(err => { - console.debug('err consent', err) + console.debug('Oauth consent error', err) }) }, [consent_challenge]) diff --git a/src/pages/Oauth/Logout.tsx b/src/pages/Oauth/Logout.tsx index c26c0d0138..e66feebdb7 100644 --- a/src/pages/Oauth/Logout.tsx +++ b/src/pages/Oauth/Logout.tsx @@ -16,10 +16,10 @@ function Logout() { KyberOauth2.oauthUi .acceptLogout(logout_challenge) .then(data => { - console.debug('logout resp', data) + console.debug('Oauth logout resp', data) }) .catch(err => { - console.debug('err logout', err) + console.debug('Oauth logout error', err) }) }, [logout_challenge]) From 85c6b26407b920b615b4803da2419f857ed6a7f4 Mon Sep 17 00:00:00 2001 From: Danh Date: Tue, 3 Oct 2023 16:54:12 +0700 Subject: [PATCH 14/21] env pro --- .github/workflows/pr.yaml | 2 +- src/pages/Oauth/AuthForm/ButtonEth.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 0b9eaa7c9a..e621c974f4 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -114,7 +114,7 @@ jobs: VITE_TAG: ${{ needs.prepare.outputs.image_tag }} CURRENT_BRANCH: ${{ needs.prepare.outputs.current_branch }} NODE_OPTIONS: '--max_old_space_size=4096' - run: yarn build-prod + run: yarn build-dev - name: Docker build and push uses: docker/build-push-action@v2 diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index fe0a39e319..08d932c325 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -43,19 +43,19 @@ const ButtonEth = ({ {showBtnCancel && ( { e.preventDefault() onClickCancel() }} - height={'36px'} + height="36px" > Cancel )} Date: Tue, 3 Oct 2023 18:03:57 +0700 Subject: [PATCH 15/21] update validate back_uri use external link validate some function move file s --- .../Announcement/Popups/CenterPopup.tsx | 2 +- .../Popups/DetailAnnouncementPopup.tsx | 2 +- .../Announcement/Popups/SnippetPopup.tsx | 2 +- .../Announcement/Popups/TopBanner.tsx | 2 +- .../InboxItemPoolPosition.tsx | 2 +- .../InboxItemPriceAlert.tsx | 2 +- .../InboxItemPrivateMessage.tsx | 2 +- .../NotificationCenter/PoolPosition.tsx | 2 +- .../NotificationCenter/PriceAlert.tsx | 2 +- .../NotificationCenter/PrivateMessage.tsx | 2 +- src/components/Announcement/helper.ts | 44 ------------ .../About/AboutKyberSwap/MeetTheTeam.tsx | 9 +-- src/pages/KyberDAO/StakeKNC/index.tsx | 4 +- .../GeneralAnnouncement/AnnouncementItem.tsx | 2 +- src/pages/Oauth/AuthForm/ButtonEth.tsx | 11 +-- src/pages/Oauth/AuthForm/index.tsx | 4 +- src/pages/Oauth/helpers.ts | 22 ------ .../TrueSightV2/components/TokenOverview.tsx | 3 +- .../TrueSightV2/pages/TokenAnalysisList.tsx | 6 +- src/theme/components.tsx | 23 ++++-- src/utils/redirect.ts | 70 +++++++++++++++++++ 21 files changed, 120 insertions(+), 98 deletions(-) create mode 100644 src/utils/redirect.ts diff --git a/src/components/Announcement/Popups/CenterPopup.tsx b/src/components/Announcement/Popups/CenterPopup.tsx index f9cfa6fb01..23e6422dbf 100644 --- a/src/components/Announcement/Popups/CenterPopup.tsx +++ b/src/components/Announcement/Popups/CenterPopup.tsx @@ -4,7 +4,6 @@ import { useMedia } from 'react-use' import styled from 'styled-components' import CtaButton from 'components/Announcement/Popups/CtaButton' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePopup, PopupContentAnnouncement, @@ -17,6 +16,7 @@ import { Z_INDEXS } from 'constants/styles' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' import { MEDIA_WIDTHS } from 'theme' +import { useNavigateToUrl } from 'utils/redirect' import { escapeScriptHtml } from 'utils/string' const Wrapper = styled.div` diff --git a/src/components/Announcement/Popups/DetailAnnouncementPopup.tsx b/src/components/Announcement/Popups/DetailAnnouncementPopup.tsx index 04fbcd68cb..5625a397cb 100644 --- a/src/components/Announcement/Popups/DetailAnnouncementPopup.tsx +++ b/src/components/Announcement/Popups/DetailAnnouncementPopup.tsx @@ -6,7 +6,6 @@ import styled from 'styled-components' import NotificationImage from 'assets/images/notification_default.png' import CtaButton from 'components/Announcement/Popups/CtaButton' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePopup } from 'components/Announcement/type' import Modal from 'components/Modal' import Row from 'components/Row' @@ -14,6 +13,7 @@ import { Z_INDEXS } from 'constants/styles' import useTheme from 'hooks/useTheme' import { useDetailAnnouncement } from 'state/application/hooks' import { MEDIA_WIDTHS } from 'theme' +import { useNavigateToUrl } from 'utils/redirect' import { escapeScriptHtml } from 'utils/string' const PaginationButton = styled.div` diff --git a/src/components/Announcement/Popups/SnippetPopup.tsx b/src/components/Announcement/Popups/SnippetPopup.tsx index 3a83802a87..3214bf6729 100644 --- a/src/components/Announcement/Popups/SnippetPopup.tsx +++ b/src/components/Announcement/Popups/SnippetPopup.tsx @@ -8,7 +8,6 @@ import { Swiper, SwiperSlide } from 'swiper/react' import NotificationImage from 'assets/images/notification_default.png' import CtaButton from 'components/Announcement/Popups/CtaButton' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePopup, PopupContentAnnouncement, @@ -20,6 +19,7 @@ import { Z_INDEXS } from 'constants/styles' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' import { useDetailAnnouncement, useRemovePopup } from 'state/application/hooks' +import { useNavigateToUrl } from 'utils/redirect' const IMAGE_HEIGHT = '124px' const PADDING_MOBILE = '16px' diff --git a/src/components/Announcement/Popups/TopBanner.tsx b/src/components/Announcement/Popups/TopBanner.tsx index 2ee8a5b0e4..bc3efc59a6 100644 --- a/src/components/Announcement/Popups/TopBanner.tsx +++ b/src/components/Announcement/Popups/TopBanner.tsx @@ -5,13 +5,13 @@ import { useMedia } from 'react-use' import styled, { css, keyframes } from 'styled-components' import CtaButton from 'components/Announcement/Popups/CtaButton' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePopup, PopupType } from 'components/Announcement/type' import Announcement from 'components/Icons/Announcement' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' import { useActivePopups, useRemoveAllPopupByType } from 'state/application/hooks' import { MEDIA_WIDTHS } from 'theme' +import { useNavigateToUrl } from 'utils/redirect' import { escapeScriptHtml } from 'utils/string' const BannerWrapper = styled.div<{ color?: string }>` diff --git a/src/components/Announcement/PrivateAnnoucement/InboxItemPoolPosition.tsx b/src/components/Announcement/PrivateAnnoucement/InboxItemPoolPosition.tsx index 01b8948cfd..8d266aa261 100644 --- a/src/components/Announcement/PrivateAnnoucement/InboxItemPoolPosition.tsx +++ b/src/components/Announcement/PrivateAnnoucement/InboxItemPoolPosition.tsx @@ -12,13 +12,13 @@ import { RowItem, Title, } from 'components/Announcement/PrivateAnnoucement/styled' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePoolPosition } from 'components/Announcement/type' import { DoubleCurrencyLogoV2 } from 'components/DoubleLogo' import { MoneyBag } from 'components/Icons' import { APP_PATHS } from 'constants/index' import { NETWORKS_INFO } from 'constants/networks' import useTheme from 'hooks/useTheme' +import { useNavigateToUrl } from 'utils/redirect' function InboxItemBridge({ announcement, diff --git a/src/components/Announcement/PrivateAnnoucement/InboxItemPriceAlert.tsx b/src/components/Announcement/PrivateAnnoucement/InboxItemPriceAlert.tsx index 588394a8ae..931e58ef45 100644 --- a/src/components/Announcement/PrivateAnnoucement/InboxItemPriceAlert.tsx +++ b/src/components/Announcement/PrivateAnnoucement/InboxItemPriceAlert.tsx @@ -6,12 +6,12 @@ import { Flex, Text } from 'rebass' import { PrivateAnnouncementProp } from 'components/Announcement/PrivateAnnoucement' import InboxIcon from 'components/Announcement/PrivateAnnoucement/Icon' import { Dot, InboxItemRow, InboxItemWrapper, RowItem, Title } from 'components/Announcement/PrivateAnnoucement/styled' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePriceAlert } from 'components/Announcement/type' import { ButtonLight } from 'components/Button' import DeltaTokenAmount from 'components/WalletPopup/Transactions/DeltaTokenAmount' import useTheme from 'hooks/useTheme' import { HistoricalPriceAlert, PriceAlertType } from 'pages/NotificationCenter/const' +import { useNavigateToUrl } from 'utils/redirect' export const getSwapUrlPriceAlert = (alert: HistoricalPriceAlert) => { const { swapURL } = alert diff --git a/src/components/Announcement/PrivateAnnoucement/InboxItemPrivateMessage.tsx b/src/components/Announcement/PrivateAnnoucement/InboxItemPrivateMessage.tsx index 7fc2c278e9..98b23a8bd9 100644 --- a/src/components/Announcement/PrivateAnnoucement/InboxItemPrivateMessage.tsx +++ b/src/components/Announcement/PrivateAnnoucement/InboxItemPrivateMessage.tsx @@ -3,8 +3,8 @@ import styled from 'styled-components' import { PrivateAnnouncementProp } from 'components/Announcement/PrivateAnnoucement' import InboxIcon from 'components/Announcement/PrivateAnnoucement/Icon' import { Dot, InboxItemRow, InboxItemWrapper, RowItem, Title } from 'components/Announcement/PrivateAnnoucement/styled' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePopup } from 'components/Announcement/type' +import { useNavigateToUrl } from 'utils/redirect' import { escapeScriptHtml } from 'utils/string' const Desc = styled.div` diff --git a/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PoolPosition.tsx b/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PoolPosition.tsx index 0aca209c00..46d5125fcd 100644 --- a/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PoolPosition.tsx +++ b/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PoolPosition.tsx @@ -7,13 +7,13 @@ import styled from 'styled-components' import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' import InboxIcon from 'components/Announcement/PrivateAnnoucement/Icon' import { PrivateAnnouncementPropCenter } from 'components/Announcement/PrivateAnnoucement/NotificationCenter' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePoolPosition } from 'components/Announcement/type' import { DoubleCurrencyLogoV2 } from 'components/DoubleLogo' import { MoneyBag } from 'components/Icons' import { APP_PATHS } from 'constants/index' import { NETWORKS_INFO } from 'constants/networks' import useTheme from 'hooks/useTheme' +import { useNavigateToUrl } from 'utils/redirect' import { formatTime } from 'utils/time' import { Desc, Time, Title, Wrapper } from './styled' diff --git a/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PriceAlert.tsx b/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PriceAlert.tsx index dde6f45330..4f2ed92249 100644 --- a/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PriceAlert.tsx +++ b/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PriceAlert.tsx @@ -5,9 +5,9 @@ import styled from 'styled-components' import InboxIcon from 'components/Announcement/PrivateAnnoucement/Icon' import { getSwapUrlPriceAlert } from 'components/Announcement/PrivateAnnoucement/InboxItemPriceAlert' import { PrivateAnnouncementPropCenter } from 'components/Announcement/PrivateAnnoucement/NotificationCenter' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePriceAlert } from 'components/Announcement/type' import AlertCondition from 'pages/NotificationCenter/PriceAlerts/AlertCondition' +import { useNavigateToUrl } from 'utils/redirect' import { formatTime } from 'utils/time' import { Desc, Time, Title, Wrapper } from './styled' diff --git a/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PrivateMessage.tsx b/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PrivateMessage.tsx index 5eeaef808c..a5c87203c1 100644 --- a/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PrivateMessage.tsx +++ b/src/components/Announcement/PrivateAnnoucement/NotificationCenter/PrivateMessage.tsx @@ -2,8 +2,8 @@ import { Flex } from 'rebass' import InboxIcon from 'components/Announcement/PrivateAnnoucement/Icon' import { PrivateAnnouncementPropCenter } from 'components/Announcement/PrivateAnnoucement/NotificationCenter' -import { useNavigateToUrl } from 'components/Announcement/helper' import { AnnouncementTemplatePopup } from 'components/Announcement/type' +import { useNavigateToUrl } from 'utils/redirect' import { escapeScriptHtml } from 'utils/string' import { formatTime } from 'utils/time' diff --git a/src/components/Announcement/helper.ts b/src/components/Announcement/helper.ts index 1dd590f0cb..6dfcf4dfca 100644 --- a/src/components/Announcement/helper.ts +++ b/src/components/Announcement/helper.ts @@ -1,12 +1,9 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { useCallback } from 'react' -import { useNavigate } from 'react-router-dom' import AnnouncementApi from 'services/announcement' import { AnnouncementTemplatePopup, PopupContentAnnouncement, PopupItemType } from 'components/Announcement/type' import { TIMES_IN_SECS } from 'constants/index' -import { useActiveWeb3React } from 'hooks' -import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' import { useAppDispatch } from 'state/hooks' const LsKey = 'ack-announcements' @@ -46,47 +43,6 @@ export const isPopupCanShow = ( return !isRead && !isExpired && isRightChain && isOwn } -/** - * this hook to navigate to specific url - * detect using window.open or navigate (react-router) - * check change chain if needed - */ -export const useNavigateToUrl = () => { - const navigate = useNavigate() - const { chainId: currentChain } = useActiveWeb3React() - const { changeNetwork } = useChangeNetwork() - - const redirect = useCallback( - (actionURL: string) => { - if (actionURL && actionURL.startsWith('/')) { - navigate(actionURL) - return - } - const { pathname, host, search } = new URL(actionURL) - if (window.location.host === host) { - navigate(`${pathname}${search}`) - } else { - window.open(actionURL) - } - }, - [navigate], - ) - - return useCallback( - (actionURL: string, chainId?: ChainId) => { - try { - if (!actionURL) return - if (chainId && chainId !== currentChain) { - changeNetwork(chainId, () => redirect(actionURL), undefined, true) - } else { - redirect(actionURL) - } - } catch (error) {} - }, - [changeNetwork, currentChain, redirect], - ) -} - export const useInvalidateTags = (reducerPath: string) => { const dispatch = useAppDispatch() return useCallback( diff --git a/src/pages/About/AboutKyberSwap/MeetTheTeam.tsx b/src/pages/About/AboutKyberSwap/MeetTheTeam.tsx index c85fe499e4..8903ddb64a 100644 --- a/src/pages/About/AboutKyberSwap/MeetTheTeam.tsx +++ b/src/pages/About/AboutKyberSwap/MeetTheTeam.tsx @@ -16,6 +16,7 @@ import VictorTran from 'assets/images/kyber_members/victor_tran.png' import { ReactComponent as LinkedInIcon } from 'assets/svg/linkedin.svg' import { ReactComponent as TwitterIcon } from 'assets/svg/solid_twitter_icon.svg' import useTheme from 'hooks/useTheme' +import { ExternalLink } from 'theme' type Member = { name: string @@ -186,14 +187,14 @@ const MemberView: React.FC = props => { }} > {props.handles.twitter && ( - + - + )} {props.handles.linkedIn && ( - + - + )} )} diff --git a/src/pages/KyberDAO/StakeKNC/index.tsx b/src/pages/KyberDAO/StakeKNC/index.tsx index c86ecf5e51..0863bafbc8 100644 --- a/src/pages/KyberDAO/StakeKNC/index.tsx +++ b/src/pages/KyberDAO/StakeKNC/index.tsx @@ -289,9 +289,9 @@ export default function StakeKNC() { You can access legacy KyberDAO v1 to read about previous KIPs{' '} - + here ↗ - + diff --git a/src/pages/NotificationCenter/GeneralAnnouncement/AnnouncementItem.tsx b/src/pages/NotificationCenter/GeneralAnnouncement/AnnouncementItem.tsx index 37d2f9fbae..73eb89ec37 100644 --- a/src/pages/NotificationCenter/GeneralAnnouncement/AnnouncementItem.tsx +++ b/src/pages/NotificationCenter/GeneralAnnouncement/AnnouncementItem.tsx @@ -7,9 +7,9 @@ import NotificationImage from 'assets/images/notification_default.png' import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' import CtaButton from 'components/Announcement/Popups/CtaButton' import { formatCtaName } from 'components/Announcement/Popups/DetailAnnouncementPopup' -import { useNavigateToUrl } from 'components/Announcement/helper' import { Announcement } from 'components/Announcement/type' import { MEDIA_WIDTHS } from 'theme' +import { useNavigateToUrl } from 'utils/redirect' import { escapeScriptHtml } from 'utils/string' import { formatTime } from 'utils/time' diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index 08d932c325..45e6ce0628 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -10,6 +10,7 @@ import { useActiveWeb3React } from 'hooks' import useAutoSignIn from 'pages/Oauth/AuthForm/useAutoSignIn' import { FlowStatus } from 'pages/Oauth/Login' import { useWalletModalToggle } from 'state/application/hooks' +import { navigateToUrl } from 'utils/redirect' const ButtonEth = ({ loading, @@ -17,12 +18,12 @@ const ButtonEth = ({ onClick, flowStatus, showBtnCancel, - onClickCancel, + backUrl, }: { disabled: boolean loading: boolean onClick: () => void - onClickCancel: () => void + backUrl: string | undefined flowStatus: FlowStatus showBtnCancel: boolean }) => { @@ -44,13 +45,13 @@ const ButtonEth = ({ {showBtnCancel && ( { e.preventDefault() - onClickCancel() + navigateToUrl(backUrl) }} - height="36px" > - Cancel + Cancel )} = ({ formConfig, signInWithEth, flowStat {showEth && ( navigateToUrl(back_uri)} + backUrl={back_uri} showBtnCancel={!!showBtnCancel} onClick={signInWithEth} disabled={processingSignIn || disableEth} diff --git a/src/pages/Oauth/helpers.ts b/src/pages/Oauth/helpers.ts index 472533ce1f..125dffee13 100644 --- a/src/pages/Oauth/helpers.ts +++ b/src/pages/Oauth/helpers.ts @@ -4,28 +4,6 @@ export const getSupportLoginMethods = (loginFlow: LoginFlow | undefined) => { return loginFlow?.oauth_client?.metadata?.allowed_login_methods ?? [] } -const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap\.com$/, /https:\/\/(.+)\.kyberengineering\.io$/] -const isValidRedirectURL = (url: string | undefined) => { - try { - if (!url) return false - const newUrl = new URL(url) // valid url - if ( - url.endsWith('.js') || - newUrl.pathname.endsWith('.js') || - !whiteListDomains.some(regex => newUrl.origin.match(regex)) - ) { - return false - } - return newUrl.protocol === 'http:' || newUrl.protocol === 'https:' - } catch (error) { - return false - } -} - -export const navigateToUrl = (url: string | undefined) => { - if (url && isValidRedirectURL(url)) window.location.href = url -} - type MessageParams = { domain: string uri: string diff --git a/src/pages/TrueSightV2/components/TokenOverview.tsx b/src/pages/TrueSightV2/components/TokenOverview.tsx index cfe195c72f..0a1a51a4ff 100644 --- a/src/pages/TrueSightV2/components/TokenOverview.tsx +++ b/src/pages/TrueSightV2/components/TokenOverview.tsx @@ -515,7 +515,8 @@ export const TokenOverview = ({ data, isLoading }: { data?: IAssetOverview; isLo KyberScore uses AI to measure the upcoming trend of a token (bullish or bearish) by taking into account multiple on-chain and off-chain indicators. The score ranges from 0 to 100. Higher the score, - more bullish the token in the short-term. Read more here ↗ + more bullish the token in the short-term. Read more{' '} + here ↗ } placement="top" diff --git a/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx b/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx index f16b3ccb95..5c500659de 100644 --- a/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx +++ b/src/pages/TrueSightV2/pages/TokenAnalysisList.tsx @@ -23,7 +23,7 @@ import { NETWORKS_INFO } from 'constants/networks' import { MIXPANEL_TYPE, useMixpanelKyberAI } from 'hooks/useMixpanel' import { useOnClickOutside } from 'hooks/useOnClickOutside' import useTheme from 'hooks/useTheme' -import { MEDIA_WIDTHS } from 'theme' +import { ExternalLink, MEDIA_WIDTHS } from 'theme' import ChevronIcon from '../components/ChevronIcon' import FeedbackSurvey from '../components/FeedbackSurvey' @@ -889,9 +889,9 @@ export default function TokenAnalysisList() { KyberScore uses AI to measure the upcoming trend of a token (bullish or bearish) by taking into account multiple on-chain and off-chain indicators. The score ranges from 0 to 100. Higher the score, more bullish the token in the short-term. Read more{' '} - + here ↗ - + } delay={200} diff --git a/src/theme/components.tsx b/src/theme/components.tsx index 1b8bcb7af9..e7318ef26d 100644 --- a/src/theme/components.tsx +++ b/src/theme/components.tsx @@ -4,6 +4,8 @@ import { ArrowLeft, ExternalLink as LinkIconFeather, X } from 'react-feather' import { Link } from 'react-router-dom' import styled, { css, keyframes } from 'styled-components' +import { isValidRedirectURL, navigateToUrl } from 'utils/redirect' + export const ButtonText = styled.button<{ color?: string; gap?: string }>` outline: none; border: none; @@ -200,7 +202,15 @@ export function ExternalLink({ }, [target, onClick], ) - return + return ( + + ) } export function ExternalLinkIcon({ @@ -217,14 +227,19 @@ export function ExternalLinkIcon({ console.debug('Fired outbound link event', href) } else { event.preventDefault() - - window.location.href = href + navigateToUrl(href, false) } }, [href, target], ) return ( - + ) diff --git a/src/utils/redirect.ts b/src/utils/redirect.ts new file mode 100644 index 0000000000..f35d69a213 --- /dev/null +++ b/src/utils/redirect.ts @@ -0,0 +1,70 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { useCallback } from 'react' +import { useNavigate } from 'react-router-dom' + +import { useActiveWeb3React } from 'hooks' +import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' + +const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap\.com$/, /https:\/\/(.+)\.kyberengineering\.io$/] +export const isValidRedirectURL = (url: string | undefined, whitelistKyberSwap = true) => { + try { + if (!url) return false + const newUrl = new URL(url) // valid url + if ( + url.endsWith('.js') || + newUrl.pathname.endsWith('.js') || + (whitelistKyberSwap && !whiteListDomains.some(regex => newUrl.origin.match(regex))) + ) { + return false + } + return newUrl.protocol === 'http:' || newUrl.protocol === 'https:' + } catch (error) { + return false + } +} + +export const navigateToUrl = (url: string | undefined, whitelistKyberSwap = true) => { + if (url && isValidRedirectURL(url, whitelistKyberSwap)) window.location.href = url +} + +/** + * this hook to navigate to specific url + * detect using window.open or navigate (react-router) + * check change chain if needed + */ +export const useNavigateToUrl = () => { + const navigate = useNavigate() + const { chainId: currentChain } = useActiveWeb3React() + const { changeNetwork } = useChangeNetwork() + + const redirect = useCallback( + (actionURL: string) => { + if (actionURL && actionURL.startsWith('/')) { + navigate(actionURL) + return + } + const { pathname, host, search } = new URL(actionURL) + if (!isValidRedirectURL(actionURL, false)) return + if (window.location.host === host) { + navigate(`${pathname}${search}`) + } else { + window.open(actionURL) + } + }, + [navigate], + ) + + return useCallback( + (actionURL: string, chainId?: ChainId) => { + try { + if (!actionURL) return + if (chainId && chainId !== currentChain) { + changeNetwork(chainId, () => redirect(actionURL), undefined, true) + } else { + redirect(actionURL) + } + } catch (error) {} + }, + [changeNetwork, currentChain, redirect], + ) +} From 8b7e1a9d81aa3bdc24d0575ec0ebe4829b583369 Mon Sep 17 00:00:00 2001 From: Danh Date: Tue, 3 Oct 2023 18:09:41 +0700 Subject: [PATCH 16/21] chore: hide cancel if invalid --- src/pages/Oauth/AuthForm/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/Oauth/AuthForm/index.tsx b/src/pages/Oauth/AuthForm/index.tsx index c755172be6..d3ca58fe66 100644 --- a/src/pages/Oauth/AuthForm/index.tsx +++ b/src/pages/Oauth/AuthForm/index.tsx @@ -9,6 +9,7 @@ import useTheme from 'hooks/useTheme' import ButtonEth from 'pages/Oauth/AuthForm/ButtonEth' import ButtonGoogle from 'pages/Oauth/AuthForm/ButtonGoogle' import { FlowStatus } from 'pages/Oauth/Login' +import { isValidRedirectURL } from 'utils/redirect' import { getSupportLoginMethods } from '../helpers' import AuthFormFieldMessage from './AuthFormMessage' @@ -40,7 +41,7 @@ const AuthForm: React.FC = ({ formConfig, signInWithEth, flowStat const showEth = loginMethods.includes(LoginMethod.ETH) && autoLoginMethod !== LoginMethod.GOOGLE const hasGoogle = loginMethods.includes(LoginMethod.GOOGLE) - const showBtnCancel = !isMobile && !hasGoogle && back_uri && !processingSignIn + const showBtnCancel = !isMobile && !hasGoogle && isValidRedirectURL(back_uri) && !processingSignIn const hasBothEthAndGoogle = hasGoogle && showEth return (
From b1995cbf9a8433c593d67da3acf4dd95c04abb1f Mon Sep 17 00:00:00 2001 From: Danh Date: Tue, 3 Oct 2023 21:58:08 +0700 Subject: [PATCH 17/21] use lib to santilize --- package.json | 4 +++- src/utils/string.ts | 3 ++- yarn.lock | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 819d521cbf..74887c4009 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "crypto-js": "4.1.1", "d3": "^7.6.1", "dayjs": "^1.11.6", + "dompurify": "^3.0.6", "ethers": "^5.4.6", "events": "^3.3.0", "find-replacement-tx": "^1.2.3", @@ -156,6 +157,7 @@ "@types/big.js": "^6.0.0", "@types/crypto-js": "4.1.1", "@types/d3": "^7.1.0", + "@types/dompurify": "^3.0.3", "@types/mixpanel-browser": "^2.38.0", "@types/multicodec": "^1.0.0", "@types/node": "^13.13.52", @@ -208,4 +210,4 @@ "@lingui/core": "3.14.0", "@lingui/conf": "3.16.0" } -} \ No newline at end of file +} diff --git a/src/utils/string.ts b/src/utils/string.ts index 4e56993b2f..78bffa3dcf 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -1,4 +1,5 @@ import { ChainId, Currency, Token } from '@kyberswap/ks-sdk-core' +import DOMPurify from 'dompurify' import { parse } from 'querystring' import { NETWORKS_INFO, SUPPORTED_NETWORKS } from 'constants/networks' @@ -34,7 +35,7 @@ export const shortString = (str: string | undefined, n: number) => { } export const escapeScriptHtml = (str: string) => { - return str.replace(/<.*?script.*?>.*?<\/.*?script.*?>/gim, '') + return DOMPurify.sanitize(str) } export const isEmailValid = (value: string | undefined) => diff --git a/yarn.lock b/yarn.lock index 430039ae3b..a4e14a88d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4613,6 +4613,13 @@ dependencies: "@types/ms" "*" +"@types/dompurify@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.3.tgz#d34ba1cf4f8b8f2cbfe5d3118dc3b7d81858fa42" + integrity sha512-odiGr/9/qMqjcBOe5UhcNLOFHSYmKFOyr+bJ/Xu3Qp4k1pNPAlNLUVNNLcLfjQI7+W7ObX58EdD3H+3p3voOvA== + dependencies: + "@types/trusted-types" "*" + "@types/estree@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" @@ -5056,6 +5063,11 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.8.tgz#511fc1569cc32b0cf50941fe9f00bf70f94116bb" integrity sha512-7axfYN8SW9pWg78NgenHasSproWQee5rzyPVLC9HpaQSDgNArsnKJD88EaMfi4Pl48AyciO3agYCFqpHS1gLpg== +"@types/trusted-types@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65" + integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ== + "@types/trusted-types@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.3.tgz#a136f83b0758698df454e328759dbd3d44555311" @@ -8879,6 +8891,11 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +dompurify@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.6.tgz#925ebd576d54a9531b5d76f0a5bef32548351dae" + integrity sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w== + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" From 1766aab0ada0ebed5b6e84df0627ad501614a604 Mon Sep 17 00:00:00 2001 From: Danh Date: Tue, 3 Oct 2023 22:21:11 +0700 Subject: [PATCH 18/21] refactor --- src/pages/Oauth/AuthForm/index.tsx | 4 ++-- src/theme/components.tsx | 18 +++--------------- src/utils/redirect.ts | 16 +++++++++------- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/pages/Oauth/AuthForm/index.tsx b/src/pages/Oauth/AuthForm/index.tsx index d3ca58fe66..0d287d748d 100644 --- a/src/pages/Oauth/AuthForm/index.tsx +++ b/src/pages/Oauth/AuthForm/index.tsx @@ -9,7 +9,7 @@ import useTheme from 'hooks/useTheme' import ButtonEth from 'pages/Oauth/AuthForm/ButtonEth' import ButtonGoogle from 'pages/Oauth/AuthForm/ButtonGoogle' import { FlowStatus } from 'pages/Oauth/Login' -import { isValidRedirectURL } from 'utils/redirect' +import { validateRedirectURL } from 'utils/redirect' import { getSupportLoginMethods } from '../helpers' import AuthFormFieldMessage from './AuthFormMessage' @@ -41,7 +41,7 @@ const AuthForm: React.FC = ({ formConfig, signInWithEth, flowStat const showEth = loginMethods.includes(LoginMethod.ETH) && autoLoginMethod !== LoginMethod.GOOGLE const hasGoogle = loginMethods.includes(LoginMethod.GOOGLE) - const showBtnCancel = !isMobile && !hasGoogle && isValidRedirectURL(back_uri) && !processingSignIn + const showBtnCancel = !isMobile && !hasGoogle && validateRedirectURL(back_uri) && !processingSignIn const hasBothEthAndGoogle = hasGoogle && showEth return ( diff --git a/src/theme/components.tsx b/src/theme/components.tsx index e7318ef26d..312afc674c 100644 --- a/src/theme/components.tsx +++ b/src/theme/components.tsx @@ -4,7 +4,7 @@ import { ArrowLeft, ExternalLink as LinkIconFeather, X } from 'react-feather' import { Link } from 'react-router-dom' import styled, { css, keyframes } from 'styled-components' -import { isValidRedirectURL, navigateToUrl } from 'utils/redirect' +import { navigateToUrl, validateRedirectURL } from 'utils/redirect' export const ButtonText = styled.button<{ color?: string; gap?: string }>` outline: none; @@ -203,13 +203,7 @@ export function ExternalLink({ [target, onClick], ) return ( - + ) } @@ -233,13 +227,7 @@ export function ExternalLinkIcon({ [href, target], ) return ( - + ) diff --git a/src/utils/redirect.ts b/src/utils/redirect.ts index f35d69a213..4dec7f623a 100644 --- a/src/utils/redirect.ts +++ b/src/utils/redirect.ts @@ -6,25 +6,27 @@ import { useActiveWeb3React } from 'hooks' import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' const whiteListDomains = [/https:\/\/(.+?\.)?kyberswap\.com$/, /https:\/\/(.+)\.kyberengineering\.io$/] -export const isValidRedirectURL = (url: string | undefined, whitelistKyberSwap = true) => { +export const validateRedirectURL = (url: string | undefined, whitelistKyberSwap = true) => { try { - if (!url) return false + if (!url) throw new Error() const newUrl = new URL(url) // valid url if ( url.endsWith('.js') || newUrl.pathname.endsWith('.js') || + !['https:', 'http:'].includes(newUrl.protocol) || (whitelistKyberSwap && !whiteListDomains.some(regex => newUrl.origin.match(regex))) ) { - return false + throw new Error() } - return newUrl.protocol === 'http:' || newUrl.protocol === 'https:' + return url } catch (error) { - return false + return '' } } export const navigateToUrl = (url: string | undefined, whitelistKyberSwap = true) => { - if (url && isValidRedirectURL(url, whitelistKyberSwap)) window.location.href = url + const urlFormatted = validateRedirectURL(url, whitelistKyberSwap) + if (urlFormatted) window.location.href = urlFormatted } /** @@ -44,7 +46,7 @@ export const useNavigateToUrl = () => { return } const { pathname, host, search } = new URL(actionURL) - if (!isValidRedirectURL(actionURL, false)) return + if (!validateRedirectURL(actionURL, false)) return if (window.location.host === host) { navigate(`${pathname}${search}`) } else { From e43b205f7d1d40bf0b30e8e483eae88e59412bb3 Mon Sep 17 00:00:00 2001 From: Danh Date: Tue, 3 Oct 2023 23:00:15 +0700 Subject: [PATCH 19/21] fix: wrong click func c --- src/pages/Oauth/AuthForm/ButtonEth.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/Oauth/AuthForm/ButtonEth.tsx b/src/pages/Oauth/AuthForm/ButtonEth.tsx index 45e6ce0628..c95509a665 100644 --- a/src/pages/Oauth/AuthForm/ButtonEth.tsx +++ b/src/pages/Oauth/AuthForm/ButtonEth.tsx @@ -59,7 +59,10 @@ const ButtonEth = ({ height="36px" className="login-btn" id={'btnLoginEth'} - onClick={onClick} + onClick={e => { + e.preventDefault() + onClickEth() + }} disabled={disabled} > {loading ? ( From eddd685f5f78133547fe475538098033b64ac79b Mon Sep 17 00:00:00 2001 From: Danh Date: Wed, 4 Oct 2023 11:11:19 +0700 Subject: [PATCH 20/21] dont show session expire in iam pages --- src/hooks/useSessionExpire.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/useSessionExpire.ts b/src/hooks/useSessionExpire.ts index 82e8faa1c9..e000a5aca1 100644 --- a/src/hooks/useSessionExpire.ts +++ b/src/hooks/useSessionExpire.ts @@ -37,7 +37,9 @@ export default function useSessionExpiredGlobal() { if (isKyberAIPage && accountId === signedAccount) { delete data.cancelText } - showConfirm(data) + + const isIAMPages = [APP_PATHS.IAM_CONSENT, APP_PATHS.IAM_LOGIN, APP_PATHS.IAM_LOGOUT].includes(pathname) + if (!isIAMPages) showConfirm(data) } KyberOauth2.on(KyberOauth2Event.SESSION_EXPIRED, listener) return () => KyberOauth2.off(KyberOauth2Event.SESSION_EXPIRED, listener) From e68da4fef3f16a96f50d569060e65d83c2a5f6ab Mon Sep 17 00:00:00 2001 From: Danh Date: Mon, 9 Oct 2023 09:56:33 +0700 Subject: [PATCH 21/21] revert env --- .github/workflows/pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index e621c974f4..0b9eaa7c9a 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -114,7 +114,7 @@ jobs: VITE_TAG: ${{ needs.prepare.outputs.image_tag }} CURRENT_BRANCH: ${{ needs.prepare.outputs.current_branch }} NODE_OPTIONS: '--max_old_space_size=4096' - run: yarn build-dev + run: yarn build-prod - name: Docker build and push uses: docker/build-push-action@v2