Skip to content

Commit

Permalink
fix: address review issues
Browse files Browse the repository at this point in the history
  • Loading branch information
schmanu committed Oct 4, 2023
1 parent c2fa020 commit 7359ae7
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 222 deletions.
2 changes: 1 addition & 1 deletion src/components/common/ConnectWallet/PasswordRecovery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const PasswordRecovery = ({
<Box>
<Typography>
This browser is not registered with your Account yet. Please enter your recovery password to restore access
to this account.
to this Account.
</Typography>
<Box mt={2} display="flex" flexDirection="column" alignItems="baseline" gap={2}>
<TextField
Expand Down
44 changes: 13 additions & 31 deletions src/components/new-safe/create/steps/ConnectWalletStep/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { useEffect, useState } from 'react'
import { Box, Button, Divider, Grid, Typography } from '@mui/material'
import { Box, Button, Divider, Typography } from '@mui/material'
import useWallet from '@/hooks/wallets/useWallet'
import { useCurrentChain } from '@/hooks/useChains'
import { isPairingSupported } from '@/services/pairing/utils'

import type { NewSafeFormData } from '@/components/new-safe/create'
import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper'
import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep'
import layoutCss from '@/components/new-safe/create/styles.module.css'
import useConnectWallet from '@/components/common/ConnectWallet/useConnectWallet'
import KeyholeIcon from '@/components/common/icons/KeyholeIcon'
import PairingDescription from '@/components/common/PairingDetails/PairingDescription'
import PairingQRCode from '@/components/common/PairingDetails/PairingQRCode'
import { usePendingSafe } from '../StatusStep/usePendingSafe'
import { MPCWallet } from '@/components/common/ConnectWallet/MPCWallet'

const ConnectWalletStep = ({ onSubmit, setStep }: StepRenderProps<NewSafeFormData>) => {
const [pendingSafe] = usePendingSafe()
const wallet = useWallet()
const chain = useCurrentChain()
const isSupported = isPairingSupported(chain?.disabledWallets)
const handleConnect = useConnectWallet()
const [, setSubmitted] = useState(false)
useSyncSafeCreationStep(setStep)
Expand All @@ -37,33 +31,21 @@ const ConnectWalletStep = ({ onSubmit, setStep }: StepRenderProps<NewSafeFormDat
return (
<>
<Box className={layoutCss.row}>
<Grid container spacing={3}>
<Grid item xs={12} md={6} display="flex" flexDirection="column" alignItems="center" gap={2}>
<Box width={100} height={100} display="flex" alignItems="center" justifyContent="center">
<KeyholeIcon />
</Box>
<Box display="flex" flexDirection="column" alignItems="center" gap={2}>
<Box width={100} height={100} display="flex" alignItems="center" justifyContent="center">
<KeyholeIcon />
</Box>

<Button onClick={handleConnect} variant="contained" size="stretched" disableElevation>
Connect
</Button>
<Button onClick={handleConnect} variant="contained" size="stretched" disableElevation>
Connect
</Button>

<Divider sx={{ width: '100%' }}>
<Typography color="primary">or</Typography>
</Divider>
<Divider sx={{ width: '100%' }}>
<Typography color="primary">or</Typography>
</Divider>

<MPCWallet />
</Grid>

{isSupported && (
<Grid item xs={12} md={6} display="flex" flexDirection="column" alignItems="center" gap={2}>
<PairingQRCode />
<Typography variant="h6" fontWeight="700">
Connect to {'Safe{Wallet}'} mobile
</Typography>
<PairingDescription />
</Grid>
)}
</Grid>
<MPCWallet />
</Box>
</Box>
</>
)
Expand Down
124 changes: 124 additions & 0 deletions src/components/settings/SignerAccountMFA/PasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { DeviceShareRecovery } from '@/hooks/wallets/mpc/recovery/DeviceShareRecovery'
import { SecurityQuestionRecovery } from '@/hooks/wallets/mpc/recovery/SecurityQuestionRecovery'
import { Typography, TextField, FormControlLabel, Checkbox, Button, Box } from '@mui/material'
import { type Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit'
import { useState, useMemo } from 'react'
import { Controller, useForm } from 'react-hook-form'
import { enableMFA } from './helper'

enum PasswordFieldNames {
oldPassword = 'oldPassword',
newPassword = 'newPassword',
confirmPassword = 'confirmPassword',
storeDeviceShare = 'storeDeviceShare',
}

type PasswordFormData = {
[PasswordFieldNames.oldPassword]: string | undefined
[PasswordFieldNames.newPassword]: string
[PasswordFieldNames.confirmPassword]: string
[PasswordFieldNames.storeDeviceShare]: boolean
}

export const PasswordForm = ({ mpcCoreKit }: { mpcCoreKit: Web3AuthMPCCoreKit }) => {
const formMethods = useForm<PasswordFormData>({
mode: 'all',
defaultValues: async () => {
const isDeviceShareStored = await new DeviceShareRecovery(mpcCoreKit).isEnabled()
return {
confirmPassword: '',
oldPassword: undefined,
newPassword: '',
storeDeviceShare: isDeviceShareStored,
}
},
})

const { register, formState, getValues, control, handleSubmit } = formMethods

const [enablingMFA, setEnablingMFA] = useState(false)

const isPasswordSet = useMemo(() => {
const securityQuestions = new SecurityQuestionRecovery(mpcCoreKit)
return securityQuestions.isEnabled()
}, [mpcCoreKit])

const onSubmit = async (data: PasswordFormData) => {
setEnablingMFA(true)
try {
await enableMFA(mpcCoreKit, data)
} finally {
setEnablingMFA(false)
}
}

return (
<form onSubmit={handleSubmit(onSubmit)}>
<Box display="flex" flexDirection="column" gap={3} alignItems="baseline">
{isPasswordSet ? (
<Typography>You already have a recovery password setup.</Typography>
) : (
<Typography>You have no password setup. We suggest adding one to secure your Account.</Typography>
)}
{isPasswordSet && (
<TextField
placeholder="Old password"
label="Old password"
type="password"
error={!!formState.errors[PasswordFieldNames.oldPassword]}
helperText={formState.errors[PasswordFieldNames.oldPassword]?.message}
{...register(PasswordFieldNames.oldPassword, {
required: true,
})}
/>
)}
<TextField
placeholder="New password"
label="New password"
type="password"
error={!!formState.errors[PasswordFieldNames.newPassword]}
helperText={formState.errors[PasswordFieldNames.newPassword]?.message}
{...register(PasswordFieldNames.newPassword, {
required: true,
minLength: 6,
})}
/>
<TextField
placeholder="Confirm new password"
label="Confirm new password"
type="password"
error={!!formState.errors[PasswordFieldNames.confirmPassword]}
helperText={formState.errors[PasswordFieldNames.confirmPassword]?.message}
{...register(PasswordFieldNames.confirmPassword, {
required: true,
validate: (value: string) => {
const currentNewPW = getValues(PasswordFieldNames.newPassword)
if (value !== currentNewPW) {
return 'Passwords do not match'
}
},
})}
/>

<Controller
control={control}
name={PasswordFieldNames.storeDeviceShare}
render={({ field: { value, ...field } }) => (
<FormControlLabel
control={<Checkbox checked={value ?? false} {...field} />}
label="Do not ask for second factor on this device"
/>
)}
/>

<Button
sx={{ justifySelf: 'flex-start' }}
disabled={!formMethods.formState.isValid || enablingMFA}
type="submit"
>
Change
</Button>
</Box>
</form>
)
}
68 changes: 68 additions & 0 deletions src/components/settings/SignerAccountMFA/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { DeviceShareRecovery } from '@/hooks/wallets/mpc/recovery/DeviceShareRecovery'
import { SecurityQuestionRecovery } from '@/hooks/wallets/mpc/recovery/SecurityQuestionRecovery'
import { logError } from '@/services/exceptions'
import ErrorCodes from '@/services/exceptions/ErrorCodes'
import { asError } from '@/services/exceptions/utils'
import { getPubKeyPoint } from '@tkey-mpc/common-types'
import { type Web3AuthMPCCoreKit } from '@web3auth/mpc-core-kit'
import BN from 'bn.js'

export const isMFAEnabled = (mpcCoreKit: Web3AuthMPCCoreKit) => {
if (!mpcCoreKit) {
return false
}
const { shareDescriptions } = mpcCoreKit?.getKeyDetails()
return !Object.entries(shareDescriptions).some((value) => value[0]?.includes('hashedShare'))
}

export const enableMFA = async (
mpcCoreKit: Web3AuthMPCCoreKit,
{
newPassword,
oldPassword,
storeDeviceShare,
}: {
newPassword: string
oldPassword: string | undefined
storeDeviceShare: boolean
},
) => {
if (!mpcCoreKit) {
return
}
const securityQuestions = new SecurityQuestionRecovery(mpcCoreKit)
const deviceShareRecovery = new DeviceShareRecovery(mpcCoreKit)
try {
// 1. setup device factor with password recovery
await securityQuestions.upsertPassword(newPassword, oldPassword)
const securityQuestionFactor = await securityQuestions.recoverWithPassword(newPassword)
if (!securityQuestionFactor) {
throw Error('Could not recover using the new password recovery')
}

if (!isMFAEnabled(mpcCoreKit)) {
// 2. enable MFA in mpcCoreKit
const recoveryFactor = await mpcCoreKit.enableMFA({})

// 3. remove the recovery factor the mpcCoreKit creates
const recoverKey = new BN(recoveryFactor, 'hex')
const recoverPubKey = getPubKeyPoint(recoverKey)
await mpcCoreKit.deleteFactor(recoverPubKey, recoverKey)
}

const hasDeviceShare = await deviceShareRecovery.isEnabled()

if (!hasDeviceShare && storeDeviceShare) {
await deviceShareRecovery.createAndStoreDeviceFactor()
}

if (hasDeviceShare && !storeDeviceShare) {
// Switch to password recovery factor such that we can delete the device factor
await mpcCoreKit.inputFactorKey(new BN(securityQuestionFactor, 'hex'))
await deviceShareRecovery.removeDeviceFactor()
}
} catch (e) {
const error = asError(e)
logError(ErrorCodes._304, error.message)
}
}
Loading

0 comments on commit 7359ae7

Please sign in to comment.