diff --git a/mobile-app/app/(tabs)/(settings)/vault-unlock.tsx b/mobile-app/app/(tabs)/(settings)/vault-unlock.tsx index 2309a45d2..d13cdffbc 100644 --- a/mobile-app/app/(tabs)/(settings)/vault-unlock.tsx +++ b/mobile-app/app/(tabs)/(settings)/vault-unlock.tsx @@ -4,10 +4,11 @@ import { ThemedSafeAreaView } from '@/components/ThemedSafeAreaView'; import { useColors } from '@/hooks/useColorScheme'; import * as LocalAuthentication from 'expo-local-authentication'; import { useState, useEffect, useMemo, useCallback } from 'react'; -import { useAuth } from '@/context/AuthContext'; +import { AuthMethod, useAuth } from '@/context/AuthContext'; export default function VaultUnlockSettingsScreen() { const colors = useColors(); + const [initialized, setInitialized] = useState(false); const { setAuthMethods, enabledAuthMethods } = useAuth(); const [hasFaceID, setHasFaceID] = useState(false); const [isFaceIDEnabled, setIsFaceIDEnabled] = useState(false); @@ -23,15 +24,25 @@ export default function VaultUnlockSettingsScreen() { if (enabledAuthMethods.includes('faceid')) { setIsFaceIDEnabled(true); } + + setInitialized(true); }, []); useEffect(() => { - if (isFaceIDEnabled) { - setAuthMethods(['faceid', 'password']); - } else { - setAuthMethods(['password']); + if (!initialized) { + return; } - }, [isFaceIDEnabled, setAuthMethods]); + + // Check if there are actually differences between the current and the new auth methods + const currentAuthMethods = enabledAuthMethods; + const newAuthMethods = isFaceIDEnabled ? ['faceid', 'password'] : ['password']; + if (currentAuthMethods.length === newAuthMethods.length && currentAuthMethods.every(method => newAuthMethods.includes(method))) { + return; + } + + console.log('Updating auth methods to', newAuthMethods); + setAuthMethods(newAuthMethods as AuthMethod[]); + }, [isFaceIDEnabled, setAuthMethods, enabledAuthMethods, initialized]); const handleFaceIDToggle = useCallback(async (value: boolean) => { if (value && !hasFaceID) { @@ -117,7 +128,7 @@ export default function VaultUnlockSettingsScreen() { > - Choose how you want to unlock your vault + Choose how you want to unlock your vault. @@ -134,7 +145,7 @@ export default function VaultUnlockSettingsScreen() { /> - Your vault decryption key will be securely stored on your local device in the iOS Keychain and can only be accessed with your face or fingerprint. + Your vault decryption key will be securely stored on your local device in the iOS Keychain and can be accessed with your face or fingerprint. diff --git a/mobile-app/ios/CredentialManager/SharedCredentialStore.swift b/mobile-app/ios/CredentialManager/SharedCredentialStore.swift index d43b2a1ff..7daf6532d 100644 --- a/mobile-app/ios/CredentialManager/SharedCredentialStore.swift +++ b/mobile-app/ios/CredentialManager/SharedCredentialStore.swift @@ -18,6 +18,8 @@ struct AuthMethods: OptionSet { class SharedCredentialStore { static let shared = SharedCredentialStore() private let keychain = Keychain(service: "net.aliasvault.autofill", accessGroup: "group.net.aliasvault.autofill") + .accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .biometryAny) + private let encryptionKeyKey = "aliasvault_encryption_key" private let encryptedDbFileName = "encrypted_db.sqlite" private let authMethodsKey = "aliasvault_auth_methods" @@ -36,6 +38,24 @@ class SharedCredentialStore { enabledAuthMethods = methods UserDefaults.standard.set(methods.rawValue, forKey: authMethodsKey) UserDefaults.standard.synchronize() + + if !enabledAuthMethods.contains(.faceID) { + // If Face ID is now disabled, remove the persisted key from keychain if it exists + print("Face ID is now disabled, removing key from keychain") + try? keychain.remove(encryptionKeyKey) + } + else { + // If Face ID is now enabled, persist the current key from memory into keychain + print("Face ID is now enabled, persisting key to keychain") + do { + if let key = encryptionKey { + try storeEncryptionKey(base64Key: key.base64EncodedString()) + } + } catch { + print("Failed to save existing key from memory to keychain: \(error)") + throw error + } + } } // MARK: - Vault Status @@ -88,18 +108,24 @@ class SharedCredentialStore { // Store the key in memory encryptionKey = keyData + print("Stored key in memory") // Store the key in the keychain if Face ID is enabled if enabledAuthMethods.contains(.faceID) { + print("Face ID is enabled, storing key in keychain") do { try keychain - .authenticationPrompt("Authenticate to unlock your vault") + .authenticationPrompt("Authenticate to save your vault decryption key in the iOS keychain") .set(keyData, key: encryptionKeyKey) + print("Key saved to keychain") } catch { print("Failed to save key to keychain: \(error)") throw error } } + else { + print("Face ID is disabled, not storing key in keychain") + } } // MARK: - Database Management