From 26ab20f86035d082f1da37173fac9dd270a8f3b2 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 19 Feb 2026 10:53:59 -0400 Subject: [PATCH] Fix possible captcha race. --- .../registration/ui/RegistrationViewModel.kt | 22 ++++++++++++++++--- .../phonenumber/EnterPhoneNumberFragment.kt | 1 + 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt index 888c1606a8..1257292f63 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt @@ -458,8 +458,8 @@ class RegistrationViewModel : ViewModel() { } fun submitCaptchaToken(context: Context) { - val e164 = getCurrentE164() ?: throw IllegalStateException("Can't submit captcha token if no phone number is set!") - val captchaToken = store.value.captchaToken ?: throw IllegalStateException("Can't submit captcha token if no captcha token is set!") + val e164 = getCurrentE164() ?: return clearChallengesAndBail { Log.w(TAG, "Phone number was null when trying to submit captcha token.") } + val captchaToken = store.value.captchaToken ?: return bail { Log.w(TAG, "Captcha token was null when trying to submit captcha token.") } store.update { it.copy(captchaToken = null, challengeInProgress = true, inProgress = true) @@ -486,7 +486,7 @@ class RegistrationViewModel : ViewModel() { fun requestAndSubmitPushToken(context: Context) { Log.v(TAG, "validatePushToken()") - val e164 = getCurrentE164() ?: throw IllegalStateException("Can't submit captcha token if no phone number is set!") + val e164 = getCurrentE164() ?: return clearChallengesAndBail { Log.w(TAG, "Phone number was null when trying to submit push token.") } viewModelScope.launch { Log.d(TAG, "Getting session in order to perform push token verification…") @@ -1063,6 +1063,22 @@ class RegistrationViewModel : ViewModel() { setInProgress(false) } + /** + * Like [bail], but also clears challenge state. This is needed when challenge handling fails due to missing phone number, + * since otherwise the stale challenges would re-trigger the observer on every config change. + */ + private fun clearChallengesAndBail(logMessage: () -> Unit) { + logMessage() + store.update { + it.copy( + inProgress = false, + challengesRequested = emptyList(), + challengeInProgress = false, + captchaToken = null + ) + } + } + fun registerWithBackupKey(context: Context, backupKey: String, e164: String?, pin: String?, aciIdentityKeyPair: IdentityKeyPair?, pniIdentityKeyPair: IdentityKeyPair?) { setInProgress(true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberFragment.kt index 18c5bd7ae2..79a74f0540 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/phonenumber/EnterPhoneNumberFragment.kt @@ -600,6 +600,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_ private fun updateEnabledControls(showProgress: Boolean, isReRegister: Boolean) { binding.countryCode.isEnabled = !showProgress binding.number.isEnabled = !showProgress + countryPickerView.isEnabled = !showProgress binding.cancelButton.visible = !showProgress && isReRegister }