Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Alert } from 'react-native'
import { InvalidVersionError, NoSaltError } from 'utils/EncryptionHelper'
import Logger from 'utils/Logger'
import { formatTimer } from 'utils/Utils'
import { BiometricType } from 'utils/BiometricsSDK'
import KeychainMigrator, {
BadPinError,
MigrationFailedError,
Expand All @@ -30,11 +29,7 @@ export function usePinOrBiometryLogin({
verifyBiometric: () => Promise<WalletLoadingResults>
disableKeypad: boolean
timeRemaining: string
bioType: BiometricType
isBiometricAvailable: boolean
} {
const [isBiometricAvailable, setIsBiometricAvailable] = useState(true)
const [bioType, setBioType] = useState<BiometricType>(BiometricType.NONE)
const [enteredPin, setEnteredPin] = useState('')
const [verified, setVerified] = useState(false)
const [disableKeypad, setDisableKeypad] = useState(false)
Expand Down Expand Up @@ -179,7 +174,6 @@ export function usePinOrBiometryLogin({
}
//already migrated
const isSuccess = await BiometricsSDK.loadEncryptionKeyWithBiometry()

if (isSuccess) {
setVerified(true)
resetRateLimiter()
Expand All @@ -203,31 +197,13 @@ export function usePinOrBiometryLogin({
}
}, [activeWalletId, alertBadData, resetRateLimiter])

useEffect(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this logic into useStoredBiometrics

async function getBiometryType(): Promise<void> {
const canUseBiometry = await BiometricsSDK.canUseBiometry()
setIsBiometricAvailable(canUseBiometry)

if (!canUseBiometry || BiometricsSDK.getAccessType() !== 'BIO') {
return
}

const type = await BiometricsSDK.getBiometryType()
setBioType(type)
}

getBiometryType()
}, [])

return {
enteredPin,
onEnterPin,
verified,
verifyBiometric,
disableKeypad,
timeRemaining,
bioType,
isBiometricAvailable
timeRemaining
}
}

Expand Down
47 changes: 19 additions & 28 deletions packages/core-mobile/app/new/common/hooks/useStoredBiometrics.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useEffect, useState } from 'react'
import { StorageKey } from 'resources/Constants'
import Logger from 'utils/Logger'
import { commonStorage } from 'utils/mmkv'
import BiometricsSDK, { BiometricType } from 'utils/BiometricsSDK'
import BiometricsSDK from 'utils/BiometricsSDK'
import { AuthenticationType } from 'expo-local-authentication'

export const useStoredBiometrics = (): {
/** Whether biometric authentication is currently enabled by the user */
Expand All @@ -12,41 +11,33 @@ export const useStoredBiometrics = (): {
/** Whether the device supports biometric authentication */
isBiometricAvailable: boolean
/** The type of biometric authentication available on the device (e.g. fingerprint, face) */
biometricType: BiometricType
biometricTypes: AuthenticationType[]
} => {
const [isBiometricAvailable, setIsBiometricAvailable] = useState(false)
const [useBiometrics, setUseBiometrics] = useState(true)
const [biometricType, setBiometricType] = useState<BiometricType>(
BiometricType.NONE
)
const [biometricTypes, setBiometricTypes] = useState<AuthenticationType[]>([])

useEffect(() => {
const getBiometryType = async (): Promise<void> => {
const type = await BiometricsSDK.getBiometryType()
setBiometricType(type)
}
getBiometryType().catch(Logger.error)
}, [])

useEffect(() => {
BiometricsSDK.canUseBiometry()
.then((canUseBiometry: boolean) => {
setIsBiometricAvailable(canUseBiometry)
})
.catch(Logger.error)
async function getBiometryTypes(): Promise<void> {
const authTypes = await BiometricsSDK.getAuthenticationTypes()
const canUseBiometry = authTypes.length > 0

const type = commonStorage.getString(StorageKey.SECURE_ACCESS_SET)
if (type) {
setUseBiometrics(type === 'BIO')
} else {
Logger.error('Secure access type not found')
if (canUseBiometry) {
setBiometricTypes(authTypes)
const type = BiometricsSDK.getAccessType()
if (type) {
setUseBiometrics(type === 'BIO')
} else {
Logger.error('Secure access type not found')
}
}
}
getBiometryTypes().catch(Logger.error)
}, [])

return {
useBiometrics,
setUseBiometrics,
isBiometricAvailable,
biometricType
isBiometricAvailable: biometricTypes.length > 0,
biometricTypes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ScrollScreen } from 'common/components/ScrollScreen'
import { useCreatePin } from 'features/onboarding/hooks/useCreatePin'
import { InteractionManager } from 'react-native'
import { useStoredBiometrics } from 'common/hooks/useStoredBiometrics'
import { AuthenticationType } from 'expo-local-authentication'

export const CreatePin = ({
useBiometrics,
Expand All @@ -33,7 +34,7 @@ export const CreatePin = ({
isModal?: boolean
}): React.JSX.Element => {
const ref = useRef<PinInputActions>(null)
const { biometricType } = useStoredBiometrics()
const { biometricTypes } = useStoredBiometrics()
const {
onEnterChosenPin,
onEnterConfirmedPin,
Expand Down Expand Up @@ -67,12 +68,19 @@ export const CreatePin = ({
}
}, [validPin, onEnteredValidPin])

const biometricTitle =
biometricTypes.length > 1
? 'Biometrics'
: biometricTypes[0] === AuthenticationType.FACIAL_RECOGNITION
? 'Face ID'
: 'Touch ID'

const renderBiometricToggle = useCallback((): React.JSX.Element => {
return (
<GroupList
data={[
{
title: `Unlock with ${biometricType}`,
title: `Unlock with ${biometricTitle}`,
accessory: (
<Toggle
onValueChange={setUseBiometrics}
Expand All @@ -88,7 +96,7 @@ export const CreatePin = ({
]}
/>
)
}, [useBiometrics, setUseBiometrics, biometricType])
}, [useBiometrics, setUseBiometrics, biometricTitle])

return (
<ScrollScreen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useStoredBiometrics } from 'common/hooks/useStoredBiometrics'
import BiometricsSDK from 'utils/BiometricsSDK'
import Logger from 'utils/Logger'
import { useActiveWallet } from 'common/hooks/useActiveWallet'
import { AuthenticationType } from 'expo-local-authentication'

const SecurityAndPrivacyScreen = (): JSX.Element => {
const {
Expand All @@ -35,7 +36,7 @@ const SecurityAndPrivacyScreen = (): JSX.Element => {
const { allApprovedDapps } = useConnectedDapps()
const wallet = useActiveWallet()
const {
biometricType,
biometricTypes,
isBiometricAvailable,
useBiometrics,
setUseBiometrics
Expand Down Expand Up @@ -94,6 +95,13 @@ const SecurityAndPrivacyScreen = (): JSX.Element => {
]
}, [allApprovedDapps.length, navigate])

const biometricTitle =
biometricTypes.length > 1
? 'Biometrics'
: biometricTypes[0] === AuthenticationType.FACIAL_RECOGNITION
? 'Face ID'
: 'Touch ID'

const pinAndBiometricData = useMemo(() => {
const data: GroupListItem[] = [
{
Expand All @@ -119,7 +127,7 @@ const SecurityAndPrivacyScreen = (): JSX.Element => {

if (isBiometricAvailable) {
data.push({
title: `Use ${biometricType}`,
title: `Use ${biometricTitle}`,
value: (
<Toggle onValueChange={handleSwitchBiometric} value={useBiometrics} />
)
Expand All @@ -131,7 +139,7 @@ const SecurityAndPrivacyScreen = (): JSX.Element => {
isBiometricAvailable,
handleToggleLockWalletWithPIN,
navigate,
biometricType,
biometricTitle,
handleSwitchBiometric,
useBiometrics
])
Expand Down
46 changes: 32 additions & 14 deletions packages/core-mobile/app/new/routes/loginWithPinOrBiometry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ScrollScreen } from 'common/components/ScrollScreen'
import { usePinOrBiometryLogin } from 'common/hooks/usePinOrBiometryLogin'
import { usePreventScreenRemoval } from 'common/hooks/usePreventScreenRemoval'
import { useStoredBiometrics } from 'common/hooks/useStoredBiometrics'
import { AuthenticationType } from 'expo-local-authentication'
import { useFocusEffect, useRouter } from 'expo-router'
import { useWallet } from 'hooks/useWallet'
import React, { useCallback, useEffect, useRef, useState } from 'react'
Expand All @@ -35,7 +36,7 @@ import { selectWalletState, WalletState } from 'store/app'
import { selectIsDeveloperMode } from 'store/settings/advanced'
import { selectSelectedAvatar } from 'store/settings/avatar'
import { selectActiveWalletId } from 'store/wallet/slice'
import BiometricsSDK, { BiometricType } from 'utils/BiometricsSDK'
import BiometricsSDK from 'utils/BiometricsSDK'
import Logger from 'utils/Logger'

const LoginWithPinOrBiometry = (): JSX.Element => {
Expand Down Expand Up @@ -98,16 +99,15 @@ const LoginWithPinOrBiometry = (): JSX.Element => {
verified,
verifyBiometric,
disableKeypad,
timeRemaining,
bioType,
isBiometricAvailable
timeRemaining
} = usePinOrBiometryLogin({
onWrongPin: handleWrongPin,
onStartLoading: handleStartLoading,
onStopLoading: handleStopLoading
})

const { useBiometrics } = useStoredBiometrics()
const { useBiometrics, biometricTypes, isBiometricAvailable } =
useStoredBiometrics()

const [isEnteringPin, setIsEnteringPin] = useState(false)

Expand Down Expand Up @@ -266,6 +266,32 @@ const LoginWithPinOrBiometry = (): JSX.Element => {
}
})

const renderBiometricIcon = (): React.JSX.Element | null => {
if (biometricTypes.length === 0) return null

if (biometricTypes.length === 1) {
return (
<CircularButton onPress={handlePromptBioLogin}>
{biometricTypes[0] === AuthenticationType.FACIAL_RECOGNITION ? (
<Icons.Custom.FaceID width={26} height={26} />
) : (
<Icons.Custom.TouchID width={26} height={26} />
)}
</CircularButton>
)
}

// if there are multiple bioTypes, we simply show TouchID icon
// since we don't distinguish between face unlock and fingerprint
// system will prompt the user for the enrolled authentication type
// or a modal to select the one of the enrolled authentication types
return (
<CircularButton onPress={handlePromptBioLogin}>
<Icons.Custom.TouchID width={26} height={26} />
</CircularButton>
)
}

return (
<ScrollScreen
shouldAvoidKeyboard
Expand Down Expand Up @@ -350,15 +376,7 @@ const LoginWithPinOrBiometry = (): JSX.Element => {
justifyContent: 'center',
gap: 30
}}>
{bioType !== BiometricType.NONE && (
<CircularButton onPress={handlePromptBioLogin}>
{bioType === BiometricType.FACE_ID ? (
<Icons.Custom.FaceID width={26} height={26} />
) : (
<Icons.Custom.TouchID width={26} height={26} />
)}
</CircularButton>
)}
{renderBiometricIcon()}
{isEnteringPin === false && !disableKeypad && (
<CircularButton onPress={handleTogglePinInput}>
<Icons.Custom.Pin width={26} height={26} />
Expand Down
Loading
Loading