mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 09:25:58 +03:00
BIT-2240: Lock UserState while handling a successful login (#1264)
This commit is contained in:
parent
b92b8f1bc4
commit
dad57de5c3
1 changed files with 58 additions and 27 deletions
|
@ -96,9 +96,11 @@ import kotlinx.coroutines.flow.flatMapLatest
|
|||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
|
@ -148,6 +150,15 @@ class AuthRepositoryImpl(
|
|||
*/
|
||||
private val mutableHasPendingAccountDeletionStateFlow = MutableStateFlow(false)
|
||||
|
||||
/**
|
||||
* Whenever a function needs to update multiple underlying data-points that contribute to the
|
||||
* [UserState], we update this [MutableStateFlow] and continue to show the original `UserState`
|
||||
* until the transaction is complete. This is accomplished by blocking the emissions of the
|
||||
* [userStateFlow] whenever this is set to a value above 0 (a count is used if more than one
|
||||
* process is updating data simultaneously).
|
||||
*/
|
||||
private val mutableUserStateTransactionCountStateFlow = MutableStateFlow(0)
|
||||
|
||||
/**
|
||||
* The auth information to make the identity token request will need to be
|
||||
* cached to make the request again in the case of two-factor authentication.
|
||||
|
@ -206,7 +217,11 @@ class AuthRepositoryImpl(
|
|||
authDiskSource.userOrganizationsListFlow,
|
||||
vaultRepository.vaultUnlockDataStateFlow,
|
||||
mutableHasPendingAccountAdditionStateFlow,
|
||||
mutableHasPendingAccountDeletionStateFlow,
|
||||
// Ignore the data in the merge, but trigger an update when they emit.
|
||||
merge(
|
||||
mutableHasPendingAccountDeletionStateFlow,
|
||||
mutableUserStateTransactionCountStateFlow,
|
||||
),
|
||||
) {
|
||||
userStateJson,
|
||||
userOrganizationsList,
|
||||
|
@ -214,18 +229,18 @@ class AuthRepositoryImpl(
|
|||
hasPendingAccountAddition,
|
||||
_,
|
||||
->
|
||||
userStateJson
|
||||
?.toUserState(
|
||||
vaultState = vaultState,
|
||||
userOrganizationsList = userOrganizationsList,
|
||||
hasPendingAccountAddition = hasPendingAccountAddition,
|
||||
isBiometricsEnabledProvider = ::isBiometricsEnabled,
|
||||
vaultUnlockTypeProvider = ::getVaultUnlockType,
|
||||
isLoggedInProvider = ::isUserLoggedIn,
|
||||
isDeviceTrustedProvider = ::isDeviceTrusted,
|
||||
)
|
||||
userStateJson?.toUserState(
|
||||
vaultState = vaultState,
|
||||
userOrganizationsList = userOrganizationsList,
|
||||
hasPendingAccountAddition = hasPendingAccountAddition,
|
||||
isBiometricsEnabledProvider = ::isBiometricsEnabled,
|
||||
vaultUnlockTypeProvider = ::getVaultUnlockType,
|
||||
isLoggedInProvider = ::isUserLoggedIn,
|
||||
isDeviceTrustedProvider = ::isDeviceTrusted,
|
||||
)
|
||||
}
|
||||
.filterNot { mutableHasPendingAccountDeletionStateFlow.value }
|
||||
.filterNot { mutableUserStateTransactionCountStateFlow.value > 0 }
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
|
@ -1188,12 +1203,26 @@ class AuthRepositoryImpl(
|
|||
password: String?,
|
||||
deviceData: DeviceDataModel?,
|
||||
orgIdentifier: String?,
|
||||
): LoginResult {
|
||||
): LoginResult = userStateTransaction {
|
||||
val userStateJson = loginResponse.toUserState(
|
||||
previousUserState = authDiskSource.userState,
|
||||
environmentUrlData = environmentRepository.environment.environmentUrlData,
|
||||
)
|
||||
val userId = userStateJson.activeUserId
|
||||
authDiskSource.storeAccountTokens(
|
||||
userId = userId,
|
||||
accountTokens = AccountTokensJson(
|
||||
accessToken = loginResponse.accessToken,
|
||||
refreshToken = loginResponse.refreshToken,
|
||||
),
|
||||
)
|
||||
authDiskSource.userState = userStateJson
|
||||
loginResponse.key?.let {
|
||||
// Only set the value if it's present, since we may have set it already
|
||||
// when we completed the pending admin auth request.
|
||||
authDiskSource.storeUserKey(userId = userId, userKey = it)
|
||||
}
|
||||
authDiskSource.storePrivateKey(userId = userId, privateKey = loginResponse.privateKey)
|
||||
|
||||
// If the user just authenticated with a two-factor code and selected the option to
|
||||
// remember it, then the API response will return a token that will be used in place
|
||||
|
@ -1290,24 +1319,10 @@ class AuthRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
authDiskSource.storeAccountTokens(
|
||||
userId = userId,
|
||||
accountTokens = AccountTokensJson(
|
||||
accessToken = loginResponse.accessToken,
|
||||
refreshToken = loginResponse.refreshToken,
|
||||
),
|
||||
)
|
||||
authDiskSource.userState = userStateJson
|
||||
loginResponse.key?.let {
|
||||
// Only set the value if it's present, since we may have set it already
|
||||
// when we completed the pending admin auth request.
|
||||
authDiskSource.storeUserKey(userId = userId, userKey = it)
|
||||
}
|
||||
authDiskSource.storePrivateKey(userId = userId, privateKey = loginResponse.privateKey)
|
||||
settingsRepository.setDefaultsIfNecessary(userId = userId)
|
||||
vaultRepository.syncIfNecessary()
|
||||
hasPendingAccountAddition = false
|
||||
return LoginResult.Success
|
||||
LoginResult.Success
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1404,4 +1419,20 @@ class AuthRepositoryImpl(
|
|||
}
|
||||
|
||||
//endregion LoginCommon
|
||||
|
||||
/**
|
||||
* Run the given [block] while preventing any updates to [UserState]. This is useful in cases
|
||||
* where many individual changes might occur that would normally affect the [UserState] but we
|
||||
* only want a single final emission. In the rare case that multiple threads are running
|
||||
* transactions simultaneously, there will be no [UserState] updates until the last
|
||||
* transaction completes.
|
||||
*/
|
||||
private inline fun <T> userStateTransaction(block: () -> T): T {
|
||||
mutableUserStateTransactionCountStateFlow.update { it.inc() }
|
||||
return try {
|
||||
block()
|
||||
} finally {
|
||||
mutableUserStateTransactionCountStateFlow.update { it.dec() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue