package com.rosetta.messenger.biometric import android.content.Context import android.content.SharedPreferences import android.util.Log import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext /** * Безопасное хранилище настроек биометрической аутентификации * Использует EncryptedSharedPreferences с MasterKey из Android Keystore * * Биометрия привязана к конкретному аккаунту (per-account), не глобальная. */ class BiometricPreferences(private val context: Context) { companion object { private const val TAG = "BiometricPreferences" private const val PREFS_FILE_NAME = "rosetta_secure_biometric_prefs" private const val KEY_BIOMETRIC_ENABLED_PREFIX = "biometric_enabled_" private const val ENCRYPTED_PASSWORD_PREFIX = "encrypted_password_" // Legacy key (global) — for migration private const val KEY_BIOMETRIC_ENABLED_LEGACY = "biometric_enabled" // Shared state for reactive UI updates private val biometricEnabledState = MutableStateFlow(false) } private val appContext = context.applicationContext private val _isBiometricEnabled = biometricEnabledState private val encryptedPrefs: SharedPreferences by lazy { createEncryptedPreferences() } private fun createEncryptedPreferences(): SharedPreferences { try { val masterKey = MasterKey.Builder(appContext) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .setUserAuthenticationRequired(false) .build() return EncryptedSharedPreferences.create( appContext, PREFS_FILE_NAME, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) } catch (e: Exception) { return appContext.getSharedPreferences(PREFS_FILE_NAME + "_fallback", Context.MODE_PRIVATE) } } val isBiometricEnabled: Flow = _isBiometricEnabled.asStateFlow() /** * Загрузить состояние биометрии для конкретного аккаунта */ fun loadForAccount(publicKey: String) { try { val key = "$KEY_BIOMETRIC_ENABLED_PREFIX$publicKey" val perAccount = encryptedPrefs.getBoolean(key, false) // Migration: если per-account нет, проверяем legacy глобальный ключ if (!perAccount && encryptedPrefs.getBoolean(KEY_BIOMETRIC_ENABLED_LEGACY, false)) { // Мигрируем: копируем глобальное значение в per-account encryptedPrefs.edit().putBoolean(key, true).apply() _isBiometricEnabled.value = true } else { _isBiometricEnabled.value = perAccount } } catch (e: Exception) { _isBiometricEnabled.value = false } } /** * Включить биометрическую аутентификацию для аккаунта */ suspend fun enableBiometric(publicKey: String) = withContext(Dispatchers.IO) { val key = "$KEY_BIOMETRIC_ENABLED_PREFIX$publicKey" encryptedPrefs.edit().putBoolean(key, true).commit() _isBiometricEnabled.value = true } /** * Отключить биометрическую аутентификацию для аккаунта */ suspend fun disableBiometric(publicKey: String) = withContext(Dispatchers.IO) { val key = "$KEY_BIOMETRIC_ENABLED_PREFIX$publicKey" encryptedPrefs.edit().putBoolean(key, false).commit() _isBiometricEnabled.value = false } /** * Проверить включена ли биометрия для аккаунта (синхронно) */ fun isBiometricEnabledForAccount(publicKey: String): Boolean { return try { encryptedPrefs.getBoolean("$KEY_BIOMETRIC_ENABLED_PREFIX$publicKey", false) } catch (_: Exception) { false } } // --- Legacy compat: old callers without publicKey --- @Deprecated("Use enableBiometric(publicKey) instead") suspend fun enableBiometric() = withContext(Dispatchers.IO) { encryptedPrefs.edit().putBoolean(KEY_BIOMETRIC_ENABLED_LEGACY, true).commit() _isBiometricEnabled.value = true } @Deprecated("Use disableBiometric(publicKey) instead") suspend fun disableBiometric() = withContext(Dispatchers.IO) { encryptedPrefs.edit().putBoolean(KEY_BIOMETRIC_ENABLED_LEGACY, false).commit() _isBiometricEnabled.value = false } suspend fun saveEncryptedPassword(publicKey: String, encryptedPassword: String) = withContext(Dispatchers.IO) { val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey" encryptedPrefs.edit().putString(key, encryptedPassword).apply() } suspend fun getEncryptedPassword(publicKey: String): String? = withContext(Dispatchers.IO) { val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey" encryptedPrefs.getString(key, null) } suspend fun removeEncryptedPassword(publicKey: String) = withContext(Dispatchers.IO) { val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey" encryptedPrefs.edit().remove(key).apply() } suspend fun clearAll() = withContext(Dispatchers.IO) { encryptedPrefs.edit().clear().commit() _isBiometricEnabled.value = false } suspend fun hasEncryptedPassword(publicKey: String): Boolean { return getEncryptedPassword(publicKey) != null } }