fix: remove magic recovery code, closes #2955

This commit is contained in:
kyranjamie
2023-07-25 11:19:22 +01:00
committed by kyranjamie
parent 89d77df751
commit fa9d13bf51
7 changed files with 2 additions and 252 deletions

View File

@@ -1,81 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import toast from 'react-hot-toast';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { decrypt } from '@stacks/wallet-sdk';
import { RouteUrls } from '@shared/route-urls';
import { useFinishAuthRequest } from '@app/common/authentication/use-finish-auth-request';
import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state';
import { useLoading } from '@app/common/hooks/use-loading';
import { delay } from '@app/common/utils';
import { useAppDispatch } from '@app/store';
import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actions';
function pullMagicRecoveryCodeFromParams(urlSearchParams: URLSearchParams) {
return urlSearchParams.get('magicRecoveryCode');
}
async function simulateShortDelayToAvoidImmediateNavigation() {
await delay(850);
}
export function useMagicRecoveryCode() {
const { isLoading, setIsLoading, setIsIdle } = useLoading('useMagicRecoveryCode');
const [urlSearchParams] = useSearchParams();
const finishSignIn = useFinishAuthRequest();
const [error, setPasswordError] = useState('');
const { decodedAuthRequest } = useOnboardingState();
const navigate = useNavigate();
const dispatch = useAppDispatch();
const handleNavigate = useCallback(() => {
if (decodedAuthRequest) {
setTimeout(() => {
void finishSignIn(0);
}, 1000);
} else {
navigate(RouteUrls.SetPassword);
}
}, [navigate, decodedAuthRequest, finishSignIn]);
const decryptMagicRecoveryCode = useCallback(
async (password: string) => {
const magicRecoveryCode = pullMagicRecoveryCodeFromParams(urlSearchParams);
if (!magicRecoveryCode) throw Error('No magic recovery seed');
setIsLoading();
try {
const codeBuffer = Buffer.from(magicRecoveryCode, 'base64');
const secretKey = await decrypt(codeBuffer, password);
toast.success('Password correct');
await simulateShortDelayToAvoidImmediateNavigation();
dispatch(inMemoryKeyActions.saveUsersSecretKeyToBeRestored(secretKey));
handleNavigate();
} catch (error) {
setPasswordError(`Incorrect password, try again.`);
setIsIdle();
}
},
[urlSearchParams, setIsLoading, dispatch, handleNavigate, setIsIdle]
);
useEffect(() => {
if (pullMagicRecoveryCodeFromParams(urlSearchParams)) return;
navigate(RouteUrls.SignIn);
}, [navigate, urlSearchParams]);
useEffect(() => {
setIsIdle();
return () => setIsIdle();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return {
error,
isLoading,
decryptMagicRecoveryCode,
};
}

View File

@@ -47,25 +47,6 @@ export function extractPhraseFromPasteEvent(event: ClipboardEvent) {
return extractPhraseFromString(pasted);
}
export function validateAndCleanRecoveryInput(value: string) {
const cleaned = value.trim();
// Base64 encoded encrypted phrase
let cleanedEncrypted = cleaned.replace(/\s/gm, '');
const isPossibleRecoveryKey = /^[a-zA-Z0-9\+\/]+=?$/.test(cleanedEncrypted);
if (isPossibleRecoveryKey && cleanedEncrypted.slice(-1) !== '=') {
// Append possibly missing equals sign padding
cleanedEncrypted = `${cleanedEncrypted}=`;
}
if (cleanedEncrypted.length >= 108) {
return {
isValid: true,
value: cleanedEncrypted,
};
}
return { isValid: false, value };
}
interface MakeTxExplorerLinkArgs {
blockchain: Blockchains;
mode: BitcoinNetworkModes;

View File

@@ -1,101 +0,0 @@
import { memo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Flex, Input, Stack, StackProps, Text } from '@stacks/ui';
import { WalletPageSelectors } from '@tests-legacy/page-objects/wallet.selectors';
import { Form, Formik } from 'formik';
import { RouteUrls } from '@shared/route-urls';
import { useMagicRecoveryCode } from '@app/common/hooks/auth/use-magic-recovery-code';
import { useRouteHeader } from '@app/common/hooks/use-route-header';
import { CenteredPageContainer } from '@app/components/centered-page-container';
import { ErrorLabel } from '@app/components/error-label';
import { Header } from '@app/components/header';
import { Caption } from '@app/components/typography';
const MagicRecoveryCodeForm: React.FC<StackProps> = memo(props => {
const navigate = useNavigate();
const { decryptMagicRecoveryCode, error, isLoading } = useMagicRecoveryCode();
return (
<Formik
initialValues={{ password: '' }}
onSubmit={values => decryptMagicRecoveryCode(values.password)}
>
{form => (
<Form>
<Stack spacing="loose" {...props}>
<Stack>
<Input
autoFocus
type="password"
name="password"
placeholder="Your password"
fontSize="16px"
autoCapitalize="off"
spellCheck={false}
width="100%"
onChange={form.handleChange}
value={form.values.password}
/>
{error && (
<ErrorLabel lineHeight="16px">
<Text
textAlign="left"
textStyle="caption"
color="feedback.error"
data-testid="sign-in-seed-error"
>
{error}
</Text>
</ErrorLabel>
)}
</Stack>
<Flex justifyContent="flex-end">
<Button
mode="tertiary"
isDisabled={isLoading}
mr="base"
type="button"
onClick={() => navigate(RouteUrls.SignIn)}
>
Go back
</Button>
<Button
isLoading={isLoading}
isDisabled={isLoading}
data-testid="decrypt-recovery-button"
type="submit"
>
Continue
</Button>
</Flex>
</Stack>
</Form>
)}
</Formik>
);
});
export const MagicRecoveryCode: React.FC = memo(() => {
const navigate = useNavigate();
useRouteHeader(
<Header title="Enter your password" onClose={() => navigate(RouteUrls.SignIn)} hideActions />
);
return (
<CenteredPageContainer>
<Caption
mb="base"
textAlign={['left', 'center']}
data-testid={WalletPageSelectors.MagicRecoveryMessage}
>
You entered a Magic Recovery Code. Enter the password you set when you first created your
Blockstack ID.
</Caption>
<MagicRecoveryCodeForm mt="auto" />
</CenteredPageContainer>
);
});

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import toast from 'react-hot-toast';
import { useNavigate } from 'react-router-dom';
import { validateMnemonic } from 'bip39';
@@ -8,11 +7,7 @@ import { RouteUrls } from '@shared/route-urls';
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { useLoading } from '@app/common/hooks/use-loading';
import {
delay,
extractPhraseFromPasteEvent,
validateAndCleanRecoveryInput,
} from '@app/common/utils';
import { delay, extractPhraseFromPasteEvent } from '@app/common/utils';
import { useAppDispatch } from '@app/store';
import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actions';
import { onboardingActions } from '@app/store/onboarding/onboarding.actions';
@@ -56,24 +51,6 @@ export function useSignIn() {
handleSetError('Entering your Secret Key is required.');
}
// recovery key?
if (parsedKeyInput.split(' ').length <= 1) {
const result = validateAndCleanRecoveryInput(parsedKeyInput);
if (result.isValid) {
toast.success('Magic recovery code detected');
await simulateShortDelayToAvoidImmediateNavigation();
dispatch(onboardingActions.hideSuggestedFirstSteps(true));
navigate({
pathname: RouteUrls.MagicRecoveryCode,
search: `?magicRecoveryCode=${parsedKeyInput}`,
});
return;
} else {
// single word and not a valid recovery key
handleSetError();
}
}
if (!validateMnemonic(parsedKeyInput)) {
handleSetError();
return;

View File

@@ -30,7 +30,6 @@ import { ChooseAccount } from '@app/pages/choose-account/choose-account';
import { FundPage } from '@app/pages/fund/fund';
import { Home } from '@app/pages/home/home';
import { BackUpSecretKeyPage } from '@app/pages/onboarding/back-up-secret-key/back-up-secret-key';
import { MagicRecoveryCode } from '@app/pages/onboarding/magic-recovery-code/magic-recovery-code';
import { SignIn } from '@app/pages/onboarding/sign-in/sign-in';
import { WelcomePage } from '@app/pages/onboarding/welcome/welcome';
import { PsbtRequest } from '@app/pages/psbt-request/psbt-request';
@@ -190,7 +189,6 @@ function useAppRoutes() {
</OnboardingGate>
}
/>
<Route path={RouteUrls.MagicRecoveryCode} element={<MagicRecoveryCode />} />
<Route
path={RouteUrls.AddNetwork}
element={

View File

@@ -1,6 +1,6 @@
import { RouteUrls } from '@shared/route-urls';
import { MAGIC_RECOVERY_KEY, MAGIC_RECOVERY_PASSWORD, SECRET_KEY } from '../../mocks';
import { SECRET_KEY } from '../../mocks';
import { WalletPage } from '../../page-objects/wallet.page';
import { SettingsSelectors } from '../settings.selectors';
import { BrowserDriver, createTestSelector, setupBrowser } from '../utils';
@@ -71,23 +71,6 @@ describe(`Onboarding integration tests`, () => {
await wallet.waitForEnterPasswordInput();
});
it('should be able to login from Magic Recovery Code', async () => {
await wallet.clickDenyAnalytics();
await wallet.clickSignIn();
await wallet.enterSecretKey(MAGIC_RECOVERY_KEY);
await wallet.waitForMagicRecoveryMessage();
const magicRecoveryElement = await wallet.page.$$(wallet.$magicRecoveryMessage);
const magicRecoveryMessage = await magicRecoveryElement[0].innerText();
expect(magicRecoveryMessage).toEqual(
'You entered a Magic Recovery Code. Enter the password you set when you first created your Blockstack ID.'
);
await wallet.decryptRecoveryCode(MAGIC_RECOVERY_PASSWORD);
await wallet.enterNewPassword('lksjdflksjlfkjsdlfjsldf');
await wallet.waitForHomePage();
const homePageVisible = await wallet.page.isVisible(wallet.$homePageContainer);
expect(homePageVisible).toBeTruthy();
});
it('should show onboarding steps for a new wallet with only one account', async () => {
await wallet.clickDenyAnalytics();
await wallet.clickSignUp();

View File

@@ -64,10 +64,3 @@ export const STX_TRANSFER_DECODED = {
export const SECRET_KEY_2 =
'derive plug aerobic cook until crucial school fine cushion panda ready crew photo typical nuclear ride steel indicate cupboard potato ignore bamboo script galaxy';
export const MAGIC_RECOVERY_KEY =
'KDR6O8gKXGmstxj4d2oQqCi806M/Cmrbiatc6g7MkQQLVreRA95IoPtvrI3N230jTTGb2XWT5joRFKPfY/2YlmRz1brxoaDJCNS4z18Iw5Y=';
export const MAGIC_RECOVERY_PASSWORD = 'test202020';
// export const APINetworkRecipientAddress = 'SPJ2XRD8PNGVAMX9GFJSP00A7Y0H9Z5WE39NE5XP';