mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-20 00:35:47 +01:00
Skip two-factor selection if there's only one option.
This commit is contained in:
+7
-3
@@ -30,6 +30,7 @@ import org.signal.registration.screens.aepentry.AepInput
|
||||
import org.signal.registration.screens.shared.AccountIdError
|
||||
import org.signal.registration.screens.shared.AccountIdFormat
|
||||
import org.signal.registration.screens.twofactorselection.TwoFactorMethod
|
||||
import org.signal.registration.screens.twofactorselection.toAuthenticationRoute
|
||||
import org.signal.registration.screens.util.navigateBack
|
||||
import org.signal.registration.screens.util.navigateTo
|
||||
|
||||
@@ -244,10 +245,13 @@ class SignalLoginCredentialEntryViewModel(
|
||||
}
|
||||
RegisterAccountError.TotpMissingOrIncorrect -> {
|
||||
// For now this error only means TOTP, but in the future it will indicate that some two-factor method is
|
||||
// required, so we treat it generically and route through the method selection screen.
|
||||
Log.w(TAG, "[Next] A two-factor code is required. Sending the user to two-factor method selection.")
|
||||
// required, so we treat it generically and let the method list decide where to go.
|
||||
val methods = listOf(TwoFactorMethod.AuthenticatorApp)
|
||||
val route = methods.toAuthenticationRoute()
|
||||
|
||||
Log.w(TAG, "[Next] A two-factor code is required. Sending the user to $route.")
|
||||
stateEmitter(inputState.copy(isLoggingIn = false))
|
||||
parentEventEmitter.navigateTo(RegistrationRoute.TwoFactorSelection(methods = listOf(TwoFactorMethod.AuthenticatorApp)))
|
||||
parentEventEmitter.navigateTo(route)
|
||||
}
|
||||
is RegisterAccountError.InvalidRequest,
|
||||
is RegisterAccountError.InvalidReceiptCredentialPresentation,
|
||||
|
||||
+14
@@ -5,6 +5,8 @@
|
||||
|
||||
package org.signal.registration.screens.twofactorselection
|
||||
|
||||
import org.signal.registration.RegistrationRoute
|
||||
|
||||
/**
|
||||
* Two-factor authentication methods that may be presented on the two-factor selection screen. Which ones are actually
|
||||
* offered depends on what the account has registered.
|
||||
@@ -16,3 +18,15 @@ enum class TwoFactorMethod {
|
||||
/** A one-time code generated by the user's authenticator app. */
|
||||
AuthenticatorApp
|
||||
}
|
||||
|
||||
/**
|
||||
* The route to send the user to in order to authenticate with one of these [TwoFactorMethod]s. When they're all of the
|
||||
* same kind, the selection screen has nothing to choose between, so we skip straight to that method. Multiple entries
|
||||
* of a kind are fine: the service checks the credential against every one the account has registered.
|
||||
*/
|
||||
fun List<TwoFactorMethod>.toAuthenticationRoute(): RegistrationRoute {
|
||||
return when (toSet().singleOrNull()) {
|
||||
TwoFactorMethod.AuthenticatorApp -> RegistrationRoute.TotpEntry
|
||||
else -> RegistrationRoute.TwoFactorSelection(methods = this)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -1938,14 +1938,16 @@ class RegistrationEndToEndTest {
|
||||
useExistingSignalLogin()
|
||||
enterSignalLogin(login)
|
||||
|
||||
// The service wants a second factor, so the user picks one and enters a code from it
|
||||
waitForTag(TestTags.TWO_FACTOR_SELECTION_AUTHENTICATOR_APP_OPTION)
|
||||
composeTestRule.onNodeWithTag(TestTags.TWO_FACTOR_SELECTION_AUTHENTICATOR_APP_OPTION).performClick()
|
||||
// The authenticator app is the only second factor available, so the user lands straight on code entry
|
||||
waitForTag(CodeEntryFieldTestTags.digit(0))
|
||||
composeTestRule.onNodeWithTag(CodeEntryFieldTestTags.digit(0)).performTextInput(totp)
|
||||
|
||||
waitFor("registration to complete") { registrationComplete }
|
||||
|
||||
assert(composeTestRule.onAllNodesWithTag(TestTags.TWO_FACTOR_SELECTION_AUTHENTICATOR_APP_OPTION).fetchSemanticsNodes().isEmpty()) {
|
||||
"There was only one two-factor method, so the selection screen should have been skipped"
|
||||
}
|
||||
|
||||
assert(networkController.lastRegisterAccountRequest?.totp == totp.toInt()) { "Expected the entered code to be sent with the login but was ${networkController.lastRegisterAccountRequest}" }
|
||||
assert(storageController.committedData?.accountData?.e164 == null) { "Expected an account with no phone number" }
|
||||
}
|
||||
|
||||
+2
-5
@@ -50,7 +50,6 @@ import org.signal.registration.screens.aepentry.AepInput
|
||||
import org.signal.registration.screens.restoreselection.ArchiveRestoreOption
|
||||
import org.signal.registration.screens.restoreselection.RegisteredState
|
||||
import org.signal.registration.screens.shared.AccountIdError
|
||||
import org.signal.registration.screens.twofactorselection.TwoFactorMethod
|
||||
import java.io.IOException
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
@@ -462,7 +461,7 @@ class SignalLoginCredentialEntryViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NextClicked requiring a two-factor code navigates to two-factor selection offering only the authenticator app`() = runTest(testDispatcher) {
|
||||
fun `NextClicked requiring a two-factor code navigates directly to TOTP entry`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.reRegisterAccountWithoutPhoneNumber(any(), any(), any(), any(), any()) } returns
|
||||
RequestResult.NonSuccess(RegisterAccountError.TotpMissingOrIncorrect)
|
||||
|
||||
@@ -472,9 +471,7 @@ class SignalLoginCredentialEntryViewModelTest {
|
||||
assertThat(emittedParentEvents.last())
|
||||
.isInstanceOf<RegistrationFlowEvent.NavigateToScreen>()
|
||||
.prop(RegistrationFlowEvent.NavigateToScreen::route)
|
||||
.isInstanceOf<RegistrationRoute.TwoFactorSelection>()
|
||||
.prop(RegistrationRoute.TwoFactorSelection::methods)
|
||||
.containsExactly(TwoFactorMethod.AuthenticatorApp)
|
||||
.isEqualTo(RegistrationRoute.TotpEntry)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user