mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-15 07:28:30 +00:00
Add basic re-reg support to regV5.
This commit is contained in:
@@ -289,6 +289,9 @@ class RealNetworkController(
|
|||||||
fcmToken: String?,
|
fcmToken: String?,
|
||||||
skipDeviceTransfer: Boolean
|
skipDeviceTransfer: Boolean
|
||||||
): RegistrationNetworkResult<RegisterAccountResponse, RegisterAccountError> = withContext(Dispatchers.IO) {
|
): RegistrationNetworkResult<RegisterAccountResponse, RegisterAccountError> = withContext(Dispatchers.IO) {
|
||||||
|
check(sessionId != null || recoveryPassword != null) { "Either sessionId or recoveryPassword must be provided" }
|
||||||
|
check(sessionId == null || recoveryPassword == null) { "Either sessionId or recoveryPassword must be provided, but not both" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val serviceAttributes = attributes.toServiceAccountAttributes()
|
val serviceAttributes = attributes.toServiceAccountAttributes()
|
||||||
val serviceAciPreKeys = aciPreKeys.toServicePreKeyCollection()
|
val serviceAciPreKeys = aciPreKeys.toServicePreKeyCollection()
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ class PinSettingsViewModel(
|
|||||||
),
|
),
|
||||||
name = null,
|
name = null,
|
||||||
pniRegistrationId = RegistrationPreferences.pniRegistrationId,
|
pniRegistrationId = RegistrationPreferences.pniRegistrationId,
|
||||||
recoveryPassword = null
|
recoveryPassword = RegistrationPreferences.masterKey?.deriveRegistrationRecoveryPassword()
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val result = networkController.setAccountAttributes(attributes)) {
|
when (val result = networkController.setAccountAttributes(attributes)) {
|
||||||
|
|||||||
@@ -28,12 +28,6 @@ class RegistrationActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val viewModel: RegistrationViewModel by viewModels(factoryProducer = {
|
|
||||||
RegistrationViewModel.Factory(
|
|
||||||
repository = repository
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|||||||
@@ -5,44 +5,24 @@
|
|||||||
|
|
||||||
package org.signal.registration
|
package org.signal.registration
|
||||||
|
|
||||||
import android.os.Parcel
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.parcelize.Parceler
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import kotlinx.parcelize.TypeParceler
|
import kotlinx.parcelize.TypeParceler
|
||||||
import org.signal.core.models.AccountEntropyPool
|
import org.signal.core.models.AccountEntropyPool
|
||||||
import org.signal.core.models.MasterKey
|
import org.signal.core.models.MasterKey
|
||||||
|
import org.signal.registration.util.AccountEntropyPoolParceler
|
||||||
|
import org.signal.registration.util.MasterKeyParceler
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@TypeParceler<MasterKey?, MasterKeyParceler>
|
@TypeParceler<MasterKey?, MasterKeyParceler>
|
||||||
@TypeParceler<AccountEntropyPool?, AepParceler>
|
@TypeParceler<AccountEntropyPool?, AccountEntropyPoolParceler>
|
||||||
data class RegistrationFlowState(
|
data class RegistrationFlowState(
|
||||||
val backStack: List<RegistrationRoute> = listOf(RegistrationRoute.Welcome),
|
val backStack: List<RegistrationRoute> = listOf(RegistrationRoute.Welcome),
|
||||||
val sessionMetadata: NetworkController.SessionMetadata? = null,
|
val sessionMetadata: NetworkController.SessionMetadata? = null,
|
||||||
val sessionE164: String? = null,
|
val sessionE164: String? = null,
|
||||||
val accountEntropyPool: AccountEntropyPool? = null,
|
val accountEntropyPool: AccountEntropyPool? = null,
|
||||||
val temporaryMasterKey: MasterKey? = null,
|
val temporaryMasterKey: MasterKey? = null,
|
||||||
val registrationLockProof: String? = null
|
val registrationLockProof: String? = null,
|
||||||
|
val preExistingRegistrationData: PreExistingRegistrationData? = null
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
object MasterKeyParceler : Parceler<MasterKey?> {
|
|
||||||
override fun create(parcel: Parcel): MasterKey? {
|
|
||||||
val bytes = parcel.createByteArray()
|
|
||||||
return bytes?.let { MasterKey(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun MasterKey?.write(parcel: Parcel, flags: Int) {
|
|
||||||
parcel.writeByteArray(this?.serialize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object AepParceler : Parceler<AccountEntropyPool?> {
|
|
||||||
override fun create(parcel: Parcel): AccountEntropyPool? {
|
|
||||||
val aep = parcel.readString()
|
|
||||||
return aep?.let { AccountEntropyPool(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun AccountEntropyPool?.write(parcel: Parcel, flags: Int) {
|
|
||||||
parcel.writeString(this?.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -108,6 +108,30 @@ class RegistrationRepository(val networkController: NetworkController, val stora
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new account using a recovery password derived from the user's [MasterKey].
|
||||||
|
*
|
||||||
|
* This method:
|
||||||
|
* 1. Generates and stores all required cryptographic key material
|
||||||
|
* 2. Creates account attributes with registration IDs and capabilities
|
||||||
|
* 3. Calls the network controller to register the account
|
||||||
|
* 4. On success, saves the registration data to persistent storage
|
||||||
|
*
|
||||||
|
* @param e164 The phone number in E.164 format (used for basic auth)
|
||||||
|
* @param recoveryPassword The recovery password, derived from the user's [MasterKey], which allows us to forgo session creation.
|
||||||
|
* @param registrationLock The registration lock token derived from the master key (if unlocking a reglocked account)
|
||||||
|
* @param skipDeviceTransfer Whether to skip device transfer flow
|
||||||
|
* @return The registration result containing account information or an error
|
||||||
|
*/
|
||||||
|
suspend fun registerAccountWithRecoveryPassword(
|
||||||
|
e164: String,
|
||||||
|
recoveryPassword: String,
|
||||||
|
registrationLock: String? = null,
|
||||||
|
skipDeviceTransfer: Boolean = true
|
||||||
|
): RegistrationNetworkResult<Pair<RegisterAccountResponse, KeyMaterial>, RegisterAccountError> = withContext(Dispatchers.IO) {
|
||||||
|
registerAccount(e164, sessionId = null, recoveryPassword, registrationLock, skipDeviceTransfer)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new account after successful phone number verification.
|
* Registers a new account after successful phone number verification.
|
||||||
*
|
*
|
||||||
@@ -123,12 +147,41 @@ class RegistrationRepository(val networkController: NetworkController, val stora
|
|||||||
* @param skipDeviceTransfer Whether to skip device transfer flow
|
* @param skipDeviceTransfer Whether to skip device transfer flow
|
||||||
* @return The registration result containing account information or an error
|
* @return The registration result containing account information or an error
|
||||||
*/
|
*/
|
||||||
suspend fun registerAccount(
|
suspend fun registerAccountWithSession(
|
||||||
e164: String,
|
e164: String,
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
registrationLock: String? = null,
|
registrationLock: String? = null,
|
||||||
skipDeviceTransfer: Boolean = true
|
skipDeviceTransfer: Boolean = true
|
||||||
): RegistrationNetworkResult<Pair<RegisterAccountResponse, KeyMaterial>, RegisterAccountError> = withContext(Dispatchers.IO) {
|
): RegistrationNetworkResult<Pair<RegisterAccountResponse, KeyMaterial>, RegisterAccountError> = withContext(Dispatchers.IO) {
|
||||||
|
registerAccount(e164, sessionId, recoveryPassword = null, registrationLock, skipDeviceTransfer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new account.
|
||||||
|
*
|
||||||
|
* This method:
|
||||||
|
* 1. Generates and stores all required cryptographic key material
|
||||||
|
* 2. Creates account attributes with registration IDs and capabilities
|
||||||
|
* 3. Calls the network controller to register the account
|
||||||
|
* 4. On success, saves the registration data to persistent storage
|
||||||
|
*
|
||||||
|
* @param e164 The phone number in E.164 format (used for basic auth)
|
||||||
|
* @param sessionId The verified session ID from phone number verification. Must provide if you're not using [recoveryPassword].
|
||||||
|
* @param recoveryPassword The recovery password, derived from the user's [MasterKey], which allows us to forgo session creation. Must provide if you're not using [sessionId].
|
||||||
|
* @param registrationLock The registration lock token derived from the master key (if unlocking a reglocked account)
|
||||||
|
* @param skipDeviceTransfer Whether to skip device transfer flow
|
||||||
|
* @return The registration result containing account information or an error
|
||||||
|
*/
|
||||||
|
private suspend fun registerAccount(
|
||||||
|
e164: String,
|
||||||
|
sessionId: String?,
|
||||||
|
recoveryPassword: String?,
|
||||||
|
registrationLock: String? = null,
|
||||||
|
skipDeviceTransfer: Boolean = true
|
||||||
|
): RegistrationNetworkResult<Pair<RegisterAccountResponse, KeyMaterial>, RegisterAccountError> = withContext(Dispatchers.IO) {
|
||||||
|
check(sessionId != null || recoveryPassword != null) { "Either sessionId or recoveryPassword must be provided" }
|
||||||
|
check(sessionId == null || recoveryPassword == null) { "Either sessionId or recoveryPassword must be provided, but not both" }
|
||||||
|
|
||||||
val keyMaterial = storageController.generateAndStoreKeyMaterial()
|
val keyMaterial = storageController.generateAndStoreKeyMaterial()
|
||||||
val fcmToken = networkController.getFcmToken()
|
val fcmToken = networkController.getFcmToken()
|
||||||
|
|
||||||
@@ -150,7 +203,7 @@ class RegistrationRepository(val networkController: NetworkController, val stora
|
|||||||
),
|
),
|
||||||
name = null,
|
name = null,
|
||||||
pniRegistrationId = keyMaterial.pniRegistrationId,
|
pniRegistrationId = keyMaterial.pniRegistrationId,
|
||||||
recoveryPassword = null
|
recoveryPassword = keyMaterial.accountEntropyPool.deriveMasterKey().deriveRegistrationRecoveryPassword()
|
||||||
)
|
)
|
||||||
|
|
||||||
val aciPreKeys = PreKeyCollection(
|
val aciPreKeys = PreKeyCollection(
|
||||||
@@ -169,7 +222,7 @@ class RegistrationRepository(val networkController: NetworkController, val stora
|
|||||||
e164 = e164,
|
e164 = e164,
|
||||||
password = keyMaterial.servicePassword,
|
password = keyMaterial.servicePassword,
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
recoveryPassword = null,
|
recoveryPassword = recoveryPassword,
|
||||||
attributes = accountAttributes,
|
attributes = accountAttributes,
|
||||||
aciPreKeys = aciPreKeys,
|
aciPreKeys = aciPreKeys,
|
||||||
pniPreKeys = pniPreKeys,
|
pniPreKeys = pniPreKeys,
|
||||||
@@ -205,4 +258,8 @@ class RegistrationRepository(val networkController: NetworkController, val stora
|
|||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getPreExistingRegistrationData(): PreExistingRegistrationData? {
|
||||||
|
return storageController.getPreExistingRegistrationData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import androidx.lifecycle.SavedStateHandle
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.createSavedStateHandle
|
import androidx.lifecycle.createSavedStateHandle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.CreationExtras
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.signal.core.ui.navigation.ResultEventBus
|
import org.signal.core.ui.navigation.ResultEventBus
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@@ -34,6 +36,14 @@ class RegistrationViewModel(private val repository: RegistrationRepository, save
|
|||||||
|
|
||||||
val resultBus = ResultEventBus()
|
val resultBus = ResultEventBus()
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
repository.getPreExistingRegistrationData()?.let {
|
||||||
|
_state.value = _state.value.copy(preExistingRegistrationData = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onEvent(event: RegistrationFlowEvent) {
|
fun onEvent(event: RegistrationFlowEvent) {
|
||||||
_state.value = applyEvent(_state.value, event)
|
_state.value = applyEvent(_state.value, event)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
|
|
||||||
package org.signal.registration
|
package org.signal.registration
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import kotlinx.parcelize.TypeParceler
|
||||||
import org.signal.core.models.AccountEntropyPool
|
import org.signal.core.models.AccountEntropyPool
|
||||||
import org.signal.core.models.MasterKey
|
import org.signal.core.models.MasterKey
|
||||||
import org.signal.core.models.ServiceId.ACI
|
import org.signal.core.models.ServiceId.ACI
|
||||||
@@ -12,6 +15,12 @@ import org.signal.core.models.ServiceId.PNI
|
|||||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||||
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
||||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||||
|
import org.signal.registration.util.ACIParceler
|
||||||
|
import org.signal.registration.util.AccountEntropyPoolParceler
|
||||||
|
import org.signal.registration.util.IdentityKeyPairParceler
|
||||||
|
import org.signal.registration.util.KyberPreKeyRecordParceler
|
||||||
|
import org.signal.registration.util.PNIParceler
|
||||||
|
import org.signal.registration.util.SignedPreKeyRecordParceler
|
||||||
|
|
||||||
interface StorageController {
|
interface StorageController {
|
||||||
|
|
||||||
@@ -63,6 +72,11 @@ interface StorageController {
|
|||||||
/**
|
/**
|
||||||
* Container for all cryptographic key material generated during registration.
|
* Container for all cryptographic key material generated during registration.
|
||||||
*/
|
*/
|
||||||
|
@Parcelize
|
||||||
|
@TypeParceler<IdentityKeyPair, IdentityKeyPairParceler>
|
||||||
|
@TypeParceler<SignedPreKeyRecord, SignedPreKeyRecordParceler>
|
||||||
|
@TypeParceler<KyberPreKeyRecord, KyberPreKeyRecordParceler>
|
||||||
|
@TypeParceler<AccountEntropyPool, AccountEntropyPoolParceler>
|
||||||
data class KeyMaterial(
|
data class KeyMaterial(
|
||||||
/** Identity key pair for the Account Identity (ACI). */
|
/** Identity key pair for the Account Identity (ACI). */
|
||||||
val aciIdentityKeyPair: IdentityKeyPair,
|
val aciIdentityKeyPair: IdentityKeyPair,
|
||||||
@@ -86,7 +100,7 @@ data class KeyMaterial(
|
|||||||
val servicePassword: String,
|
val servicePassword: String,
|
||||||
/** Account entropy pool for key derivation. */
|
/** Account entropy pool for key derivation. */
|
||||||
val accountEntropyPool: AccountEntropyPool
|
val accountEntropyPool: AccountEntropyPool
|
||||||
)
|
) : Parcelable
|
||||||
|
|
||||||
data class NewRegistrationData(
|
data class NewRegistrationData(
|
||||||
val e164: String,
|
val e164: String,
|
||||||
@@ -96,10 +110,14 @@ data class NewRegistrationData(
|
|||||||
val aep: AccountEntropyPool
|
val aep: AccountEntropyPool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@TypeParceler<AccountEntropyPool, AccountEntropyPoolParceler>
|
||||||
|
@TypeParceler<ACI, ACIParceler>
|
||||||
|
@TypeParceler<PNI, PNIParceler>
|
||||||
data class PreExistingRegistrationData(
|
data class PreExistingRegistrationData(
|
||||||
val e164: String,
|
val e164: String,
|
||||||
val aci: ACI,
|
val aci: ACI,
|
||||||
val pni: PNI,
|
val pni: PNI,
|
||||||
val servicePassword: String,
|
val servicePassword: String,
|
||||||
val aep: AccountEntropyPool
|
val aep: AccountEntropyPool
|
||||||
)
|
) : Parcelable
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
package org.signal.registration.screens.phonenumber
|
package org.signal.registration.screens.phonenumber
|
||||||
|
|
||||||
import org.signal.registration.NetworkController.SessionMetadata
|
import org.signal.registration.NetworkController.SessionMetadata
|
||||||
|
import org.signal.registration.PreExistingRegistrationData
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
data class PhoneNumberEntryState(
|
data class PhoneNumberEntryState(
|
||||||
@@ -13,9 +14,11 @@ data class PhoneNumberEntryState(
|
|||||||
val countryCode: String = "1",
|
val countryCode: String = "1",
|
||||||
val nationalNumber: String = "",
|
val nationalNumber: String = "",
|
||||||
val formattedNumber: String = "",
|
val formattedNumber: String = "",
|
||||||
|
val sessionE164: String? = null,
|
||||||
val sessionMetadata: SessionMetadata? = null,
|
val sessionMetadata: SessionMetadata? = null,
|
||||||
val showFullScreenSpinner: Boolean = false,
|
val showFullScreenSpinner: Boolean = false,
|
||||||
val oneTimeEvent: OneTimeEvent? = null
|
val oneTimeEvent: OneTimeEvent? = null,
|
||||||
|
val preExistingRegistrationData: PreExistingRegistrationData? = null
|
||||||
) {
|
) {
|
||||||
sealed interface OneTimeEvent {
|
sealed interface OneTimeEvent {
|
||||||
data object NetworkError : OneTimeEvent
|
data object NetworkError : OneTimeEvent
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ import org.signal.registration.RegistrationFlowState
|
|||||||
import org.signal.registration.RegistrationRepository
|
import org.signal.registration.RegistrationRepository
|
||||||
import org.signal.registration.RegistrationRoute
|
import org.signal.registration.RegistrationRoute
|
||||||
import org.signal.registration.screens.phonenumber.PhoneNumberEntryState.OneTimeEvent
|
import org.signal.registration.screens.phonenumber.PhoneNumberEntryState.OneTimeEvent
|
||||||
|
import org.signal.registration.screens.phonenumber.PhoneNumberEntryState.OneTimeEvent.*
|
||||||
import org.signal.registration.screens.util.navigateTo
|
import org.signal.registration.screens.util.navigateTo
|
||||||
|
import org.signal.registration.screens.verificationcode.VerificationCodeState
|
||||||
|
import org.signal.registration.screens.verificationcode.VerificationCodeViewModel
|
||||||
|
|
||||||
class PhoneNumberEntryViewModel(
|
class PhoneNumberEntryViewModel(
|
||||||
val repository: RegistrationRepository,
|
val repository: RegistrationRepository,
|
||||||
@@ -53,7 +56,7 @@ class PhoneNumberEntryViewModel(
|
|||||||
val stateEmitter: (PhoneNumberEntryState) -> Unit = { state ->
|
val stateEmitter: (PhoneNumberEntryState) -> Unit = { state ->
|
||||||
_state.value = state
|
_state.value = state
|
||||||
}
|
}
|
||||||
applyEvent(_state.value, event, stateEmitter, parentEventEmitter)
|
applyEvent(state.value, event, stateEmitter, parentEventEmitter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +89,11 @@ class PhoneNumberEntryViewModel(
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
fun applyParentState(state: PhoneNumberEntryState, parentState: RegistrationFlowState): PhoneNumberEntryState {
|
fun applyParentState(state: PhoneNumberEntryState, parentState: RegistrationFlowState): PhoneNumberEntryState {
|
||||||
return state.copy(sessionMetadata = parentState.sessionMetadata)
|
return state.copy(
|
||||||
|
sessionE164 = parentState.sessionE164,
|
||||||
|
sessionMetadata = parentState.sessionMetadata,
|
||||||
|
preExistingRegistrationData = parentState.preExistingRegistrationData
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyCountryCodeChanged(state: PhoneNumberEntryState, countryCode: String): PhoneNumberEntryState {
|
private fun applyCountryCodeChanged(state: PhoneNumberEntryState, countryCode: String): PhoneNumberEntryState {
|
||||||
@@ -122,15 +129,6 @@ class PhoneNumberEntryViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatNumber(nationalNumber: String): String {
|
|
||||||
formatter.clear()
|
|
||||||
var result = ""
|
|
||||||
for (digit in nationalNumber) {
|
|
||||||
result = formatter.inputDigit(digit)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun applyPhoneNumberSubmitted(
|
private suspend fun applyPhoneNumberSubmitted(
|
||||||
inputState: PhoneNumberEntryState,
|
inputState: PhoneNumberEntryState,
|
||||||
parentEventEmitter: (RegistrationFlowEvent) -> Unit
|
parentEventEmitter: (RegistrationFlowEvent) -> Unit
|
||||||
@@ -138,7 +136,77 @@ class PhoneNumberEntryViewModel(
|
|||||||
val e164 = "+${inputState.countryCode}${inputState.nationalNumber}"
|
val e164 = "+${inputState.countryCode}${inputState.nationalNumber}"
|
||||||
var state = inputState.copy()
|
var state = inputState.copy()
|
||||||
|
|
||||||
// TODO Consider that someone may back into this screen and change the number, requiring us to create a new session.
|
// If we're re-registering for the same number we used to be registered for, we should try to skip right to registration
|
||||||
|
if (state.preExistingRegistrationData?.e164 == e164) {
|
||||||
|
val masterKey = state.preExistingRegistrationData.aep.deriveMasterKey()
|
||||||
|
val recoveryPassword = masterKey.deriveRegistrationRecoveryPassword()
|
||||||
|
val registrationLock = masterKey.deriveRegistrationLock()
|
||||||
|
|
||||||
|
when (val registerResult = repository.registerAccountWithRecoveryPassword(e164, recoveryPassword, registrationLock, skipDeviceTransfer = true)) {
|
||||||
|
is NetworkController.RegistrationNetworkResult.Success -> {
|
||||||
|
val (response, keyMaterial) = registerResult.data
|
||||||
|
|
||||||
|
parentEventEmitter(RegistrationFlowEvent.Registered(keyMaterial.accountEntropyPool))
|
||||||
|
|
||||||
|
if (response.storageCapable) {
|
||||||
|
parentEventEmitter.navigateTo(RegistrationRoute.PinEntryForSvrRestore)
|
||||||
|
} else {
|
||||||
|
parentEventEmitter.navigateTo(RegistrationRoute.PinCreate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is NetworkController.RegistrationNetworkResult.Failure -> {
|
||||||
|
when (registerResult.error) {
|
||||||
|
is NetworkController.RegisterAccountError.SessionNotFoundOrNotVerified -> {
|
||||||
|
Log.w(TAG, "[Register] Got told that our session could not be found when registering with RRP. We should never get into this state. Resetting.")
|
||||||
|
parentEventEmitter(RegistrationFlowEvent.ResetState)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
is NetworkController.RegisterAccountError.DeviceTransferPossible -> {
|
||||||
|
Log.w(TAG, "[Register] Got told a device transfer is possible. We should never get into this state. Resetting.")
|
||||||
|
parentEventEmitter(RegistrationFlowEvent.ResetState)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
is NetworkController.RegisterAccountError.RegistrationLock -> {
|
||||||
|
Log.w(TAG, "[Register] Reglocked.")
|
||||||
|
parentEventEmitter.navigateTo(
|
||||||
|
RegistrationRoute.PinEntryForRegistrationLock(
|
||||||
|
timeRemaining = registerResult.error.data.timeRemaining,
|
||||||
|
svrCredentials = registerResult.error.data.svr2Credentials
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
is NetworkController.RegisterAccountError.RateLimited -> {
|
||||||
|
Log.w(TAG, "[Register] Rate limited.")
|
||||||
|
return state.copy(oneTimeEvent = OneTimeEvent.RateLimited(registerResult.error.retryAfter))
|
||||||
|
}
|
||||||
|
is NetworkController.RegisterAccountError.InvalidRequest -> {
|
||||||
|
Log.w(TAG, "[Register] Invalid request when registering account with RRP. Ditching pre-existing data and continuing with session creation. Message: ${registerResult.error.message}")
|
||||||
|
// TODO should we clear it in the parent state as well?
|
||||||
|
state = state.copy(preExistingRegistrationData = null)
|
||||||
|
}
|
||||||
|
is NetworkController.RegisterAccountError.RegistrationRecoveryPasswordIncorrect -> {
|
||||||
|
Log.w(TAG, "[Register] Registration recovery password incorrect. Ditching pre-existing data and continuing with session creation. Message: ${registerResult.error.message}")
|
||||||
|
// TODO should we clear it in the parent state as well?
|
||||||
|
state = state.copy(preExistingRegistrationData = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is NetworkController.RegistrationNetworkResult.NetworkError -> {
|
||||||
|
Log.w(TAG, "[Register] Network error.", registerResult.exception)
|
||||||
|
return state.copy(oneTimeEvent = OneTimeEvent.NetworkError)
|
||||||
|
}
|
||||||
|
is NetworkController.RegistrationNetworkResult.ApplicationError -> {
|
||||||
|
Log.w(TAG, "[Register] Unknown error when registering account.", registerResult.exception)
|
||||||
|
return state.copy(oneTimeEvent = OneTimeEvent.UnknownError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect if someone backed into this screen and entered a different number
|
||||||
|
if (state.sessionE164 != null && state.sessionE164 != e164) {
|
||||||
|
state = state.copy(sessionMetadata = null)
|
||||||
|
}
|
||||||
|
|
||||||
var sessionMetadata: NetworkController.SessionMetadata = state.sessionMetadata ?: when (val response = this@PhoneNumberEntryViewModel.repository.createSession(e164)) {
|
var sessionMetadata: NetworkController.SessionMetadata = state.sessionMetadata ?: when (val response = this@PhoneNumberEntryViewModel.repository.createSession(e164)) {
|
||||||
is NetworkController.RegistrationNetworkResult.Success<NetworkController.SessionMetadata> -> {
|
is NetworkController.RegistrationNetworkResult.Success<NetworkController.SessionMetadata> -> {
|
||||||
@@ -346,6 +414,15 @@ class PhoneNumberEntryViewModel(
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatNumber(nationalNumber: String): String {
|
||||||
|
formatter.clear()
|
||||||
|
var result = ""
|
||||||
|
for (digit in nationalNumber) {
|
||||||
|
result = formatter.inputDigit(digit)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
val repository: RegistrationRepository,
|
val repository: RegistrationRepository,
|
||||||
val parentState: StateFlow<RegistrationFlowState>,
|
val parentState: StateFlow<RegistrationFlowState>,
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class PinEntryForRegistrationLockViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "[PinEntered] Attempting to register with registration lock token...")
|
Log.d(TAG, "[PinEntered] Attempting to register with registration lock token...")
|
||||||
val registerResult = repository.registerAccount(
|
val registerResult = repository.registerAccountWithSession(
|
||||||
e164 = e164,
|
e164 = e164,
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
registrationLock = registrationLockToken,
|
registrationLock = registrationLockToken,
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class VerificationCodeViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to register
|
// Attempt to register
|
||||||
val registerResult = repository.registerAccount(e164 = state.e164, sessionId = sessionMetadata.id, skipDeviceTransfer = true)
|
val registerResult = repository.registerAccountWithSession(e164 = state.e164, sessionId = sessionMetadata.id, skipDeviceTransfer = true)
|
||||||
|
|
||||||
return when (registerResult) {
|
return when (registerResult) {
|
||||||
is NetworkController.RegistrationNetworkResult.Success -> {
|
is NetworkController.RegistrationNetworkResult.Success -> {
|
||||||
@@ -174,8 +174,9 @@ class VerificationCodeViewModel(
|
|||||||
state.copy(oneTimeEvent = OneTimeEvent.RegistrationError)
|
state.copy(oneTimeEvent = OneTimeEvent.RegistrationError)
|
||||||
}
|
}
|
||||||
is NetworkController.RegisterAccountError.RegistrationRecoveryPasswordIncorrect -> {
|
is NetworkController.RegisterAccountError.RegistrationRecoveryPasswordIncorrect -> {
|
||||||
Log.w(TAG, "[Register] Registration recovery password incorrect: ${registerResult.error.message}")
|
Log.w(TAG, "[Register] Got told the registration recovery password incorrect. We don't use the RRP in this flow, and should never get this error. Resetting. Message: ${registerResult.error.message}")
|
||||||
state.copy(oneTimeEvent = OneTimeEvent.RegistrationError)
|
parentEventEmitter(RegistrationFlowEvent.ResetState)
|
||||||
|
state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.registration.util
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import kotlinx.parcelize.Parceler
|
||||||
|
import org.signal.core.models.ServiceId
|
||||||
|
|
||||||
|
class ACIParceler : Parceler<ServiceId.ACI> {
|
||||||
|
override fun ServiceId.ACI.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeByteArray(this.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(parcel: Parcel): ServiceId.ACI {
|
||||||
|
return ServiceId.ACI.parseOrThrow(parcel.createByteArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.registration.util
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import kotlinx.parcelize.Parceler
|
||||||
|
import org.signal.core.models.AccountEntropyPool
|
||||||
|
|
||||||
|
object AccountEntropyPoolParceler : Parceler<AccountEntropyPool?> {
|
||||||
|
override fun create(parcel: Parcel): AccountEntropyPool? {
|
||||||
|
val aep = parcel.readString()
|
||||||
|
return aep?.let { AccountEntropyPool(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun AccountEntropyPool?.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(this?.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.registration.util
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import kotlinx.parcelize.Parceler
|
||||||
|
import org.signal.libsignal.protocol.IdentityKey
|
||||||
|
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||||
|
import org.signal.libsignal.protocol.ecc.ECPrivateKey
|
||||||
|
|
||||||
|
class IdentityKeyPairParceler : Parceler<IdentityKeyPair> {
|
||||||
|
override fun IdentityKeyPair.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeByteArray(publicKey.serialize())
|
||||||
|
parcel.writeByteArray(privateKey.serialize())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(parcel: Parcel): IdentityKeyPair {
|
||||||
|
return IdentityKeyPair(
|
||||||
|
IdentityKey(parcel.createByteArray()!!),
|
||||||
|
ECPrivateKey(parcel.createByteArray()!!)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.registration.util
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import kotlinx.parcelize.Parceler
|
||||||
|
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
||||||
|
|
||||||
|
class KyberPreKeyRecordParceler : Parceler<KyberPreKeyRecord> {
|
||||||
|
override fun KyberPreKeyRecord.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeByteArray(this.serialize())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(parcel: Parcel): KyberPreKeyRecord {
|
||||||
|
return KyberPreKeyRecord(parcel.createByteArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.registration.util
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import kotlinx.parcelize.Parceler
|
||||||
|
import org.signal.core.models.MasterKey
|
||||||
|
|
||||||
|
object MasterKeyParceler : Parceler<MasterKey?> {
|
||||||
|
override fun create(parcel: Parcel): MasterKey? {
|
||||||
|
val bytes = parcel.createByteArray()
|
||||||
|
return bytes?.let { MasterKey(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun MasterKey?.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeByteArray(this?.serialize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.registration.util
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import kotlinx.parcelize.Parceler
|
||||||
|
import org.signal.core.models.ServiceId
|
||||||
|
|
||||||
|
class PNIParceler : Parceler<ServiceId.PNI> {
|
||||||
|
override fun ServiceId.PNI.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeByteArray(this.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(parcel: Parcel): ServiceId.PNI {
|
||||||
|
return ServiceId.PNI.parseOrThrow(parcel.createByteArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.registration.util
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import kotlinx.parcelize.Parceler
|
||||||
|
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||||
|
|
||||||
|
class SignedPreKeyRecordParceler : Parceler<SignedPreKeyRecord> {
|
||||||
|
override fun SignedPreKeyRecord.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeByteArray(this.serialize())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(parcel: Parcel): SignedPreKeyRecord {
|
||||||
|
return SignedPreKeyRecord(parcel.createByteArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ class PinEntryForRegistrationLockViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(registerResponse to keyMaterial)
|
NetworkController.RegistrationNetworkResult.Success(registerResponse to keyMaterial)
|
||||||
|
|
||||||
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), stateEmitter, parentEventEmitter)
|
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), stateEmitter, parentEventEmitter)
|
||||||
@@ -200,7 +200,7 @@ class PinEntryForRegistrationLockViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.RegisterAccountError.RegistrationLock(registrationLockData)
|
NetworkController.RegisterAccountError.RegistrationLock(registrationLockData)
|
||||||
)
|
)
|
||||||
@@ -220,7 +220,7 @@ class PinEntryForRegistrationLockViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.RegisterAccountError.RateLimited(retryAfter)
|
NetworkController.RegisterAccountError.RateLimited(retryAfter)
|
||||||
)
|
)
|
||||||
@@ -242,7 +242,7 @@ class PinEntryForRegistrationLockViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.RegisterAccountError.InvalidRequest("Bad request")
|
NetworkController.RegisterAccountError.InvalidRequest("Bad request")
|
||||||
)
|
)
|
||||||
@@ -261,7 +261,7 @@ class PinEntryForRegistrationLockViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.RegisterAccountError.DeviceTransferPossible
|
NetworkController.RegisterAccountError.DeviceTransferPossible
|
||||||
)
|
)
|
||||||
@@ -280,7 +280,7 @@ class PinEntryForRegistrationLockViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.NetworkError(java.io.IOException("Network error"))
|
NetworkController.RegistrationNetworkResult.NetworkError(java.io.IOException("Network error"))
|
||||||
|
|
||||||
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), stateEmitter, parentEventEmitter)
|
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), stateEmitter, parentEventEmitter)
|
||||||
@@ -297,7 +297,7 @@ class PinEntryForRegistrationLockViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), any(), forRegistrationLock = true) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
NetworkController.RegistrationNetworkResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.ApplicationError(RuntimeException("Unexpected"))
|
NetworkController.RegistrationNetworkResult.ApplicationError(RuntimeException("Unexpected"))
|
||||||
|
|
||||||
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), stateEmitter, parentEventEmitter)
|
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), stateEmitter, parentEventEmitter)
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class VerificationCodeViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(registerResponse to keyMaterial)
|
NetworkController.RegistrationNetworkResult.Success(registerResponse to keyMaterial)
|
||||||
|
|
||||||
viewModel.applyEvent(initialState, VerificationCodeScreenEvents.CodeEntered("123456"))
|
viewModel.applyEvent(initialState, VerificationCodeScreenEvents.CodeEntered("123456"))
|
||||||
@@ -266,7 +266,7 @@ class VerificationCodeViewModelTest {
|
|||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.SubmitVerificationCodeError.SessionAlreadyVerifiedOrNoCodeRequested(verifiedSession)
|
NetworkController.SubmitVerificationCodeError.SessionAlreadyVerifiedOrNoCodeRequested(verifiedSession)
|
||||||
)
|
)
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(registerResponse to keyMaterial)
|
NetworkController.RegistrationNetworkResult.Success(registerResponse to keyMaterial)
|
||||||
|
|
||||||
viewModel.applyEvent(initialState, VerificationCodeScreenEvents.CodeEntered("123456"))
|
viewModel.applyEvent(initialState, VerificationCodeScreenEvents.CodeEntered("123456"))
|
||||||
@@ -373,7 +373,7 @@ class VerificationCodeViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.RegisterAccountError.DeviceTransferPossible
|
NetworkController.RegisterAccountError.DeviceTransferPossible
|
||||||
)
|
)
|
||||||
@@ -395,7 +395,7 @@ class VerificationCodeViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.RegisterAccountError.RateLimited(30.seconds)
|
NetworkController.RegisterAccountError.RateLimited(30.seconds)
|
||||||
)
|
)
|
||||||
@@ -422,7 +422,7 @@ class VerificationCodeViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.RegisterAccountError.InvalidRequest("Bad request")
|
NetworkController.RegisterAccountError.InvalidRequest("Bad request")
|
||||||
)
|
)
|
||||||
@@ -446,7 +446,7 @@ class VerificationCodeViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Failure(
|
NetworkController.RegistrationNetworkResult.Failure(
|
||||||
NetworkController.RegisterAccountError.RegistrationRecoveryPasswordIncorrect("Wrong password")
|
NetworkController.RegisterAccountError.RegistrationRecoveryPasswordIncorrect("Wrong password")
|
||||||
)
|
)
|
||||||
@@ -470,7 +470,7 @@ class VerificationCodeViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.NetworkError(java.io.IOException("Network error"))
|
NetworkController.RegistrationNetworkResult.NetworkError(java.io.IOException("Network error"))
|
||||||
|
|
||||||
val result = viewModel.applyEvent(
|
val result = viewModel.applyEvent(
|
||||||
@@ -492,7 +492,7 @@ class VerificationCodeViewModelTest {
|
|||||||
|
|
||||||
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
coEvery { mockRepository.submitVerificationCode(any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
NetworkController.RegistrationNetworkResult.Success(sessionMetadata)
|
||||||
coEvery { mockRepository.registerAccount(any(), any(), any()) } returns
|
coEvery { mockRepository.registerAccountWithSession(any(), any(), any()) } returns
|
||||||
NetworkController.RegistrationNetworkResult.ApplicationError(RuntimeException("Unexpected"))
|
NetworkController.RegistrationNetworkResult.ApplicationError(RuntimeException("Unexpected"))
|
||||||
|
|
||||||
val result = viewModel.applyEvent(
|
val result = viewModel.applyEvent(
|
||||||
|
|||||||
Reference in New Issue
Block a user