Update Android unlock flow to support both biometrics and pin fallback (#1562)
This commit is contained in:
committed by
Leendert de Borst
parent
510b3c95d7
commit
749980f58f
@@ -10,6 +10,9 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnable
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||
import expo.modules.ReactActivityDelegateWrapper
|
||||
import expo.modules.splashscreen.SplashScreenManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* The main activity of the app.
|
||||
@@ -148,12 +151,20 @@ class MainActivity : ReactActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
vaultStore.storeEncryptionKey(encryptionKeyBase64)
|
||||
vaultStore.unlockVault()
|
||||
promise.resolve(true)
|
||||
} catch (e: Exception) {
|
||||
promise.reject("UNLOCK_ERROR", "Failed to unlock vault: ${e.message}", e)
|
||||
// Run vault unlock on IO thread to avoid blocking the main thread
|
||||
// (unlockVault involves file I/O and database operations)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
// Use initEncryptionKey instead of storeEncryptionKey
|
||||
// storeEncryptionKey would trigger biometric prompt if biometrics is enabled
|
||||
// since it tries to store the key in the biometric-protected keystore.
|
||||
// For PIN unlock, we just want to set the key in memory.
|
||||
vaultStore.initEncryptionKey(encryptionKeyBase64)
|
||||
vaultStore.unlockVault()
|
||||
promise.resolve(true)
|
||||
} catch (e: Exception) {
|
||||
promise.reject("UNLOCK_ERROR", "Failed to unlock vault: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
net.aliasvault.app.pinunlock.PinUnlockActivity.RESULT_CANCELLED -> {
|
||||
|
||||
+23
-19
@@ -39,23 +39,24 @@ class UnlockCoordinator(
|
||||
|
||||
/**
|
||||
* Start the unlock flow by checking which auth method is enabled.
|
||||
* Priority: PIN -> Biometric -> Error
|
||||
* Priority: Biometric -> PIN -> Error
|
||||
* Biometrics takes priority, PIN serves as fallback if biometrics fails or is unavailable.
|
||||
*/
|
||||
fun startUnlockFlow() {
|
||||
val pinEnabled = vaultStore.isPinEnabled()
|
||||
val biometricEnabled = vaultStore.isBiometricAuthEnabled()
|
||||
|
||||
when {
|
||||
pinEnabled -> {
|
||||
// PIN is enabled - launch PIN unlock activity
|
||||
Log.d(TAG, "PIN unlock is enabled, launching PIN unlock activity")
|
||||
launchPinUnlock()
|
||||
}
|
||||
biometricEnabled -> {
|
||||
// Only biometric is enabled - attempt biometric unlock
|
||||
// Biometric is enabled - attempt biometric unlock first
|
||||
Log.d(TAG, "Biometric unlock is enabled, attempting biometric unlock")
|
||||
attemptBiometricUnlock()
|
||||
}
|
||||
pinEnabled -> {
|
||||
// Only PIN is enabled - launch PIN unlock activity
|
||||
Log.d(TAG, "PIN unlock is enabled, launching PIN unlock activity")
|
||||
launchPinUnlock()
|
||||
}
|
||||
else -> {
|
||||
// Neither PIN nor biometric is enabled
|
||||
Log.e(TAG, "No unlock method is enabled or available")
|
||||
@@ -66,16 +67,18 @@ class UnlockCoordinator(
|
||||
|
||||
/**
|
||||
* Launch PIN unlock activity.
|
||||
* Can be called directly to retry PIN unlock.
|
||||
*/
|
||||
private fun launchPinUnlock() {
|
||||
fun launchPinUnlock() {
|
||||
val intent = Intent(activity, PinUnlockActivity::class.java)
|
||||
activity.startActivityForResult(intent, REQUEST_CODE_PIN_UNLOCK)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt biometric unlock using the keystore provider.
|
||||
* Can be called directly to retry biometric unlock.
|
||||
*/
|
||||
private fun attemptBiometricUnlock() {
|
||||
fun attemptBiometricUnlock() {
|
||||
val keystoreProvider = AndroidKeystoreProvider(activity.applicationContext) { activity }
|
||||
keystoreProvider.retrieveKeyExternal(
|
||||
activity,
|
||||
@@ -164,20 +167,21 @@ class UnlockCoordinator(
|
||||
|
||||
/**
|
||||
* Handle errors during biometric keystore retrieval.
|
||||
* Falls back to PIN if enabled, otherwise reports the error.
|
||||
*/
|
||||
private fun handleBiometricKeystoreError(e: Exception) {
|
||||
// For any biometric error, try PIN fallback if enabled
|
||||
if (vaultStore.isPinEnabled()) {
|
||||
Log.d(TAG, "Biometric failed (${e.message}), falling back to PIN")
|
||||
launchPinUnlock()
|
||||
return // Don't call onError, we're falling back to PIN
|
||||
}
|
||||
|
||||
// No PIN fallback available - report the error
|
||||
val errorMessage = when {
|
||||
e.message?.contains("user canceled", ignoreCase = true) == true ||
|
||||
e.message?.contains("authentication failed", ignoreCase = true) == true -> {
|
||||
// User cancelled or biometric failed - check if PIN is available as fallback
|
||||
if (vaultStore.isPinEnabled()) {
|
||||
Log.d(TAG, "Biometric cancelled/failed, falling back to PIN")
|
||||
launchPinUnlock()
|
||||
return // Don't call onError, we're falling back to PIN
|
||||
} else {
|
||||
"Authentication cancelled"
|
||||
}
|
||||
}
|
||||
e.message?.contains("canceled", ignoreCase = true) == true ->
|
||||
"Authentication cancelled"
|
||||
else -> "Failed to retrieve encryption key"
|
||||
}
|
||||
onError(errorMessage)
|
||||
|
||||
Reference in New Issue
Block a user