diff --git a/src/assets/images/biometric-image.svg b/src/assets/images/biometric-image.svg new file mode 100644 index 000000000..f608622c6 --- /dev/null +++ b/src/assets/images/biometric-image.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/images/password-ico.svg b/src/assets/images/password-ico.svg new file mode 100644 index 000000000..035444214 --- /dev/null +++ b/src/assets/images/password-ico.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/assets/images/pin-icon.svg b/src/assets/images/pin-icon.svg new file mode 100644 index 000000000..fca778f41 --- /dev/null +++ b/src/assets/images/pin-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/ConfirmCredentialModal.tsx b/src/components/ConfirmCredentialModal.tsx new file mode 100644 index 000000000..19b751418 --- /dev/null +++ b/src/components/ConfirmCredentialModal.tsx @@ -0,0 +1,55 @@ +import { Box } from 'native-base'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import LoginMethod from 'src/models/enums/LoginMethod'; +import { useAppSelector } from 'src/store/hooks'; +import PasscodeVerifyModal from './Modal/PasscodeVerify'; +import PasswordModalContent from 'src/screens/AppSettings/PasswordModalContent'; + +type Props = { + close?: () => void; + success?: any; + useBiometrics?: boolean; + forcedMode?: any; + onForceSuccess?: any; + primaryText?: string; +}; + +const ConfirmCredentialModal = ({ + success, + close, + useBiometrics, + forcedMode, + onForceSuccess, + primaryText, +}: Props) => { + const { loginMethod }: { loginMethod: LoginMethod } = useAppSelector((state) => state.settings); + const fallbackLoginMethod = useSelector((state) => state.settings.fallbackLoginMethod); + + return ( + + {(loginMethod === LoginMethod.BIOMETRIC && fallbackLoginMethod === 'PIN') || + (loginMethod !== LoginMethod.BIOMETRIC && loginMethod === LoginMethod.PIN) ? ( + { + close(); + }} + forcedMode={forcedMode} + onForceSuccess={onForceSuccess} + onSuccess={success} + primaryText={primaryText} + /> + ) : ( + { + close(); + }} + onSuccess={success} + /> + )} + + ); +}; + +export default ConfirmCredentialModal; diff --git a/src/components/Modal/PasscodeVerify.tsx b/src/components/Modal/PasscodeVerify.tsx index 93d523648..e4cfaf6cf 100644 --- a/src/components/Modal/PasscodeVerify.tsx +++ b/src/components/Modal/PasscodeVerify.tsx @@ -46,7 +46,7 @@ function PasscodeVerifyModal({ const [errMessage, setErrMessage] = useState(login.Incorrect); const { isAuthenticated, authenticationFailed } = useAppSelector((state) => state.login); const { loginMethod } = useAppSelector((state) => state.settings); - const { appId, failedAttempts, lastLoginFailedAt } = useAppSelector((state) => state.storage); + const { appId } = useAppSelector((state) => state.storage); useEffect(() => { if (useBiometrics) { diff --git a/src/components/RKSignersModal.tsx b/src/components/RKSignersModal.tsx index 10000a987..ee4dcd8f3 100644 --- a/src/components/RKSignersModal.tsx +++ b/src/components/RKSignersModal.tsx @@ -14,7 +14,6 @@ import { CKTapCard } from 'cktap-protocol-react-native'; import useNfcModal from 'src/hooks/useNfcModal'; import NfcPrompt from 'src/components/NfcPromptAndroid'; import KeeperModal from 'src/components/KeeperModal'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import { Box, useColorMode } from 'native-base'; import { SIGNTRANSACTION } from 'src/navigation/contants'; import { useDispatch } from 'react-redux'; @@ -30,6 +29,7 @@ import { KeeperApp } from 'src/models/interfaces/KeeperApp'; import { LocalizationContext } from 'src/context/Localization/LocContext'; import { useAppSelector } from 'src/store/hooks'; import ShareKeyModalContent from 'src/screens/Vault/components/ShareKeyModalContent'; +import ConfirmCredentialModal from './ConfirmCredentialModal'; import Text from './KeeperText'; import WalletOperations from 'src/services/wallets/operations'; import ActivityIndicatorView from './AppActivityIndicator/ActivityIndicatorView'; @@ -388,12 +388,10 @@ const RKSignersModal = ({ signer, psbt, isMiniscript, vaultId }, ref) => { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - setConfirmPassVisible(false)} + success={signTransaction} useBiometrics={false} - close={() => { - setConfirmPassVisible(false); - }} - onSuccess={signTransaction} /> )} /> diff --git a/src/models/enums/LoginMethod.ts b/src/models/enums/LoginMethod.ts index cafce6405..2c89d61b4 100644 --- a/src/models/enums/LoginMethod.ts +++ b/src/models/enums/LoginMethod.ts @@ -1,5 +1,6 @@ enum LoginMethod { PIN = 'PIN', BIOMETRIC = 'BIOMETRIC', + PASSWORD = 'PASSWORD', } export default LoginMethod; diff --git a/src/screens/AppSettings/AppBackupSettings.tsx b/src/screens/AppSettings/AppBackupSettings.tsx index 7b72701ea..1a903a3c7 100644 --- a/src/screens/AppSettings/AppBackupSettings.tsx +++ b/src/screens/AppSettings/AppBackupSettings.tsx @@ -6,7 +6,6 @@ import { CommonActions, useNavigation } from '@react-navigation/native'; import OptionCard from 'src/components/OptionCard'; import KeeperModal from 'src/components/KeeperModal'; import ScreenWrapper from 'src/components/ScreenWrapper'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import { wp } from 'src/constants/responsive'; import { LocalizationContext } from 'src/context/Localization/LocContext'; import { RealmSchema } from 'src/storage/realm/enum'; @@ -31,6 +30,7 @@ import { NewVaultInfo } from 'src/store/sagas/wallets'; import BackupModalContent from './BackupModal'; import { credsAuthenticated } from 'src/store/reducers/login'; import WalletHeader from 'src/components/WalletHeader'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; function AppBackupSettings() { const { colorMode } = useColorMode(); @@ -160,15 +160,15 @@ function AppBackupSettings() { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={() => { + success={() => { setConfirmPassVisible(false); setBackupModalVisible(true); }} + useBiometrics={true} /> )} /> diff --git a/src/screens/AppSettings/CreatePasswordContent.tsx b/src/screens/AppSettings/CreatePasswordContent.tsx new file mode 100644 index 000000000..560de2d0a --- /dev/null +++ b/src/screens/AppSettings/CreatePasswordContent.tsx @@ -0,0 +1,104 @@ +import { Box, useColorMode } from 'native-base'; +import React, { useContext, useState } from 'react'; +import { StyleSheet } from 'react-native'; +import { useDispatch } from 'react-redux'; +import Buttons from 'src/components/Buttons'; +import Text from 'src/components/KeeperText'; +import KeeperTextInput from 'src/components/KeeperTextInput'; +import { hp } from 'src/constants/responsive'; +import { LocalizationContext } from 'src/context/Localization/LocContext'; +import LoginMethod from 'src/models/enums/LoginMethod'; +import { useAppSelector } from 'src/store/hooks'; +import { setFallbackLoginMethod } from 'src/store/reducers/settings'; +import { changeAuthCred } from 'src/store/sagaActions/login'; + +interface Props { + close?: () => void; + onSuccess?: (method: LoginMethod) => void; + oldPassword?: string; +} + +const CreatePasswordContent = ({ close, onSuccess, oldPassword }: Props) => { + const { colorMode } = useColorMode(); + const { translations } = useContext(LocalizationContext); + const { common } = translations; + const dispatch = useDispatch(); + + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [error, setError] = useState(''); + const { loginMethod }: { loginMethod: LoginMethod } = useAppSelector((state) => state.settings); + + const handleContinue = () => { + if (!password || !confirmPassword) { + setError('Please fill out both fields'); + } else if (password !== confirmPassword) { + setError('Passwords do not match'); + } else { + setError(''); + dispatch(changeAuthCred(oldPassword, password)); + if (loginMethod === LoginMethod.BIOMETRIC) { + dispatch(setFallbackLoginMethod(LoginMethod.PASSWORD)); + } + onSuccess(LoginMethod.PASSWORD); + close(); + } + }; + + return ( + + + + {!!error && ( + + {error} + + )} + + + + + ); +}; + +export default CreatePasswordContent; + +const styles = StyleSheet.create({ + container: { + flex: 1, + marginTop: hp(-10), + }, + btnsContainer: { + marginTop: hp(20), + }, + errorText: { + textAlign: 'right', + fontStyle: 'italic', + marginRight: 10, + marginTop: 8, + }, +}); diff --git a/src/screens/AppSettings/ManageWallets.tsx b/src/screens/AppSettings/ManageWallets.tsx index ef9330265..c4920d47a 100644 --- a/src/screens/AppSettings/ManageWallets.tsx +++ b/src/screens/AppSettings/ManageWallets.tsx @@ -21,7 +21,6 @@ import KeeperModal from 'src/components/KeeperModal'; import { captureError } from 'src/services/sentry'; import useWallets from 'src/hooks/useWallets'; import { useDispatch, useSelector } from 'react-redux'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import useVault from 'src/hooks/useVault'; import { Vault } from 'src/services/wallets/interfaces/vault'; import HexagonIcon from 'src/components/HexagonIcon'; @@ -45,6 +44,7 @@ import MiniscriptPathSelector, { } from 'src/components/MiniscriptPathSelector'; import WalletHeader from 'src/components/WalletHeader'; import ThemedColor from 'src/components/ThemedColor/ThemedColor'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; enum PasswordMode { DEFAULT = 'DEFAULT', @@ -413,13 +413,13 @@ function ManageWallets() { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={onProceed} + success={onProceed} + useBiometrics={false} onForceSuccess={onForceProceed} /> )} @@ -435,12 +435,12 @@ function ManageWallets() { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPasscodeVisible(false); }} - onSuccess={deleteSelectedEntity} + success={deleteSelectedEntity} /> )} /> diff --git a/src/screens/AppSettings/PasswordModalContent.tsx b/src/screens/AppSettings/PasswordModalContent.tsx new file mode 100644 index 000000000..1388ff59b --- /dev/null +++ b/src/screens/AppSettings/PasswordModalContent.tsx @@ -0,0 +1,98 @@ +import { Box, useColorMode } from 'native-base'; +import React, { useContext, useEffect, useState } from 'react'; +import { StyleSheet } from 'react-native'; +import Buttons from 'src/components/Buttons'; +import Text from 'src/components/KeeperText'; +import KeeperTextInput from 'src/components/KeeperTextInput'; +import { hp } from 'src/constants/responsive'; +import { LocalizationContext } from 'src/context/Localization/LocContext'; +import LoginMethod from 'src/models/enums/LoginMethod'; +import { useAppDispatch, useAppSelector } from 'src/store/hooks'; +import { credsAuth } from 'src/store/sagaActions/login'; +import { credsAuthenticated } from 'src/store/reducers/login'; + +interface Props { + close?: Function; + onSuccess?: Function; +} +const PasswordModalContent = ({ close, onSuccess }: Props) => { + const { colorMode } = useColorMode(); + const dispatch = useAppDispatch(); + const { translations } = useContext(LocalizationContext); + const { common } = translations; + const [password, setPassword] = useState(''); + const [loginError, setLoginError] = useState(false); + const [errMessage, setErrMessage] = useState(''); + + const { isAuthenticated, authenticationFailed } = useAppSelector((state) => state.login); + + const attemptLogin = (passcode: string) => { + dispatch(credsAuth(passcode, LoginMethod.PASSWORD, true)); + }; + + useEffect(() => { + if (isAuthenticated) { + onSuccess(password); + close(); + dispatch(credsAuthenticated(false)); + } + }, [isAuthenticated]); + useEffect(() => { + if (authenticationFailed && password !== '') { + setLoginError(true); + setErrMessage('Incorrect password'); + dispatch(credsAuthenticated(false)); + } else { + setLoginError(false); + setErrMessage(''); + } + }, [authenticationFailed]); + return ( + + setPassword(text)} + inpuBorderColor={`${colorMode}.separator`} + inpuBackgroundColor={`${colorMode}.boxSecondaryBackground`} + /> + {loginError && ( + + {errMessage} + + )} + + { + attemptLogin(password); + }} + primaryText={common.continue} + secondaryText={common.cancel} + secondaryCallback={() => { + close(); + }} + /> + + + ); +}; + +export default PasswordModalContent; + +const styles = StyleSheet.create({ + container: { + flex: 1, + marginTop: hp(-10), + }, + btnsContainer: { + marginTop: hp(20), + }, + errorText: { + textAlign: 'right', + fontStyle: 'italic', + marginRight: 10, + }, +}); diff --git a/src/screens/AppSettings/PrivacyAndDisplay.tsx b/src/screens/AppSettings/PrivacyAndDisplay.tsx index 25d847d2a..6beaa1cdf 100644 --- a/src/screens/AppSettings/PrivacyAndDisplay.tsx +++ b/src/screens/AppSettings/PrivacyAndDisplay.tsx @@ -1,8 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; -import { Box, ScrollView, useColorMode } from 'native-base'; +import { Box, useColorMode } from 'native-base'; import ReactNativeBiometrics from 'react-native-biometrics'; import ScreenWrapper from 'src/components/ScreenWrapper'; -import OptionCard from 'src/components/OptionCard'; import { LocalizationContext } from 'src/context/Localization/LocContext'; import Switch from 'src/components/Switch/Switch'; import { useAppDispatch, useAppSelector } from 'src/store/hooks'; @@ -10,7 +9,7 @@ import LoginMethod from 'src/models/enums/LoginMethod'; import { changeAuthCred, changeLoginMethod } from 'src/store/sagaActions/login'; import ToastErrorIcon from 'src/assets/images/toast_error.svg'; import useToastMessage from 'src/hooks/useToastMessage'; -import { setThemeMode } from 'src/store/reducers/settings'; +import { setFallbackLoginMethod, setThemeMode } from 'src/store/reducers/settings'; import ThemeMode from 'src/models/enums/ThemeMode'; import { Linking, StyleSheet, TouchableOpacity } from 'react-native'; import { hp, wp } from 'src/constants/responsive'; @@ -19,7 +18,6 @@ import { KeeperApp } from 'src/models/interfaces/KeeperApp'; import { useQuery } from '@realm/react'; import { RealmSchema } from 'src/storage/realm/enum'; import { getJSONFromRealmObject } from 'src/storage/realm/utils'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import KeeperModal from 'src/components/KeeperModal'; import ModalWrapper from 'src/components/Modal/ModalWrapper'; import HealthCheckComponent from 'src/components/Backup/HealthCheckComponent'; @@ -38,10 +36,23 @@ import { resetCredsChanged } from 'src/store/reducers/login'; import Buttons from 'src/components/Buttons'; import WalletHeader from 'src/components/WalletHeader'; import usePlan from 'src/hooks/usePlan'; +import SettingCard from '../Home/components/Settings/Component/SettingCard'; +import BiometricIcon from 'src/assets/images/biometric-image.svg'; +import PasswordIcon from 'src/assets/images/password-ico.svg'; +import PinIcon from 'src/assets/images/pin-icon.svg'; +import PasswordModalContent from './PasswordModalContent'; +import CreatePasswordContent from './CreatePasswordContent'; +import { useSelector } from 'react-redux'; +import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; const RNBiometrics = new ReactNativeBiometrics(); -function ConfirmPasscode({ oldPassword, setConfirmPasscodeModal, onCredsChange }) { +function ConfirmPasscode({ + oldPassword, + setConfirmPasscodeModal, + onCredsChange, + setShowSetPasscodeModal, +}) { const { colorMode } = useColorMode(); const { translations } = useContext(LocalizationContext); @@ -52,7 +63,7 @@ function ConfirmPasscode({ oldPassword, setConfirmPasscodeModal, onCredsChange } const [passcodeFlag, setPasscodeFlag] = useState(true); const [confirmPasscodeFlag, setConfirmPasscodeFlag] = useState(0); const { credsChanged } = useAppSelector((state) => state.login); - + const { loginMethod }: { loginMethod: LoginMethod } = useAppSelector((state) => state.settings); useEffect(() => { if (credsChanged === 'changed') { onCredsChange(); @@ -156,6 +167,10 @@ function ConfirmPasscode({ oldPassword, setConfirmPasscodeModal, onCredsChange } primaryText={common.confirm} primaryCallback={() => { dispatch(changeAuthCred(oldPassword, passcode)); + if (loginMethod === LoginMethod.BIOMETRIC) { + dispatch(setFallbackLoginMethod(LoginMethod.PIN)); + } + setShowSetPasscodeModal(false); }} fullWidth /> @@ -190,12 +205,15 @@ function PrivacyAndDisplay({ route }) { const [sensorType, setSensorType] = useState(null); const [sensorAvailable, setSensorAvailable] = useState(false); const [visiblePasscode, setVisiblePassCode] = useState(false); + const [visiblePassword, setVisiblePassword] = useState(false); const [showConfirmSeedModal, setShowConfirmSeedModal] = useState(false); const [confirmPasscode, setConfirmPasscode] = useState(false); const [oldPassword, setOldPassword] = useState(''); const [backupModalVisible, setBackupModalVisible] = useState(false); const [RKHealthCheckModal, setRKHealthCheckModal] = useState(false); const [passcodeHCModal, setPasscodeHCModal] = useState(false); + const [showSetPasscodeModal, setShowSetPasscodeModal] = useState(false); + const [createPasswordModal, setCreatePasswordModal] = useState(false); const { translations, formatString } = useContext(LocalizationContext); const { settings, common, error: errorText } = translations; @@ -208,6 +226,7 @@ function PrivacyAndDisplay({ route }) { const app: KeeperApp = useQuery(RealmSchema.KeeperApp).map(getJSONFromRealmObject)[0]; const [credsChanged, setCredsChanged] = useState(''); const { isOnL4 } = usePlan(); + const fallbackLoginMethod = useSelector((state) => state.settings.fallbackLoginMethod); useEffect(() => { if (credsChanged === 'changed') { @@ -223,7 +242,11 @@ function PrivacyAndDisplay({ route }) { useEffect(() => { if (RKBackedUp) { - setConfirmPasscode(true); + if (showSetPasscodeModal) { + setConfirmPasscode(true); + } else { + setCreatePasswordModal(true); + } setOldPassword(oldPasscode); } }, [route?.params]); @@ -270,7 +293,7 @@ function PrivacyAndDisplay({ route }) { try { const { available } = await RNBiometrics.isSensorAvailable(); if (available) { - if (loginMethod === LoginMethod.PIN) { + if (loginMethod === LoginMethod.PIN || loginMethod === LoginMethod.PASSWORD) { const { keysExist } = await RNBiometrics.biometricKeysExist(); if (keysExist) { await RNBiometrics.deleteKeys(); @@ -280,10 +303,10 @@ function PrivacyAndDisplay({ route }) { }); if (success) { const { publicKey } = await RNBiometrics.createKeys(); - dispatch(changeLoginMethod(LoginMethod.BIOMETRIC, publicKey)); + dispatch(changeLoginMethod(LoginMethod.BIOMETRIC, publicKey, loginMethod)); } - } else { - dispatch(changeLoginMethod(LoginMethod.PIN)); + } else if (loginMethod === LoginMethod.BIOMETRIC) { + dispatch(changeLoginMethod(fallbackLoginMethod || LoginMethod.PIN)); } } else { setSensorAvailable(false); @@ -295,54 +318,117 @@ function PrivacyAndDisplay({ route }) { } }; - return ( - - - - - - onChangeLoginMethod()} - disabled={!sensorType} - Icon={ - sensorAvailable || !sensorType ? ( - - ) : ( - - - - {common.Enable} {sensorType} - - - - ) - } - /> - + const updateBiometricAfterPasscodeChange = async (newFallbackMethod) => { + try { + const { available } = await RNBiometrics.isSensorAvailable(); - { - setVisiblePassCode(true); - }} + if (!available) { + setSensorAvailable(false); + showToast(errorText.biometricNotEnabled, ); + return; + } + const { keysExist } = await RNBiometrics.biometricKeysExist(); + const { success } = await RNBiometrics.simplePrompt({ + promptMessage: errorText.confirmIdentity, + }); + + if (!success) { + showToast('Failed to update biometric authentication.', ); + if (fallbackLoginMethod === 'PIN') { + dispatch(changeLoginMethod(LoginMethod.PIN)); + } else if (fallbackLoginMethod === 'PASSWORD') { + dispatch(changeLoginMethod(LoginMethod.PASSWORD)); + } + return; + } + if (keysExist) { + await RNBiometrics.deleteKeys(); + } + const { publicKey } = await RNBiometrics.createKeys(); + + dispatch(changeLoginMethod(LoginMethod.BIOMETRIC, publicKey, newFallbackMethod)); + showToast('Biometric updated successfully'); + } catch (error) { + showToast('Failed to update biometric authentication.', ); + setSensorAvailable(false); + } + }; + + const PrivacyAndDisplay = [ + { + title: 'PIN', + description: 'Choose a 4 digits PIN code', + onPress: () => { + if ( + loginMethod === LoginMethod.PIN || + (loginMethod === LoginMethod.BIOMETRIC && fallbackLoginMethod === 'PIN') + ) { + setVisiblePassCode(true); + } else { + setVisiblePassword(true); + } + setShowSetPasscodeModal(true); + }, + icon: , + }, + { + title: 'Password', + description: 'Choose a strong password', + onPress: () => { + if ( + loginMethod === LoginMethod.PASSWORD || + (loginMethod === LoginMethod.BIOMETRIC && fallbackLoginMethod === 'PASSWORD') + ) { + setVisiblePassword(true); + setShowSetPasscodeModal(false); + } else { + setVisiblePassCode(true); + setShowSetPasscodeModal(false); + } + }, + icon: , + }, + { + title: sensorType || settings.Biometrics, + description: sensorType + ? formatString(settings.UseBiometricSubTitle, sensorType) + : settings.NoBiometricSubTitle, + onPress: onChangeLoginMethod, + isDisabled: !sensorType, + icon: , + onRightPress: sensorAvailable || !sensorType ? onChangeLoginMethod : requestPermission, + + rightIcon: + sensorAvailable || !sensorType ? ( + - - + ) : ( + + + + {common.Enable} {sensorType} + + + + ), + }, + ]; + + return ( + + + + + + )} + /> + setVisiblePassword(false)} + title="Set password" + subTitle="Enter your existing password" + modalBackground={`${colorMode}.modalWhiteBackground`} + textColor={`${colorMode}.textGreen`} + subTitleColor={`${colorMode}.modalSubtitleBlack`} + Content={() => ( + setVisiblePassword(false)} + onSuccess={(password) => { + if (data.length === 0) { + setRKHealthCheckModal(true); + setOldPassword(password); + } else { + setOldPassword(password); + setPasscodeHCModal(true); + } + }} /> )} /> @@ -420,7 +530,11 @@ function PrivacyAndDisplay({ route }) { if (backupMethod === BackupType.SEED) { setShowConfirmSeedModal(false); dispatch(seedBackedConfirmed(true)); - setConfirmPasscode(true); + if (showSetPasscodeModal) { + setConfirmPasscode(true); + } else { + setCreatePasswordModal(true); + } } }} /> @@ -439,7 +553,15 @@ function PrivacyAndDisplay({ route }) { setCredsChanged('changed')} + onCredsChange={() => { + setCredsChanged('changed'); + if (loginMethod === LoginMethod.BIOMETRIC) { + updateBiometricAfterPasscodeChange(LoginMethod.PIN); + } else { + dispatch(changeLoginMethod(LoginMethod.PIN)); + } + }} + setShowSetPasscodeModal={setShowSetPasscodeModal} /> )} /> @@ -488,11 +610,35 @@ function PrivacyAndDisplay({ route }) { next: true, parentScreen: PRIVACYANDDISPLAY, oldPasscode: oldPassword, + showSetPasscodeModal: showSetPasscodeModal, }) ); }} Content={BackupModalContent} /> + setCreatePasswordModal(false)} + title="Set Password" + subTitle="Enter Your new Password" + modalBackground={`${colorMode}.primaryBackground`} + subTitleColor={`${colorMode}.secondaryText`} + textColor={`${colorMode}.modalGreenTitle`} + Content={() => ( + setCreatePasswordModal(false)} + onSuccess={(newFallbackMethod) => { + setCredsChanged('changed'); + if (loginMethod === LoginMethod.BIOMETRIC) { + updateBiometricAfterPasscodeChange(newFallbackMethod); + } else { + dispatch(changeLoginMethod(LoginMethod.PASSWORD)); + } + }} + oldPassword={oldPassword} + /> + )} + /> ); } @@ -500,8 +646,11 @@ const styles = StyleSheet.create({ wrapper: { marginTop: hp(35), gap: 50, + }, + container: { width: '95%', - alignSelf: 'center', + justifyContent: 'center', + alignItems: 'center', }, note: { position: 'absolute', diff --git a/src/screens/Home/components/Settings/Component/SettingCard.tsx b/src/screens/Home/components/Settings/Component/SettingCard.tsx index 96ce3802c..05b43edb4 100644 --- a/src/screens/Home/components/Settings/Component/SettingCard.tsx +++ b/src/screens/Home/components/Settings/Component/SettingCard.tsx @@ -20,6 +20,7 @@ interface SettingCardItemProps { showDot?: boolean; onPress?: () => void; onRightPress?: () => void; + isDisabled?: boolean; } interface SettingCardProps { @@ -64,11 +65,14 @@ const SettingCard: React.FC = ({ borderColor={borderColor} > {items.map((item, index) => { - const applyDiamondCheck = item?.isHodler - ? isOnL2Above - : item?.isDiamond - ? isOnL3Above - : true; + const applyDiamondCheck = + item?.isDisabled !== undefined + ? !item.isDisabled + : item?.isHodler + ? isOnL2Above + : item?.isDiamond + ? isOnL3Above + : true; return ( @@ -121,6 +125,7 @@ const SettingCard: React.FC = ({ {item.rightIcon} diff --git a/src/screens/Home/components/Settings/Component/SettingModal.tsx b/src/screens/Home/components/Settings/Component/SettingModal.tsx index 91aa9e554..e60f1a1a8 100644 --- a/src/screens/Home/components/Settings/Component/SettingModal.tsx +++ b/src/screens/Home/components/Settings/Component/SettingModal.tsx @@ -2,8 +2,8 @@ import { CommonActions, useNavigation } from '@react-navigation/native'; import { useQuery } from '@realm/react'; import { Box, useColorMode } from 'native-base'; import React, { useContext, useEffect, useState } from 'react'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; import KeeperModal from 'src/components/KeeperModal'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import { wp } from 'src/constants/responsive'; import { LocalizationContext } from 'src/context/Localization/LocContext'; import { KeeperApp } from 'src/models/interfaces/KeeperApp'; @@ -46,13 +46,13 @@ const SettingModal = ({ isUaiFlow, confirmPass, setConfirmPass }) => { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); setConfirmPass(false); }} - onSuccess={() => { + success={() => { setConfirmPassVisible(false); setBackupModalVisible(true); }} diff --git a/src/screens/InheritanceToolsAndTips/components/LetterOfAttorney.tsx b/src/screens/InheritanceToolsAndTips/components/LetterOfAttorney.tsx index b3c0cf681..cde14dc22 100644 --- a/src/screens/InheritanceToolsAndTips/components/LetterOfAttorney.tsx +++ b/src/screens/InheritanceToolsAndTips/components/LetterOfAttorney.tsx @@ -11,12 +11,12 @@ import DashedButton from 'src/components/DashedButton'; import GenerateLetterToAtternyPDFInheritanceTool from 'src/utils/GenerateLetterToAtternyPDFInheritanceTool'; import { LocalizationContext } from 'src/context/Localization/LocContext'; import useSigners from 'src/hooks/useSigners'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import KeeperModal from 'src/components/KeeperModal'; import { credsAuthenticated } from 'src/store/reducers/login'; import { useDispatch } from 'react-redux'; import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg'; import ThemedColor from 'src/components/ThemedColor/ThemedColor'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; function LetterOfAttorney() { const { signers } = useSigners(); @@ -83,12 +83,12 @@ function LetterOfAttorney() { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={() => { + success={() => { setConfirmPassVisible(false); if (fingerPrints) { GenerateLetterToAtternyPDFInheritanceTool(fingerPrints).then((res) => { diff --git a/src/screens/InheritanceToolsAndTips/components/MasterRecoveryKey.tsx b/src/screens/InheritanceToolsAndTips/components/MasterRecoveryKey.tsx index 6e2d328a1..6273b0b34 100644 --- a/src/screens/InheritanceToolsAndTips/components/MasterRecoveryKey.tsx +++ b/src/screens/InheritanceToolsAndTips/components/MasterRecoveryKey.tsx @@ -14,10 +14,10 @@ import { getJSONFromRealmObject } from 'src/storage/realm/utils'; import { CommonActions } from '@react-navigation/native'; import { LocalizationContext } from 'src/context/Localization/LocContext'; import MasterKey from 'src/assets/images/master_key.svg'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import KeeperModal from 'src/components/KeeperModal'; import { credsAuthenticated } from 'src/store/reducers/login'; import { useDispatch } from 'react-redux'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; function MasterRecoveryKey({ navigation }) { const { colorMode } = useColorMode(); @@ -80,12 +80,12 @@ function MasterRecoveryKey({ navigation }) { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={() => { + success={() => { setConfirmPassVisible(false); navigation.dispatch( CommonActions.navigate('ExportSeed', { diff --git a/src/screens/LoginScreen/CreatePin.tsx b/src/screens/LoginScreen/CreatePin.tsx index 04d21dec2..5f99eab33 100644 --- a/src/screens/LoginScreen/CreatePin.tsx +++ b/src/screens/LoginScreen/CreatePin.tsx @@ -167,7 +167,7 @@ export default function CreatePin(props) { if (success) { const { publicKey } = await RNBiometrics.createKeys(); - dispatch(changeLoginMethod(LoginMethod.BIOMETRIC, publicKey)); + dispatch(changeLoginMethod(LoginMethod.BIOMETRIC, publicKey, LoginMethod.PIN)); props.navigation.replace('OnBoardingSlides'); } else { showToast(errorText.biometicAuthFailed, ); diff --git a/src/screens/LoginScreen/Login.tsx b/src/screens/LoginScreen/Login.tsx index e40cfdb03..db7cd4988 100644 --- a/src/screens/LoginScreen/Login.tsx +++ b/src/screens/LoginScreen/Login.tsx @@ -52,6 +52,8 @@ import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg'; import CampaignModalIllustration from 'src/assets/images/CampaignModalIllustration.svg'; import { uaiType } from 'src/models/interfaces/Uai'; import { addToUaiStack, uaiChecks } from 'src/store/sagaActions/uai'; +import KeeperTextInput from 'src/components/KeeperTextInput'; +import { useSelector } from 'react-redux'; const RNBiometrics = new ReactNativeBiometrics(); @@ -98,6 +100,7 @@ function LoginScreen({ navigation, route }) { const { login } = translations; const { common } = translations; const { allAccounts, biometricEnabledAppId } = useAppSelector((state) => state.account); + const fallbackLoginMethod = useSelector((state) => state.settings.fallbackLoginMethod); const [showCampaignModal, setShowCampaignModal] = useState(false); const [campaignDetails, setCampaignDetails] = useState(null); @@ -303,8 +306,17 @@ function LoginScreen({ navigation, route }) { const attemptLogin = (passcode: string) => { setLoginModal(true); - - dispatch(credsAuth(passcode, LoginMethod.PIN, relogin)); + if ( + loginMethod === LoginMethod.PIN || + (loginMethod === LoginMethod.BIOMETRIC && fallbackLoginMethod === 'PIN') + ) { + dispatch(credsAuth(passcode, LoginMethod.PIN, relogin)); + } else if ( + loginMethod === LoginMethod.PASSWORD || + (loginMethod === LoginMethod.BIOMETRIC && fallbackLoginMethod === 'PASSWORD') + ) { + dispatch(credsAuth(passcode, LoginMethod.PASSWORD, relogin)); + } }; const modelAsset = useMemo(() => { @@ -469,55 +481,102 @@ function LoginScreen({ navigation, route }) { - - - {isTestnet() && } - - {relogin ? title : login.welcomeback} - + {loginMethod === LoginMethod.PIN || fallbackLoginMethod === 'PIN' ? ( + - - - {login.enter_your} - {login.passcode} - - + + {isTestnet() && } + + + {relogin ? title : login.welcomeback} + + + + + {login.enter_your} + {login.passcode} + + + + + + {loginError && ( + + {errMessage} + + )} - - {loginError && ( - - {errMessage} - - )} + } + bubbleEffect + keyColor={login_text_color} + /> + + { + setLoginError(false); + setLogging(true); + }} + primaryText={common.proceed} + primaryDisable={passcode.length !== 4} + primaryBackgroundColor={login_button_backGround} + primaryTextColor={login_button_text_color} + fullWidth + /> - } - bubbleEffect - keyColor={login_text_color} - /> - - { - setLoginError(false); - setLogging(true); - }} - primaryText={common.proceed} - primaryDisable={passcode.length !== 4} - primaryBackgroundColor={login_button_backGround} - primaryTextColor={login_button_text_color} - fullWidth - /> + ) : ( + + + + {isTestnet() && } + + + {relogin ? title : login.welcomeback} + + + + Unlock the app with your password + + + + + {loginError && ( + + {errMessage} + + )} + + { + setLoginError(false); + setLogging(true); + }} + primaryText={common.proceed} + primaryBackgroundColor={login_button_backGround} + primaryTextColor={login_button_text_color} + fullWidth + /> + - + )} { @@ -654,6 +717,12 @@ const styles = StyleSheet.create({ textAlign: 'center', marginTop: 18, }, + passwordError: { + fontStyle: 'italic', + fontSize: 12, + textAlign: 'right', + marginBottom: 10, + }, forgotPassWrapper: { flex: 0.8, margin: 20, @@ -713,6 +782,14 @@ const styles = StyleSheet.create({ marginTop: hp(45), gap: hp(15), }, + passwordWrapper: { + justifyContent: 'center', + marginTop: hp(120), + paddingHorizontal: wp(15), + }, + passwordInput: { + marginTop: hp(10), + }, }); export default LoginScreen; diff --git a/src/screens/SeedScreens/ExportSeedScreen.tsx b/src/screens/SeedScreens/ExportSeedScreen.tsx index 1c4ca9ac7..65cd34fe4 100644 --- a/src/screens/SeedScreens/ExportSeedScreen.tsx +++ b/src/screens/SeedScreens/ExportSeedScreen.tsx @@ -54,6 +54,7 @@ function ExportSeedScreen({ route, navigation }) { parentScreen, oldPasscode, isFromMobileKey = false, + showSetPasscodeModal, }: { vaultKey: string; vaultId: string; @@ -68,6 +69,7 @@ function ExportSeedScreen({ route, navigation }) { parentScreen?: string; oldPasscode?: string; isFromMobileKey: boolean; + showSetPasscodeModal: boolean; } = route.params; const { showToast } = useToastMessage(); const [words, setWords] = useState(seed.split(' ')); @@ -301,7 +303,12 @@ function ExportSeedScreen({ route, navigation }) { dismissible={false} close={ isChangePassword - ? () => navigation.navigate('PrivacyAndDisplay', { RKBackedUp: true, oldPasscode }) + ? () => + navigation.navigate('PrivacyAndDisplay', { + RKBackedUp: true, + oldPasscode, + showSetPasscodeModal, + }) : () => {} } title={BackupWallet.backupSuccessTitle} diff --git a/src/screens/Send/SendConfirmation.tsx b/src/screens/Send/SendConfirmation.tsx index 4af391469..466fa9f3b 100644 --- a/src/screens/Send/SendConfirmation.tsx +++ b/src/screens/Send/SendConfirmation.tsx @@ -28,7 +28,6 @@ import useToastMessage from 'src/hooks/useToastMessage'; import useBalance from 'src/hooks/useBalance'; import useWallets from 'src/hooks/useWallets'; import useVault from 'src/hooks/useVault'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import { InputUTXOs, UTXO } from 'src/services/wallets/interfaces'; import CurrencyTypeSwitch from 'src/components/Switch/CurrencyTypeSwitch'; import FeeInsights from 'src/screens/FeeInsights/FeeInsightsContent'; @@ -67,6 +66,7 @@ import SendingCardIcon from 'src/assets/images/vault_icon.svg'; import WalletIcon from 'src/assets/images/daily_wallet.svg'; import MultiSendSvg from 'src/assets/images/@.svg'; import useExchangeRates from 'src/hooks/useExchangeRates'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; export interface SendConfirmationRouteParams { sender: Wallet | Vault; @@ -797,12 +797,12 @@ function SendConfirmation({ route }) { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={onProceed} + success={onProceed} /> )} /> diff --git a/src/screens/SignTransaction/SignTransactionScreen.tsx b/src/screens/SignTransaction/SignTransactionScreen.tsx index bb03ba06b..31f281d7d 100644 --- a/src/screens/SignTransaction/SignTransactionScreen.tsx +++ b/src/screens/SignTransaction/SignTransactionScreen.tsx @@ -30,7 +30,6 @@ import { LocalizationContext } from 'src/context/Localization/LocContext'; import useSignerMap from 'src/hooks/useSignerMap'; import ActivityIndicatorView from 'src/components/AppActivityIndicator/ActivityIndicatorView'; import { getTxHexFromKeystonePSBT } from 'src/hardware/keystone'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import { DelayedTransaction } from 'src/models/interfaces/AssistedKeys'; import { hash256 } from 'src/utils/service-utilities/encryption'; import { hcStatusType } from 'src/models/interfaces/HeathCheckTypes'; @@ -60,6 +59,7 @@ import { } from './signWithSD'; import SendSuccessfulContent from '../Send/SendSuccessfulContent'; import WalletHeader from 'src/components/WalletHeader'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; function SignTransactionScreen() { const route = useRoute(); @@ -806,12 +806,12 @@ function SignTransactionScreen() { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={onSuccess} + success={onSuccess} /> )} /> diff --git a/src/screens/SigningDevices/DeleteKeys.tsx b/src/screens/SigningDevices/DeleteKeys.tsx index 611a9398a..eef2e6f07 100644 --- a/src/screens/SigningDevices/DeleteKeys.tsx +++ b/src/screens/SigningDevices/DeleteKeys.tsx @@ -2,7 +2,6 @@ import React, { useContext, useEffect, useState } from 'react'; import { Box, Pressable, useColorMode } from 'native-base'; import { hp, windowWidth, wp } from 'src/constants/responsive'; import ScreenWrapper from 'src/components/ScreenWrapper'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import KeeperModal from 'src/components/KeeperModal'; import useSigners from 'src/hooks/useSigners'; import { StyleSheet, ScrollView } from 'react-native'; @@ -34,6 +33,7 @@ import TorAsset from 'src/components/Loader'; import moment from 'moment'; import { getKeyUID } from 'src/utils/utilities'; import WalletHeader from 'src/components/WalletHeader'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; import ShowAllIcon from 'src/assets/images/show_wallet.svg'; import HideAllIcon from 'src/assets/images/hide_wallet.svg'; import HideWalletIcon from 'src/assets/images/hide_wallet.svg'; @@ -370,10 +370,11 @@ function DeleteKeys({ route }) { )} - setConfirmPassVisible(false)} - onSuccess={onSuccess} + success={onSuccess} /> )} @@ -390,11 +391,11 @@ function DeleteKeys({ route }) { subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - setConfirmPassForSigner(false)} - onSuccess={onSignerSuccess} + success={onSignerSuccess} onForceSuccess={onForceProceed} /> diff --git a/src/screens/SigningDevices/ManageSigners.tsx b/src/screens/SigningDevices/ManageSigners.tsx index ab7487b27..39c21c825 100644 --- a/src/screens/SigningDevices/ManageSigners.tsx +++ b/src/screens/SigningDevices/ManageSigners.tsx @@ -39,7 +39,6 @@ import ToastErrorIcon from 'src/assets/images/toast_error.svg'; import { setupKeeperSigner } from 'src/hardware/signerSetup'; import { getKeyUID } from 'src/utils/utilities'; import { SentryErrorBoundary } from 'src/services/sentry'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import EnhancedKeysSection from './components/EnhancedKeysSection'; import ConciergeNeedHelp from 'src/assets/images/conciergeNeedHelp.svg'; import { @@ -52,6 +51,7 @@ import HWError from 'src/hardware/HWErrorState'; import { HWErrorType } from 'src/models/enums/Hardware'; import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg'; import ThemedColor from 'src/components/ThemedColor/ThemedColor'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; type ScreenProps = NativeStackScreenProps; @@ -307,12 +307,12 @@ function ManageSigners({ route }: ScreenProps) { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={onSuccess} + success={onSuccess} /> )} /> diff --git a/src/screens/Vault/HardwareModalMap.tsx b/src/screens/Vault/HardwareModalMap.tsx index 74c772267..ddb32a1ea 100644 --- a/src/screens/Vault/HardwareModalMap.tsx +++ b/src/screens/Vault/HardwareModalMap.tsx @@ -83,7 +83,6 @@ import { setupSpecter, } from 'src/hardware/signerSetup'; import { extractColdCardExport } from 'src/hardware/coldcard'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import useCanaryWalletSetup from 'src/hooks/UseCanaryWalletSetup'; import { hcStatusType } from 'src/models/interfaces/HeathCheckTypes'; import NFC from 'src/services/nfc'; @@ -101,6 +100,7 @@ import BackupModalContent from '../AppSettings/BackupModal'; import SignerOptionCard from './components/signerOptionCard'; import ColdCardUSBInstruction from './components/ColdCardUSBInstruction'; import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; import { KRUX_LOAD_SEED, manipulateKruxData } from 'src/hardware/krux'; const RNBiometrics = new ReactNativeBiometrics(); @@ -2314,11 +2314,11 @@ function HardwareModalMap({ textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={() => { + success={() => { if (type === SignerType.MY_KEEPER && mode === InteracationMode.HEALTH_CHECK) { setConfirmPassVisible(false); setBackupModalVisible(true); diff --git a/src/screens/Vault/SignerAdvanceSettings.tsx b/src/screens/Vault/SignerAdvanceSettings.tsx index 557bf9d74..2d9ef8c51 100644 --- a/src/screens/Vault/SignerAdvanceSettings.tsx +++ b/src/screens/Vault/SignerAdvanceSettings.tsx @@ -31,7 +31,6 @@ import { getAccountFromSigner, getKeyUID } from 'src/utils/utilities'; import useSignerMap from 'src/hooks/useSignerMap'; import { getSignerNameFromType } from 'src/hardware'; import { KEEPER_KNOWLEDGEBASE } from 'src/utils/service-utilities/config'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import { NewVaultInfo } from 'src/store/sagas/wallets'; import { addNewVault, refillMobileKey } from 'src/store/sagaActions/vaults'; import { generateVaultId } from 'src/services/wallets/factories/VaultFactory'; @@ -64,6 +63,7 @@ import HardwareModalMap, { InteracationMode } from './HardwareModalMap'; import RegisterSignerContent from './components/RegisterSignerContent'; import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg'; import ThemedColor from 'src/components/ThemedColor/ThemedColor'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; const { width } = Dimensions.get('screen'); @@ -1084,12 +1084,12 @@ function SignerAdvanceSettings({ route }: any) { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={onSuccess} + success={onSuccess} /> )} /> diff --git a/src/screens/Vault/SigningDeviceDetails.tsx b/src/screens/Vault/SigningDeviceDetails.tsx index f34130599..980a2e3ff 100644 --- a/src/screens/Vault/SigningDeviceDetails.tsx +++ b/src/screens/Vault/SigningDeviceDetails.tsx @@ -48,7 +48,6 @@ import { uaiType } from 'src/models/interfaces/Uai'; import { ConciergeTag } from 'src/models/enums/ConciergeTag'; import { hcStatusType } from 'src/models/interfaces/HeathCheckTypes'; import { Signer, Vault } from 'src/services/wallets/interfaces/vault'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import BackupModalContent from 'src/screens/AppSettings/BackupModal'; import { getPersistedDocument } from 'src/services/documents'; import { generateDataFromPSBT, getAccountFromSigner, getKeyUID } from 'src/utils/utilities'; @@ -78,6 +77,7 @@ import nfcManager, { NfcTech } from 'react-native-nfc-manager'; import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg'; import ThemedColor from 'src/components/ThemedColor/ThemedColor'; import HexagonIcon from 'src/components/HexagonIcon'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; export const SignersReqVault = [ SignerType.LEDGER, @@ -1005,12 +1005,12 @@ function SigningDeviceDetails({ route }) { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={() => { + success={() => { setShowMobileKeyModal(false); setBackupModalVisible(true); }} diff --git a/src/screens/WalletDetails/WalletSettings.tsx b/src/screens/WalletDetails/WalletSettings.tsx index ed4baf1dc..f9fb27962 100644 --- a/src/screens/WalletDetails/WalletSettings.tsx +++ b/src/screens/WalletDetails/WalletSettings.tsx @@ -10,7 +10,6 @@ import TickIcon from 'src/assets/images/icon_tick.svg'; import useWallets from 'src/hooks/useWallets'; import { Pressable, StyleSheet } from 'react-native'; import ScreenWrapper from 'src/components/ScreenWrapper'; -import PasscodeVerifyModal from 'src/components/Modal/PasscodeVerify'; import useTestSats from 'src/hooks/useTestSats'; import idx from 'idx'; import dbManager from 'src/storage/realm/dbManager'; @@ -31,6 +30,7 @@ import ToastErrorIcon from 'src/assets/images/toast_error.svg'; import Instruction from 'src/components/Instruction'; import ThemedSvg from 'src/components/ThemedSvg.tsx/ThemedSvg'; import ThemedColor from 'src/components/ThemedColor/ThemedColor'; +import ConfirmCredentialModal from 'src/components/ConfirmCredentialModal'; import { refreshWallets } from 'src/store/sagaActions/wallets'; function WalletSettings({ route }) { @@ -193,12 +193,12 @@ function WalletSettings({ route }) { textColor={`${colorMode}.textGreen`} subTitleColor={`${colorMode}.modalSubtitleBlack`} Content={() => ( - { setConfirmPassVisible(false); }} - onSuccess={() => { + success={() => { setConfirmPassVisible(false); setBackupModalVisible(true); }} diff --git a/src/store/reducers/settings.ts b/src/store/reducers/settings.ts index 17c2e82a7..f71252b92 100644 --- a/src/store/reducers/settings.ts +++ b/src/store/reducers/settings.ts @@ -8,6 +8,7 @@ import * as bitcoinJS from 'bitcoinjs-lib'; const initialState: { loginMethod: LoginMethod; + fallbackLoginMethod: LoginMethod | null; themeMode: ThemeMode; currencyKind: CurrencyKind; currencyCode: string; @@ -24,6 +25,7 @@ const initialState: { appWideLoading: boolean; } = { loginMethod: LoginMethod.PIN, + fallbackLoginMethod: null, themeMode: ThemeMode.LIGHT, currencyKind: CurrencyKind.BITCOIN, currencyCode: 'USD', @@ -47,6 +49,9 @@ const settingsSlice = createSlice({ setLoginMethod: (state, action: PayloadAction) => { state.loginMethod = action.payload; }, + setFallbackLoginMethod: (state, action: PayloadAction) => { + state.fallbackLoginMethod = action.payload; + }, setThemeMode: (state, action: PayloadAction) => { state.themeMode = action.payload; }, @@ -99,6 +104,7 @@ export const { setBackupModal, setSubscription, setBitcoinNetwork, + setFallbackLoginMethod, setAppWideLoading, } = settingsSlice.actions; diff --git a/src/store/sagaActions/login.ts b/src/store/sagaActions/login.ts index 151c9c825..83d9da8ea 100644 --- a/src/store/sagaActions/login.ts +++ b/src/store/sagaActions/login.ts @@ -19,11 +19,16 @@ export const storeCreds = (passcode, callback = null) => ({ }, }); -export const changeLoginMethod = (method: LoginMethod, pubKey: string = '') => ({ +export const changeLoginMethod = ( + method: LoginMethod, + pubKey: string = '', + fallbackMethod = null +) => ({ type: CHANGE_LOGIN_METHOD, payload: { method, pubKey, + fallbackMethod, }, }); diff --git a/src/store/sagas/login.ts b/src/store/sagas/login.ts index dfd5a901e..54a9d2671 100644 --- a/src/store/sagas/login.ts +++ b/src/store/sagas/login.ts @@ -49,7 +49,7 @@ import { import { RootState, store } from '../store'; import { createWatcher } from '../utilities'; import { fetchExchangeRates } from '../sagaActions/send_and_receive'; -import { setLoginMethod } from '../reducers/settings'; +import { setLoginMethod, setFallbackLoginMethod } from '../reducers/settings'; import { setSubscription } from 'src/store/sagaActions/settings'; import { backupAllSignersAndVaults } from '../sagaActions/bhr'; import { uaiChecks } from '../sagaActions/uai'; @@ -149,7 +149,7 @@ function* credentialsAuthWorker({ payload }) { yield put(setupLoading('authenticating')); let hash; let encryptedKey; - if (method === LoginMethod.PIN) { + if (method === LoginMethod.PIN || method === LoginMethod.PASSWORD) { hash = yield call(hash512, payload.passcode); if (payload.reLogin) encryptedKey = yield call(SecureStore.fetchSpecific, hash, appId); else encryptedKey = yield call(SecureStore.fetch, hash); @@ -273,7 +273,6 @@ function* credentialsAuthWorker({ payload }) { RealmSchema.KeeperApp ); if (updatedSubs.level > 2) yield put(setAllCampaigns(true)); - const { pendingAllBackup, automaticCloudBackup } = yield select( (state: RootState) => state.bhr ); @@ -297,8 +296,17 @@ function* credentialsAuthWorker({ payload }) { } yield put(loadConciergeUserOnLogin({ appId: keeperApp.id })); yield put( + // setLoginMethod( + // keeperApp.id === biometricEnabledAppId + // ? LoginMethod.BIOMETRIC + // : LoginMethod.PIN || LoginMethod.PASSWORD + // ) setLoginMethod( - keeperApp.id === biometricEnabledAppId ? LoginMethod.BIOMETRIC : LoginMethod.PIN + keeperApp.id === biometricEnabledAppId + ? LoginMethod.BIOMETRIC + : method === LoginMethod.PIN + ? LoginMethod.PIN + : LoginMethod.PASSWORD ) ); if (backupMethodByAppId[keeperApp.id]) @@ -400,20 +408,26 @@ export const changeAuthCredWatcher = createWatcher(changeAuthCredWorker, CHANGE_ function* changeLoginMethodWorker({ payload, }: { - payload: { method: LoginMethod; pubKey: string }; + payload: { + method: LoginMethod; + pubKey: string; + fallbackMethod: any; + }; }) { try { - const { method, pubKey } = payload; + const { method, pubKey, fallbackMethod } = payload; const keeperApp = yield call(dbManager.getObjectByIndex, RealmSchema.KeeperApp); if (method === LoginMethod.BIOMETRIC) { const savePubKey = yield call(SecureStore.storeBiometricPubKey, pubKey, keeperApp?.id); if (savePubKey) { yield put(setLoginMethod(method)); + yield put(setFallbackLoginMethod(fallbackMethod)); if (keeperApp?.id) yield put(setBiometricEnabledAppId(keeperApp?.id)); } } else { yield put(setLoginMethod(method)); yield put(setBiometricEnabledAppId(null)); + yield put(setFallbackLoginMethod(null)); } } catch (err) { console.log('🚀 ~ changeLoginMethodWorker:', err);