mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
[PM-13396] Show error when logging into an unofficial Bitwarden server (#4088)
This commit is contained in:
parent
a9b6f296d8
commit
5faa30e2f2
14 changed files with 396 additions and 36 deletions
|
@ -94,6 +94,7 @@ import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
|
|||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
||||
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
|
@ -153,6 +154,7 @@ class AuthRepositoryImpl(
|
|||
private val authSdkSource: AuthSdkSource,
|
||||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val configDiskSource: ConfigDiskSource,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
|
@ -1472,7 +1474,12 @@ class AuthRepositoryImpl(
|
|||
captchaToken = captchaToken,
|
||||
)
|
||||
.fold(
|
||||
onFailure = { LoginResult.Error(errorMessage = null) },
|
||||
onFailure = {
|
||||
when (configDiskSource.serverConfig?.isOfficialBitwardenServer) {
|
||||
false -> LoginResult.UnofficialServerError
|
||||
else -> LoginResult.Error(errorMessage = null)
|
||||
}
|
||||
},
|
||||
onSuccess = { loginResponse ->
|
||||
when (loginResponse) {
|
||||
is GetTokenResponseJson.CaptchaRequired -> LoginResult.CaptchaRequired(
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.auth.manager.TrustedDeviceManager
|
|||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
|
@ -45,6 +46,7 @@ object AuthRepositoryModule {
|
|||
authSdkSource: AuthSdkSource,
|
||||
vaultSdkSource: VaultSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
configDiskSource: ConfigDiskSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
settingsRepository: SettingsRepository,
|
||||
|
@ -64,6 +66,7 @@ object AuthRepositoryModule {
|
|||
authSdkSource = authSdkSource,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = authDiskSource,
|
||||
configDiskSource = configDiskSource,
|
||||
haveIBeenPwnedService = haveIBeenPwnedService,
|
||||
dispatcherManager = dispatcherManager,
|
||||
environmentRepository = environmentRepository,
|
||||
|
|
|
@ -23,4 +23,9 @@ sealed class LoginResult {
|
|||
* There was an error logging in.
|
||||
*/
|
||||
data class Error(val errorMessage: String?) : LoginResult()
|
||||
|
||||
/**
|
||||
* There was an error while logging into an unofficial Bitwarden server.
|
||||
*/
|
||||
data object UnofficialServerError : LoginResult()
|
||||
}
|
||||
|
|
|
@ -19,4 +19,10 @@ data class ServerConfig(
|
|||
|
||||
@SerialName("serverData")
|
||||
val serverData: ConfigResponseJson,
|
||||
)
|
||||
) {
|
||||
/**
|
||||
* Whether the server is an official Bitwarden server or not.
|
||||
*/
|
||||
val isOfficialBitwardenServer: Boolean
|
||||
get() = serverData.server == null
|
||||
}
|
||||
|
|
|
@ -138,6 +138,7 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
|||
prevalidateSso()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun handleOnLoginResult(action: EnterpriseSignOnAction.Internal.OnLoginResult) {
|
||||
when (val loginResult = action.loginResult) {
|
||||
is LoginResult.CaptchaRequired -> {
|
||||
|
@ -160,6 +161,17 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
is LoginResult.UnofficialServerError -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = EnterpriseSignOnState.DialogState.Error(
|
||||
message = R.string.this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server
|
||||
.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is LoginResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
authRepository.rememberedOrgIdentifier = state.orgIdentifierInput
|
||||
|
|
|
@ -143,6 +143,7 @@ class LoginViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun handleReceiveLoginResult(action: LoginAction.Internal.ReceiveLoginResult) {
|
||||
when (val loginResult = action.loginResult) {
|
||||
is LoginResult.CaptchaRequired -> {
|
||||
|
@ -176,6 +177,17 @@ class LoginViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
LoginResult.UnofficialServerError -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = LoginState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is LoginResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
}
|
||||
|
|
|
@ -225,6 +225,7 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun handleReceiveLoginResult(
|
||||
action: LoginWithDeviceAction.Internal.ReceiveLoginResult,
|
||||
) {
|
||||
|
@ -261,6 +262,18 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
is LoginResult.UnofficialServerError -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = LoginWithDeviceState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server
|
||||
.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is LoginResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
}
|
||||
|
|
|
@ -278,6 +278,7 @@ class TwoFactorLoginViewModel @Inject constructor(
|
|||
/**
|
||||
* Handle the login result.
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
private fun handleReceiveLoginResult(action: TwoFactorLoginAction.Internal.ReceiveLoginResult) {
|
||||
// Dismiss the loading overlay.
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
|
@ -308,6 +309,18 @@ class TwoFactorLoginViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
is LoginResult.UnofficialServerError -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = TwoFactorLoginState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server
|
||||
.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// NO-OP: Let the auth flow handle navigation after this.
|
||||
is LoginResult.Success -> Unit
|
||||
}
|
||||
|
|
|
@ -1054,8 +1054,9 @@ Do you want to switch to this account?</string>
|
|||
<string name="then_done_highlight">then Done</string>
|
||||
<string name="for_your_security_be_sure_to_delete_your_saved_password_file">For your security, be sure to delete your saved password file.</string>
|
||||
<string name="delete_your_saved_password_file">delete your saved password file.</string>
|
||||
<string name="need_help_checkout_out_import_help">Need help? Checkout out import help.</string>
|
||||
<string name="need_help_checkout_out_import_help">Need help? Checkout out import help.</string>
|
||||
<string name="import_help_highlight">import help</string>
|
||||
<string name="save_the_exported_file_somewhere_on_your_computer_you_can_find_easily">Save the exported file somewhere on your computer you can find easily.</string>
|
||||
<string name="save_the_exported_file_highlight">Save the exported file</string>
|
||||
<string name="this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server">This is not a recognized Bitwarden server. You may need to check with your provider or update your server.</string>
|
||||
</resources>
|
||||
|
|
|
@ -98,6 +98,9 @@ import com.x8bit.bitwarden.data.auth.repository.util.toUserState
|
|||
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeConfigDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
|
@ -208,6 +211,7 @@ class AuthRepositoryTest {
|
|||
)
|
||||
.asSuccess()
|
||||
}
|
||||
private val configDiskSource = FakeConfigDiskSource()
|
||||
private val vaultSdkSource = mockk<VaultSdkSource> {
|
||||
coEvery {
|
||||
getAuthRequestKey(
|
||||
|
@ -249,6 +253,7 @@ class AuthRepositoryTest {
|
|||
authSdkSource = authSdkSource,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = fakeAuthDiskSource,
|
||||
configDiskSource = configDiskSource,
|
||||
environmentRepository = fakeEnvironmentRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
vaultRepository = vaultRepository,
|
||||
|
@ -1430,38 +1435,65 @@ class AuthRepositoryTest {
|
|||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `login get token fails should return Error with no message`() = runTest {
|
||||
coEvery {
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
} returns RuntimeException().asFailure()
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Error(errorMessage = null), result)
|
||||
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
fun `login get token fails should return Error with no message when server is an official Bitwarden server`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
} returns RuntimeException().asFailure()
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Error(errorMessage = null), result)
|
||||
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `login get token fails should return UnofficialServerError when server is an unofficial Bitwarden server`() =
|
||||
runTest {
|
||||
configDiskSource.serverConfig = SERVER_CONFIG_UNOFFICIAL
|
||||
coEvery {
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
} returns RuntimeException().asFailure()
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.UnofficialServerError, result)
|
||||
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login get token returns Invalid should return Error with correct message`() = runTest {
|
||||
|
@ -6513,7 +6545,36 @@ class AuthRepositoryTest {
|
|||
)
|
||||
|
||||
private val FIRST_TIME_STATE = UserState.FirstTimeState(
|
||||
showImportLoginsCard = true,
|
||||
)
|
||||
showImportLoginsCard = true,
|
||||
)
|
||||
|
||||
private val SERVER_CONFIG_DEFAULT = ServerConfig(
|
||||
lastSync = 0L,
|
||||
serverData = ConfigResponseJson(
|
||||
type = "mockType",
|
||||
version = "mockVersion",
|
||||
gitHash = "mockGitHash",
|
||||
server = null,
|
||||
environment = ConfigResponseJson.EnvironmentJson(
|
||||
cloudRegion = "mockCloudRegion",
|
||||
vaultUrl = "mockVaultUrl",
|
||||
apiUrl = "mockApiUrl",
|
||||
identityUrl = "mockIdentityUrl",
|
||||
notificationsUrl = "mockNotificationsUrl",
|
||||
ssoUrl = "mockSsoUrl",
|
||||
),
|
||||
featureStates = emptyMap(),
|
||||
),
|
||||
)
|
||||
|
||||
private val SERVER_CONFIG_UNOFFICIAL = SERVER_CONFIG_DEFAULT
|
||||
.copy(
|
||||
serverData = SERVER_CONFIG_DEFAULT.serverData.copy(
|
||||
server = ConfigResponseJson.ServerJson(
|
||||
name = "mockUnofficialServerName",
|
||||
url = "mockUnofficialServerUrl",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -304,7 +304,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ssoCallbackResultFlow Success with same state with login Error should show loading dialog then show an error`() =
|
||||
fun `ssoCallbackResultFlow Success with same state with login Error should show loading dialog then show an error when server is an official Bitwarden server`() =
|
||||
runTest {
|
||||
val orgIdentifier = "Bitwarden"
|
||||
coEvery {
|
||||
|
@ -368,6 +368,72 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ssoCallbackResultFlow Success with same state with login UnofficialServerError should show loading dialog then show unofficial error when Bitwarden server is unofficial`() =
|
||||
runTest {
|
||||
val orgIdentifier = "Bitwarden"
|
||||
coEvery {
|
||||
authRepository.login(any(), any(), any(), any(), any(), any())
|
||||
} returns LoginResult.UnofficialServerError
|
||||
|
||||
val viewModel = createViewModel(
|
||||
ssoData = DEFAULT_SSO_DATA,
|
||||
)
|
||||
val ssoCallbackResult = SsoCallbackResult.Success(state = "abc", code = "lmn")
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(
|
||||
EnterpriseSignOnAction.OrgIdentifierInputChange(orgIdentifier),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
orgIdentifierInput = orgIdentifier,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
mutableSsoCallbackResultFlow.tryEmit(ssoCallbackResult)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = EnterpriseSignOnState.DialogState.Loading(
|
||||
R.string.logging_in.asText(),
|
||||
),
|
||||
orgIdentifierInput = orgIdentifier,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = EnterpriseSignOnState.DialogState.Error(
|
||||
message = R.string.this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server.asText(),
|
||||
),
|
||||
orgIdentifierInput = orgIdentifier,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
authRepository.login(
|
||||
email = "test@gmail.com",
|
||||
ssoCode = "lmn",
|
||||
ssoCodeVerifier = "def",
|
||||
ssoRedirectUri = "bitwarden://sso-callback",
|
||||
captchaToken = null,
|
||||
organizationIdentifier = orgIdentifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ssoCallbackResultFlow Success with same state with login Success should show loading dialog, hide it, and save org identifier`() =
|
||||
|
|
|
@ -280,6 +280,44 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `LoginButtonClick login returns UnofficialServerError should update errorDialogState`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
authRepository.login(
|
||||
email = EMAIL,
|
||||
password = "",
|
||||
captchaToken = null,
|
||||
)
|
||||
} returns LoginResult.UnofficialServerError
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
viewModel.trySendAction(LoginAction.LoginButtonClick)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = LoginState.DialogState.Loading(
|
||||
message = R.string.logging_in.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = LoginState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
authRepository.login(email = EMAIL, password = "", captchaToken = null)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LoginButtonClick login returns success should update loadingDialogState`() = runTest {
|
||||
coEvery {
|
||||
|
|
|
@ -362,6 +362,69 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on createAuthRequestWithUpdates Success and login UnofficialServerError should should update the state`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
authRepository.login(
|
||||
email = EMAIL,
|
||||
requestId = DEFAULT_LOGIN_DATA.requestId,
|
||||
accessCode = DEFAULT_LOGIN_DATA.accessCode,
|
||||
asymmetricalKey = DEFAULT_LOGIN_DATA.asymmetricalKey,
|
||||
requestPrivateKey = DEFAULT_LOGIN_DATA.privateKey,
|
||||
masterPasswordHash = DEFAULT_LOGIN_DATA.masterPasswordHash,
|
||||
captchaToken = null,
|
||||
)
|
||||
} returns LoginResult.UnofficialServerError
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
mutableCreateAuthRequestWithUpdatesFlow.tryEmit(
|
||||
CreateAuthRequestResult.Success(AUTH_REQUEST, AUTH_REQUEST_RESPONSE),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
||||
fingerprintPhrase = "",
|
||||
),
|
||||
loginData = DEFAULT_LOGIN_DATA,
|
||||
dialogState = LoginWithDeviceState.DialogState.Loading(
|
||||
message = R.string.logging_in.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
||||
fingerprintPhrase = "",
|
||||
),
|
||||
dialogState = LoginWithDeviceState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server.asText(),
|
||||
),
|
||||
loginData = DEFAULT_LOGIN_DATA,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
authRepository.login(
|
||||
email = EMAIL,
|
||||
requestId = AUTH_REQUEST.id,
|
||||
accessCode = AUTH_REQUEST_RESPONSE.accessCode,
|
||||
asymmetricalKey = requireNotNull(AUTH_REQUEST.key),
|
||||
requestPrivateKey = AUTH_REQUEST_RESPONSE.privateKey,
|
||||
masterPasswordHash = AUTH_REQUEST.masterPasswordHash,
|
||||
captchaToken = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on captchaTokenResultFlow missing token should should display error dialog`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
|
|
@ -646,6 +646,66 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ContinueButtonClick login returns UnofficialServerError should update dialogState`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
authRepository.login(
|
||||
email = DEFAULT_EMAIL_ADDRESS,
|
||||
password = DEFAULT_PASSWORD,
|
||||
twoFactorData = TwoFactorDataModel(
|
||||
code = "",
|
||||
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
|
||||
remember = false,
|
||||
),
|
||||
captchaToken = null,
|
||||
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
|
||||
)
|
||||
} returns LoginResult.UnofficialServerError
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
|
||||
viewModel.trySendAction(TwoFactorLoginAction.ContinueButtonClick)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = TwoFactorLoginState.DialogState.Loading(
|
||||
message = R.string.logging_in.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = TwoFactorLoginState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.this_is_not_a_recognized_bitwarden_server_you_may_need_to_check_with_your_provider_or_update_your_server.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(TwoFactorLoginAction.DialogDismiss)
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
}
|
||||
coVerify {
|
||||
authRepository.login(
|
||||
email = DEFAULT_EMAIL_ADDRESS,
|
||||
password = DEFAULT_PASSWORD,
|
||||
twoFactorData = TwoFactorDataModel(
|
||||
code = "",
|
||||
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
|
||||
remember = false,
|
||||
),
|
||||
captchaToken = null,
|
||||
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `RememberMeToggle should update the state`() {
|
||||
val viewModel = createViewModel()
|
||||
|
|
Loading…
Reference in a new issue