Decouple unlock and sync (#264)

This commit is contained in:
David Perez
2023-11-21 11:17:46 -06:00
committed by Álison Fernandes
parent feba8595b3
commit e8ae15ddc3
7 changed files with 436 additions and 172 deletions

View File

@@ -121,33 +121,40 @@ class AuthRepositoryImpl constructor(
}
.fold(
onFailure = { LoginResult.Error(errorMessage = null) },
onSuccess = {
when (it) {
is CaptchaRequired -> LoginResult.CaptchaRequired(it.captchaKey)
onSuccess = { loginResponse ->
when (loginResponse) {
is CaptchaRequired -> LoginResult.CaptchaRequired(loginResponse.captchaKey)
is Success -> {
authDiskSource.userState = it
.toUserState(
previousUserState = authDiskSource.userState,
environmentUrlData = environmentRepository
.environment
.environmentUrlData,
)
.also { userState ->
authDiskSource.storeUserKey(
userId = userState.activeUserId,
userKey = it.key,
)
authDiskSource.storePrivateKey(
userId = userState.activeUserId,
privateKey = it.privateKey,
)
}
vaultRepository.unlockVaultAndSync(masterPassword = password)
val userStateJson = loginResponse.toUserState(
previousUserState = authDiskSource.userState,
environmentUrlData = environmentRepository
.environment
.environmentUrlData,
)
vaultRepository.unlockVault(
email = userStateJson.activeAccount.profile.email,
kdf = userStateJson.activeAccount.profile.toSdkParams(),
userKey = loginResponse.key,
privateKey = loginResponse.privateKey,
// TODO use actual organization keys BIT-1091
organizationalKeys = emptyMap(),
masterPassword = password,
)
authDiskSource.userState = userStateJson
authDiskSource.storeUserKey(
userId = userStateJson.activeUserId,
userKey = loginResponse.key,
)
authDiskSource.storePrivateKey(
userId = userStateJson.activeUserId,
privateKey = loginResponse.privateKey,
)
vaultRepository.sync()
LoginResult.Success
}
is GetTokenResponseJson.Invalid -> {
LoginResult.Error(errorMessage = it.errorModel.errorMessage)
LoginResult.Error(errorMessage = loginResponse.errorModel.errorMessage)
}
}
},

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository
import com.bitwarden.core.CipherView
import com.bitwarden.core.FolderView
import com.bitwarden.core.Kdf
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
@@ -46,7 +47,20 @@ interface VaultRepository {
fun getVaultFolderStateFlow(folderId: String): StateFlow<DataState<FolderView?>>
/**
* Attempt to initialize crypto and sync the vault data.
* Attempt to unlock the vault and sync the vault data for the currently active user.
*/
suspend fun unlockVaultAndSync(masterPassword: String): VaultUnlockResult
suspend fun unlockVaultAndSyncForCurrentUser(masterPassword: String): VaultUnlockResult
/**
* Attempt to unlock the vault with the specified user information.
*/
@Suppress("LongParameterList")
suspend fun unlockVault(
masterPassword: String,
email: String,
kdf: Kdf,
userKey: String,
privateKey: String,
organizationalKeys: Map<String, String>,
): VaultUnlockResult
}

View File

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.repository
import com.bitwarden.core.CipherView
import com.bitwarden.core.FolderView
import com.bitwarden.core.InitCryptoRequest
import com.bitwarden.core.Kdf
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
import com.x8bit.bitwarden.data.auth.repository.util.toUpdatedUserStateJson
@@ -31,7 +32,6 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -146,18 +146,62 @@ class VaultRepositoryImpl constructor(
initialValue = DataState.Loading,
)
override suspend fun unlockVaultAndSync(masterPassword: String): VaultUnlockResult {
return flow {
willSyncAfterUnlock = true
emit(initializeCrypto(masterPassword = masterPassword))
}
.onEach {
willSyncAfterUnlock = false
if (it is VaultUnlockResult.Success) sync()
@Suppress("ReturnCount")
override suspend fun unlockVaultAndSyncForCurrentUser(
masterPassword: String,
): VaultUnlockResult {
val userState = authDiskSource.userState
?: return VaultUnlockResult.InvalidStateError
val userKey = authDiskSource.getUserKey(userId = userState.activeUserId)
?: return VaultUnlockResult.InvalidStateError
val privateKey = authDiskSource.getPrivateKey(userId = userState.activeUserId)
?: return VaultUnlockResult.InvalidStateError
return unlockVault(
masterPassword = masterPassword,
email = userState.activeAccount.profile.email,
kdf = userState.activeAccount.profile.toSdkParams(),
userKey = userKey,
privateKey = privateKey,
// TODO use actual organization keys BIT-1091
organizationalKeys = emptyMap(),
)
.also {
if (it is VaultUnlockResult.Success) {
sync()
}
}
}
override suspend fun unlockVault(
masterPassword: String,
email: String,
kdf: Kdf,
userKey: String,
privateKey: String,
organizationalKeys: Map<String, String>,
): VaultUnlockResult =
flow {
willSyncAfterUnlock = true
emit(
vaultSdkSource
.initializeCrypto(
request = InitCryptoRequest(
kdfParams = kdf,
email = email,
password = masterPassword,
userKey = userKey,
privateKey = privateKey,
organizationKeys = organizationalKeys,
),
)
.fold(
onFailure = { VaultUnlockResult.GenericError },
onSuccess = { it.toVaultUnlockResult() },
),
)
}
.onCompletion { willSyncAfterUnlock = false }
.first()
}
private fun storeUserKeyAndPrivateKey(
userKey: String?,
@@ -177,32 +221,6 @@ class VaultRepositoryImpl constructor(
}
}
@Suppress("ReturnCount")
private suspend fun initializeCrypto(masterPassword: String): VaultUnlockResult {
val userState = authDiskSource.userState
?: return VaultUnlockResult.InvalidStateError
val userKey = authDiskSource.getUserKey(userId = userState.activeUserId)
?: return VaultUnlockResult.InvalidStateError
val privateKey = authDiskSource.getPrivateKey(userId = userState.activeUserId)
?: return VaultUnlockResult.InvalidStateError
return vaultSdkSource
.initializeCrypto(
request = InitCryptoRequest(
kdfParams = userState.activeAccount.profile.toSdkParams(),
email = userState.activeAccount.profile.email,
password = masterPassword,
userKey = userKey,
privateKey = privateKey,
// TODO use actual organization keys BIT-1091
organizationKeys = mapOf(),
),
)
.fold(
onFailure = { VaultUnlockResult.GenericError },
onSuccess = { it.toVaultUnlockResult() },
)
}
private suspend fun decryptSendsAndUpdateSendDataState(sendList: List<SyncResponseJson.Send>?) {
val newState = vaultSdkSource
.decryptSendList(

View File

@@ -92,7 +92,7 @@ class VaultUnlockViewModel @Inject constructor(
private fun handleUnlockClick() {
mutableStateFlow.update { it.copy(dialog = VaultUnlockState.VaultUnlockDialog.Loading) }
viewModelScope.launch {
val vaultUnlockResult = vaultRepo.unlockVaultAndSync(
val vaultUnlockResult = vaultRepo.unlockVaultAndSyncForCurrentUser(
mutableStateFlow.value.passwordInput,
)
sendAction(VaultUnlockAction.Internal.ReceiveVaultUnlockResult(vaultUnlockResult))