diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/data/RegistrationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/data/RegistrationRepository.kt index ef81d76150..be0c6f8235 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/data/RegistrationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/data/RegistrationRepository.kt @@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.registration.PushChallengeRequest import org.thoughtcrime.securesms.registration.RegistrationData import org.thoughtcrime.securesms.registration.VerifyAccountRepository import org.thoughtcrime.securesms.registration.v2.data.network.BackupAuthCheckResult +import org.thoughtcrime.securesms.registration.v2.data.network.RegisterAccountResult import org.thoughtcrime.securesms.registration.v2.data.network.RegistrationSessionCheckResult import org.thoughtcrime.securesms.registration.v2.data.network.RegistrationSessionCreationResult import org.thoughtcrime.securesms.registration.v2.data.network.RegistrationSessionResult @@ -337,10 +338,11 @@ object RegistrationRepository { /** * Submits the user-entered verification code to the service. */ - suspend fun submitVerificationCode(context: Context, e164: String, password: String, sessionId: String, registrationData: RegistrationData): NetworkResult = + suspend fun submitVerificationCode(context: Context, e164: String, password: String, sessionId: String, registrationData: RegistrationData): VerificationCodeRequestResult = withContext(Dispatchers.IO) { val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password).registrationApi - api.verifyAccount(sessionId = sessionId, verificationCode = registrationData.code) + val result = api.verifyAccount(sessionId = sessionId, verificationCode = registrationData.code) + return@withContext VerificationCodeRequestResult.from(result) } /** @@ -356,7 +358,7 @@ object RegistrationRepository { /** * Submit the necessary assets as a verified account so that the user can actually use the service. */ - suspend fun registerAccount(context: Context, sessionId: String?, registrationData: RegistrationData, pin: String? = null, masterKeyProducer: VerifyAccountRepository.MasterKeyProducer? = null): NetworkResult = + suspend fun registerAccount(context: Context, sessionId: String?, registrationData: RegistrationData, pin: String? = null, masterKeyProducer: VerifyAccountRepository.MasterKeyProducer? = null): RegisterAccountResult = withContext(Dispatchers.IO) { val api: RegistrationApi = AccountManagerFactory.getInstance().createUnauthenticated(context, registrationData.e164, SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.password).registrationApi @@ -389,7 +391,7 @@ object RegistrationRepository { val aciPreKeyCollection = org.thoughtcrime.securesms.registration.RegistrationRepository.generateSignedAndLastResortPreKeys(aciIdentity, SignalStore.account().aciPreKeys) val pniPreKeyCollection = org.thoughtcrime.securesms.registration.RegistrationRepository.generateSignedAndLastResortPreKeys(pniIdentity, SignalStore.account().pniPreKeys) - api.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true) + val result: NetworkResult = api.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true) .map { accountRegistrationResponse -> AccountRegistrationResult( uuid = accountRegistrationResponse.uuid, @@ -402,6 +404,8 @@ object RegistrationRepository { pniPreKeyCollection = pniPreKeyCollection ) } + + return@withContext RegisterAccountResult.from(result) } suspend fun createSessionAndBlockForPushChallenge(accountManager: RegistrationApi, fcmToken: String, mcc: String?, mnc: String?): NetworkResult = diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/data/network/RegisterAccountResult.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/data/network/RegisterAccountResult.kt new file mode 100644 index 0000000000..575395f4db --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/data/network/RegisterAccountResult.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.registration.v2.data.network + +import org.thoughtcrime.securesms.registration.v2.data.RegistrationRepository +import org.whispersystems.signalservice.api.NetworkResult +import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException +import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException +import org.whispersystems.signalservice.api.push.exceptions.MalformedRequestException +import org.whispersystems.signalservice.api.push.exceptions.RateLimitException +import org.whispersystems.signalservice.internal.push.AuthCredentials +import org.whispersystems.signalservice.internal.push.LockedException +import org.whispersystems.signalservice.internal.push.VerifyAccountResponse + +/** + * This is a processor to map a [VerifyAccountResponse] to all the known outcomes. + */ +sealed class RegisterAccountResult(cause: Throwable?) : RegistrationResult(cause) { + companion object { + fun from(networkResult: NetworkResult): RegisterAccountResult { + return when (networkResult) { + is NetworkResult.Success -> Success(networkResult.result) + is NetworkResult.ApplicationError -> UnknownError(networkResult.throwable) + is NetworkResult.NetworkError -> UnknownError(networkResult.exception) + is NetworkResult.StatusCodeError -> { + when (val cause = networkResult.exception) { + is IncorrectRegistrationRecoveryPasswordException -> IncorrectRecoveryPassword(cause) + is AuthorizationFailedException -> AuthorizationFailed(cause) + is MalformedRequestException -> MalformedRequest(cause) + is RateLimitException -> createRateLimitProcessor(cause) + is LockedException -> RegistrationLocked(cause = cause, timeRemaining = cause.timeRemaining, svr2Credentials = cause.svr2Credentials) + else -> { + if (networkResult.code == 422) { + ValidationError(cause) + } else { + UnknownError(cause) + } + } + } + } + } + } + private fun createRateLimitProcessor(exception: RateLimitException): RegisterAccountResult { + return if (exception.retryAfterMilliseconds.isPresent) { + RateLimited(exception, exception.retryAfterMilliseconds.get()) + } else { + AttemptsExhausted(exception) + } + } + } + class Success(val accountRegistrationResult: RegistrationRepository.AccountRegistrationResult) : RegisterAccountResult(null) + class IncorrectRecoveryPassword(cause: Throwable) : RegisterAccountResult(cause) + class AuthorizationFailed(cause: Throwable) : RegisterAccountResult(cause) + class MalformedRequest(cause: Throwable) : RegisterAccountResult(cause) + class ValidationError(cause: Throwable) : RegisterAccountResult(cause) + class RateLimited(cause: Throwable, val timeRemaining: Long) : RegisterAccountResult(cause) + class AttemptsExhausted(cause: Throwable) : RegisterAccountResult(cause) + class RegistrationLocked(cause: Throwable, val timeRemaining: Long, val svr2Credentials: AuthCredentials?) : RegisterAccountResult(cause) + class UnknownError(cause: Throwable) : RegisterAccountResult(cause) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/RegistrationV2ViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/RegistrationV2ViewModel.kt index 4b24df321e..01e3a06342 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/RegistrationV2ViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/RegistrationV2ViewModel.kt @@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.registration.RegistrationData import org.thoughtcrime.securesms.registration.RegistrationUtil import org.thoughtcrime.securesms.registration.v2.data.RegistrationRepository import org.thoughtcrime.securesms.registration.v2.data.network.BackupAuthCheckResult +import org.thoughtcrime.securesms.registration.v2.data.network.RegisterAccountResult import org.thoughtcrime.securesms.registration.v2.data.network.RegistrationSessionCheckResult import org.thoughtcrime.securesms.registration.v2.data.network.RegistrationSessionCreationResult import org.thoughtcrime.securesms.registration.v2.data.network.RegistrationSessionResult @@ -50,10 +51,8 @@ import org.thoughtcrime.securesms.registration.v2.data.network.VerificationCodeR import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.dualsim.MccMncProducer -import org.whispersystems.signalservice.api.NetworkResult import org.whispersystems.signalservice.api.SvrNoDataException import org.whispersystems.signalservice.api.kbs.MasterKey -import org.whispersystems.signalservice.internal.push.LockedException import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse import java.io.IOException import kotlin.time.Duration.Companion.minutes @@ -64,7 +63,7 @@ import kotlin.time.Duration.Companion.minutes class RegistrationV2ViewModel : ViewModel() { private val store = MutableStateFlow(RegistrationV2State()) - private val password = Util.getSecret(18) // TODO [regv2]: persist this + private val password = Util.getSecret(18) private val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> Log.w(TAG, "CoroutineExceptionHandler invoked.", exception) @@ -168,7 +167,7 @@ class RegistrationV2ViewModel : ViewModel() { } viewModelScope.launch { - val svrCredentialsResult = RegistrationRepository.hasValidSvrAuthCredentials(context, e164, password) + val svrCredentialsResult: BackupAuthCheckResult = RegistrationRepository.hasValidSvrAuthCredentials(context, e164, password) when (svrCredentialsResult) { is BackupAuthCheckResult.UnknownError -> { @@ -184,7 +183,9 @@ class RegistrationV2ViewModel : ViewModel() { return@launch } - is BackupAuthCheckResult.SuccessWithoutCredentials -> Log.d(TAG, "No local SVR auth credentials could be found and/or validated.") + is BackupAuthCheckResult.SuccessWithoutCredentials -> { + Log.d(TAG, "No local SVR auth credentials could be found and/or validated.") + } } val validSession = getOrCreateValidSession(context) ?: return@launch @@ -412,6 +413,34 @@ class RegistrationV2ViewModel : ViewModel() { return false } + /** + * @return whether the request was successful and execution should continue + */ + private suspend fun handleRegistrationResult(context: Context, registrationData: RegistrationData, registrationResult: RegisterAccountResult, reglockEnabled: Boolean, errorHandler: (RegisterAccountResult) -> Unit): Boolean { + when (registrationResult) { + is RegisterAccountResult.Success -> { + onSuccessfulRegistration(context, registrationData, registrationResult.accountRegistrationResult, reglockEnabled) + return true + } + is RegisterAccountResult.IncorrectRecoveryPassword -> { + Log.i(TAG, "Registration recovery password was incorrect, falling back to SMS verification.", registrationResult.getCause()) + setUserSkippedReRegisterFlow(true) + } + is RegisterAccountResult.RegistrationLocked -> { + Log.i(TAG, "Account is registration locked!", registrationResult.getCause()) + } + is RegisterAccountResult.AttemptsExhausted, + is RegisterAccountResult.RateLimited, + is RegisterAccountResult.AuthorizationFailed, + is RegisterAccountResult.MalformedRequest, + is RegisterAccountResult.ValidationError, + is RegisterAccountResult.UnknownError -> Log.i(TAG, "Received error when trying to register!", registrationResult.getCause()) + } + setInProgress(false) + errorHandler(registrationResult) + return false + } + private fun handleGenericError(cause: Throwable) { Log.w(TAG, "Encountered unknown error!", cause) store.update { @@ -437,7 +466,7 @@ class RegistrationV2ViewModel : ViewModel() { } } - fun verifyReRegisterWithPin(context: Context, pin: String, wrongPinHandler: () -> Unit) { + fun verifyReRegisterWithPin(context: Context, pin: String, wrongPinHandler: () -> Unit, registrationErrorHandler: (RegisterAccountResult) -> Unit) { setInProgress(true) // Local recovery password @@ -445,7 +474,7 @@ class RegistrationV2ViewModel : ViewModel() { if (RegistrationRepository.doesPinMatchLocalHash(pin)) { Log.d(TAG, "Found recovery password, attempting to re-register.") viewModelScope.launch(context = coroutineExceptionHandler) { - verifyReRegisterInternal(context, pin, SignalStore.svr().getOrCreateMasterKey()) + verifyReRegisterInternal(context, pin, SignalStore.svr().getOrCreateMasterKey(), registrationErrorHandler) setInProgress(false) } } else { @@ -465,7 +494,7 @@ class RegistrationV2ViewModel : ViewModel() { val masterKey = RegistrationRepository.fetchMasterKeyFromSvrRemote(pin, authCredentials) setRecoveryPassword(masterKey.deriveRegistrationRecoveryPassword()) updateSvrTriesRemaining(10) - verifyReRegisterInternal(context, pin, masterKey) + verifyReRegisterInternal(context, pin, masterKey, registrationErrorHandler) } catch (rejectedPin: SvrWrongPinException) { Log.w(TAG, "Submitted PIN was rejected by SVR.", rejectedPin) updateSvrTriesRemaining(rejectedPin.triesRemaining) @@ -486,7 +515,7 @@ class RegistrationV2ViewModel : ViewModel() { } } - private suspend fun verifyReRegisterInternal(context: Context, pin: String, masterKey: MasterKey) { + private suspend fun verifyReRegisterInternal(context: Context, pin: String, masterKey: MasterKey, registrationErrorHandler: (RegisterAccountResult) -> Unit) { updateFcmToken(context) val registrationData = getRegistrationData("") @@ -495,34 +524,31 @@ class RegistrationV2ViewModel : ViewModel() { val result = resultAndRegLockStatus.first val reglockEnabled = resultAndRegLockStatus.second - if (result !is NetworkResult.Success) { - Log.w(TAG, "Error during registration!", result.getCause()) - return - } - - onSuccessfulRegistration(context, registrationData, result.result, reglockEnabled) + handleRegistrationResult(context, registrationData, result, reglockEnabled, registrationErrorHandler) } - private suspend fun registerAccountInternal(context: Context, sessionId: String?, registrationData: RegistrationData, pin: String?, masterKey: MasterKey): Pair, Boolean> { - val registrationResult = RegistrationRepository.registerAccount(context = context, sessionId = sessionId, registrationData = registrationData, pin = pin) { masterKey } + /** + * @return a [Pair] containing the server response and a boolean signifying whether the current account is registration locked. + */ + private suspend fun registerAccountInternal(context: Context, sessionId: String?, registrationData: RegistrationData, pin: String?, masterKey: MasterKey): Pair { + val registrationResult: RegisterAccountResult = RegistrationRepository.registerAccount(context = context, sessionId = sessionId, registrationData = registrationData, pin = pin) { masterKey } // TODO: check for wrong recovery password // Check if reg lock is enabled - if (registrationResult !is NetworkResult.StatusCodeError || registrationResult.exception !is LockedException) { + if (registrationResult !is RegisterAccountResult.RegistrationLocked) { return Pair(registrationResult, false) } Log.i(TAG, "Received a registration lock response when trying to register an account. Retrying with master key.") - val lockedException = registrationResult.exception as LockedException store.update { - it.copy(svrAuthCredentials = lockedException.svr2Credentials) + it.copy(svrAuthCredentials = registrationResult.svr2Credentials) } return Pair(RegistrationRepository.registerAccount(context = context, sessionId = sessionId, registrationData = registrationData, pin = pin) { masterKey }, true) } - fun verifyCodeWithoutRegistrationLock(context: Context, code: String) { + fun verifyCodeWithoutRegistrationLock(context: Context, code: String, submissionErrorHandler: (VerificationCodeRequestResult, RegistrationRepository.Mode) -> Unit, registrationErrorHandler: (RegisterAccountResult) -> Unit) { store.update { it.copy(inProgress = true, registrationCheckpoint = RegistrationCheckpoint.VERIFICATION_CODE_ENTERED) } @@ -532,23 +558,23 @@ class RegistrationV2ViewModel : ViewModel() { Log.w(TAG, "Session ID was null. TODO: handle this better in the UI.") return } + val e164: String = getCurrentE164() ?: throw IllegalStateException() viewModelScope.launch(context = coroutineExceptionHandler) { val registrationData = getRegistrationData(code) - val verificationResponse = RegistrationRepository.submitVerificationCode(context, e164, password, sessionId, registrationData).successOrThrow() + val verificationResponse = RegistrationRepository.submitVerificationCode(context, e164, password, sessionId, registrationData) - if (!verificationResponse.body.verified) { + if (!verificationResponse.isSuccess()) { Log.w(TAG, "Could not verify code!") - // TODO [regv2]: error handling + handleSessionStateResult(context, verificationResponse, RegistrationRepository.Mode.NONE, submissionErrorHandler) return@launch } setRegistrationCheckpoint(RegistrationCheckpoint.VERIFICATION_CODE_VALIDATED) - val registrationResponse = RegistrationRepository.registerAccount(context, sessionId, registrationData).successOrThrow() - // TODO [regv2]: error handling - onSuccessfulRegistration(context, registrationData, registrationResponse, false) + val registrationResponse: RegisterAccountResult = RegistrationRepository.registerAccount(context, sessionId, registrationData) + handleRegistrationResult(context, registrationData, registrationResponse, false, registrationErrorHandler) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/entercode/EnterCodeV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/entercode/EnterCodeV2Fragment.kt index 439afaf155..87e1495047 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/entercode/EnterCodeV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/entercode/EnterCodeV2Fragment.kt @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.registration.fragments.ContactSupportBottomShe import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView import org.thoughtcrime.securesms.registration.fragments.SignalStrengthPhoneStateListener import org.thoughtcrime.securesms.registration.v2.data.RegistrationRepository +import org.thoughtcrime.securesms.registration.v2.data.network.RegisterAccountResult import org.thoughtcrime.securesms.registration.v2.data.network.VerificationCodeRequestResult import org.thoughtcrime.securesms.registration.v2.ui.RegistrationCheckpoint import org.thoughtcrime.securesms.registration.v2.ui.RegistrationV2ViewModel @@ -69,7 +70,7 @@ class EnterCodeV2Fragment : LoggingFragment(R.layout.fragment_registration_enter } binding.code.setOnCompleteListener { - sharedViewModel.verifyCodeWithoutRegistrationLock(requireContext(), it) + sharedViewModel.verifyCodeWithoutRegistrationLock(requireContext(), it, ::handleSessionErrorResponse, ::handleRegistrationErrorResponse) } binding.havingTroubleButton.setOnClickListener { @@ -79,14 +80,14 @@ class EnterCodeV2Fragment : LoggingFragment(R.layout.fragment_registration_enter binding.callMeCountDown.apply { setTextResources(R.string.RegistrationActivity_call, R.string.RegistrationActivity_call_me_instead_available_in) setOnClickListener { - sharedViewModel.requestVerificationCall(requireContext(), ::handleErrorResponse) + sharedViewModel.requestVerificationCall(requireContext(), ::handleSessionErrorResponse) } } binding.resendSmsCountDown.apply { setTextResources(R.string.RegistrationActivity_resend_code, R.string.RegistrationActivity_resend_sms_available_in) setOnClickListener { - sharedViewModel.requestSmsCode(requireContext(), ::handleErrorResponse) + sharedViewModel.requestSmsCode(requireContext(), ::handleSessionErrorResponse) } } @@ -111,7 +112,7 @@ class EnterCodeV2Fragment : LoggingFragment(R.layout.fragment_registration_enter } } - private fun handleErrorResponse(requestResult: VerificationCodeRequestResult, mode: RegistrationRepository.Mode) { + private fun handleSessionErrorResponse(requestResult: VerificationCodeRequestResult, mode: RegistrationRepository.Mode) { when (requestResult) { is VerificationCodeRequestResult.Success -> binding.keyboard.displaySuccess() is VerificationCodeRequestResult.RateLimited -> { @@ -149,6 +150,49 @@ class EnterCodeV2Fragment : LoggingFragment(R.layout.fragment_registration_enter } } + private fun handleRegistrationErrorResponse(result: RegisterAccountResult) { + when (result) { + is RegisterAccountResult.Success -> Log.d(TAG, "Register account was successful.") + is RegisterAccountResult.AuthorizationFailed -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_error_connecting_to_service)) + is RegisterAccountResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_error_connecting_to_service)) + is RegisterAccountResult.RegistrationLocked -> { + Log.w(TAG, "Account is registration locked, cannot register.") + findNavController().safeNavigate(EnterCodeV2FragmentDirections.actionRequireKbsLockPin(result.timeRemaining)) + } + is RegisterAccountResult.UnknownError -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_error_connecting_to_service)) + is RegisterAccountResult.ValidationError -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_error_connecting_to_service)) + is RegisterAccountResult.IncorrectRecoveryPassword -> { + Log.w(TAG, "User somehow got recovery password error while entering code. This is very suspicious!") + sharedViewModel.setUserSkippedReRegisterFlow(true) + popBackStack() + } + + is RegisterAccountResult.AttemptsExhausted, + is RegisterAccountResult.RateLimited -> presentRateLimitedDialog() + } + } + + private fun presentRateLimitedDialog() { + binding.keyboard.displayFailure().addListener( + object : AssertedSuccessListener() { + override fun onSuccess(result: Boolean?) { + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.RegistrationActivity_too_many_attempts) + setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later) + setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> + binding.callMeCountDown.visibility = View.VISIBLE + binding.resendSmsCountDown.visibility = View.VISIBLE + binding.wrongNumber.visibility = View.VISIBLE + binding.code.clear() + binding.keyboard.displayKeyboard() + } + show() + } + } + } + ) + } + private fun presentRemoteErrorDialog(message: String, title: String? = null, positiveButtonListener: DialogInterface.OnClickListener? = null) { MaterialAlertDialogBuilder(requireContext()).apply { title?.let { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/reregisterwithpin/ReRegisterWithPinV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/reregisterwithpin/ReRegisterWithPinV2Fragment.kt index f9bc892c23..6aee34483d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/reregisterwithpin/ReRegisterWithPinV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/v2/ui/reregisterwithpin/ReRegisterWithPinV2Fragment.kt @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.lock.v2.PinKeyboardType import org.thoughtcrime.securesms.lock.v2.SvrConstants import org.thoughtcrime.securesms.registration.fragments.BaseRegistrationLockFragment import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate +import org.thoughtcrime.securesms.registration.v2.data.network.RegisterAccountResult import org.thoughtcrime.securesms.registration.v2.ui.RegistrationCheckpoint import org.thoughtcrime.securesms.registration.v2.ui.RegistrationV2State import org.thoughtcrime.securesms.registration.v2.ui.RegistrationV2ViewModel @@ -125,9 +126,14 @@ class ReRegisterWithPinV2Fragment : LoggingFragment(R.layout.fragment_registrati registrationViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PIN_CONFIRMED) - registrationViewModel.verifyReRegisterWithPin(requireContext(), pin) { - reRegisterViewModel.markIncorrectGuess() - } + registrationViewModel.verifyReRegisterWithPin( + context = requireContext(), + pin = pin, + wrongPinHandler = { + reRegisterViewModel.markIncorrectGuess() + }, + registrationErrorHandler = ::registrationErrorHandler + ) // TODO [regv2]: check for registration lock + wrong pin and decrement SVR tries remaining } @@ -233,6 +239,15 @@ class ReRegisterWithPinV2Fragment : LoggingFragment(R.layout.fragment_registrati registrationViewModel.setUserSkippedReRegisterFlow(true) } + private fun presentRateLimitedDialog() { + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.RegistrationActivity_too_many_attempts) + setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later) + setPositiveButton(android.R.string.ok, null) + show() + } + } + private fun genericErrorDialog() { MaterialAlertDialogBuilder(requireContext()) .setMessage(R.string.RegistrationActivity_error_connecting_to_service) @@ -240,4 +255,26 @@ class ReRegisterWithPinV2Fragment : LoggingFragment(R.layout.fragment_registrati .create() .show() } + + private fun registrationErrorHandler(result: RegisterAccountResult) { + when (result) { + is RegisterAccountResult.Success -> Log.d(TAG, "Register account was successful.") + is RegisterAccountResult.AuthorizationFailed, + is RegisterAccountResult.MalformedRequest, + is RegisterAccountResult.UnknownError, + is RegisterAccountResult.ValidationError, + is RegisterAccountResult.RegistrationLocked -> { + Log.i(TAG, "Registration failed.", result.getCause()) + genericErrorDialog() + } + + is RegisterAccountResult.IncorrectRecoveryPassword -> { + registrationViewModel.setUserSkippedReRegisterFlow(true) + findNavController().safeNavigate(ReRegisterWithPinV2FragmentDirections.actionReRegisterWithPinFragmentToEnterPhoneNumberV2Fragment()) + } + + is RegisterAccountResult.AttemptsExhausted, + is RegisterAccountResult.RateLimited -> presentRateLimitedDialog() + } + } } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 2106095746..a5f3e60304 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -317,10 +317,6 @@ public class SignalServiceAccountManager { } } - public @Nonnull VerifyAccountResponse registerAccountV2(@Nullable String sessionId, @Nullable String recoveryPassword, AccountAttributes attributes, PreKeyCollection aciPreKeys, PreKeyCollection pniPreKeys, String fcmToken, boolean skipDeviceTransfer) throws IOException { - return pushServiceSocket.submitRegistrationRequest(sessionId, recoveryPassword, attributes, aciPreKeys, pniPreKeys, fcmToken, skipDeviceTransfer); - } - public @Nonnull ServiceResponse changeNumber(@Nonnull ChangePhoneNumberRequest changePhoneNumberRequest) { try { VerifyAccountResponse response = this.pushServiceSocket.changeNumber(changePhoneNumberRequest);