Update LoginWithDeviceScreen to support Admin Approval type (#1175)

This commit is contained in:
David Perez
2024-04-01 10:34:05 -05:00
committed by Álison Fernandes
parent 1150f01666
commit b2005f01c1
22 changed files with 675 additions and 144 deletions

View File

@@ -1,9 +1,12 @@
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
@@ -12,6 +15,15 @@ import retrofit2.http.Path
*/
interface AuthenticatedAuthRequestsApi {
/**
* Notifies the server of a new admin authentication request.
*/
@POST("/auth-requests/admin-request")
suspend fun createAdminAuthRequest(
@Header("Device-Identifier") deviceIdentifier: String,
@Body body: AuthRequestRequestJson,
): Result<AuthRequestsResponseJson.AuthRequest>
/**
* Updates an authentication request.
*/

View File

@@ -85,6 +85,7 @@ object AuthNetworkModule {
fun providesNewAuthRequestService(
retrofits: Retrofits,
): NewAuthRequestService = NewAuthRequestServiceImpl(
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
unauthenticatedAuthRequestsApi = retrofits.unauthenticatedApiRetrofit.create(),
)

View File

@@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
/**
@@ -9,12 +10,14 @@ interface NewAuthRequestService {
/**
* Informs the server of a new auth request in order to notify approving devices.
*/
@Suppress("LongParameterList")
suspend fun createAuthRequest(
email: String,
publicKey: String,
deviceId: String,
accessCode: String,
fingerprint: String,
authRequestType: AuthRequestTypeJson,
): Result<AuthRequestsResponseJson.AuthRequest>
/**
@@ -23,5 +26,6 @@ interface NewAuthRequestService {
suspend fun getAuthRequestUpdate(
requestId: String,
accessCode: String,
isSso: Boolean,
): Result<AuthRequestsResponseJson.AuthRequest>
}

View File

@@ -1,14 +1,17 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthenticatedAuthRequestsApi
import com.x8bit.bitwarden.data.auth.datasource.network.api.UnauthenticatedAuthRequestsApi
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
import com.x8bit.bitwarden.data.platform.util.asFailure
/**
* The default implementation of the [NewAuthRequestService].
*/
class NewAuthRequestServiceImpl(
private val authenticatedAuthRequestsApi: AuthenticatedAuthRequestsApi,
private val unauthenticatedAuthRequestsApi: UnauthenticatedAuthRequestsApi,
) : NewAuthRequestService {
override suspend fun createAuthRequest(
@@ -17,25 +20,54 @@ class NewAuthRequestServiceImpl(
deviceId: String,
accessCode: String,
fingerprint: String,
authRequestType: AuthRequestTypeJson,
): Result<AuthRequestsResponseJson.AuthRequest> =
unauthenticatedAuthRequestsApi.createAuthRequest(
deviceIdentifier = deviceId,
body = AuthRequestRequestJson(
email = email,
publicKey = publicKey,
deviceId = deviceId,
accessCode = accessCode,
fingerprint = fingerprint,
type = AuthRequestTypeJson.LOGIN_WITH_DEVICE,
),
)
when (authRequestType) {
AuthRequestTypeJson.LOGIN_WITH_DEVICE -> {
unauthenticatedAuthRequestsApi.createAuthRequest(
deviceIdentifier = deviceId,
body = AuthRequestRequestJson(
email = email,
publicKey = publicKey,
deviceId = deviceId,
accessCode = accessCode,
fingerprint = fingerprint,
type = authRequestType,
),
)
}
AuthRequestTypeJson.UNLOCK -> {
UnsupportedOperationException("Unlock AuthRequestType is currently unsupported")
.asFailure()
}
AuthRequestTypeJson.ADMIN_APPROVAL -> {
authenticatedAuthRequestsApi.createAdminAuthRequest(
deviceIdentifier = deviceId,
body = AuthRequestRequestJson(
email = email,
publicKey = publicKey,
deviceId = deviceId,
accessCode = accessCode,
fingerprint = fingerprint,
type = authRequestType,
),
)
}
}
override suspend fun getAuthRequestUpdate(
requestId: String,
accessCode: String,
isSso: Boolean,
): Result<AuthRequestsResponseJson.AuthRequest> =
unauthenticatedAuthRequestsApi.getAuthRequestUpdate(
requestId = requestId,
accessCode = accessCode,
)
if (isSso) {
authenticatedAuthRequestsApi.getAuthRequest(requestId)
} else {
unauthenticatedAuthRequestsApi.getAuthRequestUpdate(
requestId = requestId,
accessCode = accessCode,
)
}
}

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.manager
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequest
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestResult
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestType
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestUpdatesResult
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestsResult
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestsUpdatesResult
@@ -15,7 +16,10 @@ interface AuthRequestManager {
/**
* Creates a new authentication request and then continues to emit updates over time.
*/
fun createAuthRequestWithUpdates(email: String): Flow<CreateAuthRequestResult>
fun createAuthRequestWithUpdates(
email: String,
authRequestType: AuthRequestType,
): Flow<CreateAuthRequestResult>
/**
* Get an auth request by its [fingerprint] and emits updates for that request.

View File

@@ -2,15 +2,19 @@ package com.x8bit.bitwarden.data.auth.manager
import com.bitwarden.core.AuthRequestResponse
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsService
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequest
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestResult
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestType
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestUpdatesResult
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestsResult
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestsUpdatesResult
import com.x8bit.bitwarden.data.auth.manager.model.CreateAuthRequestResult
import com.x8bit.bitwarden.data.auth.manager.util.isSso
import com.x8bit.bitwarden.data.auth.manager.util.toAuthRequestTypeJson
import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.platform.util.flatMap
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@@ -57,11 +61,17 @@ class AuthRequestManagerImpl(
@Suppress("LongMethod")
override fun createAuthRequestWithUpdates(
email: String,
authRequestType: AuthRequestType,
): Flow<CreateAuthRequestResult> = flow {
val initialResult = createNewAuthRequest(email).getOrNull() ?: run {
emit(CreateAuthRequestResult.Error)
return@flow
}
val initialResult = createNewAuthRequest(
email = email,
authRequestType = authRequestType.toAuthRequestTypeJson(),
)
.getOrNull()
?: run {
emit(CreateAuthRequestResult.Error)
return@flow
}
val authRequestResponse = initialResult.authRequestResponse
var authRequest = initialResult.authRequest
emit(CreateAuthRequestResult.Update(authRequest))
@@ -73,6 +83,7 @@ class AuthRequestManagerImpl(
.getAuthRequestUpdate(
requestId = authRequest.id,
accessCode = authRequestResponse.accessCode,
isSso = authRequestType.isSso,
)
.map { request ->
AuthRequest(
@@ -310,6 +321,7 @@ class AuthRequestManagerImpl(
*/
private suspend fun createNewAuthRequest(
email: String,
authRequestType: AuthRequestTypeJson,
): Result<NewAuthRequestData> =
authSdkSource
.getNewAuthRequest(email)
@@ -321,6 +333,7 @@ class AuthRequestManagerImpl(
deviceId = authDiskSource.uniqueAppId,
accessCode = authRequestResponse.accessCode,
fingerprint = authRequestResponse.fingerprint,
authRequestType = authRequestType,
)
.map { request ->
AuthRequest(

View File

@@ -0,0 +1,10 @@
package com.x8bit.bitwarden.data.auth.manager.model
/**
* Represents the type of request to be made when making auth requests.
*/
enum class AuthRequestType {
OTHER_DEVICE,
SSO_OTHER_DEVICE,
SSO_ADMIN_APPROVAL,
}

View File

@@ -0,0 +1,27 @@
package com.x8bit.bitwarden.data.auth.manager.util
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestType
/**
* Indicates if the given [AuthRequestType] uses SSO authentication.
*/
val AuthRequestType.isSso: Boolean
get() = when (this) {
AuthRequestType.OTHER_DEVICE -> false
AuthRequestType.SSO_OTHER_DEVICE,
AuthRequestType.SSO_ADMIN_APPROVAL,
-> true
}
/**
* Converts the [AuthRequestType] to the appropriate [AuthRequestTypeJson].
*/
fun AuthRequestType.toAuthRequestTypeJson(): AuthRequestTypeJson =
when (this) {
AuthRequestType.OTHER_DEVICE,
AuthRequestType.SSO_OTHER_DEVICE,
-> AuthRequestTypeJson.LOGIN_WITH_DEVICE
AuthRequestType.SSO_ADMIN_APPROVAL -> AuthRequestTypeJson.ADMIN_APPROVAL
}

View File

@@ -17,6 +17,7 @@ import com.x8bit.bitwarden.ui.auth.feature.landing.landingDestination
import com.x8bit.bitwarden.ui.auth.feature.login.loginDestination
import com.x8bit.bitwarden.ui.auth.feature.login.navigateToLogin
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.masterpasswordhint.masterPasswordHintDestination
import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.navigateToMasterPasswordHint
@@ -87,6 +88,7 @@ fun NavGraphBuilder.authGraph(navController: NavHostController) {
onNavigateToLoginWithDevice = { emailAddress ->
navController.navigateToLoginWithDevice(
emailAddress = emailAddress,
loginType = LoginWithDeviceType.OTHER_DEVICE,
)
},
onNavigateToTwoFactorLogin = { emailAddress, password ->

View File

@@ -4,20 +4,29 @@ 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.auth.feature.loginwithdevice.model.LoginWithDeviceType
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val EMAIL_ADDRESS: String = "email_address"
private const val LOGIN_WITH_DEVICE_PREFIX = "login_with_device"
private const val LOGIN_WITH_DEVICE_ROUTE = "$LOGIN_WITH_DEVICE_PREFIX/{$EMAIL_ADDRESS}"
private const val LOGIN_TYPE: String = "login_type"
private const val LOGIN_WITH_DEVICE_ROUTE =
"$LOGIN_WITH_DEVICE_PREFIX/{$EMAIL_ADDRESS}/{$LOGIN_TYPE}"
/**
* Class to retrieve login with device arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class LoginWithDeviceArgs(val emailAddress: String) {
data class LoginWithDeviceArgs(
val emailAddress: String,
val loginType: LoginWithDeviceType,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
checkNotNull(savedStateHandle[EMAIL_ADDRESS]) as String,
emailAddress = checkNotNull(savedStateHandle.get<String>(EMAIL_ADDRESS)),
loginType = checkNotNull(savedStateHandle.get<LoginWithDeviceType>(LOGIN_TYPE)),
)
}
@@ -26,9 +35,13 @@ data class LoginWithDeviceArgs(val emailAddress: String) {
*/
fun NavController.navigateToLoginWithDevice(
emailAddress: String,
loginType: LoginWithDeviceType,
navOptions: NavOptions? = null,
) {
this.navigate("$LOGIN_WITH_DEVICE_PREFIX/$emailAddress", navOptions)
this.navigate(
route = "$LOGIN_WITH_DEVICE_PREFIX/$emailAddress/$loginType",
navOptions = navOptions,
)
}
/**
@@ -40,6 +53,10 @@ fun NavGraphBuilder.loginWithDeviceDestination(
) {
composableWithSlideTransitions(
route = LOGIN_WITH_DEVICE_ROUTE,
arguments = listOf(
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
navArgument(LOGIN_TYPE) { type = NavType.EnumType(LoginWithDeviceType::class.java) },
),
) {
LoginWithDeviceScreen(
onNavigateBack = onNavigateBack,

View File

@@ -96,7 +96,7 @@ fun LoginWithDeviceScreen(
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.log_in_with_device),
title = state.toolbarTitle(),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_close),
navigationIconContentDescription = stringResource(id = R.string.close),
@@ -144,7 +144,7 @@ private fun LoginWithDeviceScreenContent(
.verticalScroll(rememberScrollState()),
) {
Text(
text = stringResource(id = R.string.log_in_initiated),
text = state.title(),
textAlign = TextAlign.Start,
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -156,7 +156,7 @@ private fun LoginWithDeviceScreenContent(
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(id = R.string.a_notification_has_been_sent_to_your_device),
text = state.subtitle(),
textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -167,9 +167,8 @@ private fun LoginWithDeviceScreenContent(
Spacer(modifier = Modifier.height(16.dp))
@Suppress("MaxLineLength")
Text(
text = stringResource(id = R.string.please_make_sure_your_vault_is_unlocked_and_the_fingerprint_phrase_matches_on_the_other_device),
text = state.description(),
textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -204,33 +203,35 @@ private fun LoginWithDeviceScreenContent(
.fillMaxWidth(),
)
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.defaultMinSize(minHeight = 40.dp)
.align(Alignment.Start),
) {
if (state.isResendNotificationLoading) {
CircularProgressIndicator(
modifier = Modifier
.padding(horizontal = 64.dp)
.size(size = 16.dp),
)
} else {
BitwardenClickableText(
modifier = Modifier.semantics { testTag = "ResendNotificationButton" },
label = stringResource(id = R.string.resend_notification),
style = MaterialTheme.typography.labelLarge,
innerPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
onClick = onResendNotificationClick,
)
if (state.allowsResend) {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.defaultMinSize(minHeight = 40.dp)
.align(Alignment.Start),
) {
if (state.isResendNotificationLoading) {
CircularProgressIndicator(
modifier = Modifier
.padding(horizontal = 64.dp)
.size(size = 16.dp),
)
} else {
BitwardenClickableText(
modifier = Modifier.semantics { testTag = "ResendNotificationButton" },
label = stringResource(id = R.string.resend_notification),
style = MaterialTheme.typography.labelLarge,
innerPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
onClick = onResendNotificationClick,
)
}
}
}
Spacer(modifier = Modifier.height(28.dp))
Text(
text = stringResource(id = R.string.need_another_option),
text = state.otherOptions(),
textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,

View File

@@ -10,6 +10,8 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.model.LoginWithDeviceType
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.util.toAuthRequestType
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
@@ -35,12 +37,16 @@ class LoginWithDeviceViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : BaseViewModel<LoginWithDeviceState, LoginWithDeviceEvent, LoginWithDeviceAction>(
initialState = savedStateHandle[KEY_STATE]
?: LoginWithDeviceState(
emailAddress = LoginWithDeviceArgs(savedStateHandle).emailAddress,
viewState = LoginWithDeviceState.ViewState.Loading,
dialogState = null,
loginData = null,
),
?: run {
val args = LoginWithDeviceArgs(savedStateHandle)
LoginWithDeviceState(
loginWithDeviceType = args.loginType,
emailAddress = args.emailAddress,
viewState = LoginWithDeviceState.ViewState.Loading,
dialogState = null,
loginData = null,
)
},
) {
private var authJob: Job = Job().apply { complete() }
@@ -104,6 +110,7 @@ class LoginWithDeviceViewModel @Inject constructor(
mutableStateFlow.update {
it.copy(
viewState = LoginWithDeviceState.ViewState.Content(
loginWithDeviceType = it.loginWithDeviceType,
fingerprintPhrase = "",
isResendNotificationLoading = false,
),
@@ -125,6 +132,7 @@ class LoginWithDeviceViewModel @Inject constructor(
mutableStateFlow.update {
it.copy(
viewState = LoginWithDeviceState.ViewState.Content(
loginWithDeviceType = it.loginWithDeviceType,
fingerprintPhrase = result.authRequest.fingerprint,
isResendNotificationLoading = false,
),
@@ -137,6 +145,7 @@ class LoginWithDeviceViewModel @Inject constructor(
mutableStateFlow.update {
it.copy(
viewState = LoginWithDeviceState.ViewState.Content(
loginWithDeviceType = it.loginWithDeviceType,
fingerprintPhrase = "",
isResendNotificationLoading = false,
),
@@ -152,6 +161,7 @@ class LoginWithDeviceViewModel @Inject constructor(
mutableStateFlow.update {
it.copy(
viewState = LoginWithDeviceState.ViewState.Content(
loginWithDeviceType = it.loginWithDeviceType,
fingerprintPhrase = "",
isResendNotificationLoading = false,
),
@@ -167,6 +177,7 @@ class LoginWithDeviceViewModel @Inject constructor(
mutableStateFlow.update {
it.copy(
viewState = LoginWithDeviceState.ViewState.Content(
loginWithDeviceType = it.loginWithDeviceType,
fingerprintPhrase = "",
isResendNotificationLoading = false,
),
@@ -256,16 +267,26 @@ class LoginWithDeviceViewModel @Inject constructor(
)
}
viewModelScope.launch {
val result = authRepository.login(
email = state.emailAddress,
requestId = loginData.requestId,
accessCode = loginData.accessCode,
asymmetricalKey = loginData.asymmetricalKey,
requestPrivateKey = loginData.privateKey,
masterPasswordHash = loginData.masterPasswordHash,
captchaToken = loginData.captchaToken,
)
sendAction(LoginWithDeviceAction.Internal.ReceiveLoginResult(result))
when (state.loginWithDeviceType) {
LoginWithDeviceType.OTHER_DEVICE -> {
val result = authRepository.login(
email = state.emailAddress,
requestId = loginData.requestId,
accessCode = loginData.accessCode,
asymmetricalKey = loginData.asymmetricalKey,
requestPrivateKey = loginData.privateKey,
masterPasswordHash = loginData.masterPasswordHash,
captchaToken = loginData.captchaToken,
)
sendAction(LoginWithDeviceAction.Internal.ReceiveLoginResult(result))
}
LoginWithDeviceType.SSO_ADMIN_APPROVAL,
LoginWithDeviceType.SSO_OTHER_DEVICE,
-> {
sendEvent(LoginWithDeviceEvent.ShowToast("Not yet implemented!"))
}
}
}
}
@@ -273,7 +294,10 @@ class LoginWithDeviceViewModel @Inject constructor(
setIsResendNotificationLoading(isResend)
authJob.cancel()
authJob = authRepository
.createAuthRequestWithUpdates(email = state.emailAddress)
.createAuthRequestWithUpdates(
email = state.emailAddress,
authRequestType = state.loginWithDeviceType.toAuthRequestType(),
)
.map { LoginWithDeviceAction.Internal.NewAuthRequestResultReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
@@ -301,11 +325,25 @@ class LoginWithDeviceViewModel @Inject constructor(
*/
@Parcelize
data class LoginWithDeviceState(
val loginWithDeviceType: LoginWithDeviceType,
val emailAddress: String,
val viewState: ViewState,
val dialogState: DialogState?,
val loginData: LoginData?,
) : Parcelable {
/**
* The toolbar text for the UI.
*/
val toolbarTitle: Text
get() = when (loginWithDeviceType) {
LoginWithDeviceType.OTHER_DEVICE,
LoginWithDeviceType.SSO_OTHER_DEVICE,
-> R.string.log_in_with_device.asText()
LoginWithDeviceType.SSO_ADMIN_APPROVAL -> R.string.log_in_initiated.asText()
}
/**
* Represents the specific view states for the [LoginWithDeviceScreen].
*/
@@ -322,12 +360,74 @@ data class LoginWithDeviceState(
* Content state for the [LoginWithDeviceScreen] showing the actual content or items.
*
* @property fingerprintPhrase The fingerprint phrase to present to the user.
* @property isResendNotificationLoading Indicates if the resend loading spinner should be
* displayed.
*/
@Parcelize
data class Content(
val fingerprintPhrase: String,
val isResendNotificationLoading: Boolean,
) : ViewState()
private val loginWithDeviceType: LoginWithDeviceType,
) : ViewState() {
/**
* The title text for the UI.
*/
val title: Text
get() = when (loginWithDeviceType) {
LoginWithDeviceType.OTHER_DEVICE,
LoginWithDeviceType.SSO_OTHER_DEVICE,
-> R.string.log_in_initiated.asText()
LoginWithDeviceType.SSO_ADMIN_APPROVAL,
-> R.string.admin_approval_requested.asText()
}
/**
* The subtitle text for the UI.
*/
val subtitle: Text
get() = when (loginWithDeviceType) {
LoginWithDeviceType.OTHER_DEVICE,
LoginWithDeviceType.SSO_OTHER_DEVICE,
-> R.string.a_notification_has_been_sent_to_your_device.asText()
LoginWithDeviceType.SSO_ADMIN_APPROVAL,
-> R.string.your_request_has_been_sent_to_your_admin.asText()
}
/**
* The description text for the UI.
*/
@Suppress("MaxLineLength")
val description: Text
get() = when (loginWithDeviceType) {
LoginWithDeviceType.OTHER_DEVICE,
LoginWithDeviceType.SSO_OTHER_DEVICE,
-> R.string.please_make_sure_your_vault_is_unlocked_and_the_fingerprint_phrase_matches_on_the_other_device.asText()
LoginWithDeviceType.SSO_ADMIN_APPROVAL,
-> R.string.you_will_be_notified_once_approved.asText()
}
/**
* The text to display indicating that there are other option for logging in.
*/
@Suppress("MaxLineLength")
val otherOptions: Text
get() = when (loginWithDeviceType) {
LoginWithDeviceType.OTHER_DEVICE,
LoginWithDeviceType.SSO_OTHER_DEVICE,
-> R.string.log_in_with_device_must_be_set_up_in_the_settings_of_the_bitwarden_app_need_another_option.asText()
LoginWithDeviceType.SSO_ADMIN_APPROVAL -> R.string.trouble_logging_in.asText()
}
/**
* Indicates if the resend button should be available.
*/
val allowsResend: Boolean
get() = loginWithDeviceType != LoginWithDeviceType.SSO_ADMIN_APPROVAL
}
}
/**

View File

@@ -0,0 +1,12 @@
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.model
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.LoginWithDeviceScreen
/**
* Represents the different ways you may want to display the [LoginWithDeviceScreen].
*/
enum class LoginWithDeviceType {
OTHER_DEVICE,
SSO_ADMIN_APPROVAL,
SSO_OTHER_DEVICE,
}

View File

@@ -0,0 +1,14 @@
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.util
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestType
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.model.LoginWithDeviceType
/**
* Converts the [LoginWithDeviceType] to an appropriate [AuthRequestType].
*/
fun LoginWithDeviceType.toAuthRequestType(): AuthRequestType =
when (this) {
LoginWithDeviceType.OTHER_DEVICE -> AuthRequestType.OTHER_DEVICE
LoginWithDeviceType.SSO_ADMIN_APPROVAL -> AuthRequestType.SSO_ADMIN_APPROVAL
LoginWithDeviceType.SSO_OTHER_DEVICE -> AuthRequestType.SSO_OTHER_DEVICE
}