diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/TotpCodeManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/TotpCodeManagerImpl.kt index 617afafd89..98deb1889f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/TotpCodeManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/TotpCodeManagerImpl.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.data.vault.manager +import com.bitwarden.core.CipherRepromptType import com.bitwarden.core.CipherView import com.bitwarden.core.DateTime import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager @@ -111,6 +112,10 @@ class TotpCodeManagerImpl( id = cipherId, name = cipher.name, username = cipher.login?.username, + hasPasswordReprompt = when (cipher.reprompt) { + CipherRepromptType.PASSWORD -> true + CipherRepromptType.NONE -> false + }, ) } .onFailure { diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/model/VerificationCodeItem.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/model/VerificationCodeItem.kt index ab70db8ba4..e961d1c529 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/model/VerificationCodeItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/model/VerificationCodeItem.kt @@ -14,6 +14,7 @@ import com.bitwarden.core.LoginUriView * @property id The cipher id of the item. * @property name The name of the cipher item. * @property username The username associated with the item. + * @property hasPasswordReprompt Indicates whether this item has a master password reprompt. */ data class VerificationCodeItem( val code: String, @@ -25,4 +26,5 @@ data class VerificationCodeItem( val id: String, val name: String, val username: String?, + val hasPasswordReprompt: Boolean, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeItem.kt index f256cd9ab1..2167734f66 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeItem.kt @@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme * The verification code item displayed to the user. * * @param authCode The code for the item. + * @param hideAuthCode Indicates whether the auth / verification code should be hidden. * @param label The label for the item. * @param periodSeconds The times span where the code is valid. * @param timeLeftSeconds The seconds remaining until a new code is needed. @@ -45,6 +46,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme @Composable fun VaultVerificationCodeItem( authCode: String, + hideAuthCode: Boolean, label: String, periodSeconds: Int, timeLeftSeconds: Int, @@ -103,21 +105,23 @@ fun VaultVerificationCodeItem( periodSeconds = periodSeconds, ) - Text( - text = authCode.chunked(3).joinToString(" "), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - - IconButton( - onClick = onCopyClick, - ) { - Icon( - painter = rememberVectorPainter(id = R.drawable.ic_copy), - contentDescription = stringResource(id = R.string.copy), - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(24.dp), + if (!hideAuthCode) { + Text( + text = authCode.chunked(3).joinToString(" "), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) + + IconButton( + onClick = onCopyClick, + ) { + Icon( + painter = rememberVectorPainter(id = R.drawable.ic_copy), + contentDescription = stringResource(id = R.string.copy), + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(24.dp), + ) + } } } } @@ -132,6 +136,7 @@ private fun VerificationCodeItem_preview() { label = "Sample Label", supportingLabel = "Supporting Label", authCode = "1234567890".chunked(3).joinToString(" "), + hideAuthCode = false, timeLeftSeconds = 15, periodSeconds = 30, onCopyClick = {}, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt index 95ce83ed04..6a56f0a27e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt @@ -179,6 +179,7 @@ private fun VerificationCodeContent( timeLeftSeconds = it.timeLeftSeconds, periodSeconds = it.periodSeconds, authCode = it.authCode, + hideAuthCode = it.hideAuthCode, onCopyClick = { onCopyClick(it.authCode) }, onItemClick = { itemClick(it.id) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModel.kt index 54ecb587b8..dc2e0bb931 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModel.kt @@ -277,6 +277,7 @@ class VerificationCodeViewModel @Inject constructor( VerificationCodeDisplayItem( id = item.id, authCode = item.code, + hideAuthCode = item.hasPasswordReprompt, label = item.name, supportingLabel = item.username, periodSeconds = item.periodSeconds, @@ -381,6 +382,7 @@ data class VerificationCodeDisplayItem( val timeLeftSeconds: Int, val periodSeconds: Int, val authCode: String, + val hideAuthCode: Boolean, val startIcon: IconData = IconData.Local(R.drawable.ic_login_item), ) : Parcelable diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt index dd55c0c26f..0fb49d3600 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt @@ -41,6 +41,7 @@ fun createMockCipherView( number: Int, isDeleted: Boolean = false, cipherType: CipherType = CipherType.LOGIN, + repromptType: CipherRepromptType = CipherRepromptType.NONE, totp: String? = "mockTotp-$number", folderId: String? = "mockId-$number", clock: Clock = FIXED_CLOCK, @@ -75,7 +76,7 @@ fun createMockCipherView( }, favorite = false, passwordHistory = listOf(createMockPasswordHistoryView(number = number, clock)), - reprompt = CipherRepromptType.NONE, + reprompt = repromptType, secureNote = createMockSecureNoteView().takeIf { cipherType == CipherType.SECURE_NOTE }, edit = false, organizationUseTotp = false, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/manager/TotpCodeManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/manager/TotpCodeManagerTest.kt index 3ef92f6c3f..d38fa1fb10 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/manager/TotpCodeManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/manager/TotpCodeManagerTest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.vault.manager import app.cash.turbine.test +import com.bitwarden.core.CipherRepromptType import com.bitwarden.core.TotpResponse import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager @@ -94,16 +95,18 @@ class TotpCodeManagerTest { } @Test - fun `getTotpCodeStateFlow should have loaded item with a valid data passed in`() = runTest { - + fun `getTotpCodeStateFlow should have loaded item with valid data passed in`() = runTest { val totpResponse = TotpResponse("123456", 30u) coEvery { vaultSdkSource.generateTotp(any(), any(), any()) } returns totpResponse.asSuccess() - val cipherView = createMockCipherView(1) + val cipherView = createMockCipherView( + number = 1, + repromptType = CipherRepromptType.PASSWORD, + ) - val expected = createVerificationCodeItem() + val expected = createVerificationCodeItem().copy(hasPasswordReprompt = true) totpCodeManager.getTotpCodeStateFlow(userId, cipherView).test { assertEquals(DataState.Loaded(expected), awaitItem()) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt index 2ccec2e8c9..68c1be3b9f 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt @@ -129,6 +129,49 @@ class VerificationCodeScreenTest : BaseComposeTest() { .assertIsDisplayed() } + @Test + fun `auth code and copy button should be displayed according to state`() { + val authCode = "123 456" + mutableStateFlow.update { + DEFAULT_STATE.copy( + viewState = VerificationCodeState.ViewState.Content( + verificationCodeDisplayItems = listOf( + createDisplayItem( + number = 1, + hideAuthCode = false, + ), + ), + ), + ) + } + composeTestRule + .onNodeWithText(authCode) + .assertIsDisplayed() + composeTestRule + .onNodeWithContentDescription("Copy") + .assertIsDisplayed() + + mutableStateFlow.update { + DEFAULT_STATE.copy( + viewState = VerificationCodeState.ViewState.Content( + verificationCodeDisplayItems = listOf( + createDisplayItem( + number = 1, + hideAuthCode = true, + ), + ), + ), + ) + } + + composeTestRule + .onNodeWithText(authCode) + .assertIsNotDisplayed() + composeTestRule + .onNodeWithContentDescription("Copy") + .assertIsNotDisplayed() + } + @Test fun `Items text should be displayed according to state`() { val items = "Items" @@ -343,10 +386,14 @@ class VerificationCodeScreenTest : BaseComposeTest() { } } -private fun createDisplayItem(number: Int): VerificationCodeDisplayItem = +private fun createDisplayItem( + number: Int, + hideAuthCode: Boolean = false, +): VerificationCodeDisplayItem = VerificationCodeDisplayItem( id = number.toString(), authCode = "123456", + hideAuthCode = hideAuthCode, label = "Label $number", supportingLabel = "Supporting Label $number", periodSeconds = 30, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModelTest.kt index 7f11b5a22b..e9fcbc5906 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeViewModelTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.verificationcode import android.net.Uri import app.cash.turbine.test +import com.bitwarden.core.CipherRepromptType import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository @@ -163,7 +164,10 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { mutableAuthCodeFlow.tryEmit( value = DataState.Pending( - data = listOf(createVerificationCodeItem()), + data = listOf( + createVerificationCodeItem(number = 1), + createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true), + ), ), ) @@ -205,7 +209,10 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { mutableAuthCodeFlow.tryEmit( value = DataState.Error( - data = listOf(createVerificationCodeItem()), + data = listOf( + createVerificationCodeItem(number = 1), + createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true), + ), error = IllegalStateException(), ), ) @@ -317,7 +324,10 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { mutableAuthCodeFlow.tryEmit( value = DataState.NoNetwork( - listOf(createVerificationCodeItem()), + data = listOf( + createVerificationCodeItem(number = 1), + createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true), + ), ), ) @@ -358,7 +368,10 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { mutableAuthCodeFlow.tryEmit( value = DataState.Loaded( - listOf(createVerificationCodeItem()), + data = listOf( + createVerificationCodeItem(number = 1), + createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true), + ), ), ) @@ -461,6 +474,27 @@ class VerificationCodeViewModelTest : BaseViewModelTest() { VerificationCodeDisplayItem( id = cipherView.id.toString(), authCode = "123456", + hideAuthCode = false, + label = cipherView.name, + supportingLabel = cipherView.login?.username, + periodSeconds = 30, + timeLeftSeconds = 30, + startIcon = cipherView.login?.uris.toLoginIconData( + isIconLoadingDisabled = initialState.isIconLoadingDisabled, + baseIconUrl = initialState.baseIconUrl, + ), + ) + }, + createMockCipherView( + number = 2, + isDeleted = false, + repromptType = CipherRepromptType.PASSWORD, + ) + .let { cipherView -> + VerificationCodeDisplayItem( + id = cipherView.id.toString(), + authCode = "123456", + hideAuthCode = true, label = cipherView.name, supportingLabel = cipherView.login?.username, periodSeconds = 30, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/util/VerificationCodeDataUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/util/VerificationCodeDataUtil.kt index 7fbe72a868..566a009bc3 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/util/VerificationCodeDataUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/util/VerificationCodeDataUtil.kt @@ -3,15 +3,16 @@ package com.x8bit.bitwarden.ui.vault.feature.verificationcode.util import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginView import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem -fun createVerificationCodeItem() = +fun createVerificationCodeItem(number: Int = 1) = VerificationCodeItem( code = "123456", - totpCode = "mockTotp-1", + totpCode = "mockTotp-$number", periodSeconds = 30, - id = "mockId-1", + id = "mockId-$number", issueTime = 1698408000000, timeLeftSeconds = 30, - name = "mockName-1", + name = "mockName-$number", uriLoginViewList = createMockLoginView(1).uris, - username = "mockUsername-1", + username = "mockUsername-$number", + hasPasswordReprompt = false, )