diff --git a/packages/app/features/auth/account-recovery/account-recovery.tsx b/packages/app/features/auth/account-recovery/account-recovery.tsx deleted file mode 100644 index 197561149..000000000 --- a/packages/app/features/auth/account-recovery/account-recovery.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import type { - ChallengeResponse, - ErrorWithMessage, - VerifyChallengeRequest, -} from '@my/api/src/routers/account-recovery/types' -import { RecoveryOptions } from '@my/api/src/routers/account-recovery/types' -import { Button, ButtonText, Paragraph, Stack, XStack, YStack } from '@my/ui' -import type { SignMessageErrorType } from '@wagmi/core' -import { IconError, IconRefresh } from 'app/components/icons' -import RecoverWithEOA from 'app/features/auth/account-recovery/eoa/RecoverWithEOA' -import RecoverWithPasskey from 'app/features/auth/account-recovery/passkey/RecoverWithPasskey' -import { api } from 'app/utils/api' -import { useCallback, useEffect, useState } from 'react' -import { useRouter } from 'solito/router' - -interface Props { - onClose?: () => void -} - -export enum SignState { - NOT_COMPLETE = 0, - IN_PROGRESS = 1, - FAILED = 2, -} - -export default function AccountRecovery(props: Props) { - const [error, setError] = useState() - const [signState] = useState(SignState.NOT_COMPLETE) - const [challengeData, setChallengeData] = useState() - const { mutateAsync: getChallengeMutateAsync } = api.challenge.getChallenge.useMutation({ - retry: false, - }) - const { mutateAsync: validateSignatureMutateAsync } = api.challenge.validateSignature.useMutation( - { retry: false } - ) - const router = useRouter() - - useEffect(() => { - const loadChallenge = async () => { - await getChallengeMutateAsync() - .then((challengeResponse) => { - setChallengeData(challengeResponse as ChallengeResponse) - }) - .catch((err) => { - setError(err) - }) - } - loadChallenge() - }, [getChallengeMutateAsync]) - - const onSignSuccess = useCallback( - async (verifyChallengeRequest: VerifyChallengeRequest) => { - await validateSignatureMutateAsync({ - ...verifyChallengeRequest, - }) - .then(() => { - // JWT is set via Set-Cookie header - router.push('/') - }) - .catch((err) => { - setError(err) - }) - }, - [validateSignatureMutateAsync, router] - ) - - const onSignError = (e: SignMessageErrorType) => { - setError({ - message: `Failed to sign challenge: ${e.message.split('.')[0]}. Please try again.`, - }) - } - - const showRecoveryOptions = () => { - return ( - challengeData && ( - - - - - - ) - ) - } - - const showHeading = () => { - return ( - <> - {/* Icon */} - {error ? : } - - {/* Heading */} - {error ? ( - Unable to recover account - ) : ( - Recover account - )} - - {/* Description */} - - {error?.message ? ( - {error.message} - ) : ( - - Recover with the {getRecoveryOptionsStr()} linked to your account. - - )} - - ) - } - - return ( - - - {showHeading()} - {!error && challengeData && showRecoveryOptions()} - - - - - ) -} - -const getRecoveryOptionsStr = (): string => { - const recoveryOptions = Object.values(RecoveryOptions) - - if (recoveryOptions.length <= 2) { - return recoveryOptions.join(' or ') - } - return `${recoveryOptions.slice(0, recoveryOptions.length - 1).join(',')} or ${ - recoveryOptions[-1] - }` -} diff --git a/packages/app/features/auth/account-recovery/eoa/RecoverWithEOA.tsx b/packages/app/features/auth/account-recovery/eoa/RecoverWithEOA.tsx deleted file mode 100644 index f9b103c3a..000000000 --- a/packages/app/features/auth/account-recovery/eoa/RecoverWithEOA.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { - RecoveryEOAPreamble, - RecoveryOptions, - type ChallengeResponse, - type VerifyChallengeRequest, -} from '@my/api/src/routers/account-recovery/types' -import { Button, ButtonText } from '@my/ui' -import type { SignMessageErrorType } from '@wagmi/core' -import type { SignState } from 'app/features/auth/account-recovery/account-recovery' -import { useAccount, useSignMessage } from 'wagmi' -import type { SignMessageData, SignMessageVariables } from 'wagmi/query' - -interface Props { - challengeData: ChallengeResponse - signState: SignState - - onSignSuccess: (args: VerifyChallengeRequest) => Promise - onSignError: (e: SignMessageErrorType) => void -} - -export default function RecoverWithEOA(props: Props) { - const { signMessage } = useSignMessage() - const { address, isConnected } = useAccount() - - const onSuccess = async (data: SignMessageData, variables: SignMessageVariables) => { - await props.onSignSuccess({ - recoveryType: RecoveryOptions.EOA, - signature: data, - identifier: variables.account as string, - challengeId: props.challengeData.id, - }) - } - - const onPress = async () => { - if (props.challengeData.challenge) { - signMessage( - { - message: RecoveryEOAPreamble + props.challengeData.challenge, - account: address, - }, - { - onSuccess: onSuccess, - onError: props.onSignError, - } - ) - } - } - - return ( - - ) -} diff --git a/packages/app/features/auth/account-recovery/passkey/RecoverWithPasskey.tsx b/packages/app/features/auth/account-recovery/passkey/RecoverWithPasskey.tsx deleted file mode 100644 index 6faeda9bf..000000000 --- a/packages/app/features/auth/account-recovery/passkey/RecoverWithPasskey.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Button, ButtonText } from '@my/ui' -import { signChallenge } from 'app/utils/signChallenge' -import { - RecoveryOptions, - type ChallengeResponse, - type VerifyChallengeRequest, -} from '@my/api/src/routers/account-recovery/types' -import { bytesToHex, hexToBytes } from 'viem' -import type { SignMessageErrorType } from '@wagmi/core' - -interface Props { - challengeData: ChallengeResponse - onSignSuccess: (args: VerifyChallengeRequest) => void - // https://wagmi.sh/react/api/hooks/useSignMessage#onerror - onSignError: (e: SignMessageErrorType) => void -} - -export default function RecoverWithPasskey(props: Props) { - const onPress = async () => { - const rawIdsB64: { id: string; userHandle: string }[] = [] // this user is anon so only resident keys are allowed - const { encodedWebAuthnSig, accountName, keySlot } = await signChallenge( - props.challengeData.challenge as `0x${string}`, - rawIdsB64 - ) - - // SendVerifier.verifySignature expects the first byte to be the passkey keySlot, followed by the signature - const encodedWebAuthnSigBytes = hexToBytes(encodedWebAuthnSig) - const newEncodedWebAuthnSigBytes = new Uint8Array(encodedWebAuthnSigBytes.length + 1) - newEncodedWebAuthnSigBytes[0] = keySlot - newEncodedWebAuthnSigBytes.set(encodedWebAuthnSigBytes, 1) - - props.onSignSuccess({ - recoveryType: RecoveryOptions.WEBAUTHN, - signature: bytesToHex(newEncodedWebAuthnSigBytes), - challengeId: props.challengeData.id, - identifier: `${accountName}.${keySlot}`, - }) - } - - return ( - - ) -}