Skip to content

Commit b2a0d06

Browse files
authored
fix: resetpassword screen touch-id state (MetaMask#23798)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** * fix: resetpassword screen touch-id state * Jira: https://consensyssoftware.atlassian.net/browse/SL-371 <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: resetpassword screen touch-id state ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/SL-371 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Saves biometry choice to storage only after a successful password reset, removing premature writes and adding tests to verify the flow. > > - **ResetPassword flow (`app/components/Views/ResetPassword/index.js`)**: > - Move `updateAuthTypeStorageFlags(this.state.biometryChoice)` to run after successful password reset and vault recreation. > - Remove storage update from `updateBiometryChoice` handler. > - **Tests (`app/components/Views/ResetPassword/index.test.tsx`)**: > - Mock `updateAuthTypeStorageFlags` and add test ensuring it is called only after confirming the password change. > - Minor test scaffolding updates to support the new flow. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 937780b. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent cc420fe commit b2a0d06

2 files changed

Lines changed: 136 additions & 1 deletion

File tree

app/components/Views/ResetPassword/index.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,13 +542,22 @@ class ResetPassword extends PureComponent {
542542

543543
// Set biometrics for new password
544544
await Authentication.resetPassword();
545+
545546
try {
546547
// compute and store the new authentication method
547548
const authData = await Authentication.componentAuthenticationType(
548549
this.state.biometryChoice,
549550
this.state.rememberMe,
550551
);
551552
await Authentication.storePasswordWithFallback(password, authData);
553+
if (
554+
Authentication.authData.currentAuthType ===
555+
AUTHENTICATION_TYPE.BIOMETRIC
556+
) {
557+
await updateAuthTypeStorageFlags(this.state.biometryChoice);
558+
} else {
559+
await updateAuthTypeStorageFlags(false);
560+
}
552561
} catch (error) {
553562
Logger.error(error);
554563
}
@@ -624,7 +633,6 @@ class ResetPassword extends PureComponent {
624633
};
625634

626635
updateBiometryChoice = async (biometryChoice) => {
627-
await updateAuthTypeStorageFlags(biometryChoice);
628636
this.setState({ biometryChoice });
629637
};
630638

app/components/Views/ResetPassword/index.test.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ jest.mock('../../../core/Engine', () => ({
4747

4848
jest.mock('lottie-react-native', () => 'LottieView');
4949

50+
const mockUpdateAuthTypeStorageFlags = jest.fn().mockResolvedValue(undefined);
51+
jest.mock('../../../util/authentication', () => ({
52+
...jest.requireActual('../../../util/authentication'),
53+
updateAuthTypeStorageFlags: (biometryChoice: boolean) =>
54+
mockUpdateAuthTypeStorageFlags(biometryChoice),
55+
}));
56+
5057
jest.mock('../../../store/storage-wrapper', () => ({
5158
setItem: jest.fn(),
5259
getItem: jest.fn().mockResolvedValue(null), // Mock to return null to avoid biometrics interference
@@ -65,6 +72,7 @@ jest.mock('../../../core/Authentication', () => ({
6572
getPassword: jest.fn().mockResolvedValue(null),
6673
resetPassword: jest.fn().mockResolvedValue(undefined),
6774
storePassword: jest.fn().mockResolvedValue(undefined),
75+
storePasswordWithFallback: jest.fn().mockResolvedValue(undefined),
6876
newWalletAndKeychain: jest
6977
.fn()
7078
.mockImplementation(
@@ -77,6 +85,9 @@ jest.mock('../../../core/Authentication', () => ({
7785
),
7886
checkIsSeedlessPasswordOutdated: jest.fn().mockResolvedValue(false),
7987
lockApp: jest.fn().mockResolvedValue(undefined),
88+
authData: {
89+
currentAuthType: 'passcode',
90+
},
8091
}));
8192

8293
jest.mock('../../../core/NavigationService', () => ({
@@ -1004,4 +1015,120 @@ describe('ResetPassword', () => {
10041015
);
10051016
});
10061017
});
1018+
1019+
describe('biometry choice storage', () => {
1020+
it('saves biometry choice as false when auth type is not biometric', async () => {
1021+
mockUpdateAuthTypeStorageFlags.mockClear();
1022+
1023+
const component = await renderConfirmPasswordView();
1024+
1025+
const newPasswordInput = component.getByTestId(
1026+
ChoosePasswordSelectorsIDs.NEW_PASSWORD_INPUT_ID,
1027+
);
1028+
1029+
await act(async () => {
1030+
fireEvent.changeText(newPasswordInput, 'NewPassword123');
1031+
});
1032+
1033+
const confirmPasswordInput = component.getByTestId(
1034+
ChoosePasswordSelectorsIDs.CONFIRM_PASSWORD_INPUT_ID,
1035+
);
1036+
1037+
await act(async () => {
1038+
fireEvent.changeText(confirmPasswordInput, 'NewPassword123');
1039+
});
1040+
1041+
const submitButton = component.getByTestId(
1042+
ChoosePasswordSelectorsIDs.SUBMIT_BUTTON_ID,
1043+
);
1044+
1045+
await act(async () => {
1046+
fireEvent.press(submitButton);
1047+
});
1048+
1049+
await waitFor(() => {
1050+
expect(NavigationService.navigation.navigate).toHaveBeenCalledWith(
1051+
Routes.MODAL.ROOT_MODAL_FLOW,
1052+
expect.objectContaining({
1053+
screen: Routes.SHEET.SUCCESS_ERROR_SHEET,
1054+
}),
1055+
);
1056+
});
1057+
1058+
const navigationCall = (
1059+
NavigationService.navigation.navigate as jest.Mock
1060+
).mock.calls[0];
1061+
const onPrimaryButtonPress =
1062+
navigationCall[1].params.onPrimaryButtonPress;
1063+
1064+
await act(async () => {
1065+
await onPrimaryButtonPress();
1066+
});
1067+
1068+
await waitFor(() => {
1069+
expect(mockUpdateAuthTypeStorageFlags).toHaveBeenCalledWith(false);
1070+
});
1071+
});
1072+
1073+
it('saves biometry choice when auth type is biometric', async () => {
1074+
mockUpdateAuthTypeStorageFlags.mockClear();
1075+
1076+
const mockAuthModule = jest.requireMock('../../../core/Authentication');
1077+
const originalAuthData = mockAuthModule.authData;
1078+
mockAuthModule.authData = {
1079+
currentAuthType: AUTHENTICATION_TYPE.BIOMETRIC,
1080+
};
1081+
1082+
const component = await renderConfirmPasswordView();
1083+
1084+
const newPasswordInput = component.getByTestId(
1085+
ChoosePasswordSelectorsIDs.NEW_PASSWORD_INPUT_ID,
1086+
);
1087+
1088+
await act(async () => {
1089+
fireEvent.changeText(newPasswordInput, 'NewPassword123');
1090+
});
1091+
1092+
const confirmPasswordInput = component.getByTestId(
1093+
ChoosePasswordSelectorsIDs.CONFIRM_PASSWORD_INPUT_ID,
1094+
);
1095+
1096+
await act(async () => {
1097+
fireEvent.changeText(confirmPasswordInput, 'NewPassword123');
1098+
});
1099+
1100+
const submitButton = component.getByTestId(
1101+
ChoosePasswordSelectorsIDs.SUBMIT_BUTTON_ID,
1102+
);
1103+
1104+
await act(async () => {
1105+
fireEvent.press(submitButton);
1106+
});
1107+
1108+
await waitFor(() => {
1109+
expect(NavigationService.navigation.navigate).toHaveBeenCalledWith(
1110+
Routes.MODAL.ROOT_MODAL_FLOW,
1111+
expect.objectContaining({
1112+
screen: Routes.SHEET.SUCCESS_ERROR_SHEET,
1113+
}),
1114+
);
1115+
});
1116+
1117+
const navigationCall = (
1118+
NavigationService.navigation.navigate as jest.Mock
1119+
).mock.calls[0];
1120+
const onPrimaryButtonPress =
1121+
navigationCall[1].params.onPrimaryButtonPress;
1122+
1123+
await act(async () => {
1124+
await onPrimaryButtonPress();
1125+
});
1126+
1127+
await waitFor(() => {
1128+
expect(mockUpdateAuthTypeStorageFlags).toHaveBeenCalled();
1129+
});
1130+
1131+
mockAuthModule.authData = originalAuthData;
1132+
});
1133+
});
10071134
});

0 commit comments

Comments
 (0)