diff --git a/src/Root.tsx b/src/Root.tsx index 9a518fcb6b..dab1473a09 100644 --- a/src/Root.tsx +++ b/src/Root.tsx @@ -68,7 +68,10 @@ import AboutGroup, { import SecurityGroup, { SecurityGroupParamList, } from './navigation/tabs/settings/security/SecurityGroup'; -import AuthGroup, {AuthGroupParamList} from './navigation/auth/AuthGroup'; +import AuthGroup, { + AuthGroupParamList, + AuthScreens, +} from './navigation/auth/AuthGroup'; import BuyCryptoGroup, { BuyCryptoGroupParamList, } from './navigation/services/buy-crypto/BuyCryptoGroup'; @@ -332,6 +335,11 @@ export default () => { WalletScreens.ACCOUNT_DETAILS, WalletScreens.TRANSACTION_PROPOSAL_NOTIFICATIONS, WalletScreens.WALLET_DETAILS, + WalletScreens.VERIFY_PHRASE, + WalletScreens.EXTENDED_PRIVATE_KEY, + AuthScreens.LOGIN, + AuthScreens.CREATE_ACCOUNT, + AuthScreens.SECURE_ACCOUNT, ]; const debouncedOnStateChange = useMemo( diff --git a/src/components/form/BoxInput.tsx b/src/components/form/BoxInput.tsx index 4bd9e07fd5..b0a39ef7bd 100644 --- a/src/components/form/BoxInput.tsx +++ b/src/components/form/BoxInput.tsx @@ -1,5 +1,10 @@ -import React, {useState} from 'react'; -import {KeyboardTypeOptions, TextInput, TextInputProps} from 'react-native'; +import React, {useEffect, useState} from 'react'; +import { + AppState, + KeyboardTypeOptions, + TextInput, + TextInputProps, +} from 'react-native'; import TextInputMask, {TextInputMaskProps} from 'react-native-text-input-mask'; import styled, {css} from 'styled-components/native'; import ObfuscationHide from '../../../assets/img/obfuscation-hide.svg'; @@ -219,6 +224,15 @@ const BoxInput = React.forwardRef< ); } + useEffect(() => { + const sub = AppState.addEventListener('change', state => { + if (isPassword && (state === 'inactive' || state === 'background')) { + props.onChangeText?.(''); + } + }); + return () => sub.remove(); + }, [isPassword, props]); + return ( <> {label ? : null} diff --git a/src/navigation/auth/screens/CreateAccount.tsx b/src/navigation/auth/screens/CreateAccount.tsx index 8ff2c8dd3b..01b077f741 100644 --- a/src/navigation/auth/screens/CreateAccount.tsx +++ b/src/navigation/auth/screens/CreateAccount.tsx @@ -15,7 +15,11 @@ import yup from '../../../lib/yup'; import {navigationRef} from '../../../Root'; import {AppActions} from '../../../store/app'; import {BitPayIdActions, BitPayIdEffects} from '../../../store/bitpay-id'; -import {useAppDispatch, useAppSelector} from '../../../utils/hooks'; +import { + useAppDispatch, + useAppSelector, + useSensitiveRefClear, +} from '../../../utils/hooks'; import {AuthScreens, AuthGroupParamList} from '../AuthGroup'; import AuthFormContainer, { AuthActionRow, @@ -64,6 +68,7 @@ const CreateAccountScreen: React.FC = ({ const dispatch = useAppDispatch(); const [isRecaptchaVisible, setRecaptchaVisible] = useState(false); const captchaRef = useRef(null); + const {clearSensitive} = useSensitiveRefClear([passwordRef]); const schema = yup.object().shape({ givenName: yup.string().required().trim(), @@ -168,6 +173,7 @@ const CreateAccountScreen: React.FC = ({ agreedToMarketingCommunications, }), ); + clearSensitive(); }, () => { Keyboard.dismiss(); diff --git a/src/navigation/auth/screens/Login.tsx b/src/navigation/auth/screens/Login.tsx index f5e251df84..32d0fcd0c0 100644 --- a/src/navigation/auth/screens/Login.tsx +++ b/src/navigation/auth/screens/Login.tsx @@ -21,7 +21,11 @@ import {navigationRef, RootStacks} from '../../../Root'; import {AppActions} from '../../../store/app'; import {BitPayIdActions, BitPayIdEffects} from '../../../store/bitpay-id'; import {sleep} from '../../../utils/helper-methods'; -import {useAppDispatch, useAppSelector} from '../../../utils/hooks'; +import { + useAppDispatch, + useAppSelector, + useSensitiveRefClear, +} from '../../../utils/hooks'; import {BitpayIdScreens} from '../../bitpay-id/BitpayIdGroup'; import {AuthScreens, AuthGroupParamList} from '../AuthGroup'; import AuthFormContainer, { @@ -115,6 +119,8 @@ const LoginScreen: React.FC = ({navigation, route}) => { const captchaRef = useRef(null); const {onLoginSuccess} = route.params || {}; + const {clearSensitive} = useSensitiveRefClear([passwordRef]); + useEffect(() => { dispatch(BitPayIdEffects.startFetchSession()); }, [dispatch]); @@ -197,6 +203,7 @@ const LoginScreen: React.FC = ({navigation, route}) => { const onSubmit = handleSubmit( async ({email, password}) => { Keyboard.dismiss(); + clearSensitive(); if (session.captchaDisabled) { dispatch(BitPayIdEffects.startLogin({email, password})); } else { diff --git a/src/navigation/wallet/components/FileOrText.tsx b/src/navigation/wallet/components/FileOrText.tsx index 4a785dedbf..3ad43fdd7c 100644 --- a/src/navigation/wallet/components/FileOrText.tsx +++ b/src/navigation/wallet/components/FileOrText.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect, useRef} from 'react'; import { ImportTextInput, HeaderContainer, @@ -30,9 +30,13 @@ import {startUpdateAllWalletStatusForKey} from '../../../store/wallet/effects/st import {updatePortfolioBalance} from '../../../store/wallet/wallet.actions'; import {useTranslation} from 'react-i18next'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; -import {ScrollView, Keyboard} from 'react-native'; +import {ScrollView, Keyboard, TextInput, AppState} from 'react-native'; import {Analytics} from '../../../store/analytics/analytics.effects'; -import {useAppDispatch, useAppSelector} from '../../../utils/hooks'; +import { + useAppDispatch, + useAppSelector, + useSensitiveRefClear, +} from '../../../utils/hooks'; import {useOngoingProcess} from '../../../contexts'; const BWCProvider = BwcProvider.getInstance(); @@ -76,6 +80,8 @@ const FileOrText = () => { const walletTermsAccepted = useAppSelector( ({WALLET}: RootState) => WALLET.walletTermsAccepted, ); + const plainTextRef = useRef(null); + const {clearSensitive} = useSensitiveRefClear([plainTextRef]); const { control, @@ -157,6 +163,7 @@ const FileOrText = () => { const onSubmit = handleSubmit(formData => { const {text, password} = formData; + clearSensitive(); Keyboard.dismiss(); @@ -175,6 +182,15 @@ const FileOrText = () => { importWallet(decryptBackupText, opts); }); + useEffect(() => { + const sub = AppState.addEventListener('change', state => { + if (state === 'inactive' || state === 'background') { + clearSensitive(); + } + }); + return () => sub.remove(); + }, [clearSensitive]); + return ( { control={control} render={({field: {onChange, onBlur, value}}) => ( { passphrase: undefined as string | undefined, isMultisig: false, }); + const wordsRef = useRef(null); + const {clearSensitive} = useSensitiveRefClear([wordsRef]); const { control, @@ -347,6 +353,7 @@ const RecoveryPhrase = () => { const onSubmit = (formData: {text: string}) => { const {text} = formData; + clearSensitive(); let keyOpts: Partial = {}; @@ -573,6 +580,15 @@ const RecoveryPhrase = () => { } }, []); + useEffect(() => { + const sub = AppState.addEventListener('change', state => { + if (state === 'inactive' || state === 'background') { + clearSensitive(); + } + }); + return () => sub.remove(); + }, [clearSensitive]); + return ( { control={control} render={({field: {onChange, onBlur, value}}) => ( ) { + const node: any = ref.current; + if (!node) return; + + if (typeof node.clear === 'function') { + node.clear(); + node.blur?.(); + return; + } + + node.setNativeProps?.({text: ''}); + node.blur?.(); +} + +export function useSensitiveRefClear( + refs: Array>, +) { + const clearSensitive = useCallback(() => { + refs.forEach(ref => clearInputRef(ref)); + }, [refs]); + return {clearSensitive}; +}