Skip to content

Commit

Permalink
Merge pull request #343 from skalenetwork/fix-auth-logic
Browse files Browse the repository at this point in the history
Fix auth logic, update swell, add sign-in/sign-out buttons
  • Loading branch information
dmytrotkk authored Aug 22, 2024
2 parents 5594058 + 9f8e239 commit 9c5a156
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 71 deletions.
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"react-router-dom": "^6.15.0",
"react-social-icons": "^6.17.0",
"react-transition-group": "^4.4.5",
"siwe": "^2.3.2"
"siwe": "^2.3.2",
"tslog": "^4.9.3"
},
"devDependencies": {
"@types/react": "^18.2.15",
Expand Down
10 changes: 10 additions & 0 deletions src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ body {
}

.mp__btnConnect {
position: relative;

width: 100%;
border-radius: 18px !important;

Expand Down Expand Up @@ -106,6 +108,12 @@ body {
.mp__iconGray {
width: 12pt;
}

.icon-overlay {
position: absolute;
top: -6px;
right: -6px;
}
}

.MuiDrawer-paper {
Expand Down Expand Up @@ -759,6 +767,7 @@ input[type=number] {

.chipPreTge {
background: linear-gradient(180deg, #EBB84F, #bc923b) !important;

p {
color: black !important
}
Expand Down Expand Up @@ -917,6 +926,7 @@ input[type=number] {

.iconRed {
color: #da3a34 !important;

svg {
color: #da3a34 !important;
}
Expand Down
66 changes: 49 additions & 17 deletions src/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@
* @copyright SKALE Labs 2024-Present
*/

import React, { createContext, useState, useContext, useEffect, useCallback } from 'react'
import { useWagmiAccount } from '@skalenetwork/metaport'
import React, { createContext, useState, useContext, useEffect } from 'react'
import { Logger, type ILogObj } from 'tslog'
import { SiweMessage } from 'siwe'
import { useWagmiAccount } from '@skalenetwork/metaport'
import { API_URL } from './core/constants'

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost/api'
const log = new Logger<ILogObj>({ name: 'AuthContext' })

interface AuthContextType {
isSignedIn: boolean
handleSignIn: () => Promise<void>
handleSignOut: () => Promise<void>
checkSignInStatus: () => Promise<void>
handleAddressChange: () => Promise<void>
getSignInStatus: () => Promise<boolean>
}

Expand All @@ -40,94 +42,123 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const [isSignedIn, setIsSignedIn] = useState(false)
const { address } = useWagmiAccount()

const checkSignInStatus = useCallback(async () => {
const handleAddressChange = async function () {
log.info(`Address changed: ${address}`)
if (!address) {
log.warn('No address found, signing out')
setIsSignedIn(false)
return
}
try {
const status = await getSignInStatus()
if (status) {
log.info('User is already signed in')
setIsSignedIn(true)
} else {
log.info('User not signed in, initiating sign in process')
await handleSignOut()
await handleSignIn()
}
} catch (error) {
console.error('Error checking sign-in status:', error)
log.error('Error checking sign-in status:', error)
setIsSignedIn(false)
}
}, [address])
}

const getSignInStatus = async (): Promise<boolean> => {
try {
if (!address) return false
log.info(`Checking sign-in status for address: ${address}`)
const response = await fetch(`${API_URL}/auth/status`, {
credentials: 'include'
})
const data = await response.json()
return data.isSignedIn && data.address && data.address.toLowerCase() === address.toLowerCase()
const isSignedIn =
data.isSignedIn && data.address && data.address.toLowerCase() === address.toLowerCase()
log.info(`Sign-in status: ${isSignedIn}`)
return isSignedIn
} catch (error) {
console.error('Error checking sign-in status:', error)
log.error('Error checking sign-in status:', error)
return false
}
}

const handleSignIn = async () => {
if (!address) return
if (!address) {
log.warn('Cannot sign in: No address provided')
return
}
log.info(`Initiating sign in for address: ${address}`)
try {
const nonce = await fetchNonce()
log.debug(`Fetched nonce: ${nonce}`)
const message = new SiweMessage({
domain: window.location.host,
address: address,
statement: 'Sign in with Ethereum to the SKALE Portal.',
uri: window.location.origin,
version: '1',
chainId: 1,
nonce: await fetchNonce()
nonce: nonce
})
log.debug('SIWE message created')
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message.prepareMessage(), address]
})
log.debug('Message signed')
const response = await fetch(`${API_URL}/auth/signin`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature }),
credentials: 'include'
})
if (response.ok) {
log.info('Sign in successful')
setIsSignedIn(true)
} else {
log.error('Sign in failed', response.status, response.statusText)
}
} catch (error) {
console.error('Error signing in:', error)
log.error('Error signing in:', error)
}
}

const handleSignOut = async () => {
log.info('Initiating sign out')
try {
await fetch(`${API_URL}/auth/signout`, {
const response = await fetch(`${API_URL}/auth/signout`, {
method: 'POST',
credentials: 'include'
})
if (response.ok) {
log.info('Sign out successful')
} else {
log.error('Sign out failed', response.status, response.statusText)
}
} catch (error) {
console.error('Error signing out:', error)
log.error('Error signing out:', error)
} finally {
setIsSignedIn(false)
}
}

const fetchNonce = async () => {
log.debug('Fetching nonce')
const response = await fetch(`${API_URL}/auth/nonce`)
const data = await response.json()
log.debug(`Nonce received: ${data.nonce}`)
return data.nonce
}

useEffect(() => {
checkSignInStatus()
}, [address, checkSignInStatus])
log.info('Address changed, updating auth status')
handleAddressChange()
}, [address])

return (
<AuthContext.Provider
value={{ isSignedIn, handleSignIn, handleSignOut, checkSignInStatus, getSignInStatus }}
value={{ isSignedIn, handleSignIn, handleSignOut, handleAddressChange, getSignInStatus }}
>
{children}
</AuthContext.Provider>
Expand All @@ -137,6 +168,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
export const useAuth = () => {
const context = useContext(AuthContext)
if (context === undefined) {
log.error('useAuth must be used within an AuthProvider')
throw new Error('useAuth must be used within an AuthProvider')
}
return context
Expand Down
55 changes: 46 additions & 9 deletions src/components/AccountMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
*/

import { useState, type MouseEvent } from 'react'
import { Link } from 'react-router-dom'
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon'

import Box from '@mui/material/Box'
Expand All @@ -32,14 +31,19 @@ import Tooltip from '@mui/material/Tooltip'
import Button from '@mui/material/Button'

import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward'
import HistoryIcon from '@mui/icons-material/History'
import SignalCellularAltOutlinedIcon from '@mui/icons-material/SignalCellularAltOutlined'
import AccountCircleRoundedIcon from '@mui/icons-material/AccountCircleRounded'
import LooksRoundedIcon from '@mui/icons-material/LooksRounded'
import LoginOutlinedIcon from '@mui/icons-material/LoginOutlined'
import LogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined'
import FiberManualRecordRoundedIcon from '@mui/icons-material/FiberManualRecordRounded'

import { cls, styles, cmn, RainbowConnectButton } from '@skalenetwork/metaport'

import { useAuth } from '../AuthContext'

export default function AccountMenu(props: any) {
const { isSignedIn, handleSignIn, handleSignOut } = useAuth()
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
const open = Boolean(anchorEl)
const handleClick = (event: MouseEvent<HTMLElement>) => {
Expand Down Expand Up @@ -76,13 +80,32 @@ export default function AccountMenu(props: any) {
</div>
</Tooltip>
) : (
<Tooltip arrow title="Account info">
<Tooltip
arrow
title={isSignedIn ? 'Conneced and signed-in' : 'Wallet connect, signed-out'}
>
<Button
onClick={handleClick}
className={cls('mp__btnConnect', styles.paperGrey, cmn.pPrim, cmn.flex)}
>
<div className={cls(cmn.mri10, cmn.flexcv)} style={{ height: '20px' }}>
<div
className={cls(cmn.mri5, cmn.flexcv)}
style={{ height: '20px', position: 'relative' }}
>
<Jazzicon diameter={20} seed={jsNumberForAddress(props.address)} />
<div className={cls('icon-overlay', cmn.flex, cmn.flexcv)}>
{isSignedIn ? (
<FiberManualRecordRoundedIcon
color="success"
className={cls(styles.chainIconxs)}
/>
) : (
<FiberManualRecordRoundedIcon
color="warning"
className={cls(styles.chainIconxs)}
/>
)}
</div>
</div>
{props.address.substring(0, 5) +
'...' +
Expand Down Expand Up @@ -141,11 +164,6 @@ export default function AccountMenu(props: any) {
)
}}
</RainbowConnectButton.Custom>
<Link to="/bridge/history" className="undec fullW">
<MenuItem onClick={handleClose}>
<HistoryIcon className={cmn.mri10} /> Transfers history
</MenuItem>
</Link>
<a
className="undec fullW"
target="_blank"
Expand All @@ -162,6 +180,25 @@ export default function AccountMenu(props: any) {
</div>
</MenuItem>
</a>
<MenuItem
onClick={() => {
if (isSignedIn) {
handleSignOut()
} else {
handleSignIn()
}
handleClose()
}}
>
<div className={cmn.flex}>
{isSignedIn ? (
<LogoutOutlinedIcon className={cmn.mri10} />
) : (
<LoginOutlinedIcon className={cmn.mri10} />
)}
</div>
<div className={cls(cmn.flex, cmn.flexg)}>Sign {isSignedIn ? 'out' : 'in'}</div>
</MenuItem>
</Menu>
</div>
)
Expand Down
13 changes: 1 addition & 12 deletions src/components/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
* @copyright SKALE Labs 2023-Present
*/

import { useEffect, useState } from 'react'
import { Button } from '@mui/material'
import LooksRoundedIcon from '@mui/icons-material/LooksRounded'
import { cmn, cls, SkPaper, useWagmiAccount, RainbowConnectButton } from '@skalenetwork/metaport'
Expand All @@ -34,23 +33,13 @@ export default function ConnectWallet(props: {
const { address } = useWagmiAccount()
const { isSignedIn, handleSignIn } = useAuth()

const [signInRequested, setSignInRequested] = useState(false)

useEffect(() => {
if (address && !isSignedIn && signInRequested) {
handleSignIn()
setSignInRequested(false)
}
}, [address])

const handleButtonClick = (openConnectModal: any) => {
if (address) {
if (!isSignedIn) {
handleSignIn()
}
} else {
openConnectModal()
setSignInRequested(true)
}
}

Expand All @@ -75,7 +64,7 @@ export default function ConnectWallet(props: {
className={cls(cmn.pCent, cmn.mtop10, cmn.flex, 'btn')}
>
<LooksRoundedIcon className={cls(cmn.mri10)} />
Connect wallet
{address ? 'Sign in' : 'Connect Wallet'}
</Button>
)
}}
Expand Down
Loading

0 comments on commit 9c5a156

Please sign in to comment.