From 782c0a7c86861ab7d03ea7401ed111cd843d2521 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Tue, 8 Sep 2026 22:15:32 -0400 Subject: [PATCH] Consider numberless unsolvable reglock to be invalid. --- .../SignalLoginCredentialEntryViewModel.kt | 19 ++++-------- .../registration/RegistrationEndToEndTest.kt | 30 ------------------- ...SignalLoginCredentialEntryViewModelTest.kt | 14 ++++----- 3 files changed, 11 insertions(+), 52 deletions(-) diff --git a/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryViewModel.kt b/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryViewModel.kt index 5c8484bae1..9ed976042d 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryViewModel.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryViewModel.kt @@ -225,19 +225,12 @@ class SignalLoginCredentialEntryViewModel( stateEmitter(inputState.copy(isLoggingIn = false, areCredentialsIncorrect = true)) } is RegisterAccountError.RegistrationLock -> { - if (provideRegistrationLock) { - Log.w(TAG, "[Next] Still registration locked after providing the reglock token derived from the recovery key. Falling back to PIN entry.") - stateEmitter(inputState.copy(isLoggingIn = false)) - parentEventEmitter.navigateTo( - RegistrationRoute.PinEntryForRegistrationLock( - timeRemaining = error.data.timeRemaining, - svrCredentials = error.data.svr2Credentials - ) - ) - } else { - Log.w(TAG, "[Next] Registration locked. Retrying with the reglock token derived from the recovery key.") - attemptToLogIn(inputState, aci, aep, totp, provideRegistrationLock = true, parentEventEmitter, stateEmitter) - } + // An account with no phone number can't have a registration lock enabled in the first place, and the only + // PIN that could clear one is behind a phone number we don't have. There is nothing to fall back to. + check(!provideRegistrationLock) { "[Next] Still registration locked after providing the reglock derived from the recovery key. A phone-numberless account cannot be registration locked!" } + + Log.w(TAG, "[Next] Registration locked. Retrying with the reglock token derived from the recovery key.") + attemptToLogIn(inputState, aci, aep, totp, provideRegistrationLock = true, parentEventEmitter, stateEmitter) } is RegisterAccountError.RateLimited -> { Log.w(TAG, "[Next] Rate limited (retryAfter: ${error.retryAfter}).") diff --git a/feature/registration/src/test/java/org/signal/registration/RegistrationEndToEndTest.kt b/feature/registration/src/test/java/org/signal/registration/RegistrationEndToEndTest.kt index d30d009128..0d079f1f40 100644 --- a/feature/registration/src/test/java/org/signal/registration/RegistrationEndToEndTest.kt +++ b/feature/registration/src/test/java/org/signal/registration/RegistrationEndToEndTest.kt @@ -1895,36 +1895,6 @@ class RegistrationEndToEndTest { } } - @Test - fun `a reglock the entered recovery key cannot derive falls back to asking for the pin`() { - enableSignalLoginRegistration() - val login = SignalLogin(ACI.from(UUID.randomUUID()), AccountEntropyPool.generate()) - - networkController.onRegisterAccount = { - RequestResult.NonSuccess( - RegisterAccountError.RegistrationLock( - RegistrationLockResponse( - timeRemaining = 14.days.inWholeMilliseconds, - svr2Credentials = SvrCredentials(username = "svr-user", password = "svr-pass") - ) - ) - ) - } - - launchRegistrationFlow() - - startSignalLoginRegistration() - useExistingSignalLogin() - enterSignalLogin(login) - - // The account's reglock isn't the one the recovery key derives, so the PIN behind it is the only way in - waitForTag(TestTags.PIN_ENTRY_SCREEN) - assert(networkController.lastRegisterAccountRequest?.registrationLock == login.aep.deriveMasterKey().deriveRegistrationLock()) { - "Expected the derived reglock to have been tried before falling back but was ${networkController.lastRegisterAccountRequest}" - } - assert(storageController.committedData == null) { "Expected no registration data to be committed while still locked out" } - } - @Test fun `restoring a remote backup after logging in with a signal login completes registration without a pin`() { enableSignalLoginRegistration() diff --git a/feature/registration/src/test/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryViewModelTest.kt b/feature/registration/src/test/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryViewModelTest.kt index 0daf06e4af..61bd364b58 100644 --- a/feature/registration/src/test/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryViewModelTest.kt +++ b/feature/registration/src/test/java/org/signal/registration/screens/signallogincredentials/SignalLoginCredentialEntryViewModelTest.kt @@ -5,6 +5,7 @@ package org.signal.registration.screens.signallogincredentials +import assertk.assertFailure import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.hasSize @@ -411,18 +412,13 @@ class SignalLoginCredentialEntryViewModelTest { } @Test - fun `NextClicked still registration locked after providing the reglock token falls back to PIN entry`() = runTest(testDispatcher) { + fun `NextClicked still registration locked after providing the reglock token throws, since there is nothing to fall back to`() = runTest(testDispatcher) { coEvery { mockRepository.reRegisterAccountWithoutPhoneNumber(any(), any(), any(), any(), any()) } returns RequestResult.NonSuccess(RegisterAccountError.RegistrationLock(registrationLockResponse())) - applyEvent(completeState(), SignalLoginCredentialEntryScreenEvents.NextClicked) - - assertThat(emittedStates.last().isLoggingIn).isEqualTo(false) - assertThat(emittedParentEvents).hasSize(2) - assertThat(emittedParentEvents[1]) - .isInstanceOf() - .prop(RegistrationFlowEvent.NavigateToScreen::route) - .isInstanceOf() + assertFailure { + applyEvent(completeState(), SignalLoginCredentialEntryScreenEvents.NextClicked) + }.isInstanceOf() } @Test