Skip to content

Commit 576552a

Browse files
committed
feat: reset wallet on MaxKeyChainLengthExceeded error in social login
1 parent 87f4c43 commit 576552a

File tree

6 files changed

+77
-110
lines changed

6 files changed

+77
-110
lines changed

app/scripts/metamask-controller.js

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3980,11 +3980,9 @@ export default class MetamaskController extends EventEmitter {
39803980
* Unlock the vault with the latest global password.
39813981
*
39823982
* @param {string} password - latest global seedless password
3983-
* @returns {boolean} true if the sync was successful, false otherwise. Sync can fail if user is on a very old device
3984-
* and user has changed password more than 20 times since the last time they used the app on this device.
3983+
* @returns {void}
39853984
*/
39863985
async syncPasswordAndUnlockWallet(password) {
3987-
let isPasswordSynced = false;
39883986
const isSocialLoginFlow = this.onboardingController.getIsSocialLoginFlow();
39893987
// check if the password is outdated
39903988
let isPasswordOutdated = false;
@@ -4020,8 +4018,7 @@ export default class MetamaskController extends EventEmitter {
40204018
});
40214019
});
40224020
}
4023-
isPasswordSynced = true;
4024-
return isPasswordSynced;
4021+
return;
40254022
}
40264023
const releaseLock = await this.seedlessOperationMutex.acquire();
40274024

@@ -4046,10 +4043,6 @@ export default class MetamaskController extends EventEmitter {
40464043
globalPassword: password,
40474044
maxKeyChainLength: 20,
40484045
})
4049-
.then(() => {
4050-
// Case 1.
4051-
isPasswordSynced = true;
4052-
})
40534046
.catch((err) => {
40544047
log.error(`error while submitting global password: ${err.message}`);
40554048
if (err instanceof RecoveryError) {
@@ -4064,28 +4057,10 @@ export default class MetamaskController extends EventEmitter {
40644057
);
40654058
}
40664059
throw new JsonRpcError(-32603, err.message, err.data);
4067-
} else if (
4068-
err.message ===
4069-
SeedlessOnboardingControllerErrorMessage.MaxKeyChainLengthExceeded
4070-
) {
4071-
isPasswordSynced = false;
4072-
} else {
4073-
throw err;
40744060
}
4061+
throw err;
40754062
});
40764063

4077-
// we are unable to recover the old pwd enc key as user is on a very old device.
4078-
// create a new vault and encrypt the new vault with the latest global password.
4079-
// also show a info popup to user.
4080-
if (!isPasswordSynced) {
4081-
// refresh the current auth tokens to get the latest auth tokens
4082-
await this.seedlessOnboardingController.refreshAuthTokens();
4083-
// create a new vault and encrypt the new vault with the latest global password
4084-
await this.restoreSocialBackupAndGetSeedPhrase(password);
4085-
// display info popup to user based on the password sync status
4086-
return isPasswordSynced;
4087-
}
4088-
40894064
// re-encrypt the old vault data with the latest global password
40904065
const keyringEncryptionKey =
40914066
await this.seedlessOnboardingController.loadKeyringEncryptionKey();
@@ -4151,7 +4126,6 @@ export default class MetamaskController extends EventEmitter {
41514126
data: { success: changePasswordSuccess },
41524127
});
41534128
}
4154-
return isPasswordSynced;
41554129
} finally {
41564130
releaseLock();
41574131
}

test/e2e/page-objects/pages/login-page.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class LoginPage {
1616

1717
private resetWalletButton: string;
1818

19+
private connectionsRemovedModal: string;
20+
21+
private connectionsRemovedModalButton: string;
22+
1923
private incorrectPasswordMessage: { css: string; text: string };
2024

2125
constructor(driver: Driver) {
@@ -37,6 +41,9 @@ class LoginPage {
3741
};
3842

3943
this.resetWalletButton = '[data-testid="login-error-modal-button"]';
44+
this.connectionsRemovedModal = '[data-testid="connections-removed-modal"]';
45+
this.connectionsRemovedModalButton =
46+
'[data-testid="connections-removed-modal-button"]';
4047
}
4148

4249
async checkPageIsLoaded(): Promise<void> {
@@ -87,6 +94,16 @@ class LoginPage {
8794
);
8895
await this.driver.clickElementAndWaitToDisappear(this.resetWalletButton);
8996
}
97+
98+
async checkConnectionsRemovedModalIsDisplayed(): Promise<void> {
99+
console.log('Checking if connections removed modal is displayed');
100+
await this.driver.waitForSelector(this.connectionsRemovedModal);
101+
}
102+
103+
async resetWalletFromConnectionsRemovedModal(): Promise<void> {
104+
console.log('Resetting wallet from connections removed modal');
105+
await this.driver.clickElement(this.connectionsRemovedModalButton);
106+
}
90107
}
91108

92109
export default LoginPage;

test/e2e/tests/account/unlock-wallet.spec.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Mockttp } from 'mockttp';
2+
import { Browser } from 'selenium-webdriver';
23
import { withFixtures } from '../../helpers';
34
import FixtureBuilder from '../../fixture-builder';
45
import { Driver } from '../../webdriver/driver';
@@ -9,11 +10,15 @@ import LoginPage from '../../page-objects/pages/login-page';
910
import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
1011
import { MOCK_GOOGLE_ACCOUNT, WALLET_PASSWORD } from '../../constants';
1112
import { OAuthMockttpService } from '../../helpers/seedless-onboarding/mocks';
12-
import { importWalletWithSocialLoginOnboardingFlow } from '../../page-objects/flows/onboarding.flow';
13+
import {
14+
importWalletWithSocialLoginOnboardingFlow,
15+
onboardingMetricsFlow,
16+
} from '../../page-objects/flows/onboarding.flow';
1317
import SettingsPage from '../../page-objects/pages/settings/settings-page';
1418
import PrivacySettings from '../../page-objects/pages/settings/privacy-settings';
1519
import HeaderNavbar from '../../page-objects/pages/header-navbar';
1620
import ChangePasswordPage from '../../page-objects/pages/settings/change-password-page';
21+
import StartOnboardingPage from '../../page-objects/pages/onboarding/start-onboarding-page';
1722

1823
describe('Unlock wallet - ', function () {
1924
it('handle incorrect password during unlock and login successfully', async function () {
@@ -59,6 +64,7 @@ describe('Unlock wallet - ', function () {
5964
},
6065
},
6166
async ({ driver }: { driver: Driver }) => {
67+
await driver.delay(5_000);
6268
await importWalletWithSocialLoginOnboardingFlow({
6369
driver,
6470
});
@@ -96,9 +102,20 @@ describe('Unlock wallet - ', function () {
96102

97103
const loginPage = new LoginPage(driver);
98104
await loginPage.loginToHomepage(WALLET_PASSWORD);
105+
await loginPage.checkConnectionsRemovedModalIsDisplayed();
106+
// reset wallet from connections removed modal
107+
await loginPage.resetWalletFromConnectionsRemovedModal();
99108

100-
await homePage.checkPageIsLoaded();
101-
await homePage.checkConnectionsRemovedModalIsDisplayed();
109+
if (process.env.SELENIUM_BROWSER === Browser.FIREFOX) {
110+
await onboardingMetricsFlow(driver, {
111+
participateInMetaMetrics: false,
112+
dataCollectionForMarketing: false,
113+
});
114+
}
115+
116+
// check onboarding welcome page is loaded after resetting the wallet
117+
const startOnboardingPage = new StartOnboardingPage(driver);
118+
await startOnboardingPage.checkLoginPageIsLoaded();
102119
},
103120
);
104121
});

test/e2e/tests/reset-wallet/reset-wallet.spec.ts

Lines changed: 6 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Mockttp } from 'mockttp';
2-
import { AuthConnection } from '@metamask/seedless-onboarding-controller';
32
import { Browser } from 'selenium-webdriver';
43
import FixtureBuilder from '../../fixture-builder';
54
import { withFixtures } from '../../helpers';
@@ -21,70 +20,6 @@ import OnboardingPasswordPage from '../../page-objects/pages/onboarding/onboardi
2120
import SecureWalletPage from '../../page-objects/pages/onboarding/secure-wallet-page';
2221

2322
describe('Reset Wallet - ', function () {
24-
it('should be able to reset wallet when encounters un-recoverable error in social login unlock', async function () {
25-
await withFixtures(
26-
{
27-
fixtures: new FixtureBuilder({ onboarding: true }).build(),
28-
title: this.test?.fullTitle(),
29-
ignoredConsoleErrors: [
30-
'unable to proceed, wallet is locked',
31-
'The snap "npm:@metamask/message-signing-snap" has been terminated during execution', // issue #37342
32-
'npm:@metamask/message-signing-snap was stopped and the request was cancelled. This is likely because the Snap crashed.',
33-
],
34-
testSpecificMock: (server: Mockttp) => {
35-
// using this to mock the OAuth Service (Web Authentication flow + Auth server)
36-
const oAuthMockttpService = new OAuthMockttpService();
37-
return oAuthMockttpService.setup(server, {
38-
userEmail: MOCK_GOOGLE_ACCOUNT,
39-
throwAuthenticationErrorAtUnlock: true, // <=== This is intentional error to test the reset wallet flow
40-
passwordOutdated: true,
41-
});
42-
},
43-
},
44-
async ({ driver }: { driver: Driver }) => {
45-
await importWalletWithSocialLoginOnboardingFlow({
46-
driver,
47-
});
48-
49-
const homePage = new HomePage(driver);
50-
await homePage.checkPageIsLoaded();
51-
52-
const headerNavbar = new HeaderNavbar(driver);
53-
54-
await headerNavbar.lockMetaMask();
55-
56-
const loginPage = new LoginPage(driver);
57-
58-
// login should fail due to Authentication Error
59-
await loginPage.loginToHomepage(WALLET_PASSWORD);
60-
61-
// reset the wallet
62-
await loginPage.resetWallet();
63-
64-
if (process.env.SELENIUM_BROWSER === Browser.FIREFOX) {
65-
// In Firefox, we need to go to the metametrics page first
66-
await onboardingMetricsFlow(driver, {
67-
participateInMetaMetrics: true,
68-
dataCollectionForMarketing: false,
69-
});
70-
}
71-
72-
// should be on the welcome page after resetting the wallet
73-
const startOnboardingPage = new StartOnboardingPage(driver);
74-
await startOnboardingPage.checkLoginPageIsLoaded();
75-
76-
// import wallet with social login and start a new session
77-
await startOnboardingPage.importWalletWithSocialLogin(
78-
AuthConnection.Google,
79-
);
80-
81-
await loginPage.checkPageIsLoaded();
82-
await loginPage.loginToHomepage(WALLET_PASSWORD);
83-
await homePage.headerNavbar.checkPageIsLoaded();
84-
},
85-
);
86-
});
87-
8823
it('creates a new wallet with SRP and completes the onboarding process after resetting the wallet', async function () {
8924
await withFixtures(
9025
{
@@ -123,7 +58,9 @@ describe('Reset Wallet - ', function () {
12358
await loginPage.loginToHomepage(WALLET_PASSWORD);
12459

12560
// reset the wallet
126-
await loginPage.resetWallet();
61+
await loginPage.checkConnectionsRemovedModalIsDisplayed();
62+
// reset wallet from connections removed modal
63+
await loginPage.resetWalletFromConnectionsRemovedModal();
12764

12865
if (process.env.SELENIUM_BROWSER === Browser.FIREFOX) {
12966
// In Firefox, we need to go to the metametrics page first
@@ -205,7 +142,9 @@ describe('Reset Wallet - ', function () {
205142
await loginPage.loginToHomepage(WALLET_PASSWORD);
206143

207144
// reset the wallet
208-
await loginPage.resetWallet();
145+
await loginPage.checkConnectionsRemovedModalIsDisplayed();
146+
// reset wallet from connections removed modal
147+
await loginPage.resetWalletFromConnectionsRemovedModal();
209148

210149
if (process.env.SELENIUM_BROWSER === Browser.FIREFOX) {
211150
// In Firefox, we need to go to the metametrics page first

ui/components/app/connections-removed-modal/connections-removed-modal.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import React, { useCallback } from 'react';
2-
import { useDispatch } from 'react-redux';
1+
import React from 'react';
32
import { useI18nContext } from '../../../hooks/useI18nContext';
43
import {
54
AlignItems,
@@ -24,17 +23,17 @@ import {
2423
ModalBody,
2524
ButtonSize,
2625
} from '../../component-library';
27-
import { setShowConnectionsRemovedModal } from '../../../store/actions';
26+
27+
type ConnectionsRemovedModalProps = {
28+
onConfirm: () => void;
29+
};
2830

2931
// TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860
3032
// eslint-disable-next-line @typescript-eslint/naming-convention
31-
export default function ConnectionsRemovedModal() {
33+
export default function ConnectionsRemovedModal({
34+
onConfirm,
35+
}: ConnectionsRemovedModalProps) {
3236
const t = useI18nContext();
33-
const dispatch = useDispatch();
34-
35-
const onConfirm = useCallback(() => {
36-
dispatch(setShowConnectionsRemovedModal(false));
37-
}, [dispatch]);
3837

3938
return (
4039
<Modal
@@ -64,7 +63,12 @@ export default function ConnectionsRemovedModal() {
6463
</ModalHeader>
6564
<ModalBody>{t('connectionsRemovedModalDescription')}</ModalBody>
6665
<ModalFooter>
67-
<Button size={ButtonSize.Lg} block onClick={onConfirm}>
66+
<Button
67+
size={ButtonSize.Lg}
68+
block
69+
onClick={onConfirm}
70+
data-testid="connections-removed-modal-button"
71+
>
6872
{t('gotIt')}
6973
</Button>
7074
</ModalFooter>

ui/pages/unlock-page/unlock-page.component.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { withMetaMetrics } from '../../contexts/metametrics';
4545
import LoginErrorModal from '../onboarding-flow/welcome/login-error-modal';
4646
import { LOGIN_ERROR } from '../onboarding-flow/welcome/types';
4747
import MetaFoxHorizontalLogo from '../../components/ui/metafox-logo/horizontal-logo';
48+
import ConnectionsRemovedModal from '../../components/app/connections-removed-modal';
4849
import { getCaretCoordinates } from './unlock-page.util';
4950
import ResetPasswordModal from './reset-password-modal';
5051
import FormattedCounter from './formatted-counter';
@@ -129,6 +130,7 @@ class UnlockPage extends Component {
129130
isSubmitting: false,
130131
unlockDelayPeriod: 0,
131132
showLoginErrorModal: false,
133+
showConnectionsRemovedModal: false,
132134
};
133135

134136
failed_attempts = 0;
@@ -269,6 +271,7 @@ class UnlockPage extends Component {
269271
let finalUnlockDelayPeriod = 0;
270272
let errorReason;
271273
let shouldShowLoginErrorModal = false;
274+
let shouldShowConnectionsRemovedModal = false;
272275

273276
// Check if we are in the onboarding flow
274277
if (!isOnboardingCompleted) {
@@ -308,6 +311,10 @@ class UnlockPage extends Component {
308311
shouldShowLoginErrorModal = true;
309312
}
310313
break;
314+
case SeedlessOnboardingControllerErrorMessage.MaxKeyChainLengthExceeded:
315+
finalErrorMessage = message;
316+
shouldShowConnectionsRemovedModal = true;
317+
break;
311318
default:
312319
finalErrorMessage = message;
313320
break;
@@ -340,6 +347,7 @@ class UnlockPage extends Component {
340347
error: finalErrorMessage,
341348
unlockDelayPeriod: finalUnlockDelayPeriod,
342349
showLoginErrorModal: shouldShowLoginErrorModal,
350+
showConnectionsRemovedModal: shouldShowConnectionsRemovedModal,
343351
});
344352
};
345353

@@ -461,7 +469,11 @@ class UnlockPage extends Component {
461469
};
462470

463471
onResetWallet = async () => {
464-
this.setState({ showLoginErrorModal: false });
472+
this.setState({
473+
showLoginErrorModal: false,
474+
showConnectionsRemovedModal: false,
475+
showResetPasswordModal: false,
476+
});
465477
await this.props.resetWallet();
466478
await this.props.forceUpdateMetamaskState();
467479
this.props.navigate(DEFAULT_ROUTE, { replace: true });
@@ -474,6 +486,7 @@ class UnlockPage extends Component {
474486
isLocked,
475487
showResetPasswordModal,
476488
showLoginErrorModal,
489+
showConnectionsRemovedModal,
477490
} = this.state;
478491
const { isOnboardingCompleted, isSocialLoginFlow } = this.props;
479492
const { t } = this.context;
@@ -504,6 +517,9 @@ class UnlockPage extends Component {
504517
loginError={LOGIN_ERROR.RESET_WALLET}
505518
/>
506519
)}
520+
{showConnectionsRemovedModal && (
521+
<ConnectionsRemovedModal onConfirm={this.onResetWallet} />
522+
)}
507523
<Box
508524
as="form"
509525
display={Display.Flex}

0 commit comments

Comments
 (0)