Make sure that PerformCreate[ExternalEmail]User can be invoked multiple times without throwing errors

Recover from "Username already taken or not allowed" error when creating new user accounts.
This commit is contained in:
Mateusz Armatys 2021-11-12 18:31:57 +01:00
parent f4b7ecbb67
commit ff710350f3
3 changed files with 37 additions and 17 deletions

View File

@ -19,13 +19,14 @@
package me.proton.core.auth.domain.usecase.signup
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.auth.domain.usecase.PerformLogin
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpCrypto
import me.proton.core.domain.entity.UserId
import me.proton.core.user.domain.entity.CreateUserType
import me.proton.core.user.domain.entity.User
import me.proton.core.user.domain.repository.UserRepository
import javax.inject.Inject
@ -33,15 +34,21 @@ class PerformCreateExternalEmailUser @Inject constructor(
private val authRepository: AuthRepository,
private val userRepository: UserRepository,
private val srpCrypto: SrpCrypto,
private val keyStoreCrypto: KeyStoreCrypto
private val keyStoreCrypto: KeyStoreCrypto,
private val performLogin: PerformLogin
) {
suspend operator fun invoke(
email: String,
password: EncryptedString,
referrer: String?
): User {
): UserId {
require(email.isNotBlank()) { "Email must not be empty." }
if (!userRepository.isUsernameAvailable(email)) {
return performLogin.invoke(email, password).userId
}
val modulus = authRepository.randomModulus()
password.decrypt(keyStoreCrypto).toByteArray().use { decryptedPassword ->
@ -57,7 +64,7 @@ class PerformCreateExternalEmailUser @Inject constructor(
referrer = referrer,
type = CreateUserType.Normal,
auth = auth
)
).userId
}
}
}

View File

@ -19,13 +19,14 @@
package me.proton.core.auth.domain.usecase.signup
import me.proton.core.auth.domain.repository.AuthRepository
import me.proton.core.auth.domain.usecase.PerformLogin
import me.proton.core.crypto.common.keystore.EncryptedString
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.decrypt
import me.proton.core.crypto.common.keystore.use
import me.proton.core.crypto.common.srp.SrpCrypto
import me.proton.core.domain.entity.UserId
import me.proton.core.user.domain.entity.CreateUserType
import me.proton.core.user.domain.entity.User
import me.proton.core.user.domain.repository.UserRepository
import javax.inject.Inject
@ -33,7 +34,8 @@ class PerformCreateUser @Inject constructor(
private val authRepository: AuthRepository,
private val userRepository: UserRepository,
private val srpCrypto: SrpCrypto,
private val keyStoreCrypto: KeyStoreCrypto
private val keyStoreCrypto: KeyStoreCrypto,
private val performLogin: PerformLogin
) {
suspend operator fun invoke(
@ -42,13 +44,18 @@ class PerformCreateUser @Inject constructor(
recoveryEmail: String?,
recoveryPhone: String?,
referrer: String?,
type: CreateUserType,
): User {
type: CreateUserType
): UserId {
require(
(recoveryEmail == null && recoveryPhone == null) ||
(recoveryEmail == null && recoveryPhone != null) ||
(recoveryEmail != null && recoveryPhone == null)
recoveryEmail == null && recoveryPhone == null ||
recoveryEmail == null && recoveryPhone != null ||
recoveryEmail != null && recoveryPhone == null
) { "Recovery Email and Phone could not be set together" }
if (!userRepository.isUsernameAvailable(username)) {
return performLogin.invoke(username, password).userId
}
val modulus = authRepository.randomModulus()
password.decrypt(keyStoreCrypto).toByteArray().use { decryptedPassword ->
@ -66,7 +73,7 @@ class PerformCreateUser @Inject constructor(
referrer = referrer,
type = type,
auth = auth
)
).userId
}
}
}

View File

@ -35,6 +35,8 @@ import kotlinx.coroutines.launch
import me.proton.core.account.domain.entity.AccountType
import me.proton.core.auth.domain.usecase.signup.PerformCreateExternalEmailUser
import me.proton.core.auth.domain.usecase.signup.PerformCreateUser
import me.proton.core.auth.domain.usecase.userAlreadyExists
import me.proton.core.auth.presentation.LogTag
import me.proton.core.auth.presentation.entity.signup.RecoveryMethod
import me.proton.core.auth.presentation.entity.signup.RecoveryMethodType
import me.proton.core.auth.presentation.entity.signup.SubscriptionDetails
@ -45,8 +47,6 @@ import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.humanverification.domain.HumanVerificationManager
import me.proton.core.humanverification.presentation.HumanVerificationOrchestrator
import me.proton.core.humanverification.presentation.onHumanVerificationFailed
import me.proton.core.humanverification.presentation.onHumanVerificationNeeded
import me.proton.core.humanverification.presentation.onHumanVerificationSucceeded
import me.proton.core.network.domain.client.ClientIdProvider
import me.proton.core.payment.domain.entity.SubscriptionCycle
import me.proton.core.payment.presentation.PaymentsOrchestrator
@ -55,7 +55,9 @@ import me.proton.core.plan.presentation.PlansOrchestrator
import me.proton.core.presentation.savedstate.flowState
import me.proton.core.presentation.savedstate.state
import me.proton.core.user.domain.entity.createUserType
import me.proton.core.util.kotlin.CoreLogger
import me.proton.core.util.kotlin.exhaustive
import me.proton.core.util.kotlin.retryOnceWhen
import javax.inject.Inject
@HiltViewModel
@ -228,7 +230,9 @@ internal class SignupViewModel @Inject constructor(
username = username, password = encryptedPassword, recoveryEmail = verification.first,
recoveryPhone = verification.second, referrer = null, type = currentAccountType.createUserType()
)
emit(State.Success(result.userId.id, username, encryptedPassword))
emit(State.Success(result.id, username, encryptedPassword))
}.retryOnceWhen(Throwable::userAlreadyExists) {
CoreLogger.e(LogTag.FLOW_ERROR_RETRY, it, "Retrying to create a user")
}.catch { error ->
emit(State.Error.Message(error.message))
}.onEach {
@ -239,12 +243,14 @@ internal class SignupViewModel @Inject constructor(
val externalEmail = requireNotNull(externalEmail) { "External email is not set." }
val encryptedPassword = requireNotNull(_password) { "Password is not set (initialized)." }
emit(State.Processing)
val user = performCreateExternalEmailUser(
val userId = performCreateExternalEmailUser(
email = externalEmail,
password = encryptedPassword,
referrer = null
)
emit(State.Success(user.userId.id, externalEmail, encryptedPassword))
emit(State.Success(userId.id, externalEmail, encryptedPassword))
}.retryOnceWhen(Throwable::userAlreadyExists) {
CoreLogger.e(LogTag.FLOW_ERROR_RETRY, it, "Retrying to create an external user")
}.catch { error ->
emit(State.Error.Message(error.message))
}.onEach {