feat: Implement secure database operations and caching for encrypted accounts

This commit is contained in:
k1ngsterr1
2026-01-09 01:09:40 +05:00
parent 4adbbd095d
commit 2f77c16484
7 changed files with 523 additions and 105 deletions

View File

@@ -1,11 +1,12 @@
package com.rosetta.messenger.providers
import android.content.Context
import android.util.Log
import androidx.compose.runtime.*
import com.rosetta.messenger.data.AccountManager
import com.rosetta.messenger.data.DecryptedAccount
import com.rosetta.messenger.data.EncryptedAccount
import com.rosetta.messenger.crypto.CryptoManager
import com.rosetta.messenger.database.DatabaseService
import com.rosetta.messenger.database.DecryptedAccountData
import com.rosetta.messenger.network.ProtocolManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
@@ -13,30 +14,41 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Auth state management - similar to AuthContext in React Native version
* Auth state management - matches React Native architecture
*/
sealed class AuthStatus {
object Loading : AuthStatus()
object Unauthenticated : AuthStatus()
data class Authenticated(val account: DecryptedAccount) : AuthStatus()
data class Authenticated(val account: DecryptedAccountData) : AuthStatus()
data class Locked(val publicKey: String) : AuthStatus()
}
data class AuthStateData(
val status: AuthStatus = AuthStatus.Loading,
val accounts: List<EncryptedAccount> = emptyList(),
val hasExistingAccounts: Boolean = false
val hasExistingAccounts: Boolean = false,
val availableAccounts: List<String> = emptyList()
)
class AuthStateManager(
private val context: Context,
private val scope: CoroutineScope
) {
private val accountManager = AccountManager(context)
private val databaseService = DatabaseService.getInstance(context)
private val _state = MutableStateFlow(AuthStateData())
val state: StateFlow<AuthStateData> = _state.asStateFlow()
private var currentDecryptedAccount: DecryptedAccountData? = null
// 🚀 ОПТИМИЗАЦИЯ: Кэш списка аккаунтов для UI
private var accountsCache: List<String>? = null
private var lastAccountsLoadTime = 0L
private val accountsCacheTTL = 5000L // 5 секунд
companion object {
private const val TAG = "AuthStateManager"
}
init {
scope.launch {
loadAccounts()
@@ -45,74 +57,132 @@ class AuthStateManager(
}
private suspend fun loadAccounts() = withContext(Dispatchers.IO) {
val accounts = accountManager.getAllAccounts()
_state.update { it.copy(
accounts = accounts,
hasExistingAccounts = accounts.isNotEmpty()
)}
try {
// 🚀 ОПТИМИЗАЦИЯ: Используем кэш если он свежий
val currentTime = System.currentTimeMillis()
if (accountsCache != null && (currentTime - lastAccountsLoadTime) < accountsCacheTTL) {
Log.d(TAG, "📚 Using cached accounts list")
_state.update { it.copy(
hasExistingAccounts = accountsCache!!.isNotEmpty(),
availableAccounts = accountsCache!!
)}
return@withContext
}
val accounts = databaseService.getAllEncryptedAccounts()
val hasAccounts = accounts.isNotEmpty()
val accountKeys = accounts.map { it.publicKey }
// Обновляем кэш
accountsCache = accountKeys
lastAccountsLoadTime = currentTime
Log.d(TAG, "📚 Loaded ${accounts.size} accounts from database")
_state.update { it.copy(
hasExistingAccounts = hasAccounts,
availableAccounts = accountKeys
)}
} catch (e: Exception) {
Log.e(TAG, "❌ Failed to load accounts", e)
}
}
private suspend fun checkAuthStatus() {
accountManager.isLoggedIn.collect { isLoggedIn ->
if (isLoggedIn) {
accountManager.currentPublicKey.first()?.let { publicKey ->
_state.update { it.copy(
status = AuthStatus.Locked(publicKey)
)}
}
try {
val hasAccounts = databaseService.hasAccounts()
if (!hasAccounts) {
_state.update { it.copy(
status = AuthStatus.Unauthenticated
)}
} else {
_state.update { it.copy(
status = AuthStatus.Unauthenticated
)}
}
} catch (e: Exception) {
Log.e(TAG, "❌ Failed to check auth status", e)
_state.update { it.copy(
status = AuthStatus.Unauthenticated
)}
}
}
/**
* Create new account from seed phrase
* Matches createAccountFromSeedPhrase from React Native
* 🚀 ОПТИМИЗАЦИЯ: Dispatchers.Default для CPU-интенсивной криптографии
*/
suspend fun createAccount(
seedPhrase: List<String>,
password: String,
name: String = "Rosetta Account"
): Result<DecryptedAccount> = withContext(Dispatchers.Default) {
password: String
): Result<DecryptedAccountData> = withContext(Dispatchers.Default) {
try {
val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase)
val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
Log.d(TAG, "🔧 Creating new account from seed phrase")
Log.d(TAG, " - Seed phrase: ${seedPhrase.size} words")
Log.d(TAG, " - Password length: ${password.length}")
// Step 1: Generate key pair from seed phrase (using BIP39)
val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase)
Log.d(TAG, "🔑 Generated keys from seed phrase")
Log.d(TAG, " - Public Key: ${keyPair.publicKey.take(20)}...")
Log.d(TAG, " - Private Key length: ${keyPair.privateKey.length}")
// Step 2: Generate private key hash for protocol
val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
Log.d(TAG, "🔐 Generated private key hash: $privateKeyHash")
// Step 3: Encrypt private key with password
val encryptedPrivateKey = CryptoManager.encryptWithPassword(
keyPair.privateKey, password
)
Log.d(TAG, "🔒 Encrypted private key: ${encryptedPrivateKey.take(50)}...")
// Step 4: Encrypt seed phrase with password
val encryptedSeedPhrase = CryptoManager.encryptWithPassword(
seedPhrase.joinToString(" "), password
)
Log.d(TAG, "🔒 Encrypted seed phrase: ${encryptedSeedPhrase.take(50)}...")
withContext(Dispatchers.IO) {
val encryptedAccount = EncryptedAccount(
// Step 5: Save to database
val saved = withContext(Dispatchers.IO) {
databaseService.saveEncryptedAccount(
publicKey = keyPair.publicKey,
encryptedPrivateKey = encryptedPrivateKey,
encryptedSeedPhrase = encryptedSeedPhrase,
name = name
privateKeyEncrypted = encryptedPrivateKey,
seedPhraseEncrypted = encryptedSeedPhrase
)
accountManager.saveAccount(encryptedAccount)
accountManager.setCurrentAccount(keyPair.publicKey)
}
val decryptedAccount = DecryptedAccount(
if (!saved) {
return@withContext Result.failure(Exception("Failed to save account to database"))
}
Log.d(TAG, "✅ Account saved to database successfully")
// Step 6: Create decrypted account object
val decryptedAccount = DecryptedAccountData(
publicKey = keyPair.publicKey,
privateKey = keyPair.privateKey,
seedPhrase = seedPhrase,
privateKeyHash = privateKeyHash,
name = name
seedPhrase = seedPhrase
)
// Step 7: Update state and reload accounts
currentDecryptedAccount = decryptedAccount
_state.update { it.copy(
status = AuthStatus.Authenticated(decryptedAccount)
)}
loadAccounts()
// Step 8: Authenticate with protocol
Log.d(TAG, "🌐 Authenticating with protocol server...")
ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash)
Log.d(TAG, "✅ Account created and authenticated successfully!")
Result.success(decryptedAccount)
} catch (e: Exception) {
Log.e(TAG, "❌ Failed to create account", e)
Result.failure(e)
}
}
@@ -122,113 +192,106 @@ class AuthStateManager(
*/
suspend fun importAccount(
seedPhrase: List<String>,
password: String,
name: String = "Imported Account"
): Result<DecryptedAccount> {
return createAccount(seedPhrase, password, name)
password: String
): Result<DecryptedAccountData> {
Log.d(TAG, "📥 Importing account from seed phrase")
return createAccount(seedPhrase, password)
}
/**
* Unlock account with password
* Matches loginWithPassword from React Native
*/
suspend fun unlock(
publicKey: String,
password: String
): Result<DecryptedAccount> = withContext(Dispatchers.Default) {
): Result<DecryptedAccountData> = withContext(Dispatchers.Default) {
try {
val encryptedAccount = withContext(Dispatchers.IO) {
accountManager.getAccount(publicKey)
} ?: return@withContext Result.failure(Exception("Account not found"))
Log.d(TAG, "🔓 Unlocking account: ${publicKey.take(20)}...")
Log.d(TAG, " - Password length: ${password.length}")
val privateKey = CryptoManager.decryptWithPassword(
encryptedAccount.encryptedPrivateKey, password
) ?: return@withContext Result.failure(Exception("Invalid password"))
val seedPhraseStr = CryptoManager.decryptWithPassword(
encryptedAccount.encryptedSeedPhrase, password
) ?: return@withContext Result.failure(Exception("Invalid password"))
val seedPhrase = seedPhraseStr.split(" ")
val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase)
if (keyPair.publicKey != publicKey) {
return@withContext Result.failure(Exception("Invalid password or corrupted data"))
// Decrypt account from database
val decryptedAccount = withContext(Dispatchers.IO) {
databaseService.decryptAccount(publicKey, password)
}
val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey)
if (decryptedAccount == null) {
Log.e(TAG, "❌ Failed to decrypt account - wrong password or account not found")
return@withContext Result.failure(Exception("Invalid password or account not found"))
}
val decryptedAccount = DecryptedAccount(
publicKey = publicKey,
privateKey = privateKey,
seedPhrase = seedPhrase,
privateKeyHash = privateKeyHash,
name = encryptedAccount.name
)
Log.d(TAG, "✅ Account decrypted successfully")
Log.d(TAG, " - Public Key: ${decryptedAccount.publicKey.take(20)}...")
Log.d(TAG, " - Private Key Hash: ${decryptedAccount.privateKeyHash}")
Log.d(TAG, " - Seed Phrase: ${decryptedAccount.seedPhrase.size} words")
// Update last used timestamp
withContext(Dispatchers.IO) {
accountManager.setCurrentAccount(publicKey)
databaseService.updateLastUsed(publicKey)
}
// Update state
currentDecryptedAccount = decryptedAccount
_state.update { it.copy(
status = AuthStatus.Authenticated(decryptedAccount)
)}
// Authenticate with protocol
Log.d(TAG, "🌐 Authenticating with protocol server...")
ProtocolManager.authenticate(decryptedAccount.publicKey, decryptedAccount.privateKeyHash)
Log.d(TAG, "✅ Account unlocked and authenticated successfully!")
Result.success(decryptedAccount)
} catch (e: Exception) {
Log.e(TAG, "❌ Failed to unlock account", e)
Result.failure(e)
}
}
/**
* Lock account
* Logout - clears decrypted account from memory
*/
fun lock() {
val currentStatus = _state.value.status
if (currentStatus is AuthStatus.Authenticated) {
_state.update { it.copy(
status = AuthStatus.Locked(currentStatus.account.publicKey)
)}
}
}
/**
* Logout
*/
suspend fun logout() {
withContext(Dispatchers.IO) {
accountManager.logout()
}
fun logout() {
Log.d(TAG, "🚪 Logging out, clearing decrypted keys from memory")
currentDecryptedAccount = null
_state.update { it.copy(
status = AuthStatus.Unauthenticated
)}
loadAccounts()
}
/**
* Switch to different account
* Delete account from database
*/
fun switchAccount(publicKey: String) {
_state.update { it.copy(
status = AuthStatus.Locked(publicKey)
)}
suspend fun deleteAccount(publicKey: String): Result<Unit> = withContext(Dispatchers.IO) {
try {
Log.d(TAG, "🗑️ Deleting account: ${publicKey.take(20)}...")
val success = databaseService.deleteAccount(publicKey)
if (!success) {
return@withContext Result.failure(Exception("Failed to delete account"))
}
// If deleting current account, logout
if (currentDecryptedAccount?.publicKey == publicKey) {
withContext(Dispatchers.Main) {
logout()
}
}
loadAccounts()
Log.d(TAG, "✅ Account deleted successfully")
Result.success(Unit)
} catch (e: Exception) {
Log.e(TAG, "❌ Failed to delete account", e)
Result.failure(e)
}
}
/**
* Delete account permanently
* Get current decrypted account (if authenticated)
*/
suspend fun deleteAccount(publicKey: String) = withContext(Dispatchers.IO) {
val accounts = accountManager.getAllAccounts().toMutableList()
accounts.removeAll { it.publicKey == publicKey }
accountManager.clearAll()
accounts.forEach { accountManager.saveAccount(it) }
loadAccounts()
val currentStatus = _state.value.status
if ((currentStatus is AuthStatus.Authenticated && currentStatus.account.publicKey == publicKey) ||
(currentStatus is AuthStatus.Locked && currentStatus.publicKey == publicKey)) {
logout()
fun getCurrentAccount(): DecryptedAccountData? = currentDecryptedAccount
}
}
}
}