mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-01-12 22:53:27 +08:00
fix: remove magic recovery code, closes #2955
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user