Skip to content

Commit

Permalink
set up
Browse files Browse the repository at this point in the history
c

c

s
  • Loading branch information
nguyenhoaidanh committed Sep 14, 2023
1 parent 0f929f4 commit c6f7302
Show file tree
Hide file tree
Showing 13 changed files with 557 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,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 = {
Expand Down
7 changes: 7 additions & 0 deletions src/pages/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -426,6 +430,9 @@ export default function App() {
<Route path={`/:network/*`} element={<RoutesWithNetworkPrefix />} />

<Route path={APP_PATHS.VERIFY_AUTH} element={<VerifyAuth />} />
<Route path={APP_PATHS.IAM_LOGIN} element={<Login />} />
<Route path={APP_PATHS.IAM_LOGOUT} element={<Logout />} />
<Route path={APP_PATHS.IAM_CONSENT} element={<Consent />} />

<Route path="*" element={<RedirectPathToSwapV3Network />} />
</Routes>
Expand Down
27 changes: 27 additions & 0 deletions src/pages/Oauth/Consent.tsx
Original file line number Diff line number Diff line change
@@ -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 <PageContainer msg={'Checking data...'} />
}

export default Page
173 changes: 173 additions & 0 deletions src/pages/Oauth/Login.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<span>
Time to sign-in is Expired, please{' '}
<a href={queryStringToObject(window.location.search)?.back_uri + ''}>go back</a> and try again.

Check warning

Code scanning / CodeQL

Client-side cross-site scripting Medium

Cross-site scripting vulnerability due to
user-provided value
.

Check warning

Code scanning / CodeQL

Client-side URL redirect Medium

Untrusted URL redirection depends on a
user-provided value
.
</span>
)
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<LoginFlow>()
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 ? (
<TextDesc>
Your address is mismatched. The expected address is {getShortenAddress(wallet_address)}, but the address
provided is {getShortenAddress(address)}. Please change your wallet address accordingly.
</TextDesc>
) : (
address && (
<TextDesc>
To get started, please sign-in to verify your ownership of this wallet address {getShortenAddress(address)}
</TextDesc>
)
)

return (
<Container>
<Col>
<KyberLogo />
{error ? (
<TextDesc>{error}</TextDesc>
) : autoLogin ? (
<TextDesc style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<Loader /> Checking data ...
</TextDesc>
) : isSignInEth && address ? (
renderEthMsg()
) : (
appName && <TextDesc>Please sign in to continue with {appName}</TextDesc>
)}
<AuthForm
formConfig={authFormConfig}
autoLogin={autoLogin}
signInWithEth={signInWithEth}
processingSignEth={processingSignEth}
disableEth={!!isMismatchEthAddress}
/>
</Col>
</Container>
)
}

export default Login
27 changes: 27 additions & 0 deletions src/pages/Oauth/Logout.tsx
Original file line number Diff line number Diff line change
@@ -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 <PageContainer msg={'Logging out...'} />
}

export default Logout
28 changes: 28 additions & 0 deletions src/pages/Oauth/components/AuthForm/AuthFormField.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement> {
field: LoginFlowUiNode
outline?: boolean
}

const AuthFormField: React.FC<AuthFormFieldProps> = ({ 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
20 changes: 20 additions & 0 deletions src/pages/Oauth/components/AuthForm/AuthFormFieldMessage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Text as="label" key={index} color={value.type === 'warn' ? theme.warning : theme.red}>
{value.text}
</Text>
)
})

return <div className="form-text">{messageList}</div>
}

export default AuthFormFieldMessage
Loading

0 comments on commit c6f7302

Please sign in to comment.