[PM-24642] Remove captcha connector code (#5677)

This commit is contained in:
aj-rosado 2025-08-12 21:56:18 +01:00 committed by GitHub
parent 29243c8f44
commit 694865c213
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 71 additions and 1569 deletions

View File

@ -133,16 +133,6 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="captcha-callback"
android:scheme="bitwarden" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="duo-callback"
android:scheme="bitwarden" />

View File

@ -3,7 +3,6 @@ package com.x8bit.bitwarden
import android.content.Intent
import com.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.getDuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.getSsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.getWebAuthResultOrNull
@ -27,7 +26,6 @@ class AuthCallbackViewModel @Inject constructor(
private fun handleIntentReceived(action: AuthCallbackAction.IntentReceive) {
val yubiKeyResult = action.intent.getYubiKeyResultOrNull()
val webAuthResult = action.intent.getWebAuthResultOrNull()
val captchaCallbackTokenResult = action.intent.getCaptchaCallbackTokenResult()
val duoCallbackTokenResult = action.intent.getDuoCallbackTokenResult()
val ssoCallbackResult = action.intent.getSsoCallbackResult()
when {
@ -35,12 +33,6 @@ class AuthCallbackViewModel @Inject constructor(
authRepository.setYubiKeyResult(yubiKeyResult = yubiKeyResult)
}
captchaCallbackTokenResult != null -> {
authRepository.setCaptchaCallbackTokenResult(
tokenResult = captchaCallbackTokenResult,
)
}
duoCallbackTokenResult != null -> {
authRepository.setDuoCallbackTokenResult(
tokenResult = duoCallbackTokenResult,

View File

@ -32,7 +32,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
@ -56,12 +55,6 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
*/
val userStateFlow: StateFlow<UserState?>
/**
* Flow of the current [CaptchaCallbackTokenResult]. Subscribers should listen to the flow
* in order to receive updates whenever [setCaptchaCallbackTokenResult] is called.
*/
val captchaTokenResultFlow: Flow<CaptchaCallbackTokenResult>
/**
* Flow of the current [DuoCallbackTokenResult]. Subscribers should listen to the flow
* in order to receive updates whenever [setDuoCallbackTokenResult] is called.
@ -186,7 +179,6 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
suspend fun login(
email: String,
password: String,
captchaToken: String?,
): LoginResult
/**
@ -201,7 +193,6 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
asymmetricalKey: String,
requestPrivateKey: String,
masterPasswordHash: String?,
captchaToken: String?,
): LoginResult
/**
@ -213,7 +204,6 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
email: String,
password: String?,
twoFactorData: TwoFactorDataModel,
captchaToken: String?,
orgIdentifier: String?,
): LoginResult
@ -226,7 +216,6 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
ssoCode: String,
ssoCodeVerifier: String,
ssoRedirectUri: String,
captchaToken: String?,
organizationIdentifier: String,
): LoginResult
@ -239,7 +228,6 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
email: String,
password: String?,
newDeviceOtp: String,
captchaToken: String?,
orgIdentifier: String?,
): LoginResult
@ -294,7 +282,6 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
masterPassword: String,
masterPasswordHint: String?,
emailVerificationToken: String? = null,
captchaToken: String?,
shouldCheckDataBreaches: Boolean,
isMasterPasswordStrong: Boolean,
): RegisterResult
@ -332,11 +319,6 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
passwordHint: String?,
): SetPasswordResult
/**
* Set the value of [captchaTokenResultFlow].
*/
fun setCaptchaCallbackTokenResult(tokenResult: CaptchaCallbackTokenResult)
/**
* Set the value of [duoTokenResultFlow].
*/

View File

@ -87,7 +87,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
import com.x8bit.bitwarden.data.auth.repository.model.toLoginErrorResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
@ -331,10 +330,6 @@ class AuthRepositoryImpl(
),
)
private val captchaTokenChannel = Channel<CaptchaCallbackTokenResult>(capacity = Int.MAX_VALUE)
override val captchaTokenResultFlow: Flow<CaptchaCallbackTokenResult> =
captchaTokenChannel.receiveAsFlow()
private val duoTokenChannel = Channel<DuoCallbackTokenResult>(capacity = Int.MAX_VALUE)
override val duoTokenResultFlow: Flow<DuoCallbackTokenResult> = duoTokenChannel.receiveAsFlow()
@ -619,7 +614,6 @@ class AuthRepositoryImpl(
override suspend fun login(
email: String,
password: String,
captchaToken: String?,
): LoginResult = identityService
.preLogin(email = email)
.flatMap {
@ -638,7 +632,6 @@ class AuthRepositoryImpl(
username = email,
password = passwordHash,
),
captchaToken = captchaToken,
)
}
.fold(
@ -658,7 +651,6 @@ class AuthRepositoryImpl(
asymmetricalKey: String,
requestPrivateKey: String,
masterPasswordHash: String?,
captchaToken: String?,
): LoginResult =
loginCommon(
email = email,
@ -673,14 +665,12 @@ class AuthRepositoryImpl(
asymmetricalKey = asymmetricalKey,
privateKey = requestPrivateKey,
),
captchaToken = captchaToken,
)
override suspend fun login(
email: String,
password: String?,
twoFactorData: TwoFactorDataModel,
captchaToken: String?,
orgIdentifier: String?,
): LoginResult = identityTokenAuthModel
?.let {
@ -689,7 +679,6 @@ class AuthRepositoryImpl(
password = password,
authModel = it,
twoFactorData = twoFactorData,
captchaToken = captchaToken ?: twoFactorResponse?.captchaToken,
deviceData = twoFactorDeviceData,
orgIdentifier = orgIdentifier,
)
@ -703,7 +692,6 @@ class AuthRepositoryImpl(
email: String,
password: String?,
newDeviceOtp: String,
captchaToken: String?,
orgIdentifier: String?,
): LoginResult = identityTokenAuthModel
?.let {
@ -712,7 +700,6 @@ class AuthRepositoryImpl(
password = password,
authModel = it,
newDeviceOtp = newDeviceOtp,
captchaToken = captchaToken ?: twoFactorResponse?.captchaToken,
deviceData = twoFactorDeviceData,
orgIdentifier = orgIdentifier,
)
@ -746,7 +733,6 @@ class AuthRepositoryImpl(
ssoCode: String,
ssoCodeVerifier: String,
ssoRedirectUri: String,
captchaToken: String?,
organizationIdentifier: String,
): LoginResult = loginCommon(
email = email,
@ -755,7 +741,6 @@ class AuthRepositoryImpl(
ssoCodeVerifier = ssoCodeVerifier,
ssoRedirectUri = ssoRedirectUri,
),
captchaToken = captchaToken,
orgIdentifier = organizationIdentifier,
)
@ -905,7 +890,6 @@ class AuthRepositoryImpl(
masterPassword: String,
masterPasswordHint: String?,
emailVerificationToken: String?,
captchaToken: String?,
shouldCheckDataBreaches: Boolean,
isMasterPasswordStrong: Boolean,
): RegisterResult {
@ -940,7 +924,6 @@ class AuthRepositoryImpl(
email = email,
masterPasswordHash = registerKeyResponse.masterPasswordHash,
masterPasswordHint = masterPasswordHint,
captchaResponse = captchaToken,
key = registerKeyResponse.encryptedUserKey,
keys = RegisterRequestJson.Keys(
publicKey = registerKeyResponse.keys.public,
@ -957,7 +940,6 @@ class AuthRepositoryImpl(
masterPasswordHash = registerKeyResponse.masterPasswordHash,
masterPasswordHint = masterPasswordHint,
emailVerificationToken = emailVerificationToken,
captchaResponse = captchaToken,
userSymmetricKey = registerKeyResponse.encryptedUserKey,
userAsymmetricKeys = RegisterFinishRequestJson.Keys(
publicKey = registerKeyResponse.keys.public,
@ -972,18 +954,9 @@ class AuthRepositoryImpl(
.fold(
onSuccess = {
when (it) {
is RegisterResponseJson.CaptchaRequired -> {
it.validationErrors.captchaKeys.firstOrNull()
?.let { key -> RegisterResult.CaptchaRequired(captchaId = key) }
?: RegisterResult.Error(
errorMessage = null,
error = MissingPropertyException("Captcha ID"),
)
}
is RegisterResponseJson.Success -> {
settingsRepository.hasUserLoggedInOrCreatedAccount = true
RegisterResult.Success(captchaToken = it.captchaBypassToken)
RegisterResult.Success
}
is RegisterResponseJson.Invalid -> {
@ -1229,10 +1202,6 @@ class AuthRepositoryImpl(
)
}
override fun setCaptchaCallbackTokenResult(tokenResult: CaptchaCallbackTokenResult) {
captchaTokenChannel.trySend(tokenResult)
}
override fun setDuoCallbackTokenResult(tokenResult: DuoCallbackTokenResult) {
duoTokenChannel.trySend(tokenResult)
}
@ -1624,7 +1593,6 @@ class AuthRepositoryImpl(
twoFactorData: TwoFactorDataModel? = null,
deviceData: DeviceDataModel? = null,
orgIdentifier: String? = null,
captchaToken: String?,
newDeviceOtp: String? = null,
): LoginResult = identityService
.getToken(
@ -1632,7 +1600,6 @@ class AuthRepositoryImpl(
email = email,
authModel = authModel,
twoFactorData = twoFactorData ?: getRememberedTwoFactorData(email),
captchaToken = captchaToken,
newDeviceOtp = newDeviceOtp,
)
.fold(
@ -1651,10 +1618,6 @@ class AuthRepositoryImpl(
},
onSuccess = { loginResponse ->
when (loginResponse) {
is GetTokenResponseJson.CaptchaRequired -> LoginResult.CaptchaRequired(
captchaId = loginResponse.captchaKey,
)
is GetTokenResponseJson.TwoFactorRequired -> handleLoginCommonTwoFactorRequired(
loginResponse = loginResponse,
email = email,

View File

@ -9,11 +9,6 @@ sealed class LoginResult {
*/
data object Success : LoginResult()
/**
* Captcha verification is required.
*/
data class CaptchaRequired(val captchaId: String) : LoginResult()
/**
* Encryption key migration is required.
*/

View File

@ -7,16 +7,8 @@ sealed class RegisterResult {
/**
* Register succeeded.
*
* @param captchaToken the captcha bypass token to bypass future captcha verifications.
*/
data class Success(val captchaToken: String?) : RegisterResult()
/**
* Captcha verification is required.
*
* @param captchaId the captcha id for performing the captcha verification.
*/
data class CaptchaRequired(val captchaId: String) : RegisterResult()
data object Success : RegisterResult()
/**
* There was an error logging in.

View File

@ -1,74 +0,0 @@
package com.x8bit.bitwarden.data.auth.repository.util
import android.content.Intent
import android.net.Uri
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import java.net.URLEncoder
import java.util.Base64
import java.util.Locale
private const val CAPTCHA_HOST: String = "captcha-callback"
private const val CALLBACK_URI = "bitwarden://$CAPTCHA_HOST"
/**
* Generates a [Uri] to display a CAPTCHA challenge for Bitwarden authentication.
*/
fun generateUriForCaptcha(captchaId: String): Uri {
val json = buildJsonObject {
put(key = "siteKey", value = captchaId)
put(key = "locale", value = Locale.getDefault().toString())
put(key = "callbackUri", value = CALLBACK_URI)
put(key = "captchaRequiredText", value = "Captcha required")
}
val base64Data = Base64
.getEncoder()
.encodeToString(
json
.toString()
.toByteArray(Charsets.UTF_8),
)
val parentParam = URLEncoder.encode(CALLBACK_URI, "UTF-8")
val url = "https://vault.bitwarden.com/captcha-mobile-connector.html" +
"?data=$base64Data&parent=$parentParam&v=1"
return Uri.parse(url)
}
/**
* Retrieves a [CaptchaCallbackTokenResult] from an Intent. There are three possible cases.
*
* - `null`: Intent is not a captcha callback, or data is null.
*
* - [CaptchaCallbackTokenResult.MissingToken]:
* Intent is the captcha callback, but its missing a token value.
*
* - [CaptchaCallbackTokenResult.Success]:
* Intent is the captcha callback, and it has a token.
*/
fun Intent.getCaptchaCallbackTokenResult(): CaptchaCallbackTokenResult? {
val localData = data
return if (
action == Intent.ACTION_VIEW && localData != null && localData.host == CAPTCHA_HOST
) {
localData.getQueryParameter("token")?.let {
CaptchaCallbackTokenResult.Success(token = it)
} ?: CaptchaCallbackTokenResult.MissingToken
} else {
null
}
}
/**
* Sealed class representing the result of captcha callback token extraction.
*/
sealed class CaptchaCallbackTokenResult {
/**
* Represents a missing token in the captcha callback.
*/
data object MissingToken : CaptchaCallbackTokenResult()
/**
* Represents a token present in the captcha callback.
*/
data class Success(val token: String) : CaptchaCallbackTokenResult()
}

View File

@ -84,10 +84,9 @@ fun NavGraphBuilder.authGraph(
onNavigateToPreventAccountLockout = {
navController.navigateToPreventAccountLockout()
},
onNavigateToLogin = { emailAddress, captchaToken ->
onNavigateToLogin = { emailAddress ->
navController.navigateToLogin(
emailAddress = emailAddress,
captchaToken = captchaToken,
navOptions = navOptions {
popUpTo(route = LandingRoute)
},
@ -110,7 +109,6 @@ fun NavGraphBuilder.authGraph(
onNavigateToLogin = { emailAddress ->
navController.navigateToLogin(
emailAddress = emailAddress,
captchaToken = null,
)
},
onNavigateToEnvironment = {

View File

@ -65,7 +65,7 @@ fun NavGraphBuilder.completeRegistrationDestination(
onNavigateBack: () -> Unit,
onNavigateToPasswordGuidance: () -> Unit,
onNavigateToPreventAccountLockout: () -> Unit,
onNavigateToLogin: (email: String, token: String?) -> Unit,
onNavigateToLogin: (email: String) -> Unit,
) {
composableWithSlideTransitions<CompleteRegistrationRoute> {
CompleteRegistrationScreen(

View File

@ -69,7 +69,7 @@ fun CompleteRegistrationScreen(
onNavigateBack: () -> Unit,
onNavigateToPasswordGuidance: () -> Unit,
onNavigateToPreventAccountLockout: () -> Unit,
onNavigateToLogin: (email: String, token: String?) -> Unit,
onNavigateToLogin: (email: String) -> Unit,
viewModel: CompleteRegistrationViewModel = hiltViewModel(),
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
@ -94,7 +94,6 @@ fun CompleteRegistrationScreen(
is CompleteRegistrationEvent.NavigateToLogin -> {
onNavigateToLogin(
event.email,
event.captchaToken,
)
}
}

View File

@ -177,13 +177,6 @@ class CompleteRegistrationViewModel @Inject constructor(
action: Internal.ReceiveRegisterResult,
) {
when (val registerAccountResult = action.registerResult) {
// TODO PM-6675: Remove captcha from RegisterResult when old flow gets removed
is RegisterResult.CaptchaRequired -> {
throw IllegalStateException(
"Captcha should not be required for the new registration flow",
)
}
is RegisterResult.Error -> {
mutableStateFlow.update {
it.copy(
@ -202,12 +195,10 @@ class CompleteRegistrationViewModel @Inject constructor(
val loginResult = authRepository.login(
email = state.userEmail,
password = state.passwordInput,
captchaToken = registerAccountResult.captchaToken,
)
sendAction(
Internal.ReceiveLoginResult(
loginResult = loginResult,
captchaToken = registerAccountResult.captchaToken,
),
)
}
@ -266,7 +257,6 @@ class CompleteRegistrationViewModel @Inject constructor(
sendEvent(
CompleteRegistrationEvent.NavigateToLogin(
email = state.userEmail,
captchaToken = action.captchaToken,
),
)
}
@ -390,7 +380,6 @@ class CompleteRegistrationViewModel @Inject constructor(
email = state.userEmail,
masterPassword = state.passwordInput,
masterPasswordHint = state.passwordHintInput.ifBlank { null },
captchaToken = null,
)
sendAction(
Internal.ReceiveRegisterResult(
@ -527,11 +516,10 @@ sealed class CompleteRegistrationEvent {
data object NavigateToMakePasswordStrong : CompleteRegistrationEvent()
/**
* Navigates to the captcha verification screen.
* Navigates to the Login screen.
*/
data class NavigateToLogin(
val email: String,
val captchaToken: String?,
) : CompleteRegistrationEvent()
}
@ -615,14 +603,11 @@ sealed class CompleteRegistrationAction {
/**
* Indicates registration was successful and will now attempt to login and unlock the vault.
* @property captchaToken The captcha token to use for login. With the login function this
* is possible to be negative.
*
* @see [AuthRepository.login]
*/
data class ReceiveLoginResult(
val loginResult: LoginResult,
val captchaToken: String?,
) : Internal()
}
}

View File

@ -64,10 +64,6 @@ fun EnterpriseSignOnScreen(
intentManager.startCustomTabsActivity(event.uri)
}
is EnterpriseSignOnEvent.NavigateToCaptcha -> {
intentManager.startCustomTabsActivity(event.uri)
}
is EnterpriseSignOnEvent.NavigateToSetPassword -> {
onNavigateToSetPassword()
}

View File

@ -14,10 +14,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.model.PrevalidateSsoResult
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.SSO_URI
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForSso
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
@ -53,7 +51,6 @@ class EnterpriseSignOnViewModel @Inject constructor(
?: EnterpriseSignOnState(
dialogState = null,
orgIdentifierInput = "",
captchaToken = null,
),
) {
@ -80,16 +77,6 @@ class EnterpriseSignOnViewModel @Inject constructor(
}
.launchIn(viewModelScope)
// Automatically attempt to login again if a captcha token is received.
authRepository
.captchaTokenResultFlow
.onEach {
sendAction(
EnterpriseSignOnAction.Internal.OnReceiveCaptchaToken(it),
)
}
.launchIn(viewModelScope)
checkOrganizationDomainSsoDetails()
}
@ -118,10 +105,6 @@ class EnterpriseSignOnViewModel @Inject constructor(
handleOnLoginResult(action)
}
is EnterpriseSignOnAction.Internal.OnReceiveCaptchaToken -> {
handleOnReceiveCaptchaToken(action)
}
is EnterpriseSignOnAction.Internal.OnVerifiedOrganizationDomainSsoDetailsReceive -> {
handleOnVerifiedOrganizationDomainSsoDetailsReceive(action)
}
@ -151,15 +134,6 @@ class EnterpriseSignOnViewModel @Inject constructor(
@Suppress("LongMethod")
private fun handleOnLoginResult(action: EnterpriseSignOnAction.Internal.OnLoginResult) {
when (val loginResult = action.loginResult) {
is LoginResult.CaptchaRequired -> {
mutableStateFlow.update { it.copy(dialogState = null) }
sendEvent(
event = EnterpriseSignOnEvent.NavigateToCaptcha(
uri = generateUriForCaptcha(captchaId = loginResult.captchaId),
),
)
}
is LoginResult.Error -> {
showError(
message = loginResult.errorMessage?.asText()
@ -304,30 +278,6 @@ class EnterpriseSignOnViewModel @Inject constructor(
attemptLogin()
}
private fun handleOnReceiveCaptchaToken(
action: EnterpriseSignOnAction.Internal.OnReceiveCaptchaToken,
) {
when (val tokenResult = action.tokenResult) {
CaptchaCallbackTokenResult.MissingToken -> {
mutableStateFlow.update {
it.copy(
dialogState = EnterpriseSignOnState.DialogState.Error(
title = BitwardenString.log_in_denied.asText(),
message = BitwardenString.captcha_failed.asText(),
),
)
}
}
is CaptchaCallbackTokenResult.Success -> {
mutableStateFlow.update {
it.copy(captchaToken = tokenResult.token)
}
attemptLogin()
}
}
}
private fun prevalidateSso() {
if (!networkConnectionManager.isNetworkConnected) {
mutableStateFlow.update {
@ -393,7 +343,6 @@ class EnterpriseSignOnViewModel @Inject constructor(
ssoCode = ssoCallbackResult.code,
ssoCodeVerifier = ssoData.codeVerifier,
ssoRedirectUri = SSO_URI,
captchaToken = state.captchaToken,
organizationIdentifier = state.orgIdentifierInput,
)
sendAction(EnterpriseSignOnAction.Internal.OnLoginResult(result))
@ -511,7 +460,6 @@ class EnterpriseSignOnViewModel @Inject constructor(
data class EnterpriseSignOnState(
val dialogState: DialogState?,
val orgIdentifierInput: String,
val captchaToken: String?,
) : Parcelable {
/**
* Represents the current state of any dialogs on the screen.
@ -560,11 +508,6 @@ sealed class EnterpriseSignOnEvent {
*/
data class NavigateToSsoLogin(val uri: Uri) : EnterpriseSignOnEvent()
/**
* Navigates to the captcha verification screen.
*/
data class NavigateToCaptcha(val uri: Uri) : EnterpriseSignOnEvent()
/**
* Navigates to the set master password screen.
*/
@ -644,11 +587,6 @@ sealed class EnterpriseSignOnAction {
val error: Throwable?,
) : Internal()
/**
* A captcha callback result has been received
*/
data class OnReceiveCaptchaToken(val tokenResult: CaptchaCallbackTokenResult) : Internal()
/**
* A result was received when requesting an [VerifiedOrganizationDomainSsoDetailsResult].
*/

View File

@ -14,13 +14,12 @@ import kotlinx.serialization.Serializable
@Serializable
data class LoginRoute(
val emailAddress: String,
val captchaToken: String?,
)
/**
* Class to retrieve login arguments from the [SavedStateHandle].
*/
data class LoginArgs(val emailAddress: String, val captchaToken: String?)
data class LoginArgs(val emailAddress: String)
/**
* Constructs a [LoginArgs] from the [SavedStateHandle] and internal route data.
@ -29,7 +28,6 @@ fun SavedStateHandle.toLoginArgs(): LoginArgs {
val route = this.toRoute<LoginRoute>()
return LoginArgs(
emailAddress = route.emailAddress,
captchaToken = route.captchaToken,
)
}
@ -38,11 +36,10 @@ fun SavedStateHandle.toLoginArgs(): LoginArgs {
*/
fun NavController.navigateToLogin(
emailAddress: String,
captchaToken: String?,
navOptions: NavOptions? = null,
) {
this.navigate(
route = LoginRoute(emailAddress = emailAddress, captchaToken = captchaToken),
route = LoginRoute(emailAddress = emailAddress),
navOptions = navOptions,
)
}

View File

@ -50,8 +50,6 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -68,7 +66,6 @@ fun LoginScreen(
onNavigateToLoginWithDevice: (emailAddress: String) -> Unit,
onNavigateToTwoFactorLogin: (String, String?, Boolean) -> Unit,
viewModel: LoginViewModel = hiltViewModel(),
intentManager: IntentManager = LocalIntentManager.current,
keyboardController: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current,
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
@ -79,10 +76,6 @@ fun LoginScreen(
onNavigateToMasterPasswordHint(event.emailAddress)
}
is LoginEvent.NavigateToCaptcha -> {
intentManager.startCustomTabsActivity(uri = event.uri)
}
is LoginEvent.NavigateToEnterpriseSignOn -> {
onNavigateToEnterpriseSignOn(event.emailAddress)
}

View File

@ -2,7 +2,6 @@
package com.x8bit.bitwarden.ui.auth.feature.login
import android.net.Uri
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
@ -15,16 +14,12 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.util.toUriOrNull
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.parcelize.IgnoredOnParcel
@ -53,7 +48,6 @@ class LoginViewModel @Inject constructor(
passwordInput = "",
environmentLabel = environmentRepository.environment.label,
dialogState = LoginState.DialogState.Loading(BitwardenString.loading.asText()),
captchaToken = args.captchaToken,
accountSummaries = authRepository
.userStateFlow
.value
@ -65,16 +59,6 @@ class LoginViewModel @Inject constructor(
) {
init {
authRepository.captchaTokenResultFlow
.onEach {
sendAction(
LoginAction.Internal.ReceiveCaptchaToken(
tokenResult = it,
),
)
}
.launchIn(viewModelScope)
viewModelScope.launch {
trySendAction(
LoginAction.Internal.ReceiveKnownDeviceResult(
@ -98,9 +82,6 @@ class LoginViewModel @Inject constructor(
LoginAction.SingleSignOnClick -> handleSingleSignOnClicked()
is LoginAction.PasswordInputChanged -> handlePasswordInputChanged(action)
is LoginAction.ErrorDialogDismiss -> handleErrorDialogDismiss()
is LoginAction.Internal.ReceiveCaptchaToken -> {
handleCaptchaTokenReceived(action.tokenResult)
}
is LoginAction.Internal.ReceiveLoginResult -> {
handleReceiveLoginResult(action = action)
@ -159,15 +140,6 @@ class LoginViewModel @Inject constructor(
@Suppress("MaxLineLength", "LongMethod")
private fun handleReceiveLoginResult(action: LoginAction.Internal.ReceiveLoginResult) {
when (val loginResult = action.loginResult) {
is LoginResult.CaptchaRequired -> {
mutableStateFlow.update { it.copy(dialogState = null) }
sendEvent(
event = LoginEvent.NavigateToCaptcha(
uri = generateUriForCaptcha(captchaId = loginResult.captchaId),
),
)
}
is LoginResult.EncryptionKeyMigrationRequired -> {
val vaultUrl =
environmentRepository
@ -256,28 +228,6 @@ class LoginViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialogState = null) }
}
private fun handleCaptchaTokenReceived(tokenResult: CaptchaCallbackTokenResult) {
when (tokenResult) {
CaptchaCallbackTokenResult.MissingToken -> {
mutableStateFlow.update {
it.copy(
dialogState = LoginState.DialogState.Error(
title = BitwardenString.log_in_denied.asText(),
message = BitwardenString.captcha_failed.asText(),
),
)
}
}
is CaptchaCallbackTokenResult.Success -> {
mutableStateFlow.update {
it.copy(captchaToken = tokenResult.token)
}
attemptLogin()
}
}
}
private fun handleCloseButtonClicked() {
sendEvent(LoginEvent.NavigateBack)
}
@ -302,7 +252,6 @@ class LoginViewModel @Inject constructor(
val result = authRepository.login(
email = state.emailAddress,
password = state.passwordInput,
captchaToken = state.captchaToken,
)
sendAction(
LoginAction.Internal.ReceiveLoginResult(
@ -344,7 +293,6 @@ data class LoginState(
// We never want this saved since the input is sensitive data.
@IgnoredOnParcel val passwordInput: String = "",
val emailAddress: String,
val captchaToken: String?,
val environmentLabel: String,
val isLoginButtonEnabled: Boolean,
val dialogState: DialogState?,
@ -391,11 +339,6 @@ sealed class LoginEvent {
val emailAddress: String,
) : LoginEvent()
/**
* Navigates to the captcha verification screen.
*/
data class NavigateToCaptcha(val uri: Uri) : LoginEvent()
/**
* Navigates to the enterprise single sign on screen.
*/
@ -496,12 +439,6 @@ sealed class LoginAction {
* Models actions that the [LoginViewModel] itself might send.
*/
sealed class Internal : LoginAction() {
/**
* Indicates a captcha callback token has been received.
*/
data class ReceiveCaptchaToken(
val tokenResult: CaptchaCallbackTokenResult,
) : Internal()
/**
* Indicates that a [KnownDeviceResult] has been received and state should be updated.

View File

@ -41,8 +41,6 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
/**
* The top level composable for the Login with Device screen.
@ -53,15 +51,11 @@ fun LoginWithDeviceScreen(
onNavigateBack: () -> Unit,
onNavigateToTwoFactorLogin: (emailAddress: String) -> Unit,
viewModel: LoginWithDeviceViewModel = hiltViewModel(),
intentManager: IntentManager = LocalIntentManager.current,
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
EventsEffect(viewModel = viewModel) { event ->
when (event) {
LoginWithDeviceEvent.NavigateBack -> onNavigateBack()
is LoginWithDeviceEvent.NavigateToCaptcha -> {
intentManager.startCustomTabsActivity(uri = event.uri)
}
is LoginWithDeviceEvent.NavigateToTwoFactorLogin -> {
onNavigateToTwoFactorLogin(event.emailAddress)

View File

@ -1,6 +1,5 @@
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
import android.net.Uri
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
@ -11,8 +10,6 @@ import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.manager.model.CreateAuthRequestResult
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.components.snackbar.BitwardenSnackbarData
@ -56,11 +53,6 @@ class LoginWithDeviceViewModel @Inject constructor(
init {
sendNewAuthRequest(isResend = false)
authRepository
.captchaTokenResultFlow
.map { LoginWithDeviceAction.Internal.ReceiveCaptchaToken(tokenResult = it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
}
override fun handleAction(action: LoginWithDeviceAction) {
@ -95,10 +87,6 @@ class LoginWithDeviceViewModel @Inject constructor(
handleNewAuthRequestResultReceived(action)
}
is LoginWithDeviceAction.Internal.ReceiveCaptchaToken -> {
handleReceiveCaptchaToken(action)
}
is LoginWithDeviceAction.Internal.ReceiveLoginResult -> {
handleReceiveLoginResult(action)
}
@ -125,7 +113,6 @@ class LoginWithDeviceViewModel @Inject constructor(
masterPasswordHash = result.authRequest.masterPasswordHash,
asymmetricalKey = requireNotNull(result.authRequest.key),
privateKey = result.privateKey,
captchaToken = null,
),
)
}
@ -183,44 +170,11 @@ class LoginWithDeviceViewModel @Inject constructor(
}
}
private fun handleReceiveCaptchaToken(
action: LoginWithDeviceAction.Internal.ReceiveCaptchaToken,
) {
when (val tokenResult = action.tokenResult) {
CaptchaCallbackTokenResult.MissingToken -> {
mutableStateFlow.update {
it.copy(
dialogState = LoginWithDeviceState.DialogState.Error(
title = BitwardenString.log_in_denied.asText(),
message = BitwardenString.captcha_failed.asText(),
),
)
}
}
is CaptchaCallbackTokenResult.Success -> {
mutableStateFlow.update {
it.copy(loginData = it.loginData?.copy(captchaToken = tokenResult.token))
}
attemptLogin()
}
}
}
@Suppress("MaxLineLength", "LongMethod")
private fun handleReceiveLoginResult(
action: LoginWithDeviceAction.Internal.ReceiveLoginResult,
) {
when (val loginResult = action.loginResult) {
is LoginResult.CaptchaRequired -> {
mutableStateFlow.update { it.copy(dialogState = null) }
sendEvent(
event = LoginWithDeviceEvent.NavigateToCaptcha(
uri = generateUriForCaptcha(captchaId = loginResult.captchaId),
),
)
}
is LoginResult.TwoFactorRequired -> {
mutableStateFlow.update { it.copy(dialogState = null) }
sendEvent(
@ -315,7 +269,6 @@ class LoginWithDeviceViewModel @Inject constructor(
asymmetricalKey = loginData.asymmetricalKey,
requestPrivateKey = loginData.privateKey,
masterPasswordHash = loginData.masterPasswordHash,
captchaToken = loginData.captchaToken,
)
}
@ -502,7 +455,6 @@ data class LoginWithDeviceState(
data class LoginData(
val accessCode: String,
val requestId: String,
val captchaToken: String?,
val masterPasswordHash: String?,
val asymmetricalKey: String,
val privateKey: String,
@ -518,11 +470,6 @@ sealed class LoginWithDeviceEvent {
*/
data object NavigateBack : LoginWithDeviceEvent()
/**
* Navigates to the captcha verification screen.
*/
data class NavigateToCaptcha(val uri: Uri) : LoginWithDeviceEvent()
/**
* Navigates to the two-factor login screen.
*/
@ -566,13 +513,6 @@ sealed class LoginWithDeviceAction {
val result: CreateAuthRequestResult,
) : Internal()
/**
* Indicates a captcha callback token has been received.
*/
data class ReceiveCaptchaToken(
val tokenResult: CaptchaCallbackTokenResult,
) : Internal()
/**
* Indicates a login result has been received.
*/

View File

@ -104,10 +104,6 @@ fun TwoFactorLoginScreen(
intentManager.launchUri(uri = event.uri)
}
is TwoFactorLoginEvent.NavigateToCaptcha -> {
intentManager.startCustomTabsActivity(uri = event.uri)
}
is TwoFactorLoginEvent.NavigateToDuo -> {
intentManager.startCustomTabsActivity(uri = event.uri)
}
@ -352,7 +348,6 @@ private fun TwoFactorLoginScreenContentPreview() {
displayEmail = "email@dot.com",
isContinueButtonEnabled = true,
isRememberEnabled = true,
captchaToken = null,
email = "",
password = "",
orgIdentifier = null,

View File

@ -20,10 +20,8 @@ import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForWebAuth
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
@ -71,7 +69,6 @@ class TwoFactorLoginViewModel @Inject constructor(
.preferredAuthMethod
.isContinueButtonEnabled,
isRememberEnabled = false,
captchaToken = null,
email = args.emailAddress,
password = args.password,
orgIdentifier = args.orgIdentifier,
@ -95,13 +92,6 @@ class TwoFactorLoginViewModel @Inject constructor(
.onEach { savedStateHandle[KEY_STATE] = it }
.launchIn(viewModelScope)
// Automatically attempt to login again if a captcha token is received.
authRepository
.captchaTokenResultFlow
.map { TwoFactorLoginAction.Internal.ReceiveCaptchaToken(tokenResult = it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
// Process the Duo result when it is received.
authRepository
.duoTokenResultFlow
@ -147,9 +137,6 @@ class TwoFactorLoginViewModel @Inject constructor(
private fun handleInternalAction(action: TwoFactorLoginAction.Internal) {
when (action) {
is TwoFactorLoginAction.Internal.ReceiveLoginResult -> handleReceiveLoginResult(action)
is TwoFactorLoginAction.Internal.ReceiveCaptchaToken -> {
handleCaptchaTokenReceived(action)
}
is TwoFactorLoginAction.Internal.ReceiveDuoResult -> {
handleReceiveDuoResult(action)
@ -173,30 +160,6 @@ class TwoFactorLoginViewModel @Inject constructor(
}
}
private fun handleCaptchaTokenReceived(
action: TwoFactorLoginAction.Internal.ReceiveCaptchaToken,
) {
when (val tokenResult = action.tokenResult) {
CaptchaCallbackTokenResult.MissingToken -> {
mutableStateFlow.update {
it.copy(
dialogState = TwoFactorLoginState.DialogState.Error(
title = BitwardenString.log_in_denied.asText(),
message = BitwardenString.captcha_failed.asText(),
),
)
}
}
is CaptchaCallbackTokenResult.Success -> {
mutableStateFlow.update {
it.copy(captchaToken = tokenResult.token)
}
initiateLogin()
}
}
}
/**
* Update the state with the new text and enable or disable the continue button.
*/
@ -298,14 +261,6 @@ class TwoFactorLoginViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialogState = null) }
when (val loginResult = action.loginResult) {
// Launch the captcha flow if necessary.
is LoginResult.CaptchaRequired -> {
sendEvent(
event = TwoFactorLoginEvent.NavigateToCaptcha(
uri = generateUriForCaptcha(captchaId = loginResult.captchaId),
),
)
}
// NO-OP: This error shouldn't be possible at this stage.
is LoginResult.TwoFactorRequired -> Unit
@ -611,7 +566,6 @@ class TwoFactorLoginViewModel @Inject constructor(
email = state.email,
password = state.password,
newDeviceOtp = code,
captchaToken = state.captchaToken,
orgIdentifier = state.orgIdentifier,
)
} else {
@ -623,7 +577,6 @@ class TwoFactorLoginViewModel @Inject constructor(
method = state.authMethod.value.toString(),
remember = state.isRememberEnabled,
),
captchaToken = state.captchaToken,
orgIdentifier = state.orgIdentifier,
)
}
@ -650,7 +603,6 @@ data class TwoFactorLoginState(
val isRememberEnabled: Boolean,
val isNewDeviceVerification: Boolean,
// Internal
val captchaToken: String?,
val email: String,
val password: String?,
val orgIdentifier: String?,
@ -711,11 +663,6 @@ sealed class TwoFactorLoginEvent {
*/
data object NavigateBack : TwoFactorLoginEvent()
/**
* Navigates to the captcha verification screen.
*/
data class NavigateToCaptcha(val uri: Uri) : TwoFactorLoginEvent()
/**
* Navigates to the Duo 2-factor authentication screen.
*/
@ -804,13 +751,6 @@ sealed class TwoFactorLoginAction {
* Models actions that the [TwoFactorLoginViewModel] itself might send.
*/
sealed class Internal : TwoFactorLoginAction() {
/**
* Indicates a captcha callback token has been received.
*/
data class ReceiveCaptchaToken(
val tokenResult: CaptchaCallbackTokenResult,
) : Internal()
/**
* Indicates that a Dup callback token has been received.
*/

View File

@ -3,11 +3,9 @@ package com.x8bit.bitwarden
import android.content.Intent
import com.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.getDuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.getSsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.getWebAuthResultOrNull
@ -26,7 +24,6 @@ import org.junit.jupiter.api.Test
class AuthCallbackViewModelTest : BaseViewModelTest() {
private val authRepository = mockk<AuthRepository> {
every { setCaptchaCallbackTokenResult(any()) } just runs
every { setSsoCallbackResult(any()) } just runs
every { setDuoCallbackTokenResult(any()) } just runs
every { setYubiKeyResult(any()) } just runs
@ -38,7 +35,6 @@ class AuthCallbackViewModelTest : BaseViewModelTest() {
mockkStatic(
Intent::getYubiKeyResultOrNull,
Intent::getWebAuthResultOrNull,
Intent::getCaptchaCallbackTokenResult,
Intent::getDuoCallbackTokenResult,
Intent::getSsoCallbackResult,
)
@ -49,35 +45,16 @@ class AuthCallbackViewModelTest : BaseViewModelTest() {
unmockkStatic(
Intent::getYubiKeyResultOrNull,
Intent::getWebAuthResultOrNull,
Intent::getCaptchaCallbackTokenResult,
Intent::getDuoCallbackTokenResult,
Intent::getSsoCallbackResult,
)
}
@Test
fun `on IntentReceive with captcha host should call setCaptchaCallbackToken`() {
val viewModel = createViewModel()
val mockIntent = mockk<Intent>()
val captchaCallbackTokenResult = CaptchaCallbackTokenResult.Success(token = "mockk_token")
every { mockIntent.getCaptchaCallbackTokenResult() } returns captchaCallbackTokenResult
every { mockIntent.getDuoCallbackTokenResult() } returns null
every { mockIntent.getYubiKeyResultOrNull() } returns null
every { mockIntent.getWebAuthResultOrNull() } returns null
every { mockIntent.getSsoCallbackResult() } returns null
viewModel.trySendAction(AuthCallbackAction.IntentReceive(intent = mockIntent))
verify(exactly = 1) {
authRepository.setCaptchaCallbackTokenResult(tokenResult = captchaCallbackTokenResult)
}
}
@Test
fun `on IntentReceive with duo host should call setDuoCallbackToken`() {
val viewModel = createViewModel()
val mockIntent = mockk<Intent>()
val duoCallbackTokenResult = DuoCallbackTokenResult.Success(token = "mockk_token")
every { mockIntent.getCaptchaCallbackTokenResult() } returns null
every { mockIntent.getDuoCallbackTokenResult() } returns duoCallbackTokenResult
every { mockIntent.getYubiKeyResultOrNull() } returns null
every { mockIntent.getWebAuthResultOrNull() } returns null
@ -100,7 +77,6 @@ class AuthCallbackViewModelTest : BaseViewModelTest() {
every { mockIntent.getSsoCallbackResult() } returns sseCallbackResult
every { mockIntent.getYubiKeyResultOrNull() } returns null
every { mockIntent.getWebAuthResultOrNull() } returns null
every { mockIntent.getCaptchaCallbackTokenResult() } returns null
every { mockIntent.getDuoCallbackTokenResult() } returns null
viewModel.trySendAction(AuthCallbackAction.IntentReceive(intent = mockIntent))
@ -116,7 +92,6 @@ class AuthCallbackViewModelTest : BaseViewModelTest() {
val yubiKeyResult = mockk<YubiKeyResult>()
every { mockIntent.getYubiKeyResultOrNull() } returns yubiKeyResult
every { mockIntent.getWebAuthResultOrNull() } returns null
every { mockIntent.getCaptchaCallbackTokenResult() } returns null
every { mockIntent.getDuoCallbackTokenResult() } returns null
every { mockIntent.getSsoCallbackResult() } returns null
@ -133,7 +108,6 @@ class AuthCallbackViewModelTest : BaseViewModelTest() {
val mockIntent = mockk<Intent> {
every { getWebAuthResultOrNull() } returns webAuthResult
every { getYubiKeyResultOrNull() } returns null
every { getCaptchaCallbackTokenResult() } returns null
every { getDuoCallbackTokenResult() } returns null
every { getSsoCallbackResult() } returns null
}

View File

@ -1,66 +0,0 @@
package com.x8bit.bitwarden.data.auth.repository.util
import android.content.Intent
import android.net.Uri
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Test
class CaptchaUtilsTest : BitwardenComposeTest() {
@Test
fun `generateIntentForCaptcha should return valid Uri`() {
val actualUri = generateUriForCaptcha(captchaId = "testCaptchaId")
val expectedUrl = "https://vault.bitwarden.com/captcha-mobile-connector.html" +
"?data=eyJzaXRlS2V5IjoidGVzdENhcHRjaGFJZCIsImxvY2FsZSI6ImVuX1VTIiwiY2Fsb" +
"GJhY2tVcmkiOiJiaXR3YXJkZW46Ly9jYXB0Y2hhLWNhbGxiYWNrIiwiY2FwdGNoYVJlcXVp" +
"cmVkVGV4dCI6IkNhcHRjaGEgcmVxdWlyZWQifQ==&parent=bitwarden%3A%2F%2F" +
"captcha-callback&v=1"
val expectedUri = Uri.parse(expectedUrl)
assertEquals(expectedUri, actualUri)
}
@Test
fun `getCaptchaCallbackToken should return null when data is null`() {
val intent = mockk<Intent> {
every { data } returns null
every { action } returns Intent.ACTION_VIEW
}
val result = intent.getCaptchaCallbackTokenResult()
assertEquals(null, result)
}
@Test
fun `getCaptchaCallbackToken should return null when action is not Intent ACTION_VIEW`() {
val intent = mockk<Intent> {
every { data } returns null
every { action } returns Intent.ACTION_ANSWER
}
val result = intent.getCaptchaCallbackTokenResult()
assertEquals(null, result)
}
@Test
fun `getCaptchaCallbackToken should return MissingToken with missing token parameter`() {
val intent = mockk<Intent> {
every { data?.getQueryParameter("token") } returns null
every { action } returns Intent.ACTION_VIEW
every { data?.host } returns "captcha-callback"
}
val result = intent.getCaptchaCallbackTokenResult()
assertEquals(CaptchaCallbackTokenResult.MissingToken, result)
}
@Test
fun `getCaptchaCallbackToken should return Success when token query parameter is present`() {
val intent = mockk<Intent> {
every { data?.getQueryParameter("token") } returns "myToken"
every { action } returns Intent.ACTION_VIEW
every { data?.host } returns "captcha-callback"
}
val result = intent.getCaptchaCallbackTokenResult()
assertEquals(CaptchaCallbackTokenResult.Success("myToken"), result)
}
}

View File

@ -61,10 +61,9 @@ class CompleteRegistrationScreenTest : BitwardenComposeTest() {
onNavigateToPreventAccountLockout = {
onNavigateToPreventAccountLockoutCalled = true
},
onNavigateToLogin = { email, captchaToken ->
onNavigateToLogin = { email ->
onNavigateToLoginCalled = true
assertTrue(email == EMAIL)
assertTrue(captchaToken == TOKEN)
},
viewModel = viewModel,
)
@ -261,7 +260,6 @@ class CompleteRegistrationScreenTest : BitwardenComposeTest() {
mutableEventFlow.tryEmit(
CompleteRegistrationEvent.NavigateToLogin(
email = EMAIL,
captchaToken = TOKEN,
),
)

View File

@ -18,7 +18,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance.RegistrationEvent
@ -63,7 +62,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
login(
email = any(),
password = any(),
captchaToken = any(),
)
} returns LoginResult.Success
@ -73,11 +71,10 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
masterPassword = any(),
masterPasswordHint = any(),
emailVerificationToken = any(),
captchaToken = any(),
shouldCheckDataBreaches = any(),
isMasterPasswordStrong = any(),
)
} returns RegisterResult.Success(captchaToken = CAPTCHA_BYPASS_TOKEN)
} returns RegisterResult.Success
coEvery {
setOnboardingStatus(OnboardingStatus.NOT_STARTED)
@ -103,7 +100,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
specialCircumstanceManager.specialCircumstance = mockCompleteRegistrationCircumstance
mockkStatic(
SavedStateHandle::toCompleteRegistrationArgs,
::generateUriForCaptcha,
)
}
@ -111,7 +107,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
fun tearDown() {
unmockkStatic(
SavedStateHandle::toCompleteRegistrationArgs,
::generateUriForCaptcha,
)
}
@ -153,11 +148,10 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
masterPassword = PASSWORD,
masterPasswordHint = null,
emailVerificationToken = TOKEN,
captchaToken = null,
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
} returns RegisterResult.Success(captchaToken = CAPTCHA_BYPASS_TOKEN)
} returns RegisterResult.Success
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
viewModel.stateEventFlow(backgroundScope) { stateFlow, eventFlow ->
assertEquals(VALID_INPUT_STATE, stateFlow.awaitItem())
@ -186,7 +180,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
masterPassword = PASSWORD,
masterPasswordHint = null,
emailVerificationToken = TOKEN,
captchaToken = null,
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
@ -220,7 +213,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
mockAuthRepository.login(
email = EMAIL,
password = PASSWORD,
captchaToken = CAPTCHA_BYPASS_TOKEN,
)
}
}
@ -246,7 +238,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
mockAuthRepository.login(
email = EMAIL,
password = PASSWORD,
captchaToken = CAPTCHA_BYPASS_TOKEN,
)
} returns LoginResult.TwoFactorRequired
@ -255,7 +246,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
viewModel.eventFlow.test {
assertTrue(awaitItem() is CompleteRegistrationEvent.ShowToast)
assertEquals(
CompleteRegistrationEvent.NavigateToLogin(EMAIL, CAPTCHA_BYPASS_TOKEN),
CompleteRegistrationEvent.NavigateToLogin(EMAIL),
awaitItem(),
)
}
@ -272,7 +263,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
masterPassword = PASSWORD,
masterPasswordHint = null,
emailVerificationToken = TOKEN,
captchaToken = null,
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
@ -285,7 +275,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
masterPassword = PASSWORD,
masterPasswordHint = null,
emailVerificationToken = TOKEN,
captchaToken = null,
shouldCheckDataBreaches = false,
isMasterPasswordStrong = true,
)
@ -302,7 +291,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
masterPassword = PASSWORD,
masterPasswordHint = null,
emailVerificationToken = TOKEN,
captchaToken = null,
shouldCheckDataBreaches = true,
isMasterPasswordStrong = true,
)
@ -341,7 +329,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
masterPassword = PASSWORD,
masterPasswordHint = null,
emailVerificationToken = TOKEN,
captchaToken = null,
shouldCheckDataBreaches = true,
isMasterPasswordStrong = false,
)
@ -374,7 +361,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
masterPassword = PASSWORD,
masterPasswordHint = null,
emailVerificationToken = TOKEN,
captchaToken = null,
shouldCheckDataBreaches = true,
isMasterPasswordStrong = false,
)
@ -681,7 +667,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
private const val PASSWORD = "longenoughtpassword"
private const val EMAIL = "test@test.com"
private const val TOKEN = "token"
private const val CAPTCHA_BYPASS_TOKEN = "captcha_bypass"
private val DEFAULT_STATE = CompleteRegistrationState(
userEmail = EMAIL,
emailVerificationToken = TOKEN,

View File

@ -111,15 +111,6 @@ class EnterpriseSignOnScreenTest : BitwardenComposeTest() {
}
}
@Test
fun `NavigateToCaptcha should call startCustomTabsActivity`() {
val captchaUri = Uri.parse("https://captcha.com")
mutableEventFlow.tryEmit(EnterpriseSignOnEvent.NavigateToCaptcha(captchaUri))
verify(exactly = 1) {
intentManager.startCustomTabsActivity(captchaUri)
}
}
@Test
fun `NavigateToSetPassword should call onNavigateToSetPassword`() {
mutableEventFlow.tryEmit(EnterpriseSignOnEvent.NavigateToSetPassword)
@ -279,7 +270,6 @@ class EnterpriseSignOnScreenTest : BitwardenComposeTest() {
private val DEFAULT_STATE = EnterpriseSignOnState(
dialogState = null,
orgIdentifierInput = "",
captchaToken = null,
)
}
}

View File

@ -14,9 +14,7 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.model.PrevalidateSsoResult
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForSso
import com.x8bit.bitwarden.data.platform.manager.model.NetworkConnection
import com.x8bit.bitwarden.data.platform.manager.util.FakeNetworkConnectionManager
@ -43,11 +41,8 @@ import org.junit.jupiter.api.Test
class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
private val mutableSsoCallbackResultFlow = bufferedMutableSharedFlow<SsoCallbackResult>()
private val mutableCaptchaTokenResultFlow =
bufferedMutableSharedFlow<CaptchaCallbackTokenResult>()
private val authRepository: AuthRepository = mockk {
every { ssoCallbackResultFlow } returns mutableSsoCallbackResultFlow
every { captchaTokenResultFlow } returns mutableCaptchaTokenResultFlow
every { rememberedOrgIdentifier } returns null
every { rememberedOrgIdentifier = "Bitwarden" } just runs
coEvery {
@ -341,7 +336,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
val orgIdentifier = "Bitwarden"
val error = Throwable("Fail!")
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.Error(errorMessage = null, error = error)
val viewModel = createViewModel(
@ -397,7 +392,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = orgIdentifier,
)
}
@ -409,7 +403,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
runTest {
val orgIdentifier = "Bitwarden"
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.NewDeviceVerification(errorMessage = "new device verification required")
val viewModel = createViewModel(
@ -464,7 +458,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = orgIdentifier,
)
}
@ -476,7 +469,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
runTest {
val orgIdentifier = "Bitwarden"
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.EncryptionKeyMigrationRequired
environmentRepository.environment = Environment.SelfHosted(
@ -539,7 +532,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = orgIdentifier,
)
}
@ -551,7 +543,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
runTest {
val orgIdentifier = "Bitwarden"
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.EncryptionKeyMigrationRequired
environmentRepository.environment = Environment.SelfHosted(
@ -614,7 +606,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = orgIdentifier,
)
}
@ -626,7 +617,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
runTest {
val orgIdentifier = "Bitwarden"
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.EncryptionKeyMigrationRequired
environmentRepository.environment = Environment.SelfHosted(
@ -689,7 +680,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = orgIdentifier,
)
}
@ -701,7 +691,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
runTest {
val orgIdentifier = "Bitwarden"
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.CertificateError
val viewModel = createViewModel(
@ -756,7 +746,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = orgIdentifier,
)
}
@ -767,8 +756,9 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
fun `ssoCallbackResultFlow Success with same state with login Success should show loading dialog, hide it, and save org identifier`() =
runTest {
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.Success
every {
authRepository.rememberedOrgIdentifier
} returns "Bitwarden"
@ -809,7 +799,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = "Bitwarden",
)
}
@ -818,71 +807,12 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `ssoCallbackResultFlow Success with same state with login CaptchaRequired should show loading dialog, hide it, and send NavigateToCaptcha event`() =
runTest {
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
} returns LoginResult.CaptchaRequired("captcha")
every {
authRepository.rememberedOrgIdentifier
} returns "Bitwarden"
val uri: Uri = mockk()
every {
generateUriForCaptcha(captchaId = "captcha")
} returns uri
val initialState = DEFAULT_STATE.copy(orgIdentifierInput = "Bitwarden")
val viewModel = createViewModel(
initialState = initialState,
ssoData = DEFAULT_SSO_DATA,
)
val ssoCallbackResult = SsoCallbackResult.Success(state = "abc", code = "lmn")
viewModel.stateEventFlow(backgroundScope) { stateFlow, eventFlow ->
assertEquals(initialState, stateFlow.awaitItem())
mutableSsoCallbackResultFlow.tryEmit(ssoCallbackResult)
assertEquals(
initialState.copy(
dialogState = EnterpriseSignOnState.DialogState.Loading(
BitwardenString.logging_in.asText(),
),
),
stateFlow.awaitItem(),
)
assertEquals(
initialState,
stateFlow.awaitItem(),
)
assertEquals(
EnterpriseSignOnEvent.NavigateToCaptcha(uri),
eventFlow.awaitItem(),
)
}
coVerify(exactly = 1) {
authRepository.login(
email = "test@gmail.com",
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = "Bitwarden",
)
}
}
@Suppress("MaxLineLength")
@Test
fun `ssoCallbackResultFlow Success with same state with login TwoFactorRequired should show loading dialog, hide it, and send NavigateToTwoFactorLogin event`() =
runTest {
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.TwoFactorRequired
every {
authRepository.rememberedOrgIdentifier
@ -925,7 +855,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = "Bitwarden",
)
}
@ -936,7 +865,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
runTest {
val orgIdentifier = "Bitwarden"
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
authRepository.login(any(), any(), any(), any(), any())
} returns LoginResult.ConfirmKeyConnectorDomain("bitwarden.com")
val viewModel = createViewModel(
@ -990,75 +919,11 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
ssoCode = "lmn",
ssoCodeVerifier = "def",
ssoRedirectUri = "bitwarden://sso-callback",
captchaToken = null,
organizationIdentifier = orgIdentifier,
)
}
}
@Test
fun `captchaTokenResultFlow MissingToken should show error dialog`() = runTest {
val viewModel = createViewModel()
viewModel.stateFlow.test {
assertEquals(DEFAULT_STATE, awaitItem())
mutableCaptchaTokenResultFlow.tryEmit(CaptchaCallbackTokenResult.MissingToken)
assertEquals(
DEFAULT_STATE.copy(
dialogState = EnterpriseSignOnState.DialogState.Error(
title = BitwardenString.log_in_denied.asText(),
message = BitwardenString.captcha_failed.asText(),
),
),
awaitItem(),
)
}
}
@Test
fun `captchaTokenResultFlow Success should update the state and attempt to login`() = runTest {
coEvery {
authRepository.login(any(), any(), any(), any(), any(), any())
} returns LoginResult.Success
every {
authRepository.rememberedOrgIdentifier
} returns "Bitwarden"
val initialState = DEFAULT_STATE.copy(orgIdentifierInput = "Bitwarden")
val viewModel = createViewModel(
initialState = initialState,
ssoData = DEFAULT_SSO_DATA,
ssoCallbackResult = SsoCallbackResult.Success(
state = "abc",
code = "lmn",
),
)
viewModel.stateFlow.test {
assertEquals(
initialState,
awaitItem(),
)
mutableCaptchaTokenResultFlow.tryEmit(CaptchaCallbackTokenResult.Success("token"))
assertEquals(
initialState.copy(
captchaToken = "token",
dialogState = EnterpriseSignOnState.DialogState.Loading(
BitwardenString.logging_in.asText(),
),
),
awaitItem(),
)
assertEquals(
initialState.copy(captchaToken = "token"),
awaitItem(),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `OrganizationDomainSsoDetails failure should make a request, hide the dialog, and update the org input based on the remembered org`() =
@ -1415,7 +1280,6 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
private val DEFAULT_STATE = EnterpriseSignOnState(
dialogState = null,
orgIdentifierInput = "",
captchaToken = null,
)
private val DEFAULT_SSO_DATA = SsoResponseData(
state = "abc",

View File

@ -1,6 +1,5 @@
package com.x8bit.bitwarden.ui.auth.feature.login
import android.net.Uri
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsDisplayed
@ -318,13 +317,6 @@ class LoginScreenTest : BitwardenComposeTest() {
assertTrue(onNavigateBackCalled)
}
@Test
fun `NavigateToCaptcha should call intentManager startCustomTabsActivity`() {
val mockUri = mockk<Uri>()
mutableEventFlow.tryEmit(LoginEvent.NavigateToCaptcha(mockUri))
verify { intentManager.startCustomTabsActivity(mockUri) }
}
@Test
fun `NavigateToMasterPasswordHint should call onNavigateToMasterPasswordHint`() {
mutableEventFlow.tryEmit(LoginEvent.NavigateToMasterPasswordHint("email"))
@ -359,7 +351,6 @@ private val ACTIVE_ACCOUNT_SUMMARY = AccountSummary(
private val DEFAULT_STATE =
LoginState(
emailAddress = EMAIL,
captchaToken = null,
isLoginButtonEnabled = false,
passwordInput = "",
environmentLabel = "",

View File

@ -1,9 +1,7 @@
package com.x8bit.bitwarden.ui.auth.feature.login
import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson
import com.bitwarden.data.repository.model.Environment
import com.bitwarden.ui.platform.base.BaseViewModelTest
@ -15,8 +13,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
@ -41,14 +37,11 @@ import org.junit.jupiter.api.Test
@Suppress("LargeClass")
class LoginViewModelTest : BaseViewModelTest() {
private val mutableCaptchaTokenResultFlow =
bufferedMutableSharedFlow<CaptchaCallbackTokenResult>()
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
private val authRepository: AuthRepository = mockk(relaxed = true) {
coEvery {
getIsKnownDevice(EMAIL)
} returns KnownDeviceResult.Success(false)
every { captchaTokenResultFlow } returns mutableCaptchaTokenResultFlow
every { userStateFlow } returns mutableUserStateFlow
every { logout(any()) } just runs
}
@ -60,7 +53,6 @@ class LoginViewModelTest : BaseViewModelTest() {
@BeforeEach
fun setUp() {
mockkStatic(
::generateUriForCaptcha,
SavedStateHandle::toLoginArgs,
)
}
@ -68,7 +60,6 @@ class LoginViewModelTest : BaseViewModelTest() {
@AfterEach
fun tearDown() {
unmockkStatic(
::generateUriForCaptcha,
SavedStateHandle::toLoginArgs,
)
}
@ -265,7 +256,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = null,
)
} returns LoginResult.Error(errorMessage = "mock_error", error = error)
val viewModel = createViewModel()
@ -292,7 +282,7 @@ class LoginViewModelTest : BaseViewModelTest() {
)
}
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = null)
authRepository.login(email = EMAIL, password = "")
}
}
@ -304,7 +294,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = null,
)
} returns LoginResult.UnofficialServerError
val viewModel = createViewModel()
@ -330,7 +319,7 @@ class LoginViewModelTest : BaseViewModelTest() {
)
}
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = null)
authRepository.login(email = EMAIL, password = "")
}
}
@ -342,7 +331,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = null,
)
} returns LoginResult.EncryptionKeyMigrationRequired
fakeEnvironmentRepository.environment = Environment.SelfHosted(
@ -378,7 +366,7 @@ class LoginViewModelTest : BaseViewModelTest() {
)
}
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = null)
authRepository.login(email = EMAIL, password = "")
}
}
@ -390,7 +378,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = null,
)
} returns LoginResult.EncryptionKeyMigrationRequired
fakeEnvironmentRepository.environment = Environment.SelfHosted(
@ -424,7 +411,7 @@ class LoginViewModelTest : BaseViewModelTest() {
)
}
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = null)
authRepository.login(email = EMAIL, password = "")
}
}
@ -436,7 +423,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = null,
)
} returns LoginResult.EncryptionKeyMigrationRequired
fakeEnvironmentRepository.environment = Environment.SelfHosted(
@ -470,7 +456,7 @@ class LoginViewModelTest : BaseViewModelTest() {
)
}
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = null)
authRepository.login(email = EMAIL, password = "")
}
}
@ -482,7 +468,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = null,
)
} returns LoginResult.CertificateError
val viewModel = createViewModel()
@ -508,7 +493,7 @@ class LoginViewModelTest : BaseViewModelTest() {
)
}
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = null)
authRepository.login(email = EMAIL, password = "")
}
}
@ -518,7 +503,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = null,
)
} returns LoginResult.Success
val viewModel = createViewModel()
@ -539,35 +523,10 @@ class LoginViewModelTest : BaseViewModelTest() {
)
}
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = null)
authRepository.login(email = EMAIL, password = "")
}
}
@Test
fun `LoginButtonClick login returns CaptchaRequired should emit NavigateToCaptcha`() =
runTest {
val mockkUri = mockk<Uri>()
every {
generateUriForCaptcha(captchaId = "mock_captcha_id")
} returns mockkUri
coEvery {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = null,
)
} returns LoginResult.CaptchaRequired(captchaId = "mock_captcha_id")
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(LoginAction.LoginButtonClick)
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
assertEquals(LoginEvent.NavigateToCaptcha(uri = mockkUri), awaitItem())
}
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = null)
}
}
@Suppress("MaxLineLength")
@Test
fun `LoginButtonClick login returns TwoFactorRequired should base64 URL encode password and emit NavigateToTwoFactorLogin`() =
@ -577,7 +536,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = password,
captchaToken = null,
)
} returns LoginResult.TwoFactorRequired
@ -604,7 +562,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = password,
captchaToken = null,
)
}
}
@ -618,7 +575,6 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository.login(
email = EMAIL,
password = password,
captchaToken = null,
)
} returns LoginResult.NewDeviceVerification(errorMessage = "new device verification needed")
@ -643,7 +599,7 @@ class LoginViewModelTest : BaseViewModelTest() {
)
}
coVerify {
authRepository.login(email = EMAIL, password = password, captchaToken = null)
authRepository.login(email = EMAIL, password = password)
}
}
@ -743,22 +699,6 @@ class LoginViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `captchaTokenFlow success update should trigger a login`() = runTest {
coEvery {
authRepository.login(
email = EMAIL,
password = "",
captchaToken = "token",
)
} returns LoginResult.Success
createViewModel()
mutableCaptchaTokenResultFlow.tryEmit(CaptchaCallbackTokenResult.Success("token"))
coVerify {
authRepository.login(email = EMAIL, password = "", captchaToken = "token")
}
}
private fun createViewModel(state: LoginState? = null): LoginViewModel =
LoginViewModel(
authRepository = authRepository,
@ -766,7 +706,7 @@ class LoginViewModelTest : BaseViewModelTest() {
vaultRepository = vaultRepository,
savedStateHandle = SavedStateHandle().apply {
set(key = "state", value = state)
every { toLoginArgs() } returns LoginArgs(emailAddress = EMAIL, captchaToken = null)
every { toLoginArgs() } returns LoginArgs(emailAddress = EMAIL)
},
)
@ -778,7 +718,6 @@ class LoginViewModelTest : BaseViewModelTest() {
isLoginButtonEnabled = false,
environmentLabel = Environment.Us.label,
dialogState = null,
captchaToken = null,
accountSummaries = emptyList(),
shouldShowLoginWithDevice = false,
)

View File

@ -1,6 +1,5 @@
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
import android.net.Uri
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsDisplayed
@ -113,15 +112,6 @@ class LoginWithDeviceScreenTest : BitwardenComposeTest() {
assertEquals(email, onNavigateToTwoFactorLoginEmail)
}
@Test
fun `NavigateToCaptcha should call launchUri on intentManager`() {
val uri = mockk<Uri>()
mutableEventFlow.tryEmit(LoginWithDeviceEvent.NavigateToCaptcha(uri))
verify(exactly = 1) {
intentManager.startCustomTabsActivity(uri)
}
}
@Test
fun `progress bar should be displayed according to state`() {
mutableStateFlow.update {

View File

@ -11,12 +11,10 @@ import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestType
import com.x8bit.bitwarden.data.auth.manager.model.CreateAuthRequestResult
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.ui.auth.feature.loginwithdevice.model.LoginWithDeviceType
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
import io.mockk.awaits
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
@ -38,13 +36,10 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
private val mutableCreateAuthRequestWithUpdatesFlow =
bufferedMutableSharedFlow<CreateAuthRequestResult>()
private val mutableCaptchaTokenResultFlow =
bufferedMutableSharedFlow<CaptchaCallbackTokenResult>()
private val authRepository = mockk<AuthRepository> {
coEvery {
createAuthRequestWithUpdates(email = EMAIL, authRequestType = any())
} returns mutableCreateAuthRequestWithUpdatesFlow
coEvery { captchaTokenResultFlow } returns mutableCaptchaTokenResultFlow
}
private val snackbarRelayManager: SnackbarRelayManager = mockk {
every { sendSnackbarData(data = any(), relay = any()) } just runs
@ -181,7 +176,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = DEFAULT_LOGIN_DATA.asymmetricalKey,
requestPrivateKey = DEFAULT_LOGIN_DATA.privateKey,
masterPasswordHash = DEFAULT_LOGIN_DATA.masterPasswordHash,
captchaToken = null,
)
} returns LoginResult.Success
val viewModel = createViewModel()
@ -232,7 +226,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = requireNotNull(AUTH_REQUEST.key),
requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY,
masterPasswordHash = AUTH_REQUEST.masterPasswordHash,
captchaToken = null,
)
}
}
@ -315,7 +308,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = DEFAULT_LOGIN_DATA.asymmetricalKey,
requestPrivateKey = DEFAULT_LOGIN_DATA.privateKey,
masterPasswordHash = DEFAULT_LOGIN_DATA.masterPasswordHash,
captchaToken = null,
)
} returns LoginResult.TwoFactorRequired
val viewModel = createViewModel()
@ -341,7 +333,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = requireNotNull(AUTH_REQUEST.key),
requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY,
masterPasswordHash = AUTH_REQUEST.masterPasswordHash,
captchaToken = null,
)
}
}
@ -358,7 +349,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = DEFAULT_LOGIN_DATA.asymmetricalKey,
requestPrivateKey = DEFAULT_LOGIN_DATA.privateKey,
masterPasswordHash = DEFAULT_LOGIN_DATA.masterPasswordHash,
captchaToken = null,
)
} returns LoginResult.Error(errorMessage = null, error = error)
val viewModel = createViewModel()
@ -409,7 +399,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = requireNotNull(AUTH_REQUEST.key),
requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY,
masterPasswordHash = AUTH_REQUEST.masterPasswordHash,
captchaToken = null,
)
}
}
@ -426,7 +415,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = DEFAULT_LOGIN_DATA.asymmetricalKey,
requestPrivateKey = DEFAULT_LOGIN_DATA.privateKey,
masterPasswordHash = DEFAULT_LOGIN_DATA.masterPasswordHash,
captchaToken = null,
)
} returns LoginResult.UnofficialServerError
val viewModel = createViewModel()
@ -476,7 +464,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = requireNotNull(AUTH_REQUEST.key),
requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY,
masterPasswordHash = AUTH_REQUEST.masterPasswordHash,
captchaToken = null,
)
}
}
@ -493,7 +480,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = DEFAULT_LOGIN_DATA.asymmetricalKey,
requestPrivateKey = DEFAULT_LOGIN_DATA.privateKey,
masterPasswordHash = DEFAULT_LOGIN_DATA.masterPasswordHash,
captchaToken = null,
)
} returns LoginResult.CertificateError
val viewModel = createViewModel()
@ -543,7 +529,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = requireNotNull(AUTH_REQUEST.key),
requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY,
masterPasswordHash = AUTH_REQUEST.masterPasswordHash,
captchaToken = null,
)
}
}
@ -560,7 +545,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = DEFAULT_LOGIN_DATA.asymmetricalKey,
requestPrivateKey = DEFAULT_LOGIN_DATA.privateKey,
masterPasswordHash = DEFAULT_LOGIN_DATA.masterPasswordHash,
captchaToken = null,
)
} returns LoginResult.NewDeviceVerification(errorMessage = "new device verification required")
val viewModel = createViewModel()
@ -610,69 +594,10 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
asymmetricalKey = requireNotNull(AUTH_REQUEST.key),
requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY,
masterPasswordHash = AUTH_REQUEST.masterPasswordHash,
captchaToken = null,
)
}
}
@Test
fun `on captchaTokenResultFlow missing token should should display error dialog`() = runTest {
val viewModel = createViewModel()
mutableCaptchaTokenResultFlow.tryEmit(CaptchaCallbackTokenResult.MissingToken)
assertEquals(
DEFAULT_STATE.copy(
dialogState = LoginWithDeviceState.DialogState.Error(
title = BitwardenString.log_in_denied.asText(),
message = BitwardenString.captcha_failed.asText(),
),
),
viewModel.stateFlow.value,
)
}
@Test
fun `on captchaTokenResultFlow success should update the token`() = runTest {
val captchaToken = "captchaToken"
val initialState = DEFAULT_STATE.copy(loginData = DEFAULT_LOGIN_DATA)
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 = captchaToken,
)
} just awaits
val viewModel = createViewModel(initialState)
viewModel.stateFlow.test {
assertEquals(initialState, awaitItem())
mutableCaptchaTokenResultFlow.tryEmit(CaptchaCallbackTokenResult.Success(captchaToken))
assertEquals(
initialState.copy(
loginData = DEFAULT_LOGIN_DATA.copy(captchaToken = captchaToken),
dialogState = LoginWithDeviceState.DialogState.Loading(
message = BitwardenString.logging_in.asText(),
),
),
awaitItem(),
)
}
coVerify(exactly = 1) {
authRepository.login(
email = EMAIL,
requestId = AUTH_REQUEST.id,
accessCode = AUTH_REQUEST_ACCESS_CODE,
asymmetricalKey = requireNotNull(AUTH_REQUEST.key),
requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY,
masterPasswordHash = AUTH_REQUEST.masterPasswordHash,
captchaToken = captchaToken,
)
}
}
@Test
fun `on createAuthRequestWithUpdates Error received should show content with error dialog`() {
val error = Throwable("Fail!")
@ -803,5 +728,4 @@ private val DEFAULT_LOGIN_DATA = LoginWithDeviceState.LoginData(
masterPasswordHash = "verySecureHash",
asymmetricalKey = "public",
privateKey = "private_key",
captchaToken = null,
)

View File

@ -1,6 +1,5 @@
package com.x8bit.bitwarden.ui.auth.feature.startregistration
import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
@ -10,7 +9,6 @@ import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.CloseClick
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.ContinueClick
@ -33,20 +31,13 @@ import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@Suppress("LargeClass")
class StartRegistrationViewModelTest : BaseViewModelTest() {
private val mockAuthRepository = mockk<AuthRepository> {
every { captchaTokenResultFlow } returns flowOf()
}
private val mockAuthRepository = mockk<AuthRepository>()
private val mutableSnackbarSharedFlow = bufferedMutableSharedFlow<BitwardenSnackbarData>()
private val snackbarRelayManager = mockk<SnackbarRelayManager> {
every {
@ -55,16 +46,6 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
}
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
@BeforeEach
fun setUp() {
mockkStatic(::generateUriForCaptcha)
}
@AfterEach
fun tearDown() {
unmockkStatic(::generateUriForCaptcha)
}
@Test
fun `initial state should be correct`() {
val viewModel = createViewModel()
@ -186,10 +167,6 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
@Test
fun `ContinueClick register returns Success with emailVerificationToken should emit NavigateToCompleteRegistration`() =
runTest {
val mockkUri = mockk<Uri>()
every {
generateUriForCaptcha(captchaId = "mock_captcha_id")
} returns mockkUri
coEvery {
mockAuthRepository.sendVerificationEmail(
email = EMAIL,
@ -216,10 +193,6 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
@Test
fun `ContinueClick register returns Success without emailVerificationToken should emit NavigateToCheckEmail`() =
runTest {
val mockkUri = mockk<Uri>()
every {
generateUriForCaptcha(captchaId = "mock_captcha_id")
} returns mockkUri
coEvery {
mockAuthRepository.sendVerificationEmail(
email = EMAIL,

View File

@ -270,13 +270,6 @@ class TwoFactorLoginScreenTest : BitwardenComposeTest() {
TestCase.assertTrue(onNavigateBackCalled)
}
@Test
fun `NavigateToCaptcha should call intentManager startCustomTabsActivity`() {
val mockUri = mockk<Uri>()
mutableEventFlow.tryEmit(TwoFactorLoginEvent.NavigateToCaptcha(mockUri))
verify { intentManager.startCustomTabsActivity(mockUri) }
}
@Test
fun `NavigateToDuo should call intentManager startCustomTabsActivity`() {
val mockUri = mockk<Uri>()
@ -351,7 +344,6 @@ private val DEFAULT_STATE = TwoFactorLoginState(
isContinueButtonEnabled = false,
isRememberEnabled = false,
isNewDeviceVerification = false,
captchaToken = null,
email = "example@email.com",
password = "password123",
orgIdentifier = "orgIdentifier",

View File

@ -16,10 +16,8 @@ import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForWebAuth
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
@ -42,14 +40,11 @@ import org.junit.jupiter.api.Test
@Suppress("LargeClass")
class TwoFactorLoginViewModelTest : BaseViewModelTest() {
private val mutableCaptchaTokenResultFlow =
bufferedMutableSharedFlow<CaptchaCallbackTokenResult>()
private val mutableDuoTokenResultFlow = bufferedMutableSharedFlow<DuoCallbackTokenResult>()
private val mutableYubiKeyResultFlow = bufferedMutableSharedFlow<YubiKeyResult>()
private val mutableWebAuthResultFlow = bufferedMutableSharedFlow<WebAuthResult>()
private val authRepository: AuthRepository = mockk {
every { twoFactorResponse } returns TWO_FACTOR_RESPONSE
every { captchaTokenResultFlow } returns mutableCaptchaTokenResultFlow
every { duoTokenResultFlow } returns mutableDuoTokenResultFlow
every { yubiKeyResultFlow } returns mutableYubiKeyResultFlow
every { webAuthResultFlow } returns mutableWebAuthResultFlow
@ -59,7 +54,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
email = any(),
password = any(),
twoFactorData = any(),
captchaToken = any(),
orgIdentifier = any(),
)
} returns LoginResult.Success
@ -72,7 +66,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
@BeforeEach
fun setUp() {
mockkStatic(
::generateUriForCaptcha,
::generateUriForWebAuth,
SavedStateHandle::toTwoFactorLoginArgs,
)
@ -82,7 +75,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
@AfterEach
fun tearDown() {
unmockkStatic(
::generateUriForCaptcha,
::generateUriForWebAuth,
SavedStateHandle::toTwoFactorLoginArgs,
)
@ -177,7 +169,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.YUBI_KEY.value.toString(),
remember = DEFAULT_STATE.isRememberEnabled,
),
captchaToken = DEFAULT_STATE.captchaToken,
orgIdentifier = DEFAULT_STATE.orgIdentifier,
)
}
@ -196,7 +187,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.WEB_AUTH.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.Success
@ -217,7 +207,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.WEB_AUTH.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
@ -254,38 +243,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
)
}
@Test
fun `captchaTokenFlow success update should trigger a login`() = runTest {
coEvery {
authRepository.login(
email = DEFAULT_EMAIL_ADDRESS,
password = DEFAULT_PASSWORD,
twoFactorData = TwoFactorDataModel(
code = "",
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = "token",
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.Success
createViewModel()
mutableCaptchaTokenResultFlow.tryEmit(CaptchaCallbackTokenResult.Success("token"))
coVerify {
authRepository.login(
email = DEFAULT_EMAIL_ADDRESS,
password = DEFAULT_PASSWORD,
twoFactorData = TwoFactorDataModel(
code = "",
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = "token",
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
}
@Test
fun `duoTokenResultFlow success update should trigger a login`() = runTest {
coEvery {
@ -297,7 +254,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.DUO.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.Success
@ -316,7 +272,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.DUO.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
@ -411,7 +366,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.Success
@ -443,7 +397,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
@ -460,7 +413,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
)
val response = GetTokenResponseJson.TwoFactorRequired(
authMethodsData = authMethodsData,
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -495,7 +447,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
)
val response = GetTokenResponseJson.TwoFactorRequired(
authMethodsData = authMethodsData,
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -531,7 +482,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
val data = JsonObject(mapOf("AuthUrl" to JsonPrimitive("bitwarden.com")))
val response = GetTokenResponseJson.TwoFactorRequired(
authMethodsData = mapOf(TwoFactorAuthMethod.WEB_AUTH to data),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -574,7 +524,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
runTest {
val response = GetTokenResponseJson.TwoFactorRequired(
authMethodsData = emptyMap(),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -595,55 +544,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `ContinueButtonClick login returns CaptchaRequired should emit NavigateToCaptcha`() =
runTest {
val mockkUri = mockk<Uri>()
every {
generateUriForCaptcha(captchaId = "mock_captcha_id")
} returns mockkUri
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.CaptchaRequired(captchaId = "mock_captcha_id")
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(TwoFactorLoginAction.ContinueButtonClick)
assertEquals(
DEFAULT_STATE,
viewModel.stateFlow.value,
)
assertEquals(
TwoFactorLoginEvent.NavigateToCaptcha(uri = mockkUri),
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 `ContinueButtonClick login returns Error should update dialogState`() = runTest {
val error = Throwable("Fail!")
@ -656,7 +556,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.Error(errorMessage = null, error = error)
@ -698,7 +597,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
@ -717,7 +615,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.Error(errorMessage = "Mock error message", error = error)
@ -759,7 +656,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
@ -778,7 +674,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.UnofficialServerError
@ -819,7 +714,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
@ -838,7 +732,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.CertificateError
@ -879,7 +772,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
@ -898,7 +790,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
} returns LoginResult.NewDeviceVerification(errorMessage = "new device verification required")
@ -939,7 +830,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
method = TwoFactorAuthMethod.AUTHENTICATOR_APP.value.toString(),
remember = false,
),
captchaToken = null,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,
)
}
@ -1204,7 +1094,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
)
val localAuthRepository: AuthRepository = mockk {
every { twoFactorResponse } returns TWO_FACTOR_RESPONSE
every { captchaTokenResultFlow } returns mutableCaptchaTokenResultFlow
every { duoTokenResultFlow } returns mutableDuoTokenResultFlow
every { yubiKeyResultFlow } returns mutableYubiKeyResultFlow
every { webAuthResultFlow } returns mutableWebAuthResultFlow
@ -1213,7 +1102,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
email = any(),
password = any(),
newDeviceOtp = any(),
captchaToken = any(),
orgIdentifier = any(),
)
} returns LoginResult.Success
@ -1243,7 +1131,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
email = DEFAULT_STATE.email,
password = DEFAULT_STATE.password,
newDeviceOtp = code.trim(),
captchaToken = DEFAULT_STATE.captchaToken,
orgIdentifier = DEFAULT_STATE.orgIdentifier,
)
}
@ -1325,7 +1212,6 @@ private val TWO_FACTOR_AUTH_METHODS_DATA = mapOf(
private val TWO_FACTOR_RESPONSE = GetTokenResponseJson.TwoFactorRequired(
authMethodsData = TWO_FACTOR_AUTH_METHODS_DATA,
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -1345,7 +1231,6 @@ private val DEFAULT_STATE = TwoFactorLoginState(
dialogState = null,
isContinueButtonEnabled = false,
isRememberEnabled = false,
captchaToken = null,
email = DEFAULT_EMAIL_ADDRESS,
password = DEFAULT_PASSWORD,
orgIdentifier = DEFAULT_ORG_IDENTIFIER,

View File

@ -39,7 +39,6 @@ internal interface UnauthenticatedIdentityApi {
@Field(value = "deviceName") deviceName: String,
@Field(value = "deviceType") deviceType: String,
@Field(value = "grant_type") grantType: String,
@Field(value = "captchaResponse") captchaResponse: String?,
@Field(value = "code") ssoCode: String?,
@Field(value = "code_verifier") ssoCodeVerifier: String?,
@Field(value = "redirect_uri") ssoRedirectUri: String?,

View File

@ -83,15 +83,6 @@ sealed class GetTokenResponseJson {
val keyConnectorUrl: String?,
) : GetTokenResponseJson()
/**
* Models json body of a captcha error.
*/
@Serializable
data class CaptchaRequired(
@SerialName("HCaptcha_SiteKey")
val captchaKey: String,
) : GetTokenResponseJson()
/**
* Models json body of an invalid request.
*
@ -134,11 +125,12 @@ sealed class GetTokenResponseJson {
get() = if (errorMessage?.lowercase() == "new device verification required") {
InvalidType.NewDeviceVerification
} else if (errorMessage
?.lowercase()
?.contains(
"encryption key migration is required. please log in to the web vault at",
) == true) {
InvalidType.EncryptionKeyMigrationRequired
?.lowercase()
?.contains(
"encryption key migration is required. please log in to the web vault at",
) == true
) {
InvalidType.EncryptionKeyMigrationRequired
} else {
InvalidType.GenericInvalid
}
@ -164,9 +156,6 @@ sealed class GetTokenResponseJson {
* `{"1":{"Email":"sh*****@example.com"},"0":{"Email":null}}`
* The keys are the raw values of the [TwoFactorAuthMethod],
* and the map is any extra information for the method.
* @property captchaToken The captcha token used in the second
* login attempt if the user has already passed a captcha
* authentication in the first attempt.
* @property ssoToken If the user is logging on via Single
* Sign On, they'll need this value to complete authentication
* after entering their two-factor code.
@ -179,9 +168,6 @@ sealed class GetTokenResponseJson {
@SerialName("TwoFactorProviders")
val twoFactorProviders: List<String>?,
@SerialName("CaptchaBypassToken")
val captchaToken: String?,
@SerialName("SsoEmail2faSessionToken")
val ssoToken: String?,
) : GetTokenResponseJson()

View File

@ -10,7 +10,6 @@ import kotlinx.serialization.Serializable
* @param emailVerificationToken token used to finish the registration process.
* @param masterPasswordHash the master password (encrypted).
* @param masterPasswordHint the hint for the master password (nullable).
* @param captchaResponse the captcha bypass token.
* @param userSymmetricKey the user key for the request (encrypted).
* @param userAsymmetricKeys a [Keys] object containing public and private keys.
* @param kdfType the kdf type represented as an [Int].
@ -30,9 +29,6 @@ data class RegisterFinishRequestJson(
@SerialName("masterPasswordHint")
val masterPasswordHint: String?,
@SerialName("captchaResponse")
val captchaResponse: String?,
@SerialName("userSymmetricKey")
val userSymmetricKey: String,

View File

@ -9,7 +9,6 @@ import kotlinx.serialization.Serializable
* @param email the email to be registered.
* @param masterPasswordHash the master password (encrypted).
* @param masterPasswordHint the hint for the master password (nullable).
* @param captchaResponse the captcha bypass token.
* @param key the user key for the request (encrypted).
* @param keys a [Keys] object containing public and private keys.
* @param kdfType the kdf type represented as an [Int].
@ -26,9 +25,6 @@ data class RegisterRequestJson(
@SerialName("masterPasswordHint")
val masterPasswordHint: String?,
@SerialName("captchaResponse")
val captchaResponse: String?,
@SerialName("key")
val key: String,

View File

@ -13,37 +13,9 @@ sealed class RegisterResponseJson {
/**
* Models a successful json response of the register request.
*
* @param captchaBypassToken the bypass token.
*/
@Serializable
data class Success(
@SerialName("captchaBypassToken")
val captchaBypassToken: String?,
) : RegisterResponseJson()
/**
* Models a json body of a captcha error.
*
* @param validationErrors object containing error validations of the response.
*/
@Serializable
data class CaptchaRequired(
@SerialName("validationErrors")
val validationErrors: ValidationErrors,
) : RegisterResponseJson() {
/**
* Error validations containing a HCaptcha Site Key.
*
* @param captchaKeys keys for attempting captcha verification.
*/
@Serializable
data class ValidationErrors(
@SerialName("HCaptcha_SiteKey")
val captchaKeys: List<String>,
)
}
data object Success : RegisterResponseJson()
/**
* Represents the json body of an invalid register request.

View File

@ -36,7 +36,6 @@ interface IdentityService {
* @param email user's email address.
* @param authModel information necessary to authenticate with any
* of the available login methods.
* @param captchaToken captcha token to be passed to the API (nullable).
* @param twoFactorData the two-factor data, if applicable.
*/
@Suppress("LongParameterList")
@ -44,7 +43,6 @@ interface IdentityService {
uniqueAppId: String,
email: String,
authModel: IdentityTokenAuthModel,
captchaToken: String?,
twoFactorData: TwoFactorDataModel? = null,
newDeviceOtp: String? = null,
): Result<GetTokenResponseJson>

View File

@ -43,11 +43,7 @@ internal class IdentityServiceImpl(
.recoverCatching { throwable ->
val bitwardenError = throwable.toBitwardenError()
bitwardenError
.parseErrorBodyOrNull<RegisterResponseJson.CaptchaRequired>(
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
codes = listOf(
NetworkErrorCode.BAD_REQUEST,
NetworkErrorCode.TOO_MANY_REQUESTS,
@ -61,7 +57,6 @@ internal class IdentityServiceImpl(
uniqueAppId: String,
email: String,
authModel: IdentityTokenAuthModel,
captchaToken: String?,
twoFactorData: TwoFactorDataModel?,
newDeviceOtp: String?,
): Result<GetTokenResponseJson> = unauthenticatedIdentityApi
@ -81,7 +76,6 @@ internal class IdentityServiceImpl(
twoFactorCode = twoFactorData?.code,
twoFactorMethod = twoFactorData?.method,
twoFactorRemember = twoFactorData?.remember?.let { if (it) "1" else "0 " },
captchaResponse = captchaToken,
authRequestId = authModel.authRequestId,
newDeviceOtp = newDeviceOtp,
)
@ -89,11 +83,7 @@ internal class IdentityServiceImpl(
.recoverCatching { throwable ->
val bitwardenError = throwable.toBitwardenError()
bitwardenError
.parseErrorBodyOrNull<GetTokenResponseJson.CaptchaRequired>(
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.TwoFactorRequired>(
.parseErrorBodyOrNull<GetTokenResponseJson.TwoFactorRequired>(
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)

View File

@ -134,10 +134,8 @@ class IdentityServiceTest : BaseServiceTest() {
@Test
fun `register success json should be Success`() = runTest {
val expectedResponse = RegisterResponseJson.Success(
captchaBypassToken = "mock_token",
)
val response = MockResponse().setBody(CAPTCHA_BYPASS_TOKEN_RESPONSE_JSON)
val expectedResponse = RegisterResponseJson.Success
val response = MockResponse().setBody(LOGIN_SUCCESS_JSON)
server.enqueue(response)
assertEquals(
expectedResponse.asSuccess(),
@ -175,30 +173,6 @@ class IdentityServiceTest : BaseServiceTest() {
)
}
@Test
fun `register captcha json should be CaptchaRequired`() = runTest {
val json = """
{
"validationErrors": {
"HCaptcha_SiteKey": [
"mock_token"
]
}
}
"""
val expectedResponse = RegisterResponseJson.CaptchaRequired(
validationErrors = RegisterResponseJson.CaptchaRequired.ValidationErrors(
captchaKeys = listOf("mock_token"),
),
)
val response = MockResponse().setResponseCode(400).setBody(json)
server.enqueue(response)
assertEquals(
expectedResponse.asSuccess(),
identityService.register(registerRequestBody),
)
}
@Test
fun `getToken when request response is Success should return Success`() = runTest {
server.enqueue(MockResponse().setBody(LOGIN_SUCCESS_JSON))
@ -208,7 +182,6 @@ class IdentityServiceTest : BaseServiceTest() {
username = EMAIL,
password = PASSWORD_HASH,
),
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
assertEquals(LOGIN_SUCCESS.asSuccess(), result)
@ -223,27 +196,11 @@ class IdentityServiceTest : BaseServiceTest() {
username = EMAIL,
password = PASSWORD_HASH,
),
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
assertTrue(result.isFailure)
}
@Test
fun `getToken when response is CaptchaRequired should return CaptchaRequired`() = runTest {
server.enqueue(MockResponse().setResponseCode(400).setBody(CAPTCHA_BODY_JSON))
val result = identityService.getToken(
email = EMAIL,
authModel = IdentityTokenAuthModel.MasterPassword(
username = EMAIL,
password = PASSWORD_HASH,
),
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
assertEquals(CAPTCHA_BODY.asSuccess(), result)
}
@Test
fun `getToken when response is TwoFactorRequired should return TwoFactorRequired`() = runTest {
server.enqueue(MockResponse().setResponseCode(400).setBody(TWO_FACTOR_BODY_JSON))
@ -253,7 +210,6 @@ class IdentityServiceTest : BaseServiceTest() {
username = EMAIL,
password = PASSWORD_HASH,
),
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
assertEquals(TWO_FACTOR_BODY.asSuccess(), result)
@ -268,7 +224,6 @@ class IdentityServiceTest : BaseServiceTest() {
username = EMAIL,
password = PASSWORD_HASH,
),
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
assertEquals(INVALID_LOGIN.asSuccess(), result)
@ -284,7 +239,6 @@ class IdentityServiceTest : BaseServiceTest() {
username = EMAIL,
password = PASSWORD_HASH,
),
captchaToken = null,
uniqueAppId = UNIQUE_APP_ID,
)
assertEquals(INVALID_LOGIN.asSuccess(), result)
@ -349,10 +303,8 @@ class IdentityServiceTest : BaseServiceTest() {
@Test
fun `registerFinish success json should be Success`() = runTest {
val expectedResponse = RegisterResponseJson.Success(
captchaBypassToken = "mock_token",
)
val response = MockResponse().setBody(CAPTCHA_BYPASS_TOKEN_RESPONSE_JSON)
val expectedResponse = RegisterResponseJson.Success
val response = MockResponse().setBody(LOGIN_SUCCESS_JSON)
server.enqueue(response)
assertEquals(
expectedResponse.asSuccess(),
@ -494,7 +446,6 @@ class IdentityServiceTest : BaseServiceTest() {
email = EMAIL,
masterPasswordHash = "mockk_masterPasswordHash",
masterPasswordHint = "mockk_masterPasswordHint",
captchaResponse = "mockk_captchaResponse",
key = "mockk_key",
keys = RegisterRequestJson.Keys(
publicKey = "mockk_publicKey",
@ -508,7 +459,6 @@ class IdentityServiceTest : BaseServiceTest() {
masterPasswordHash = "mockk_masterPasswordHash",
masterPasswordHint = "mockk_masterPasswordHint",
emailVerificationToken = "mock_emailVerificationToken",
captchaResponse = "mockk_captchaResponse",
userSymmetricKey = "mockk_key",
userAsymmetricKeys = RegisterFinishRequestJson.Keys(
publicKey = "mockk_publicKey",
@ -566,18 +516,10 @@ private val REFRESH_TOKEN_SUCCESS_BODY = RefreshTokenResponseJson.Success(
tokenType = "Bearer",
)
private const val CAPTCHA_BODY_JSON = """
{
"HCaptcha_SiteKey": "123"
}
"""
private val CAPTCHA_BODY = GetTokenResponseJson.CaptchaRequired("123")
private const val TWO_FACTOR_BODY_JSON = """
{
"TwoFactorProviders2": {"1": {"Email": "ex***@email.com"}, "0": {"Email": null}},
"SsoEmail2faSessionToken": "exampleToken",
"CaptchaBypassToken": "BWCaptchaBypass_ABCXYZ",
"TwoFactorProviders": ["1", "3", "0"]
}
"""
@ -587,7 +529,6 @@ private val TWO_FACTOR_BODY = GetTokenResponseJson.TwoFactorRequired(
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("Email" to JsonNull)),
),
ssoToken = "exampleToken",
captchaToken = "BWCaptchaBypass_ABCXYZ",
twoFactorProviders = listOf("1", "3", "0"),
)
@ -708,12 +649,6 @@ private const val INVALID_MODEL_STATE_EMAIL_TAKEN_ERROR_JSON = """
}
"""
private const val CAPTCHA_BYPASS_TOKEN_RESPONSE_JSON = """
{
"captchaBypassToken": "mock_token"
}
"""
private val INVALID_LOGIN = GetTokenResponseJson.Invalid(
errorModel = GetTokenResponseJson.Invalid.ErrorModel(
errorMessage = "123",

View File

@ -19,7 +19,6 @@ class TwoFactorRequiredExtensionTest {
),
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("Email" to JsonNull)),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -42,7 +41,6 @@ class TwoFactorRequiredExtensionTest {
),
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("AuthUrl" to JsonNull)),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -58,7 +56,6 @@ class TwoFactorRequiredExtensionTest {
),
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("AuthUrl" to JsonNull)),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -71,7 +68,6 @@ class TwoFactorRequiredExtensionTest {
authMethodsData = mapOf(
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("AuthUrl" to JsonNull)),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -87,7 +83,6 @@ class TwoFactorRequiredExtensionTest {
),
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("Email" to JsonNull)),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -100,7 +95,6 @@ class TwoFactorRequiredExtensionTest {
authMethodsData = mapOf(
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("Email" to JsonNull)),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -116,7 +110,6 @@ class TwoFactorRequiredExtensionTest {
),
TwoFactorAuthMethod.AUTHENTICATOR_APP to JsonObject(mapOf("Email" to JsonNull)),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -132,7 +125,6 @@ class TwoFactorRequiredExtensionTest {
mapOf("AuthUrl" to JsonPrimitive(authUrl)),
),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)
@ -148,7 +140,6 @@ class TwoFactorRequiredExtensionTest {
mapOf("AuthUrl" to JsonPrimitive(authUrl)),
),
),
captchaToken = null,
ssoToken = null,
twoFactorProviders = null,
)

View File

@ -483,7 +483,6 @@ Scanning will happen automatically.</string>
<string name="password_prompt">Master password re-prompt</string>
<string name="password_confirmation">Master password confirmation</string>
<string name="password_confirmation_desc">This action is protected, to continue please re-enter your master password to verify your identity.</string>
<string name="captcha_failed">Captcha failed. Please try again.</string>
<string name="update_master_password">Update master password</string>
<string name="update_master_password_warning">Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour.</string>
<string name="updating_password">Updating password</string>