From 3ee80d9278536b4103225b3be31b17ea590e0b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C4=9Fur=20Eren?= Date: Sun, 26 May 2024 15:34:48 +0300 Subject: [PATCH] biometrics support (#106) --- JoyboyCommunity/app.json | 11 +-- JoyboyCommunity/src/modules/login/index.tsx | 45 ++++++------ JoyboyCommunity/src/utils/keychain.ts | 78 --------------------- JoyboyCommunity/src/utils/storage.ts | 14 ++++ 4 files changed, 44 insertions(+), 104 deletions(-) delete mode 100644 JoyboyCommunity/src/utils/keychain.ts diff --git a/JoyboyCommunity/app.json b/JoyboyCommunity/app.json index acd78ac3..7a9adae7 100644 --- a/JoyboyCommunity/app.json +++ b/JoyboyCommunity/app.json @@ -10,9 +10,7 @@ "image": "./assets/splash.png", "backgroundColor": "#ffffff" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true }, @@ -27,7 +25,12 @@ "favicon": "./assets/favicon.png" }, "plugins": [ - "expo-secure-store" + [ + "expo-secure-store", + { + "faceIDPermission": "Allow $(PRODUCT_NAME) to use Face ID." + } + ] ] } } diff --git a/JoyboyCommunity/src/modules/login/index.tsx b/JoyboyCommunity/src/modules/login/index.tsx index a3e7ed03..08aeac1c 100644 --- a/JoyboyCommunity/src/modules/login/index.tsx +++ b/JoyboyCommunity/src/modules/login/index.tsx @@ -1,18 +1,16 @@ +import * as SecureStore from 'expo-secure-store'; import React, {useEffect, useState} from 'react'; -import {Platform} from 'react-native'; +import {Alert} from 'react-native'; import {Typography} from '../../components'; import {useAuth} from '../../store/auth'; import {useNavigationStore} from '../../store/navigation'; -import { - getCredentialsWithBiometry, - isBiometrySupported, - saveCredentialsWithBiometry, -} from '../../utils/keychain'; import {generateRandomKeypair, getPublicKeyFromSecret} from '../../utils/keypair'; import { retrieveAndDecryptPrivateKey, + retrievePassword, retrievePublicKey, + storePassword, storePrivateKey, storePublicKey, } from '../../utils/storage'; @@ -34,8 +32,6 @@ enum LoginStep { } export default function Login() { - const bypassBiometric = Platform.OS == 'web' ? true : false; - const setNavigationStack = useNavigationStore((state) => state.setStack); const setAuth = useAuth((state) => state.setAuth); @@ -52,12 +48,11 @@ export default function Login() { useEffect(() => { (async () => { - const biometrySupported = await isBiometrySupported(); - - if (biometrySupported && !bypassBiometric) { - const credentials = await getCredentialsWithBiometry(); + const biometrySupported = SecureStore.canUseBiometricAuthentication(); + if (biometrySupported) { + const storedPassword = await retrievePassword(); - if (credentials) setPassword(credentials.password); + if (storedPassword) setPassword(storedPassword); } })(); }, []); @@ -78,9 +73,21 @@ export default function Login() { await storePrivateKey(secretKeyHex, password); await storePublicKey(publicKey); - const biometrySupported = await isBiometrySupported(); - if (biometrySupported && !bypassBiometric) { - await saveCredentialsWithBiometry(publicKey, password); + const biometySupported = SecureStore.canUseBiometricAuthentication(); + if (biometySupported) { + Alert.alert('Easy login', 'Would you like to use biometrics to login?', [ + { + text: 'No', + style: 'cancel', + }, + { + text: 'Yes', + style: 'default', + onPress: async () => { + storePassword(password); + }, + }, + ]); } setAuth(publicKey, secretKey); @@ -109,12 +116,6 @@ export default function Login() { await storePrivateKey(secretKeyHex, password); await storePublicKey(publicKey); - const biometrySupported = await isBiometrySupported(); - - if (biometrySupported && !bypassBiometric) { - await saveCredentialsWithBiometry(publicKey, password); - } - setAuth(publicKey, secretKey); setNavigationStack('app'); }; diff --git a/JoyboyCommunity/src/utils/keychain.ts b/JoyboyCommunity/src/utils/keychain.ts deleted file mode 100644 index b940f7ef..00000000 --- a/JoyboyCommunity/src/utils/keychain.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as Keychain from 'react-native-keychain'; - -export const generatePassword = async (username: string, password: string) => { - try { - // Store the credentials - await Keychain.setGenericPassword(username, password); - - try { - // Retrieve the credentials - const credentials = await Keychain.getGenericPassword(); - if (credentials) { - console.log('Credentials successfully loaded for user ' + credentials.username); - } else { - console.log('No credentials stored'); - } - } catch (error) { - console.log("Keychain couldn't be accessed!", error); - } - await Keychain.resetGenericPassword(); - } catch (e) { - console.log('Error generatePassword', e); - } -}; - -// Function to check if biometric authentication is supported -export const isBiometrySupported = async () => { - try { - const biometryType = await Keychain.getSupportedBiometryType(); - console.log('getSupportedBiometryType', biometryType); - return !!biometryType; - } catch (error) { - console.log('Error checking biometry support:', error.message); - return false; - } -}; - -// Function to save credentials with biometric protection -export const saveCredentialsWithBiometry = async (username, password) => { - try { - console.log('saveCredentialsWithBiometry'); - await Keychain.setGenericPassword(username, password, { - accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY, - accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED, - }); - console.log('Credentials saved successfully with biometry protection in keychain'); - alert(JSON.stringify('Credentials saved successfully with biometry protection in keychain')); - } catch (error) { - console.log('Error saving credentials:', error.message); - // Handle specific errors (e.g., user canceled biometric enrollment) - if (error.name === 'BiometryEnrollmentCancel') { - console.log('Biometric enrollment canceled by the user.'); - alert(JSON.stringify('Biometric enrollment canceled by the user.')); - } else { - console.log('Unknown error:', error); - } - } -}; - -// Function to retrieve credentials with biometric authentication -export const getCredentialsWithBiometry = async () => { - try { - const credentials = await Keychain.getGenericPassword({ - accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY, - }); - return credentials; - } catch (error) { - console.log('Error retrieving credentials:', error.message); - // Handle specific errors (e.g., biometric authentication failed) - if (error.message.includes('authentication failed')) { - console.log('Biometric authentication failed.'); - alert(JSON.stringify('Biometric authentication failed.')); - } else { - console.log('Unknown error:', error); - alert(JSON.stringify(error)); - } - return null; - } -}; diff --git a/JoyboyCommunity/src/utils/storage.ts b/JoyboyCommunity/src/utils/storage.ts index 09ba5705..2abdd96d 100644 --- a/JoyboyCommunity/src/utils/storage.ts +++ b/JoyboyCommunity/src/utils/storage.ts @@ -69,3 +69,17 @@ export const retrieveAndDecryptPrivateKey = async ( throw new Error('Error retrieving and decrypting private key'); } }; + +export const storePassword = async (password: string) => { + if (isSecureStoreAvailable) { + return SecureStore.setItemAsync('password', password, {requireAuthentication: true}); + } +}; + +export const retrievePassword = async () => { + if (isSecureStoreAvailable) { + return SecureStore.getItemAsync('password', {requireAuthentication: true}); + } + + return null; +};