mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 09:50:15 +01:00
196 lines
6.0 KiB
TypeScript
196 lines
6.0 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { Alert, Platform } from 'react-native';
|
|
import ReactNativeBiometrics, { BiometryTypes as RNBiometryTypes } from 'react-native-biometrics';
|
|
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
|
|
import loc from '../loc';
|
|
import * as NavigationService from '../NavigationService';
|
|
import presentAlert from '../components/Alert';
|
|
import { useStorage } from './context/useStorage';
|
|
|
|
const STORAGEKEY = 'Biometrics';
|
|
const rnBiometrics = new ReactNativeBiometrics({ allowDeviceCredentials: true });
|
|
|
|
const FaceID = 'Face ID';
|
|
const TouchID = 'Touch ID';
|
|
const Biometrics = 'Biometrics';
|
|
|
|
const clearKeychain = async () => {
|
|
try {
|
|
console.debug('Wiping keychain');
|
|
console.debug('Wiping key: data');
|
|
await RNSecureKeyStore.set('data', JSON.stringify({ data: { wallets: [] } }), {
|
|
accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
});
|
|
console.debug('Wiped key: data');
|
|
console.debug('Wiping key: data_encrypted');
|
|
await RNSecureKeyStore.set('data_encrypted', '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
|
|
console.debug('Wiped key: data_encrypted');
|
|
console.debug('Wiping key: STORAGEKEY');
|
|
await RNSecureKeyStore.set(STORAGEKEY, '', { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
|
|
console.debug('Wiped key: STORAGEKEY');
|
|
NavigationService.reset();
|
|
} catch (error: any) {
|
|
console.warn(error);
|
|
presentAlert({ message: error.message });
|
|
}
|
|
};
|
|
|
|
const unlockWithBiometrics = async () => {
|
|
try {
|
|
const { available } = await rnBiometrics.isSensorAvailable();
|
|
if (!available) {
|
|
return false;
|
|
}
|
|
|
|
return new Promise<boolean>(resolve => {
|
|
rnBiometrics
|
|
.simplePrompt({ promptMessage: loc.settings.biom_conf_identity })
|
|
.then((result: { success: any }) => {
|
|
if (result.success) {
|
|
resolve(true);
|
|
} else {
|
|
console.debug('Biometrics authentication failed');
|
|
resolve(false);
|
|
}
|
|
})
|
|
.catch((error: Error) => {
|
|
console.debug('Biometrics authentication error');
|
|
presentAlert({ message: error.message });
|
|
resolve(false);
|
|
});
|
|
});
|
|
} catch (e: Error | any) {
|
|
console.debug('Biometrics authentication error', e);
|
|
presentAlert({ message: e.message });
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const showKeychainWipeAlert = () => {
|
|
if (Platform.OS === 'ios') {
|
|
Alert.alert(
|
|
loc.settings.encrypt_tstorage,
|
|
loc.settings.biom_10times,
|
|
[
|
|
{
|
|
text: loc._.cancel,
|
|
onPress: () => {
|
|
console.debug('Cancel Pressed');
|
|
},
|
|
style: 'cancel',
|
|
},
|
|
{
|
|
text: loc._.ok,
|
|
onPress: async () => {
|
|
const { available } = await rnBiometrics.isSensorAvailable();
|
|
if (!available) {
|
|
presentAlert({ message: loc.settings.biom_no_passcode });
|
|
return;
|
|
}
|
|
const isAuthenticated = await unlockWithBiometrics();
|
|
if (isAuthenticated) {
|
|
Alert.alert(
|
|
loc.settings.encrypt_tstorage,
|
|
loc.settings.biom_remove_decrypt,
|
|
[
|
|
{ text: loc._.cancel, style: 'cancel' },
|
|
{
|
|
text: loc._.ok,
|
|
style: 'destructive',
|
|
onPress: async () => await clearKeychain(),
|
|
},
|
|
],
|
|
{ cancelable: false },
|
|
);
|
|
}
|
|
},
|
|
style: 'default',
|
|
},
|
|
],
|
|
{ cancelable: false },
|
|
);
|
|
}
|
|
};
|
|
|
|
const useBiometrics = () => {
|
|
const { getItem, setItem } = useStorage();
|
|
const [biometricEnabled, setBiometricEnabled] = useState(false);
|
|
const [deviceBiometricType, setDeviceBiometricType] = useState<'TouchID' | 'FaceID' | 'Biometrics' | undefined>(undefined);
|
|
|
|
useEffect(() => {
|
|
const fetchBiometricEnabledStatus = async () => {
|
|
const enabled = await isBiometricUseEnabled();
|
|
setBiometricEnabled(enabled);
|
|
|
|
const biometricType = await type();
|
|
setDeviceBiometricType(biometricType);
|
|
};
|
|
|
|
fetchBiometricEnabledStatus();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
const isDeviceBiometricCapable = useCallback(async () => {
|
|
try {
|
|
const { available } = await rnBiometrics.isSensorAvailable();
|
|
return available;
|
|
} catch (e) {
|
|
console.debug('Biometrics isDeviceBiometricCapable failed');
|
|
console.debug(e);
|
|
setBiometricUseEnabled(false);
|
|
}
|
|
return false;
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
const type = useCallback(async () => {
|
|
try {
|
|
const { available, biometryType } = await rnBiometrics.isSensorAvailable();
|
|
if (!available) {
|
|
return undefined;
|
|
}
|
|
|
|
return biometryType;
|
|
} catch (e) {
|
|
console.debug('Biometrics biometricType failed');
|
|
console.debug(e);
|
|
return undefined;
|
|
}
|
|
}, []);
|
|
|
|
const isBiometricUseEnabled = useCallback(async () => {
|
|
try {
|
|
const enabledBiometrics = await getItem(STORAGEKEY);
|
|
return !!enabledBiometrics;
|
|
} catch (_) {}
|
|
|
|
return false;
|
|
}, [getItem]);
|
|
|
|
const isBiometricUseCapableAndEnabled = useCallback(async () => {
|
|
const isEnabled = await isBiometricUseEnabled();
|
|
const isCapable = await isDeviceBiometricCapable();
|
|
return isEnabled && isCapable;
|
|
}, [isBiometricUseEnabled, isDeviceBiometricCapable]);
|
|
|
|
const setBiometricUseEnabled = useCallback(
|
|
async (value: boolean) => {
|
|
await setItem(STORAGEKEY, value === true ? '1' : '');
|
|
setBiometricEnabled(value);
|
|
},
|
|
[setItem],
|
|
);
|
|
|
|
return {
|
|
isDeviceBiometricCapable,
|
|
deviceBiometricType,
|
|
isBiometricUseEnabled,
|
|
isBiometricUseCapableAndEnabled,
|
|
setBiometricUseEnabled,
|
|
clearKeychain,
|
|
biometricEnabled,
|
|
};
|
|
};
|
|
|
|
export { FaceID, TouchID, Biometrics, RNBiometryTypes as BiometricType, useBiometrics, showKeychainWipeAlert, unlockWithBiometrics };
|