mirror of
https://github.com/bitwarden/android.git
synced 2026-06-02 02:36:58 -05:00
Add initial UI flow for TDE (#1235)
This commit is contained in:
committed by
Álison Fernandes
parent
bfbb8d47a6
commit
a6a4c40693
@@ -1137,11 +1137,13 @@ class AuthRepositoryImpl(
|
||||
|
||||
// Handle the Trusted Device Encryption flow
|
||||
loginResponse.userDecryptionOptions?.trustedDeviceUserDecryptionOptions?.let { options ->
|
||||
handleLoginCommonSuccessTrustedDeviceUserDecryptionOptions(
|
||||
trustedDeviceDecryptionOptions = options,
|
||||
userStateJson = userStateJson,
|
||||
privateKey = requireNotNull(loginResponse.privateKey),
|
||||
)
|
||||
loginResponse.privateKey?.let { privateKey ->
|
||||
handleLoginCommonSuccessTrustedDeviceUserDecryptionOptions(
|
||||
trustedDeviceDecryptionOptions = options,
|
||||
userStateJson = userStateJson,
|
||||
privateKey = privateKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any cached data after successfully logging in.
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.masterPasswordHint
|
||||
import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.navigateToMasterPasswordHint
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.navigateToSetPassword
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.setPasswordDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.trustedDeviceDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.navigateToTwoFactorLogin
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.twoFactorLoginDestination
|
||||
|
||||
@@ -113,7 +112,6 @@ fun NavGraphBuilder.authGraph(navController: NavHostController) {
|
||||
masterPasswordHintDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
)
|
||||
trustedDeviceDestination()
|
||||
twoFactorLoginDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.navigation
|
||||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.loginWithDeviceDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.model.LoginWithDeviceType
|
||||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.navigateToLoginWithDevice
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.navigateToTwoFactorLogin
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.twoFactorLoginDestination
|
||||
|
||||
const val TRUSTED_DEVICE_GRAPH_ROUTE: String = "trusted_device_graph"
|
||||
|
||||
/**
|
||||
* Add trusted device destinations to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.trustedDeviceGraph(navController: NavHostController) {
|
||||
navigation(
|
||||
startDestination = TRUSTED_DEVICE_ROUTE,
|
||||
route = TRUSTED_DEVICE_GRAPH_ROUTE,
|
||||
) {
|
||||
loginWithDeviceDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToTwoFactorLogin = {
|
||||
navController.navigateToTwoFactorLogin(
|
||||
emailAddress = it,
|
||||
password = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
trustedDeviceDestination(
|
||||
onNavigateToAdminApproval = {
|
||||
navController.navigateToLoginWithDevice(
|
||||
emailAddress = it,
|
||||
loginType = LoginWithDeviceType.SSO_ADMIN_APPROVAL,
|
||||
)
|
||||
},
|
||||
onNavigateToLoginWithOtherDevice = {
|
||||
navController.navigateToLoginWithDevice(
|
||||
emailAddress = it,
|
||||
loginType = LoginWithDeviceType.SSO_OTHER_DEVICE,
|
||||
)
|
||||
},
|
||||
)
|
||||
twoFactorLoginDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the trusted device graph.
|
||||
*/
|
||||
fun NavController.navigateToTrustedDeviceGraph(
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
navigate(TRUSTED_DEVICE_GRAPH_ROUTE, navOptions)
|
||||
}
|
||||
@@ -1,39 +1,29 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
|
||||
private const val EMAIL_ADDRESS: String = "email_address"
|
||||
private const val TRUSTED_DEVICE_PREFIX: String = "trusted_device"
|
||||
private const val TRUSTED_DEVICE_ROUTE: String = "$TRUSTED_DEVICE_PREFIX/{${EMAIL_ADDRESS}}"
|
||||
|
||||
/**
|
||||
* Class to retrieve trusted device arguments from the [SavedStateHandle].
|
||||
* The route for navigating to the [TrustedDeviceScreen].
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
data class TrustedDeviceArgs(val emailAddress: String) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
emailAddress = checkNotNull(savedStateHandle.get<String>(EMAIL_ADDRESS)),
|
||||
)
|
||||
}
|
||||
const val TRUSTED_DEVICE_ROUTE: String = "trusted_device"
|
||||
|
||||
/**
|
||||
* Add the Trusted Device Screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.trustedDeviceDestination() {
|
||||
fun NavGraphBuilder.trustedDeviceDestination(
|
||||
onNavigateToAdminApproval: (emailAddress: String) -> Unit,
|
||||
onNavigateToLoginWithOtherDevice: (emailAddress: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = TRUSTED_DEVICE_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
|
||||
),
|
||||
) {
|
||||
TrustedDeviceScreen()
|
||||
TrustedDeviceScreen(
|
||||
onNavigateToAdminApproval = onNavigateToAdminApproval,
|
||||
onNavigateToLoginWithOtherDevice = onNavigateToLoginWithOtherDevice,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +31,7 @@ fun NavGraphBuilder.trustedDeviceDestination() {
|
||||
* Navigate to the Trusted Device Screen.
|
||||
*/
|
||||
fun NavController.navigateToTrustedDevice(
|
||||
emailAddress: String,
|
||||
navOptions: NavOptions? = null,
|
||||
) {
|
||||
this.navigate("$TRUSTED_DEVICE_PREFIX/$emailAddress", navOptions)
|
||||
this.navigate(TRUSTED_DEVICE_ROUTE, navOptions)
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
*/
|
||||
@Composable
|
||||
fun TrustedDeviceScreen(
|
||||
onNavigateToAdminApproval: (emailAddress: String) -> Unit,
|
||||
onNavigateToLoginWithOtherDevice: (emailAddress: String) -> Unit,
|
||||
viewModel: TrustedDeviceViewModel = hiltViewModel(),
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
@@ -54,6 +56,14 @@ fun TrustedDeviceScreen(
|
||||
val context = LocalContext.current
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is TrustedDeviceEvent.NavigateToApproveWithAdmin -> {
|
||||
onNavigateToAdminApproval(event.email)
|
||||
}
|
||||
|
||||
is TrustedDeviceEvent.NavigateToApproveWithDevice -> {
|
||||
onNavigateToLoginWithOtherDevice(event.email)
|
||||
}
|
||||
|
||||
is TrustedDeviceEvent.ShowToast -> {
|
||||
Toast
|
||||
.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT)
|
||||
|
||||
@@ -25,14 +25,19 @@ class TrustedDeviceViewModel @Inject constructor(
|
||||
) : BaseViewModel<TrustedDeviceState, TrustedDeviceEvent, TrustedDeviceAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: run {
|
||||
val account = authRepository.userStateFlow.value?.activeAccount
|
||||
val trustedDevice = account?.trustedDevice
|
||||
if (trustedDevice == null) authRepository.logout()
|
||||
TrustedDeviceState(
|
||||
emailAddress = TrustedDeviceArgs(savedStateHandle).emailAddress,
|
||||
emailAddress = account?.email.orEmpty(),
|
||||
environmentLabel = environmentRepository.environment.label,
|
||||
isRemembered = false,
|
||||
showContinueButton = false,
|
||||
showOtherDeviceButton = false,
|
||||
showRequestAdminButton = false,
|
||||
showMasterPasswordButton = false,
|
||||
isRemembered = true,
|
||||
showContinueButton = trustedDevice
|
||||
?.let { !it.hasAdminApproval && !it.hasMasterPassword }
|
||||
?: false,
|
||||
showOtherDeviceButton = trustedDevice?.hasLoginApprovingDevice ?: false,
|
||||
showRequestAdminButton = trustedDevice?.hasAdminApproval ?: false,
|
||||
showMasterPasswordButton = trustedDevice?.hasMasterPassword ?: false,
|
||||
)
|
||||
},
|
||||
) {
|
||||
@@ -61,11 +66,13 @@ class TrustedDeviceViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleApproveWithAdminClick() {
|
||||
sendEvent(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()))
|
||||
authRepository.shouldTrustDevice = state.isRemembered
|
||||
sendEvent(TrustedDeviceEvent.NavigateToApproveWithAdmin(state.emailAddress))
|
||||
}
|
||||
|
||||
private fun handleApproveWithDeviceClick() {
|
||||
sendEvent(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()))
|
||||
authRepository.shouldTrustDevice = state.isRemembered
|
||||
sendEvent(TrustedDeviceEvent.NavigateToApproveWithDevice(state.emailAddress))
|
||||
}
|
||||
|
||||
private fun handleApproveWithPasswordClick() {
|
||||
@@ -95,6 +102,20 @@ data class TrustedDeviceState(
|
||||
* Models events for the Trusted Device screen.
|
||||
*/
|
||||
sealed class TrustedDeviceEvent {
|
||||
/**
|
||||
* Navigates to the approve with admin screen.
|
||||
*/
|
||||
data class NavigateToApproveWithAdmin(
|
||||
val email: String,
|
||||
) : TrustedDeviceEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the approve with device screen.
|
||||
*/
|
||||
data class NavigateToApproveWithDevice(
|
||||
val email: String,
|
||||
) : TrustedDeviceEvent()
|
||||
|
||||
/**
|
||||
* Displays the [message] as a toast.
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,9 @@ import com.x8bit.bitwarden.ui.auth.feature.resetpassword.navigateToResetPassword
|
||||
import com.x8bit.bitwarden.ui.auth.feature.resetpassword.resetPasswordDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.SET_PASSWORD_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.navigateToSetPassword
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.TRUSTED_DEVICE_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.navigateToTrustedDeviceGraph
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.trustedDeviceGraph
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.VAULT_UNLOCK_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.navigateToVaultUnlock
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.vaultUnlockDestination
|
||||
@@ -83,6 +86,7 @@ fun RootNavScreen(
|
||||
splashDestination()
|
||||
authGraph(navController)
|
||||
resetPasswordDestination()
|
||||
trustedDeviceGraph(navController)
|
||||
vaultUnlockDestination()
|
||||
vaultUnlockedGraph(navController)
|
||||
}
|
||||
@@ -90,8 +94,9 @@ fun RootNavScreen(
|
||||
val targetRoute = when (state) {
|
||||
RootNavState.Auth -> AUTH_GRAPH_ROUTE
|
||||
RootNavState.ResetPassword -> RESET_PASSWORD_ROUTE
|
||||
is RootNavState.SetPassword -> SET_PASSWORD_ROUTE
|
||||
RootNavState.SetPassword -> SET_PASSWORD_ROUTE
|
||||
RootNavState.Splash -> SPLASH_ROUTE
|
||||
RootNavState.TrustedDevice -> TRUSTED_DEVICE_GRAPH_ROUTE
|
||||
RootNavState.VaultLocked -> VAULT_UNLOCK_ROUTE
|
||||
is RootNavState.VaultUnlocked,
|
||||
is RootNavState.VaultUnlockedForAutofillSave,
|
||||
@@ -130,8 +135,9 @@ fun RootNavScreen(
|
||||
when (val currentState = state) {
|
||||
RootNavState.Auth -> navController.navigateToAuthGraph(rootNavOptions)
|
||||
RootNavState.ResetPassword -> navController.navigateToResetPasswordGraph(rootNavOptions)
|
||||
is RootNavState.SetPassword -> navController.navigateToSetPassword(rootNavOptions)
|
||||
RootNavState.SetPassword -> navController.navigateToSetPassword(rootNavOptions)
|
||||
RootNavState.Splash -> navController.navigateToSplash(rootNavOptions)
|
||||
RootNavState.TrustedDevice -> navController.navigateToTrustedDeviceGraph(rootNavOptions)
|
||||
RootNavState.VaultLocked -> navController.navigateToVaultUnlock(rootNavOptions)
|
||||
is RootNavState.VaultUnlocked -> navController.navigateToVaultUnlockedGraph(
|
||||
rootNavOptions,
|
||||
|
||||
@@ -54,6 +54,7 @@ class RootNavViewModel @Inject constructor(
|
||||
authRepository.updateLastActiveTime()
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun handleUserStateUpdateReceive(
|
||||
action: RootNavAction.Internal.UserStateUpdateReceive,
|
||||
) {
|
||||
@@ -64,6 +65,9 @@ class RootNavViewModel @Inject constructor(
|
||||
|
||||
userState?.activeAccount?.needsPasswordReset == true -> RootNavState.ResetPassword
|
||||
|
||||
userState?.activeAccount?.trustedDevice?.isDeviceTrusted == false &&
|
||||
!userState.activeAccount.isVaultUnlocked -> RootNavState.TrustedDevice
|
||||
|
||||
userState == null ||
|
||||
!userState.activeAccount.isLoggedIn ||
|
||||
userState.hasPendingAccountAddition -> RootNavState.Auth
|
||||
@@ -131,6 +135,12 @@ sealed class RootNavState : Parcelable {
|
||||
@Parcelize
|
||||
data object Splash : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show the trusted device destination.
|
||||
*/
|
||||
@Parcelize
|
||||
data object TrustedDevice : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show vault locked nav graph.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user