From fcf63a9329726516d7f91090a6dcba229e86e6aa Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Thu, 27 Apr 2023 21:51:13 +0200 Subject: [PATCH] Revert "[PhoneNumber Verification] Display only valid numbers in user search." --- src/CONST.js | 6 +++ src/libs/LoginUtils.js | 16 +++++++- src/libs/OptionsListUtils.js | 41 +++++++++++-------- src/libs/ValidationUtils.js | 10 ++--- src/pages/DetailsPage.js | 6 +-- .../EnablePayments/AdditionalDetailsStep.js | 4 +- src/pages/ReimbursementAccount/CompanyStep.js | 4 +- .../Profile/Contacts/NewContactMethodPage.js | 5 +-- src/pages/signin/LoginForm.js | 9 ++-- 9 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index edd5e3b61fa8..20e293011f1c 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -963,10 +963,15 @@ const CONST = { }, REGEX: { SPECIAL_CHARS_WITHOUT_NEWLINE: /((?!\n)[()-\s\t])/g, + US_PHONE: /^\+1\d{10}$/, + US_PHONE_WITH_OPTIONAL_COUNTRY_CODE: /^(\+1)?\d{10}$/, DIGITS_AND_PLUS: /^\+?[0-9]*$/, + PHONE_E164_PLUS: /^\+?[1-9]\d{1,14}$/, + PHONE_WITH_SPECIAL_CHARS: /^\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})(?: *x(\d+))?\s*$/, ALPHABETIC_CHARS: /[a-zA-Z]+/, ALPHABETIC_CHARS_WITH_NUMBER: /^[a-zA-Z0-9 ]*$/, POSITIVE_INTEGER: /^\d+$/, + NON_ALPHA_NUMERIC: /[^A-Za-z0-9+]/g, PO_BOX: /\b[P|p]?(OST|ost)?\.?\s*[O|o|0]?(ffice|FFICE)?\.?\s*[B|b][O|o|0]?[X|x]?\.?\s+[#]?(\d+)\b/, ANY_VALUE: /^.+$/, ZIP_CODE: /[0-9]{5}(?:[- ][0-9]{4})?/, @@ -988,6 +993,7 @@ const CONST = { // Extract attachment's source from the data's html string ATTACHMENT_DATA: /(data-expensify-source|data-name)="([^"]+)"/g, + NON_NUMERIC_WITH_PLUS: /[^0-9+]/g, EMOJI_NAME: /:[\w+-]+:/g, EMOJI_SUGGESTIONS: /:[a-zA-Z0-9_+-]{1,40}$/, AFTER_FIRST_LINE_BREAK: /\n.*/g, diff --git a/src/libs/LoginUtils.js b/src/libs/LoginUtils.js index 6da0f1617e38..c4f160f8cf40 100644 --- a/src/libs/LoginUtils.js +++ b/src/libs/LoginUtils.js @@ -1,3 +1,4 @@ +import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; @@ -18,6 +19,16 @@ function getPhoneNumberWithoutSpecialChars(phone) { return phone.replace(CONST.REGEX.SPECIAL_CHARS_WITHOUT_NEWLINE, ''); } +/** + * Remove +1 and special chars from the phone number + * + * @param {String} phone + * @return {String} + */ +function getPhoneNumberWithoutUSCountryCodeAndSpecialChars(phone) { + return getPhoneNumberWithoutSpecialChars(phone.replace(/^\+1/, '')); +} + /** * Append user country code to the phone number * @@ -25,10 +36,13 @@ function getPhoneNumberWithoutSpecialChars(phone) { * @return {String} */ function appendCountryCode(phone) { - return phone.startsWith('+') ? phone : `+${countryCodeByIP}${phone}`; + return (Str.isValidPhone(phone) && !phone.includes('+')) + ? `+${countryCodeByIP}${phone}` + : phone; } export { getPhoneNumberWithoutSpecialChars, + getPhoneNumberWithoutUSCountryCodeAndSpecialChars, appendCountryCode, }; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index ffed87332ec2..e05c3613207d 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -4,7 +4,6 @@ import Onyx from 'react-native-onyx'; import lodashOrderBy from 'lodash/orderBy'; import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; -import {parsePhoneNumber} from 'awesome-phonenumber'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import * as ReportUtils from './ReportUtils'; @@ -33,6 +32,12 @@ Onyx.connect({ callback: val => loginList = _.isEmpty(val) ? {} : val, }); +let countryCodeByIP; +Onyx.connect({ + key: ONYXKEYS.COUNTRY_CODE, + callback: val => countryCodeByIP = val || 1, +}); + let preferredLocale; Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, @@ -129,9 +134,9 @@ function getPolicyExpenseReportOptions(report) { * @return {String} */ function addSMSDomainIfPhoneNumber(login) { - const parsedPhoneNumber = parsePhoneNumber(login); - if (parsedPhoneNumber.possible && !Str.isValidEmail(login)) { - return parsedPhoneNumber.number.e164 + CONST.SMS.DOMAIN; + if (Str.isValidPhone(login) && !Str.isValidEmail(login)) { + const smsLogin = login + CONST.SMS.DOMAIN; + return smsLogin.includes('+') ? smsLogin : `+${countryCodeByIP}${smsLogin}`; } return login; } @@ -527,8 +532,8 @@ function getOptions(reports, personalDetails, { let recentReportOptions = []; let personalDetailsOptions = []; const reportMapForLogins = {}; - const parsedPhoneNumber = parsePhoneNumber(LoginUtils.appendCountryCode(searchInputValue)); - const searchValue = parsedPhoneNumber.possible ? parsedPhoneNumber.number.e164 : searchInputValue; + const isPhoneNumber = CONST.REGEX.PHONE_WITH_SPECIAL_CHARS.test(searchInputValue); + const searchValue = isPhoneNumber ? searchInputValue.replace(CONST.REGEX.NON_NUMERIC_WITH_PLUS, '') : searchInputValue; // Filter out all the reports that shouldn't be displayed const filteredReports = _.filter(reports, report => ReportUtils.shouldReportBeInOptionList( @@ -671,21 +676,25 @@ function getOptions(reports, personalDetails, { const noOptions = (recentReportOptions.length + personalDetailsOptions.length) === 0; const noOptionsMatchExactly = !_.find(personalDetailsOptions.concat(recentReportOptions), option => option.login === searchValue.toLowerCase()); - if (searchValue && (noOptions || noOptionsMatchExactly) - && !isCurrentUser({login: searchValue}) - && _.every(selectedOptions, option => option.login !== searchValue) - && ((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue)) || parsedPhoneNumber.possible) - && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase())) - && (searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) + // If the phone number doesn't have an international code then let's prefix it with the + // current user's international code based on their IP address. + const login = LoginUtils.appendCountryCode(searchValue); + + if (login && (noOptions || noOptionsMatchExactly) + && !isCurrentUser({login}) + && _.every(selectedOptions, option => option.login !== login) + && ((Str.isValidEmail(login) && !Str.isDomainEmail(login)) || Str.isValidPhone(login)) + && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === addSMSDomainIfPhoneNumber(login).toLowerCase())) + && (login !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) ) { - userToInvite = createOption([searchValue], personalDetails, null, reportActions, { + userToInvite = createOption([login], personalDetails, null, reportActions, { showChatPreviewLine, }); // If user doesn't exist, use a default avatar userToInvite.icons = [{ - source: ReportUtils.getAvatar('', searchValue), - name: searchValue, + source: ReportUtils.getAvatar('', login), + name: login, type: CONST.ICON_TYPE_AVATAR, }]; } @@ -855,7 +864,7 @@ function getHeaderMessage(hasSelectableOptions, hasUserToInvite, searchValue, ma return Localize.translate(preferredLocale, 'common.maxParticipantsReached', {count: CONST.REPORT.MAXIMUM_PARTICIPANTS}); } - const isValidPhone = parsePhoneNumber(LoginUtils.appendCountryCode(searchValue)).possible; + const isValidPhone = Str.isValidPhone(LoginUtils.appendCountryCode(searchValue)); const isValidEmail = Str.isValidEmail(searchValue); diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.js index 7e0f65f0a91d..90eccdd7e94b 100644 --- a/src/libs/ValidationUtils.js +++ b/src/libs/ValidationUtils.js @@ -1,7 +1,6 @@ import moment from 'moment'; import _ from 'underscore'; import {URL_REGEX_WITH_REQUIRED_PROTOCOL} from 'expensify-common/lib/Url'; -import {parsePhoneNumber} from 'awesome-phonenumber'; import CONST from '../CONST'; import * as CardUtils from './CardUtils'; import * as LoginUtils from './LoginUtils'; @@ -298,11 +297,12 @@ function validateIdentity(identity) { * @returns {Boolean} */ function isValidUSPhone(phoneNumber = '', isCountryCodeOptional) { - const phone = phoneNumber || ''; - const regionCode = isCountryCodeOptional ? CONST.COUNTRY.US : null; + // Remove non alphanumeric characters from the phone number + const sanitizedPhone = (phoneNumber || '').replace(CONST.REGEX.NON_ALPHA_NUMERIC, ''); + const isUsPhone = isCountryCodeOptional + ? CONST.REGEX.US_PHONE_WITH_OPTIONAL_COUNTRY_CODE.test(sanitizedPhone) : CONST.REGEX.US_PHONE.test(sanitizedPhone); - const parsedPhoneNumber = parsePhoneNumber(phone, {regionCode}); - return parsedPhoneNumber.possible && parsedPhoneNumber.regionCode === CONST.COUNTRY.US; + return CONST.REGEX.PHONE_E164_PLUS.test(sanitizedPhone) && isUsPhone; } /** diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index 003823bc3ca8..84c36ea945d0 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -5,7 +5,6 @@ import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; -import {parsePhoneNumber} from 'awesome-phonenumber'; import styles from '../styles/styles'; import Text from '../components/Text'; import ONYXKEYS from '../ONYXKEYS'; @@ -74,9 +73,8 @@ const defaultProps = { */ const getPhoneNumber = (details) => { // If the user hasn't set a displayName, it is set to their phone number, so use that - const parsedPhoneNumber = parsePhoneNumber(details.displayName); - if (parsedPhoneNumber.possible) { - return parsedPhoneNumber.number.e164; + if (Str.isValidPhone(details.displayName)) { + return details.displayName; } // If the user has set a displayName, get the phone number from the SMS login diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index dc5bbd6edd3e..bbc9a991d14d 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; import moment from 'moment/moment'; -import {parsePhoneNumber} from 'awesome-phonenumber'; import IdologyQuestions from './IdologyQuestions'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; @@ -19,6 +18,7 @@ import TextLink from '../../components/TextLink'; import TextInput from '../../components/TextInput'; import * as Wallet from '../../libs/actions/Wallet'; import * as ValidationUtils from '../../libs/ValidationUtils'; +import * as LoginUtils from '../../libs/LoginUtils'; import * as ErrorUtils from '../../libs/ErrorUtils'; import AddressForm from '../ReimbursementAccount/AddressForm'; import DatePicker from '../../components/DatePicker'; @@ -173,7 +173,7 @@ class AdditionalDetailsStep extends React.Component { */ activateWallet(values) { const personalDetails = { - phoneNumber: parsePhoneNumber(values[INPUT_IDS.PHONE_NUMBER], {regionCode: CONST.COUNTRY.US}).number.significant, + phoneNumber: LoginUtils.getPhoneNumberWithoutUSCountryCodeAndSpecialChars(values[INPUT_IDS.PHONE_NUMBER]), legalFirstName: values[INPUT_IDS.LEGAL_FIRST_NAME], legalLastName: values[INPUT_IDS.LEGAL_LAST_NAME], addressStreet: values[INPUT_IDS.ADDRESS.street], diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 9acc6296bb40..e8480b4b9436 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -5,7 +5,6 @@ import {View} from 'react-native'; import Str from 'expensify-common/lib/str'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; -import {parsePhoneNumber} from 'awesome-phonenumber'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import CONST from '../../CONST'; import * as BankAccounts from '../../libs/actions/BankAccounts'; @@ -19,6 +18,7 @@ import TextLink from '../../components/TextLink'; import StatePicker from '../../components/StatePicker'; import withLocalize from '../../components/withLocalize'; import * as ValidationUtils from '../../libs/ValidationUtils'; +import * as LoginUtils from '../../libs/LoginUtils'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import Picker from '../../components/Picker'; @@ -148,7 +148,7 @@ class CompanyStep extends React.Component { // Fields from Company step ...values, companyTaxID: values.companyTaxID.replace(CONST.REGEX.NON_NUMERIC, ''), - companyPhone: parsePhoneNumber(values.companyPhone, {regionCode: CONST.COUNTRY.US}).number.significant, + companyPhone: LoginUtils.getPhoneNumberWithoutUSCountryCodeAndSpecialChars(values.companyPhone), }; BankAccounts.updateCompanyInformationForBankAccount(bankAccount); diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js index 7e15c606404c..0c59cb17ba4e 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js @@ -8,7 +8,6 @@ import {withOnyx} from 'react-native-onyx'; import {compose} from 'underscore'; import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; -import {parsePhoneNumber} from 'awesome-phonenumber'; import Button from '../../../../components/Button'; import FixedFooter from '../../../../components/FixedFooter'; import HeaderWithCloseButton from '../../../../components/HeaderWithCloseButton'; @@ -69,10 +68,10 @@ function NewContactMethodPage(props) { }, []); const isFormValid = useMemo(() => { - const phoneLogin = LoginUtils.appendCountryCode(LoginUtils.getPhoneNumberWithoutSpecialChars(login)); + const phoneLogin = LoginUtils.getPhoneNumberWithoutSpecialChars(login); return (Permissions.canUsePasswordlessLogins(props.betas) || password) - && (Str.isValidEmail(login) || parsePhoneNumber(phoneLogin).possible); + && (Str.isValidEmail(login) || Str.isValidPhone(phoneLogin)); }, [login, password, props.betas]); const submitForm = useCallback(() => { diff --git a/src/pages/signin/LoginForm.js b/src/pages/signin/LoginForm.js index e9ead825a3da..78633db86ac0 100755 --- a/src/pages/signin/LoginForm.js +++ b/src/pages/signin/LoginForm.js @@ -4,7 +4,6 @@ import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import _ from 'underscore'; import Str from 'expensify-common/lib/str'; -import {parsePhoneNumber} from 'awesome-phonenumber'; import styles from '../../styles/styles'; import Text from '../../components/Text'; import * as Session from '../../libs/actions/Session'; @@ -144,10 +143,10 @@ class LoginForm extends React.Component { return; } - const phoneLogin = LoginUtils.appendCountryCode(LoginUtils.getPhoneNumberWithoutSpecialChars(login)); - const parsedPhoneNumber = parsePhoneNumber(phoneLogin); + const phoneLogin = LoginUtils.getPhoneNumberWithoutSpecialChars(login); + const isValidPhoneLogin = Str.isValidPhone(phoneLogin); - if (!Str.isValidEmail(login) && !parsedPhoneNumber.possible) { + if (!Str.isValidEmail(login) && !isValidPhoneLogin) { if (ValidationUtils.isNumericWithSpecialChars(login)) { this.setState({formError: 'common.error.phoneNumber'}); } else { @@ -161,7 +160,7 @@ class LoginForm extends React.Component { }); // Check if this login has an account associated with it or not - Session.beginSignIn(parsedPhoneNumber.possible ? parsedPhoneNumber.number.e164 : login); + Session.beginSignIn(isValidPhoneLogin ? phoneLogin : login); } render() {