Skip to content

Commit

Permalink
feat: check recovery password before exporting account
Browse files Browse the repository at this point in the history
  • Loading branch information
schmanu committed Oct 18, 2023
1 parent 0eb4d19 commit 6c59117
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 32 deletions.
15 changes: 8 additions & 7 deletions src/components/common/ConnectWallet/MPCWalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMPCWallet, MPCWalletState } from '@/hooks/wallets/mpc/useMPCWallet'
import { type UserInfo } from '@web3auth/mpc-core-kit'
import { createContext, type ReactElement } from 'react'
import { createContext, useMemo, type ReactElement } from 'react'

type MPCWalletContext = {
loginPending: boolean
Expand All @@ -10,6 +10,7 @@ type MPCWalletContext = {
recoverFactorWithPassword: (password: string, storeDeviceFactor: boolean) => Promise<void>
walletState: MPCWalletState
userInfo: UserInfo | undefined
exportPk: (password: string) => Promise<string | undefined>
}

export const MpcWalletContext = createContext<MPCWalletContext>({
Expand All @@ -20,16 +21,16 @@ export const MpcWalletContext = createContext<MPCWalletContext>({
upsertPasswordBackup: () => Promise.resolve(),
recoverFactorWithPassword: () => Promise.resolve(),
userInfo: undefined,
exportPk: () => Promise.resolve(undefined),
})

export const MpcWalletProvider = ({ children }: { children: ReactElement }) => {
const mpcValue = useMPCWallet()

return (
<MpcWalletContext.Provider
value={{ ...mpcValue, loginPending: mpcValue.walletState === MPCWalletState.AUTHENTICATING }}
>
{children}
</MpcWalletContext.Provider>
const providedValue = useMemo(
() => ({ ...mpcValue, loginPending: mpcValue.walletState === MPCWalletState.AUTHENTICATING }),
[mpcValue],
)

return <MpcWalletContext.Provider value={providedValue}>{children}</MpcWalletContext.Provider>
}
70 changes: 46 additions & 24 deletions src/components/settings/ExportMPCAccount/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
import { MpcWalletContext } from '@/components/common/ConnectWallet/MPCWalletProvider'
import CopyButton from '@/components/common/CopyButton'
import useMPC from '@/hooks/wallets/mpc/useMPC'
import { Alert, Box, Button, TextField, Typography } from '@mui/material'
import { COREKIT_STATUS } from '@web3auth/mpc-core-kit'
import { useState } from 'react'
import { useContext, useState } from 'react'
import { useForm } from 'react-hook-form'

enum ExportFieldNames {
password = 'password',
}

type ExportFormData = {
[ExportFieldNames.password]: string
}

const ExportMPCAccount = () => {
const mpcCoreKit = useMPC()
const { exportPk, walletState } = useContext(MpcWalletContext)

const [pk, setPk] = useState<string | undefined>()
const [isExporting, setIsExporting] = useState(false)
const formMethods = useForm<ExportFormData>({
mode: 'all',
defaultValues: {
[ExportFieldNames.password]: '',
},
})
const { register, formState, handleSubmit } = formMethods

const isLoggedIn = mpcCoreKit?.status === COREKIT_STATUS.LOGGED_IN

const exportPK = async () => {
const onSubmit = async (data: ExportFormData) => {
try {
setIsExporting(true)
const exportedPK = await mpcCoreKit?._UNSAFE_exportTssKey()
setPk(exportedPK)
const pk = await exportPk(data[ExportFieldNames.password])
setPk(pk)
} catch (err) {
} finally {
setIsExporting(false)
}
Expand All @@ -26,22 +40,18 @@ const ExportMPCAccount = () => {
setPk(undefined)
}

if (!isLoggedIn) {
return null
}
return (
<Box>
<Box>
<form onSubmit={handleSubmit(onSubmit)}>
<Box display="flex" flexDirection="column" gap={1} alignItems="flex-start">
<Typography>
This action reveals the seed phrase / private key of your logged in signer account. This export can be used to
move the account into a self-custodial wallet application.
Accounts created via Google can be exported and imported to any non-custodial wallet outside of Safe.
</Typography>
<Alert severity="warning" sx={{ mt: 3 }}>
Anyone who has access to this seed phrase / private key has <b>full access</b> over your Signer Account. You
should never disclose or share it with anyone and store it securely.
<Alert severity="warning" sx={{ mt: 3, mb: 3 }}>
Never disclose your keys or seed phrase to anyone. If someone gains access to them, they have full access over
your signer account.
</Alert>
{pk ? (
<Box display="flex" flexDirection="row" alignItems="center" mt={3} gap={1}>
<Box display="flex" flexDirection="row" alignItems="center" gap={1}>
<TextField
label="Signer account key"
value={pk}
Expand All @@ -53,12 +63,24 @@ const ExportMPCAccount = () => {
</Button>
</Box>
) : (
<Button color="primary" variant="contained" disabled={isExporting} onClick={exportPK} sx={{ mt: 3 }}>
Export
</Button>
<>
<TextField
placeholder="Enter recovery password"
label="Password"
type="password"
error={!!formState.errors[ExportFieldNames.password]}
helperText={formState.errors[ExportFieldNames.password]?.message}
{...register(ExportFieldNames.password, {
required: true,
})}
/>
<Button color="primary" variant="contained" disabled={isExporting} type="submit" sx={{ mt: 3 }}>
Export
</Button>
</>
)}
</Box>
</Box>
</form>
)
}

Expand Down
22 changes: 21 additions & 1 deletion src/hooks/wallets/mpc/useMPCWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type MPCWalletHook = {
triggerLogin: () => Promise<void>
resetAccount: () => Promise<void>
userInfo: UserInfo | undefined
exportPk: (password: string) => Promise<string | undefined>
}

export const useMPCWallet = (): MPCWalletHook => {
Expand All @@ -33,7 +34,7 @@ export const useMPCWallet = (): MPCWalletHook => {
// This is a critical function that should only be used for testing purposes
// Resetting your account means clearing all the metadata associated with it from the metadata server
// The key details will be deleted from our server and you will not be able to recover your account
if (!mpcCoreKit || !mpcCoreKit.metadataKey) {
if (!mpcCoreKit?.metadataKey) {
throw new Error('MPC Core Kit is not initialized or the user is not logged in')
}

Expand Down Expand Up @@ -125,12 +126,31 @@ export const useMPCWallet = (): MPCWalletHook => {
}
}

const exportPk = async (password: string): Promise<string | undefined> => {
if (!mpcCoreKit) {
throw new Error('MPC Core Kit is not initialized')
}
const securityQuestions = new SecurityQuestionRecovery(mpcCoreKit)

try {
if (securityQuestions.isEnabled()) {
// Only export PK if recovery works
await securityQuestions.recoverWithPassword(password)
}
const exportedPK = await mpcCoreKit?._UNSAFE_exportTssKey()
return exportedPK
} catch (err) {
// TODO: Throw error through sentry
}
}

return {
triggerLogin,
walletState,
recoverFactorWithPassword,
resetAccount: criticalResetAccount,
upsertPasswordBackup: () => Promise.resolve(),
userInfo: mpcCoreKit?.state.userInfo,
exportPk,
}
}

0 comments on commit 6c59117

Please sign in to comment.