Skip to content

Commit 29b4539

Browse files
committed
fix(wallet): require magic code when setting phone for first time
1 parent b9061cc commit 29b4539

2 files changed

Lines changed: 24 additions & 11 deletions

File tree

src/pages/EnablePayments/shared/useWalletPhoneMagicCode.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,23 @@ import ONYXKEYS from '@src/ONYXKEYS';
1111
import ROUTES from '@src/ROUTES';
1212

1313
/**
14-
* Shared magic-code handling for the wallet KYC personal-details flows. Changing an existing phone number is protected
15-
* by a magic code because it is used for card 3DS verification, so both flows send the user to a dedicated
16-
* confirmation screen to enter the code before the change is submitted.
14+
* Shared magic-code handling for the wallet KYC personal-details flows. Setting a phone number, whether for the first
15+
* time or changing an existing one, is protected by a magic code because it is used for card 3DS verification, so
16+
* both flows send the user to a dedicated confirmation screen to enter the code before the change is submitted.
1717
*/
1818
function useWalletPhoneMagicCode() {
1919
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
2020

21-
// Submits the personal details, first routing to the magic-code screen when an existing phone number is changed.
21+
// Submits the personal details, first routing to the magic-code screen when a phone number is being set to a new
22+
// value. Setting a phone for the first time must be protected too: it is used for card 3DS verification, so an
23+
// attacker who gains account access before any phone is on file could otherwise set one without a code.
2224
const submitPersonalDetails = (personalDetails: UpdatePersonalDetailsForWalletParams) => {
2325
// The stored phone number keeps its country code, so normalize it the same way as the submitted one before
2426
// comparing, otherwise an unchanged phone would look like a change and wrongly prompt for a magic code.
2527
const storedPhoneNumber = privatePersonalDetails?.phoneNumber;
2628
const normalizedStoredPhoneNumber = (storedPhoneNumber && parsePhoneNumber(storedPhoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '';
27-
const hasPhoneNumberChanged = !!normalizedStoredPhoneNumber && personalDetails.phoneNumber !== normalizedStoredPhoneNumber;
28-
if (hasPhoneNumberChanged) {
29+
const isSettingPhoneNumber = !!personalDetails.phoneNumber && personalDetails.phoneNumber !== normalizedStoredPhoneNumber;
30+
if (isSettingPhoneNumber) {
2931
Navigation.navigate(ROUTES.SETTINGS_ENABLE_PAYMENTS_CONFIRM_MAGIC_CODE.getRoute());
3032
return;
3133
}

tests/unit/useWalletPhoneMagicCodeTest.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,15 @@ describe('useWalletPhoneMagicCode', () => {
8383
expect(mockNavigate).not.toHaveBeenCalled();
8484
});
8585

86-
it('submits directly without routing to the magic-code screen when no phone number is saved yet', async () => {
86+
it('routes to the magic-code screen when a phone number is set for the first time (none saved yet)', async () => {
8787
const {result} = await renderWalletPhoneMagicCode();
8888

89-
const params = buildParams();
9089
act(() => {
91-
result.current.submitPersonalDetails(params);
90+
result.current.submitPersonalDetails(buildParams());
9291
});
9392

94-
expect(mockUpdatePersonalDetails).toHaveBeenCalledWith(params);
95-
expect(mockNavigate).not.toHaveBeenCalled();
93+
expect(mockUpdatePersonalDetails).not.toHaveBeenCalled();
94+
expect(mockNavigate).toHaveBeenCalledWith(ROUTES.SETTINGS_ENABLE_PAYMENTS_CONFIRM_MAGIC_CODE.getRoute());
9695
});
9796

9897
it('routes to the magic-code screen and holds the submission when an existing phone number is changed', async () => {
@@ -109,4 +108,16 @@ describe('useWalletPhoneMagicCode', () => {
109108
expect(mockUpdatePersonalDetails).not.toHaveBeenCalled();
110109
expect(mockNavigate).toHaveBeenCalledWith(ROUTES.SETTINGS_ENABLE_PAYMENTS_CONFIRM_MAGIC_CODE.getRoute());
111110
});
111+
112+
it('submits directly without routing to the magic-code screen when no phone number is provided', async () => {
113+
const {result} = await renderWalletPhoneMagicCode();
114+
115+
const params = buildParams({phoneNumber: ''});
116+
act(() => {
117+
result.current.submitPersonalDetails(params);
118+
});
119+
120+
expect(mockUpdatePersonalDetails).toHaveBeenCalledWith(params);
121+
expect(mockNavigate).not.toHaveBeenCalled();
122+
});
112123
});

0 commit comments

Comments
 (0)