Fix multiple bugs and erroneous sad path handling in registration flows.
This commit is contained in:
parent
e0553a59d5
commit
f1782d06a4
23 changed files with 586 additions and 215 deletions
|
@ -20,13 +20,22 @@ class ActionCountDownButton @JvmOverloads constructor(
|
||||||
private var countDownToTime: Long = 0
|
private var countDownToTime: Long = 0
|
||||||
private var listener: Listener? = null
|
private var listener: Listener? = null
|
||||||
|
|
||||||
|
private var updateRunnable = Runnable {
|
||||||
|
updateCountDown()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a count down to the specified {@param time}.
|
* Starts a count down to the specified {@param time}.
|
||||||
*/
|
*/
|
||||||
fun startCountDownTo(time: Long) {
|
fun startCountDownTo(time: Long) {
|
||||||
if (time > 0) {
|
if (time > 0) {
|
||||||
countDownToTime = time
|
countDownToTime = time
|
||||||
|
removeCallbacks(updateRunnable)
|
||||||
updateCountDown()
|
updateCountDown()
|
||||||
|
} else {
|
||||||
|
setText(enabledText)
|
||||||
|
isEnabled = false
|
||||||
|
alpha = 0.5f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,15 +47,16 @@ class ActionCountDownButton @JvmOverloads constructor(
|
||||||
|
|
||||||
private fun updateCountDown() {
|
private fun updateCountDown() {
|
||||||
val remainingMillis = countDownToTime - System.currentTimeMillis()
|
val remainingMillis = countDownToTime - System.currentTimeMillis()
|
||||||
if (remainingMillis > 0) {
|
if (remainingMillis > 1000) {
|
||||||
isEnabled = false
|
isEnabled = false
|
||||||
alpha = 0.5f
|
alpha = 0.5f
|
||||||
val totalRemainingSeconds = TimeUnit.MILLISECONDS.toSeconds(remainingMillis).toInt()
|
val totalRemainingSeconds = TimeUnit.MILLISECONDS.toSeconds(remainingMillis).toInt()
|
||||||
val minutesRemaining = totalRemainingSeconds / 60
|
val minutesRemaining = totalRemainingSeconds / 60
|
||||||
val secondsRemaining = totalRemainingSeconds % 60
|
val secondsRemaining = totalRemainingSeconds % 60
|
||||||
|
|
||||||
text = resources.getString(disabledText, minutesRemaining, secondsRemaining)
|
text = resources.getString(disabledText, minutesRemaining, secondsRemaining)
|
||||||
listener?.onRemaining(this, totalRemainingSeconds)
|
listener?.onRemaining(this, totalRemainingSeconds)
|
||||||
postDelayed({ updateCountDown() }, 250)
|
postDelayed(updateRunnable, 250)
|
||||||
} else {
|
} else {
|
||||||
setActionEnabled()
|
setActionEnabled()
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,6 @@ class ChangeNumberEnterCodeFragment : LoggingFragment(R.layout.fragment_change_n
|
||||||
when (result) {
|
when (result) {
|
||||||
is VerificationCodeRequestResult.Success -> binding.codeEntryLayout.keyboard.displaySuccess()
|
is VerificationCodeRequestResult.Success -> binding.codeEntryLayout.keyboard.displaySuccess()
|
||||||
is VerificationCodeRequestResult.RateLimited -> presentRateLimitedDialog()
|
is VerificationCodeRequestResult.RateLimited -> presentRateLimitedDialog()
|
||||||
is VerificationCodeRequestResult.AttemptsExhausted -> presentAccountLocked()
|
|
||||||
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
||||||
else -> presentGenericError(result)
|
else -> presentGenericError(result)
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,6 @@ class ChangeNumberRegistrationLockFragment : LoggingFragment(R.layout.fragment_c
|
||||||
when (requestResult) {
|
when (requestResult) {
|
||||||
is VerificationCodeRequestResult.Success -> Unit
|
is VerificationCodeRequestResult.Success -> Unit
|
||||||
is VerificationCodeRequestResult.RateLimited -> onRateLimited()
|
is VerificationCodeRequestResult.RateLimited -> onRateLimited()
|
||||||
is VerificationCodeRequestResult.AttemptsExhausted,
|
|
||||||
is VerificationCodeRequestResult.RegistrationLocked -> {
|
is VerificationCodeRequestResult.RegistrationLocked -> {
|
||||||
navigateToAccountLocked()
|
navigateToAccountLocked()
|
||||||
}
|
}
|
||||||
|
@ -166,7 +165,8 @@ class ChangeNumberRegistrationLockFragment : LoggingFragment(R.layout.fragment_c
|
||||||
is VerificationCodeRequestResult.ImpossibleNumber,
|
is VerificationCodeRequestResult.ImpossibleNumber,
|
||||||
is VerificationCodeRequestResult.InvalidTransportModeFailure,
|
is VerificationCodeRequestResult.InvalidTransportModeFailure,
|
||||||
is VerificationCodeRequestResult.MalformedRequest,
|
is VerificationCodeRequestResult.MalformedRequest,
|
||||||
is VerificationCodeRequestResult.MustRetry,
|
is VerificationCodeRequestResult.RequestVerificationCodeRateLimited,
|
||||||
|
is VerificationCodeRequestResult.SubmitVerificationCodeRateLimited,
|
||||||
is VerificationCodeRequestResult.NoSuchSession,
|
is VerificationCodeRequestResult.NoSuchSession,
|
||||||
is VerificationCodeRequestResult.NonNormalizedNumber,
|
is VerificationCodeRequestResult.NonNormalizedNumber,
|
||||||
is VerificationCodeRequestResult.TokenNotAccepted,
|
is VerificationCodeRequestResult.TokenNotAccepted,
|
||||||
|
|
|
@ -240,7 +240,7 @@ class ChangeNumberViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun verifyCodeInternal(context: Context, pin: String?, verificationErrorHandler: (VerificationCodeRequestResult) -> Unit, numberChangeErrorHandler: (ChangeNumberResult) -> Unit) {
|
private suspend fun verifyCodeInternal(context: Context, pin: String?, verificationErrorHandler: (VerificationCodeRequestResult) -> Unit, numberChangeErrorHandler: (ChangeNumberResult) -> Unit) {
|
||||||
val sessionId = getOrCreateValidSession(context)?.body?.id ?: return bail { Log.i(TAG, "Bailing from code verification due to invalid session.") }
|
val sessionId = getOrCreateValidSession(context)?.metadata?.id ?: return bail { Log.i(TAG, "Bailing from code verification due to invalid session.") }
|
||||||
val registrationData = getRegistrationData(context)
|
val registrationData = getRegistrationData(context)
|
||||||
|
|
||||||
val verificationResponse = RegistrationRepository.submitVerificationCode(context, sessionId, registrationData)
|
val verificationResponse = RegistrationRepository.submitVerificationCode(context, sessionId, registrationData)
|
||||||
|
@ -286,7 +286,7 @@ class ChangeNumberViewModel : ViewModel() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
Log.d(TAG, "Getting session in order to submit captcha token…")
|
Log.d(TAG, "Getting session in order to submit captcha token…")
|
||||||
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Bailing captcha token submission due to invalid session.") }
|
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Bailing captcha token submission due to invalid session.") }
|
||||||
if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.CAPTCHA)) {
|
if (!Challenge.parse(session.metadata.requestedInformation).contains(Challenge.CAPTCHA)) {
|
||||||
Log.d(TAG, "Captcha submission no longer necessary, bailing.")
|
Log.d(TAG, "Captcha submission no longer necessary, bailing.")
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
@ -297,7 +297,7 @@ class ChangeNumberViewModel : ViewModel() {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Submitting captcha token…")
|
Log.d(TAG, "Submitting captcha token…")
|
||||||
val captchaSubmissionResult = RegistrationRepository.submitCaptchaToken(context, e164, password, session.body.id, captchaToken)
|
val captchaSubmissionResult = RegistrationRepository.submitCaptchaToken(context, e164, password, session.metadata.id, captchaToken)
|
||||||
Log.d(TAG, "Captcha token submitted.")
|
Log.d(TAG, "Captcha token submitted.")
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(inProgress = false, changeNumberOutcome = ChangeNumberOutcome.ChangeNumberRequestOutcome(captchaSubmissionResult))
|
it.copy(inProgress = false, changeNumberOutcome = ChangeNumberOutcome.ChangeNumberRequestOutcome(captchaSubmissionResult))
|
||||||
|
@ -316,7 +316,7 @@ class ChangeNumberViewModel : ViewModel() {
|
||||||
Log.d(TAG, "Getting session in order to perform push token verification…")
|
Log.d(TAG, "Getting session in order to perform push token verification…")
|
||||||
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Bailing from push token verification due to invalid session.") }
|
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Bailing from push token verification due to invalid session.") }
|
||||||
|
|
||||||
if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) {
|
if (!Challenge.parse(session.metadata.requestedInformation).contains(Challenge.PUSH)) {
|
||||||
Log.d(TAG, "Push submission no longer necessary, bailing.")
|
Log.d(TAG, "Push submission no longer necessary, bailing.")
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
@ -328,7 +328,7 @@ class ChangeNumberViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Requesting push challenge token…")
|
Log.d(TAG, "Requesting push challenge token…")
|
||||||
val pushSubmissionResult = RegistrationRepository.requestAndVerifyPushToken(context, session.body.id, e164, password)
|
val pushSubmissionResult = RegistrationRepository.requestAndVerifyPushToken(context, session.metadata.id, e164, password)
|
||||||
Log.d(TAG, "Push challenge token submitted.")
|
Log.d(TAG, "Push challenge token submitted.")
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(inProgress = false, changeNumberOutcome = ChangeNumberOutcome.ChangeNumberRequestOutcome(pushSubmissionResult))
|
it.copy(inProgress = false, changeNumberOutcome = ChangeNumberOutcome.ChangeNumberRequestOutcome(pushSubmissionResult))
|
||||||
|
@ -364,7 +364,7 @@ class ChangeNumberViewModel : ViewModel() {
|
||||||
private fun updateLocalStateFromSession(response: RegistrationSessionMetadataResponse) {
|
private fun updateLocalStateFromSession(response: RegistrationSessionMetadataResponse) {
|
||||||
Log.v(TAG, "updateLocalStateFromSession()")
|
Log.v(TAG, "updateLocalStateFromSession()")
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(sessionId = response.body.id, challengesRequested = Challenge.parse(response.body.requestedInformation), allowedToRequestCode = response.body.allowedToRequestCode)
|
it.copy(sessionId = response.metadata.id, challengesRequested = Challenge.parse(response.metadata.requestedInformation), allowedToRequestCode = response.metadata.allowedToRequestCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,15 +477,15 @@ class ChangeNumberViewModel : ViewModel() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = if (!validSession.body.allowedToRequestCode) {
|
val result = if (!validSession.metadata.allowedToRequestCode) {
|
||||||
val challenges = validSession.body.requestedInformation.joinToString()
|
val challenges = validSession.metadata.requestedInformation.joinToString()
|
||||||
Log.i(TAG, "Not allowed to request code! Remaining challenges: $challenges")
|
Log.i(TAG, "Not allowed to request code! Remaining challenges: $challenges")
|
||||||
VerificationCodeRequestResult.ChallengeRequired(Challenge.parse(validSession.body.requestedInformation))
|
VerificationCodeRequestResult.ChallengeRequired(Challenge.parse(validSession.metadata.requestedInformation))
|
||||||
} else {
|
} else {
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(changeNumberOutcome = null, challengesRequested = emptyList())
|
it.copy(changeNumberOutcome = null, challengesRequested = emptyList())
|
||||||
}
|
}
|
||||||
val response = RegistrationRepository.requestSmsCode(context = context, sessionId = validSession.body.id, e164 = e164, password = password, mode = mode)
|
val response = RegistrationRepository.requestSmsCode(context = context, sessionId = validSession.metadata.id, e164 = e164, password = password, mode = mode)
|
||||||
Log.d(TAG, "SMS code request submitted")
|
Log.d(TAG, "SMS code request submitted")
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,7 +307,7 @@ object RegistrationRepository {
|
||||||
val result = RegistrationSessionCreationResult.from(registrationSessionResult)
|
val result = RegistrationSessionCreationResult.from(registrationSessionResult)
|
||||||
if (result is RegistrationSessionCreationResult.Success) {
|
if (result is RegistrationSessionCreationResult.Success) {
|
||||||
Log.d(TAG, "Updating registration session and E164 in value store.")
|
Log.d(TAG, "Updating registration session and E164 in value store.")
|
||||||
SignalStore.registration.sessionId = result.getMetadata().body.id
|
SignalStore.registration.sessionId = result.getMetadata().metadata.id
|
||||||
SignalStore.registration.sessionE164 = e164
|
SignalStore.registration.sessionE164 = e164
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,8 +472,8 @@ object RegistrationRepository {
|
||||||
if (receivedPush) {
|
if (receivedPush) {
|
||||||
val challenge = subscriber.challenge
|
val challenge = subscriber.challenge
|
||||||
if (challenge != null) {
|
if (challenge != null) {
|
||||||
Log.w(TAG, "Push challenge token received.")
|
Log.i(TAG, "Push challenge token received.")
|
||||||
return@withContext accountManager.submitPushChallengeToken(sessionCreationResponse.result.body.id, challenge)
|
return@withContext accountManager.submitPushChallengeToken(sessionCreationResponse.result.metadata.id, challenge)
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Push received but challenge token was null.")
|
Log.w(TAG, "Push received but challenge token was null.")
|
||||||
}
|
}
|
||||||
|
@ -494,7 +494,7 @@ object RegistrationRepository {
|
||||||
return 0L
|
return 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
val timestamp: Long = headers.timestamp
|
val timestamp: Long = headers.serverDeliveredTimestamp
|
||||||
return timestamp + deltaSeconds.seconds.inWholeMilliseconds
|
return timestamp + deltaSeconds.seconds.inWholeMilliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package org.thoughtcrime.securesms.registration.data.network
|
package org.thoughtcrime.securesms.registration.data.network
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.signal.core.util.orNull
|
||||||
import org.whispersystems.signalservice.api.NetworkResult
|
import org.whispersystems.signalservice.api.NetworkResult
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.MalformedRequestException
|
import org.whispersystems.signalservice.api.push.exceptions.MalformedRequestException
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
|
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
|
||||||
|
@ -35,7 +36,7 @@ sealed class RegistrationSessionCreationResult(cause: Throwable?) : Registration
|
||||||
is NetworkResult.NetworkError -> UnknownError(networkResult.exception)
|
is NetworkResult.NetworkError -> UnknownError(networkResult.exception)
|
||||||
is NetworkResult.StatusCodeError -> {
|
is NetworkResult.StatusCodeError -> {
|
||||||
when (val cause = networkResult.exception) {
|
when (val cause = networkResult.exception) {
|
||||||
is RateLimitException -> createRateLimitProcessor(cause)
|
is RateLimitException -> RateLimited(cause, cause.retryAfterMilliseconds.orNull())
|
||||||
is MalformedRequestException -> MalformedRequest(cause)
|
is MalformedRequestException -> MalformedRequest(cause)
|
||||||
else -> if (networkResult.code == 422) {
|
else -> if (networkResult.code == 422) {
|
||||||
ServerUnableToParse(cause)
|
ServerUnableToParse(cause)
|
||||||
|
@ -46,14 +47,6 @@ sealed class RegistrationSessionCreationResult(cause: Throwable?) : Registration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRateLimitProcessor(exception: RateLimitException): RegistrationSessionCreationResult {
|
|
||||||
return if (exception.retryAfterMilliseconds.isPresent) {
|
|
||||||
RateLimited(exception, exception.retryAfterMilliseconds.get())
|
|
||||||
} else {
|
|
||||||
AttemptsExhausted(exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Success(private val metadata: RegistrationSessionMetadataResponse) : RegistrationSessionCreationResult(null), SessionMetadataHolder {
|
class Success(private val metadata: RegistrationSessionMetadataResponse) : RegistrationSessionCreationResult(null), SessionMetadataHolder {
|
||||||
|
@ -62,7 +55,7 @@ sealed class RegistrationSessionCreationResult(cause: Throwable?) : Registration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RateLimited(cause: Throwable, val timeRemaining: Long) : RegistrationSessionCreationResult(cause)
|
class RateLimited(cause: Throwable, val timeRemaining: Long?) : RegistrationSessionCreationResult(cause)
|
||||||
class AttemptsExhausted(cause: Throwable) : RegistrationSessionCreationResult(cause)
|
class AttemptsExhausted(cause: Throwable) : RegistrationSessionCreationResult(cause)
|
||||||
class ServerUnableToParse(cause: Throwable) : RegistrationSessionCreationResult(cause)
|
class ServerUnableToParse(cause: Throwable) : RegistrationSessionCreationResult(cause)
|
||||||
class MalformedRequest(cause: Throwable) : RegistrationSessionCreationResult(cause)
|
class MalformedRequest(cause: Throwable) : RegistrationSessionCreationResult(cause)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package org.thoughtcrime.securesms.registration.data.network
|
package org.thoughtcrime.securesms.registration.data.network
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.signal.core.util.orNull
|
||||||
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
|
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
|
||||||
import org.whispersystems.signalservice.api.NetworkResult
|
import org.whispersystems.signalservice.api.NetworkResult
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException
|
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException
|
||||||
|
@ -17,13 +18,15 @@ import org.whispersystems.signalservice.api.push.exceptions.MalformedRequestExce
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
|
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException
|
import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException
|
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.RegistrationRetryException
|
import org.whispersystems.signalservice.api.push.exceptions.RequestVerificationCodeRateLimitException
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.SubmitVerificationCodeRateLimitException
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException
|
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException
|
||||||
import org.whispersystems.signalservice.api.svr.Svr3Credentials
|
import org.whispersystems.signalservice.api.svr.Svr3Credentials
|
||||||
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
import org.whispersystems.signalservice.internal.push.AuthCredentials
|
||||||
import org.whispersystems.signalservice.internal.push.LockedException
|
import org.whispersystems.signalservice.internal.push.LockedException
|
||||||
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson
|
|
||||||
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse
|
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a processor to map a [RegistrationSessionMetadataResponse] to all the known outcomes.
|
* This is a processor to map a [RegistrationSessionMetadataResponse] to all the known outcomes.
|
||||||
|
@ -37,19 +40,19 @@ sealed class VerificationCodeRequestResult(cause: Throwable?) : RegistrationResu
|
||||||
fun from(networkResult: NetworkResult<RegistrationSessionMetadataResponse>): VerificationCodeRequestResult {
|
fun from(networkResult: NetworkResult<RegistrationSessionMetadataResponse>): VerificationCodeRequestResult {
|
||||||
return when (networkResult) {
|
return when (networkResult) {
|
||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
val challenges = Challenge.parse(networkResult.result.body.requestedInformation)
|
val challenges = Challenge.parse(networkResult.result.metadata.requestedInformation)
|
||||||
if (challenges.isNotEmpty()) {
|
if (challenges.isNotEmpty()) {
|
||||||
Log.d(TAG, "Received \"successful\" response that contains challenges: ${challenges.joinToString { it.key }}")
|
Log.d(TAG, "Received \"successful\" response that contains challenges: ${challenges.joinToString { it.key }}")
|
||||||
ChallengeRequired(challenges)
|
ChallengeRequired(challenges)
|
||||||
} else {
|
} else {
|
||||||
Success(
|
Success(
|
||||||
sessionId = networkResult.result.body.id,
|
sessionId = networkResult.result.metadata.id,
|
||||||
nextSmsTimestamp = RegistrationRepository.deriveTimestamp(networkResult.result.headers, networkResult.result.body.nextSms),
|
nextSmsTimestamp = RegistrationRepository.deriveTimestamp(networkResult.result.headers, networkResult.result.metadata.nextSms),
|
||||||
nextCallTimestamp = RegistrationRepository.deriveTimestamp(networkResult.result.headers, networkResult.result.body.nextCall),
|
nextCallTimestamp = RegistrationRepository.deriveTimestamp(networkResult.result.headers, networkResult.result.metadata.nextCall),
|
||||||
nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.result.headers, networkResult.result.body.nextVerificationAttempt),
|
nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.result.headers, networkResult.result.metadata.nextVerificationAttempt),
|
||||||
allowedToRequestCode = networkResult.result.body.allowedToRequestCode,
|
allowedToRequestCode = networkResult.result.metadata.allowedToRequestCode,
|
||||||
challengesRequested = Challenge.parse(networkResult.result.body.requestedInformation),
|
challengesRequested = Challenge.parse(networkResult.result.metadata.requestedInformation),
|
||||||
verified = networkResult.result.body.verified
|
verified = networkResult.result.metadata.verified
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,14 +62,23 @@ sealed class VerificationCodeRequestResult(cause: Throwable?) : RegistrationResu
|
||||||
is NetworkResult.StatusCodeError -> {
|
is NetworkResult.StatusCodeError -> {
|
||||||
when (val cause = networkResult.exception) {
|
when (val cause = networkResult.exception) {
|
||||||
is ChallengeRequiredException -> createChallengeRequiredProcessor(cause.response)
|
is ChallengeRequiredException -> createChallengeRequiredProcessor(cause.response)
|
||||||
is RateLimitException -> createRateLimitProcessor(cause)
|
is RateLimitException -> RateLimited(cause, cause.retryAfterMilliseconds.orNull())
|
||||||
is ImpossiblePhoneNumberException -> ImpossibleNumber(cause)
|
is ImpossiblePhoneNumberException -> ImpossibleNumber(cause)
|
||||||
is NonNormalizedPhoneNumberException -> NonNormalizedNumber(cause = cause, originalNumber = cause.originalNumber, normalizedNumber = cause.normalizedNumber)
|
is NonNormalizedPhoneNumberException -> NonNormalizedNumber(cause = cause, originalNumber = cause.originalNumber, normalizedNumber = cause.normalizedNumber)
|
||||||
is TokenNotAcceptedException -> TokenNotAccepted(cause)
|
is TokenNotAcceptedException -> TokenNotAccepted(cause)
|
||||||
is ExternalServiceFailureException -> ExternalServiceFailure(cause)
|
is ExternalServiceFailureException -> ExternalServiceFailure(cause)
|
||||||
is InvalidTransportModeException -> InvalidTransportModeFailure(cause)
|
is InvalidTransportModeException -> InvalidTransportModeFailure(cause)
|
||||||
is MalformedRequestException -> MalformedRequest(cause)
|
is MalformedRequestException -> MalformedRequest(cause)
|
||||||
is RegistrationRetryException -> MustRetry(cause)
|
is SubmitVerificationCodeRateLimitException -> {
|
||||||
|
SubmitVerificationCodeRateLimited(cause)
|
||||||
|
}
|
||||||
|
is RequestVerificationCodeRateLimitException -> {
|
||||||
|
RequestVerificationCodeRateLimited(
|
||||||
|
cause = cause,
|
||||||
|
nextSmsTimestamp = RegistrationRepository.deriveTimestamp(cause.sessionMetadata.headers, cause.sessionMetadata.metadata.nextSms),
|
||||||
|
nextCallTimestamp = RegistrationRepository.deriveTimestamp(cause.sessionMetadata.headers, cause.sessionMetadata.metadata.nextCall)
|
||||||
|
)
|
||||||
|
}
|
||||||
is LockedException -> RegistrationLocked(cause = cause, timeRemaining = cause.timeRemaining, svr2Credentials = cause.svr2Credentials, svr3Credentials = cause.svr3Credentials)
|
is LockedException -> RegistrationLocked(cause = cause, timeRemaining = cause.timeRemaining, svr2Credentials = cause.svr2Credentials, svr3Credentials = cause.svr3Credentials)
|
||||||
is NoSuchSessionException -> NoSuchSession(cause)
|
is NoSuchSessionException -> NoSuchSession(cause)
|
||||||
is AlreadyVerifiedException -> AlreadyVerified(cause)
|
is AlreadyVerifiedException -> AlreadyVerified(cause)
|
||||||
|
@ -76,16 +88,8 @@ sealed class VerificationCodeRequestResult(cause: Throwable?) : RegistrationResu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createChallengeRequiredProcessor(response: RegistrationSessionMetadataJson): VerificationCodeRequestResult {
|
private fun createChallengeRequiredProcessor(response: RegistrationSessionMetadataResponse): VerificationCodeRequestResult {
|
||||||
return ChallengeRequired(Challenge.parse(response.requestedInformation))
|
return ChallengeRequired(Challenge.parse(response.metadata.requestedInformation))
|
||||||
}
|
|
||||||
|
|
||||||
private fun createRateLimitProcessor(exception: RateLimitException): VerificationCodeRequestResult {
|
|
||||||
return if (exception.retryAfterMilliseconds.isPresent) {
|
|
||||||
RateLimited(exception, exception.retryAfterMilliseconds.get())
|
|
||||||
} else {
|
|
||||||
AttemptsExhausted(exception)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,9 +97,7 @@ sealed class VerificationCodeRequestResult(cause: Throwable?) : RegistrationResu
|
||||||
|
|
||||||
class ChallengeRequired(val challenges: List<Challenge>) : VerificationCodeRequestResult(null)
|
class ChallengeRequired(val challenges: List<Challenge>) : VerificationCodeRequestResult(null)
|
||||||
|
|
||||||
class RateLimited(cause: Throwable, val timeRemaining: Long) : VerificationCodeRequestResult(cause)
|
class RateLimited(cause: Throwable, val timeRemaining: Long?) : VerificationCodeRequestResult(cause)
|
||||||
|
|
||||||
class AttemptsExhausted(cause: Throwable) : VerificationCodeRequestResult(cause)
|
|
||||||
|
|
||||||
class ImpossibleNumber(cause: Throwable) : VerificationCodeRequestResult(cause)
|
class ImpossibleNumber(cause: Throwable) : VerificationCodeRequestResult(cause)
|
||||||
|
|
||||||
|
@ -109,7 +111,26 @@ sealed class VerificationCodeRequestResult(cause: Throwable?) : RegistrationResu
|
||||||
|
|
||||||
class MalformedRequest(cause: Throwable) : VerificationCodeRequestResult(cause)
|
class MalformedRequest(cause: Throwable) : VerificationCodeRequestResult(cause)
|
||||||
|
|
||||||
class MustRetry(cause: Throwable) : VerificationCodeRequestResult(cause)
|
class RequestVerificationCodeRateLimited(cause: Throwable, val nextSmsTimestamp: Long, val nextCallTimestamp: Long) : VerificationCodeRequestResult(cause) {
|
||||||
|
val willBeAbleToRequestAgain: Boolean = nextSmsTimestamp > 0 || nextCallTimestamp > 0
|
||||||
|
fun log(now: Duration = System.currentTimeMillis().milliseconds): String {
|
||||||
|
val sms = if (nextSmsTimestamp > 0) {
|
||||||
|
"${(nextSmsTimestamp.milliseconds - now).inWholeSeconds}s"
|
||||||
|
} else {
|
||||||
|
"Never"
|
||||||
|
}
|
||||||
|
|
||||||
|
val call = if (nextCallTimestamp > 0) {
|
||||||
|
"${(nextCallTimestamp.milliseconds - now).inWholeSeconds}s"
|
||||||
|
} else {
|
||||||
|
"Never"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Request verification code rate limited! nextSms: $sms nextCall: $call"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubmitVerificationCodeRateLimited(cause: Throwable) : VerificationCodeRequestResult(cause)
|
||||||
|
|
||||||
class RegistrationLocked(cause: Throwable, val timeRemaining: Long, val svr2Credentials: AuthCredentials, val svr3Credentials: Svr3Credentials) : VerificationCodeRequestResult(cause)
|
class RegistrationLocked(cause: Throwable, val timeRemaining: Long, val svr2Credentials: AuthCredentials, val svr3Credentials: Svr3Credentials) : VerificationCodeRequestResult(cause)
|
||||||
|
|
||||||
|
|
|
@ -46,17 +46,17 @@ import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionC
|
||||||
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AlreadyVerified
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AlreadyVerified
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AttemptsExhausted
|
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ChallengeRequired
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ChallengeRequired
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ExternalServiceFailure
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ExternalServiceFailure
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ImpossibleNumber
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ImpossibleNumber
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.InvalidTransportModeFailure
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.InvalidTransportModeFailure
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.MalformedRequest
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.MalformedRequest
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.MustRetry
|
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.NoSuchSession
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.NoSuchSession
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.NonNormalizedNumber
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.NonNormalizedNumber
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RateLimited
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RateLimited
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RegistrationLocked
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RegistrationLocked
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RequestVerificationCodeRateLimited
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.SubmitVerificationCodeRateLimited
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.Success
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.Success
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.TokenNotAccepted
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.TokenNotAccepted
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.UnknownError
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.UnknownError
|
||||||
|
@ -271,26 +271,26 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for confirming the entered E164.") }
|
val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for confirming the entered E164.") }
|
||||||
|
|
||||||
if (validSession.body.verified) {
|
if (validSession.metadata.verified) {
|
||||||
Log.i(TAG, "Session is already verified, registering account.")
|
Log.i(TAG, "Session is already verified, registering account.")
|
||||||
registerVerifiedSession(context, validSession.body.id)
|
registerVerifiedSession(context, validSession.metadata.id)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validSession.body.allowedToRequestCode) {
|
if (!validSession.metadata.allowedToRequestCode) {
|
||||||
if (System.currentTimeMillis() > (validSession.body.nextVerificationAttempt ?: Int.MAX_VALUE)) {
|
if (System.currentTimeMillis() > (validSession.metadata.nextVerificationAttempt ?: Int.MAX_VALUE)) {
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(registrationCheckpoint = RegistrationCheckpoint.VERIFICATION_CODE_REQUESTED)
|
it.copy(registrationCheckpoint = RegistrationCheckpoint.VERIFICATION_CODE_REQUESTED)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val challenges = validSession.body.requestedInformation
|
val challenges = validSession.metadata.requestedInformation
|
||||||
Log.i(TAG, "Not allowed to request code! Remaining challenges: ${challenges.joinToString()}")
|
Log.i(TAG, "Not allowed to request code! Remaining challenges: ${challenges.joinToString()}")
|
||||||
handleSessionStateResult(context, ChallengeRequired(Challenge.parse(validSession.body.requestedInformation)))
|
handleSessionStateResult(context, ChallengeRequired(Challenge.parse(validSession.metadata.requestedInformation)))
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
requestSmsCodeInternal(context, validSession.body.id, e164)
|
requestSmsCodeInternal(context, validSession.metadata.id, e164)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,7 +299,7 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for requesting an SMS code.") }
|
val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for requesting an SMS code.") }
|
||||||
requestSmsCodeInternal(context, validSession.body.id, e164)
|
requestSmsCodeInternal(context, validSession.metadata.id, e164)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +317,7 @@ class RegistrationViewModel : ViewModel() {
|
||||||
Log.d(TAG, "Requesting voice call code…")
|
Log.d(TAG, "Requesting voice call code…")
|
||||||
val codeRequestResponse = RegistrationRepository.requestSmsCode(
|
val codeRequestResponse = RegistrationRepository.requestSmsCode(
|
||||||
context = context,
|
context = context,
|
||||||
sessionId = validSession.body.id,
|
sessionId = validSession.metadata.id,
|
||||||
e164 = e164,
|
e164 = e164,
|
||||||
password = password,
|
password = password,
|
||||||
mode = RegistrationRepository.E164VerificationMode.PHONE_CALL
|
mode = RegistrationRepository.E164VerificationMode.PHONE_CALL
|
||||||
|
@ -391,13 +391,13 @@ class RegistrationViewModel : ViewModel() {
|
||||||
successListener = { networkResult ->
|
successListener = { networkResult ->
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
sessionId = networkResult.body.id,
|
sessionId = networkResult.metadata.id,
|
||||||
nextSmsTimestamp = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextSms),
|
nextSmsTimestamp = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.metadata.nextSms),
|
||||||
nextCallTimestamp = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextCall),
|
nextCallTimestamp = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.metadata.nextCall),
|
||||||
nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextVerificationAttempt),
|
nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.metadata.nextVerificationAttempt),
|
||||||
allowedToRequestCode = networkResult.body.allowedToRequestCode,
|
allowedToRequestCode = networkResult.metadata.allowedToRequestCode,
|
||||||
challengesRequested = Challenge.parse(networkResult.body.requestedInformation),
|
challengesRequested = Challenge.parse(networkResult.metadata.requestedInformation),
|
||||||
verified = networkResult.body.verified
|
verified = networkResult.metadata.verified
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -424,7 +424,7 @@ class RegistrationViewModel : ViewModel() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a captcha token.") }
|
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a captcha token.") }
|
||||||
Log.d(TAG, "Submitting captcha token…")
|
Log.d(TAG, "Submitting captcha token…")
|
||||||
val captchaSubmissionResult = RegistrationRepository.submitCaptchaToken(context, e164, password, session.body.id, captchaToken)
|
val captchaSubmissionResult = RegistrationRepository.submitCaptchaToken(context, e164, password, session.metadata.id, captchaToken)
|
||||||
Log.d(TAG, "Captcha token submitted.")
|
Log.d(TAG, "Captcha token submitted.")
|
||||||
|
|
||||||
handleSessionStateResult(context, captchaSubmissionResult)
|
handleSessionStateResult(context, captchaSubmissionResult)
|
||||||
|
@ -442,12 +442,12 @@ class RegistrationViewModel : ViewModel() {
|
||||||
Log.d(TAG, "Getting session in order to perform push token verification…")
|
Log.d(TAG, "Getting session in order to perform push token verification…")
|
||||||
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") }
|
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") }
|
||||||
|
|
||||||
if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) {
|
if (!Challenge.parse(session.metadata.requestedInformation).contains(Challenge.PUSH)) {
|
||||||
return@launch bail { Log.i(TAG, "Push challenge token no longer needed, bailing.") }
|
return@launch bail { Log.i(TAG, "Push challenge token no longer needed, bailing.") }
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Requesting push challenge token…")
|
Log.d(TAG, "Requesting push challenge token…")
|
||||||
val pushSubmissionResult = RegistrationRepository.requestAndVerifyPushToken(context, session.body.id, e164, password)
|
val pushSubmissionResult = RegistrationRepository.requestAndVerifyPushToken(context, session.metadata.id, e164, password)
|
||||||
Log.d(TAG, "Push challenge token submitted.")
|
Log.d(TAG, "Push challenge token submitted.")
|
||||||
handleSessionStateResult(context, pushSubmissionResult)
|
handleSessionStateResult(context, pushSubmissionResult)
|
||||||
}
|
}
|
||||||
|
@ -489,8 +489,6 @@ class RegistrationViewModel : ViewModel() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
is AttemptsExhausted -> Log.i(TAG, "Received AttemptsExhausted.", sessionResult.getCause())
|
|
||||||
|
|
||||||
is ImpossibleNumber -> Log.i(TAG, "Received ImpossibleNumber.", sessionResult.getCause())
|
is ImpossibleNumber -> Log.i(TAG, "Received ImpossibleNumber.", sessionResult.getCause())
|
||||||
|
|
||||||
is NonNormalizedNumber -> Log.i(TAG, "Received NonNormalizedNumber.", sessionResult.getCause())
|
is NonNormalizedNumber -> Log.i(TAG, "Received NonNormalizedNumber.", sessionResult.getCause())
|
||||||
|
@ -503,7 +501,24 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
is MalformedRequest -> Log.i(TAG, "Received MalformedRequest.", sessionResult.getCause())
|
is MalformedRequest -> Log.i(TAG, "Received MalformedRequest.", sessionResult.getCause())
|
||||||
|
|
||||||
is MustRetry -> Log.i(TAG, "Received MustRetry.", sessionResult.getCause())
|
is RequestVerificationCodeRateLimited -> {
|
||||||
|
Log.i(TAG, "Received RequestVerificationCodeRateLimited.", sessionResult.getCause())
|
||||||
|
|
||||||
|
if (sessionResult.willBeAbleToRequestAgain) {
|
||||||
|
store.update {
|
||||||
|
it.copy(
|
||||||
|
nextSmsTimestamp = sessionResult.nextSmsTimestamp,
|
||||||
|
nextCallTimestamp = sessionResult.nextCallTimestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Request verification code rate limit is forever, need to start new session")
|
||||||
|
SignalStore.registration.sessionId = null
|
||||||
|
store.update { RegistrationState() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SubmitVerificationCodeRateLimited -> Log.i(TAG, "Received SubmitVerificationCodeRateLimited.", sessionResult.getCause())
|
||||||
|
|
||||||
is TokenNotAccepted -> Log.i(TAG, "Received TokenNotAccepted.", sessionResult.getCause())
|
is TokenNotAccepted -> Log.i(TAG, "Received TokenNotAccepted.", sessionResult.getCause())
|
||||||
|
|
||||||
|
@ -733,7 +748,7 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
var reglock = registrationLocked
|
var reglock = registrationLocked
|
||||||
|
|
||||||
val session: RegistrationSessionMetadataJson? = getOrCreateValidSession(context)?.body
|
val session: RegistrationSessionMetadataJson? = getOrCreateValidSession(context)?.metadata
|
||||||
val sessionId: String = session?.id ?: return
|
val sessionId: String = session?.id ?: return
|
||||||
val registrationData: RegistrationData = getRegistrationData()
|
val registrationData: RegistrationData = getRegistrationData()
|
||||||
|
|
||||||
|
@ -811,8 +826,22 @@ class RegistrationViewModel : ViewModel() {
|
||||||
private suspend fun registerVerifiedSession(context: Context, sessionId: String) {
|
private suspend fun registerVerifiedSession(context: Context, sessionId: String) {
|
||||||
Log.v(TAG, "registerVerifiedSession()")
|
Log.v(TAG, "registerVerifiedSession()")
|
||||||
val registrationData = getRegistrationData()
|
val registrationData = getRegistrationData()
|
||||||
val registrationResponse: RegisterAccountResult = RegistrationRepository.registerAccount(context = context, sessionId = sessionId, registrationData = registrationData, recoveryPassword = null)
|
val registrationResult: RegisterAccountResult = RegistrationRepository.registerAccount(context = context, sessionId = sessionId, registrationData = registrationData, recoveryPassword = null)
|
||||||
handleRegistrationResult(context, registrationData, registrationResponse, false)
|
|
||||||
|
val reglockEnabled = if (registrationResult is RegisterAccountResult.RegistrationLocked) {
|
||||||
|
Log.i(TAG, "Received a registration lock response when trying to register verified session. Retrying with master key.")
|
||||||
|
store.update {
|
||||||
|
it.copy(
|
||||||
|
svr2AuthCredentials = registrationResult.svr2Credentials,
|
||||||
|
svr3AuthCredentials = registrationResult.svr3Credentials
|
||||||
|
)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRegistrationResult(context, registrationData, registrationResult, reglockEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: AccountRegistrationResult, reglockEnabled: Boolean) {
|
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: AccountRegistrationResult, reglockEnabled: Boolean) {
|
||||||
|
|
|
@ -27,6 +27,9 @@ import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle
|
||||||
import org.thoughtcrime.securesms.databinding.FragmentRegistrationEnterCodeBinding
|
import org.thoughtcrime.securesms.databinding.FragmentRegistrationEnterCodeBinding
|
||||||
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
|
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
|
||||||
import org.thoughtcrime.securesms.registration.data.network.RegistrationResult
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationResult
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCheckResult
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCreationResult
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
|
||||||
import org.thoughtcrime.securesms.registration.fragments.ContactSupportBottomSheetFragment
|
import org.thoughtcrime.securesms.registration.fragments.ContactSupportBottomSheetFragment
|
||||||
import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView
|
import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView
|
||||||
|
@ -37,6 +40,7 @@ import org.thoughtcrime.securesms.registration.ui.RegistrationViewModel
|
||||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener
|
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The final screen of account registration, where the user enters their verification code.
|
* The final screen of account registration, where the user enters their verification code.
|
||||||
|
@ -44,11 +48,11 @@ import org.thoughtcrime.securesms.util.visible
|
||||||
class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_code) {
|
class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_code) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val TAG = Log.tag(EnterCodeFragment::class.java)
|
||||||
|
|
||||||
private const val BOTTOM_SHEET_TAG = "support_bottom_sheet"
|
private const val BOTTOM_SHEET_TAG = "support_bottom_sheet"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val TAG = Log.tag(EnterCodeFragment::class.java)
|
|
||||||
|
|
||||||
private val sharedViewModel by activityViewModels<RegistrationViewModel>()
|
private val sharedViewModel by activityViewModels<RegistrationViewModel>()
|
||||||
private val fragmentViewModel by viewModels<EnterCodeViewModel>()
|
private val fragmentViewModel by viewModels<EnterCodeViewModel>()
|
||||||
private val bottomSheet = ContactSupportBottomSheetFragment()
|
private val bottomSheet = ContactSupportBottomSheetFragment()
|
||||||
|
@ -116,6 +120,11 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedViewModel.uiState.observe(viewLifecycleOwner) {
|
sharedViewModel.uiState.observe(viewLifecycleOwner) {
|
||||||
|
it.sessionCreationError?.let { error ->
|
||||||
|
handleSessionCreationError(error)
|
||||||
|
sharedViewModel.sessionCreationErrorShown()
|
||||||
|
}
|
||||||
|
|
||||||
it.sessionStateError?.let { error ->
|
it.sessionStateError?.let { error ->
|
||||||
handleSessionErrorResponse(error)
|
handleSessionErrorResponse(error)
|
||||||
sharedViewModel.sessionStateErrorShown()
|
sharedViewModel.sessionStateErrorShown()
|
||||||
|
@ -160,18 +169,65 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSessionCreationError(result: RegistrationSessionResult) {
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
Log.i(TAG, "[sessionCreateError] Handling error response of ${result.javaClass.name}", result.getCause())
|
||||||
|
}
|
||||||
|
when (result) {
|
||||||
|
is RegistrationSessionCheckResult.Success,
|
||||||
|
is RegistrationSessionCreationResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
||||||
|
is RegistrationSessionCreationResult.AttemptsExhausted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_service))
|
||||||
|
is RegistrationSessionCreationResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
|
||||||
|
|
||||||
|
is RegistrationSessionCreationResult.RateLimited -> {
|
||||||
|
val timeRemaining = result.timeRemaining?.milliseconds
|
||||||
|
Log.i(TAG, "Session creation rate limited! Next attempt: $timeRemaining")
|
||||||
|
if (timeRemaining != null) {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, timeRemaining.toString()))
|
||||||
|
} else {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationSessionCreationResult.ServerUnableToParse -> presentGenericError(result)
|
||||||
|
is RegistrationSessionCheckResult.SessionNotFound -> presentGenericError(result)
|
||||||
|
is RegistrationSessionCheckResult.UnknownError,
|
||||||
|
is RegistrationSessionCreationResult.UnknownError -> presentGenericError(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSessionErrorResponse(result: VerificationCodeRequestResult) {
|
private fun handleSessionErrorResponse(result: VerificationCodeRequestResult) {
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
Log.i(TAG, "[sessionError] Handling error response of ${result.javaClass.name}", result.getCause())
|
||||||
|
}
|
||||||
|
|
||||||
when (result) {
|
when (result) {
|
||||||
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
||||||
is VerificationCodeRequestResult.RateLimited -> presentRateLimitedDialog()
|
is VerificationCodeRequestResult.RateLimited -> {
|
||||||
is VerificationCodeRequestResult.AttemptsExhausted -> presentAccountLocked()
|
val timeRemaining = result.timeRemaining?.milliseconds
|
||||||
|
Log.i(TAG, "Session patch rate limited! Next attempt: $timeRemaining")
|
||||||
|
if (timeRemaining != null) {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, timeRemaining.toString()))
|
||||||
|
} else {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later))
|
||||||
|
}
|
||||||
|
}
|
||||||
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
||||||
is VerificationCodeRequestResult.ExternalServiceFailure -> presentSmsGenericError(result)
|
is VerificationCodeRequestResult.ExternalServiceFailure -> presentSmsGenericError(result)
|
||||||
|
is VerificationCodeRequestResult.RequestVerificationCodeRateLimited -> {
|
||||||
|
Log.i(TAG, result.log())
|
||||||
|
handleRequestVerificationCodeRateLimited(result)
|
||||||
|
}
|
||||||
|
is VerificationCodeRequestResult.SubmitVerificationCodeRateLimited -> presentSubmitVerificationCodeRateLimited()
|
||||||
else -> presentGenericError(result)
|
else -> presentGenericError(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRegistrationErrorResponse(result: RegisterAccountResult) {
|
private fun handleRegistrationErrorResponse(result: RegisterAccountResult) {
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
Log.i(TAG, "[registrationError] Handling error response of ${result.javaClass.name}", result.getCause())
|
||||||
|
}
|
||||||
|
|
||||||
when (result) {
|
when (result) {
|
||||||
is RegisterAccountResult.Success -> throw IllegalStateException("Register account error handler called on successful response!")
|
is RegisterAccountResult.Success -> throw IllegalStateException("Register account error handler called on successful response!")
|
||||||
is RegisterAccountResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
is RegisterAccountResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
||||||
|
@ -248,6 +304,14 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun presentRemoteErrorDialog(message: String, positiveButtonListener: DialogInterface.OnClickListener? = null) {
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setMessage(message)
|
||||||
|
setPositiveButton(android.R.string.ok, positiveButtonListener)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun presentGenericError(requestResult: RegistrationResult) {
|
private fun presentGenericError(requestResult: RegistrationResult) {
|
||||||
binding.keyboard.displayFailure().addListener(
|
binding.keyboard.displayFailure().addListener(
|
||||||
object : AssertedSuccessListener<Boolean>() {
|
object : AssertedSuccessListener<Boolean>() {
|
||||||
|
@ -263,6 +327,36 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleRequestVerificationCodeRateLimited(result: VerificationCodeRequestResult.RequestVerificationCodeRateLimited) {
|
||||||
|
if (result.willBeAbleToRequestAgain) {
|
||||||
|
Log.i(TAG, "Attempted to request new code too soon, timers should be updated")
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Request for new verification code impossible, need to restart registration")
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ -> popBackStack() }
|
||||||
|
setCancelable(false)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun presentSubmitVerificationCodeRateLimited() {
|
||||||
|
binding.keyboard.displayFailure().addListener(
|
||||||
|
object : AssertedSuccessListener<Boolean>() {
|
||||||
|
override fun onSuccess(result: Boolean?) {
|
||||||
|
Log.w(TAG, "Submit verification code impossible, need to request a new code and restart registration")
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ -> popBackStack() }
|
||||||
|
setCancelable(false)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun popBackStack() {
|
private fun popBackStack() {
|
||||||
sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PUSH_NETWORK_AUDITED)
|
sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PUSH_NETWORK_AUDITED)
|
||||||
NavHostFragment.findNavController(this).popBackStack()
|
NavHostFragment.findNavController(this).popBackStack()
|
||||||
|
|
|
@ -329,8 +329,13 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
is RegistrationSessionCreationResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
is RegistrationSessionCreationResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
||||||
|
|
||||||
is RegistrationSessionCreationResult.RateLimited -> {
|
is RegistrationSessionCreationResult.RateLimited -> {
|
||||||
Log.i(TAG, "Session creation rate limited! Next attempt: ${result.timeRemaining.milliseconds}")
|
val timeRemaining = result.timeRemaining?.milliseconds
|
||||||
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, result.timeRemaining.milliseconds.toString()))
|
Log.i(TAG, "Session creation rate limited! Next attempt: $timeRemaining")
|
||||||
|
if (timeRemaining != null) {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, timeRemaining.toString()))
|
||||||
|
} else {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is RegistrationSessionCreationResult.ServerUnableToParse -> presentGenericError(result)
|
is RegistrationSessionCreationResult.ServerUnableToParse -> presentGenericError(result)
|
||||||
|
@ -346,7 +351,6 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
}
|
}
|
||||||
when (result) {
|
when (result) {
|
||||||
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
||||||
is VerificationCodeRequestResult.AttemptsExhausted -> presentRateLimitedDialog()
|
|
||||||
is VerificationCodeRequestResult.ChallengeRequired -> handleChallenges(result.challenges)
|
is VerificationCodeRequestResult.ChallengeRequired -> handleChallenges(result.challenges)
|
||||||
is VerificationCodeRequestResult.ExternalServiceFailure -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_sms_provider_error))
|
is VerificationCodeRequestResult.ExternalServiceFailure -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_sms_provider_error))
|
||||||
is VerificationCodeRequestResult.ImpossibleNumber -> {
|
is VerificationCodeRequestResult.ImpossibleNumber -> {
|
||||||
|
@ -369,11 +373,20 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
}
|
}
|
||||||
|
|
||||||
is VerificationCodeRequestResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
is VerificationCodeRequestResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
||||||
is VerificationCodeRequestResult.MustRetry -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
is VerificationCodeRequestResult.RequestVerificationCodeRateLimited -> {
|
||||||
|
Log.i(TAG, result.log())
|
||||||
|
handleRequestVerificationCodeRateLimited(result)
|
||||||
|
}
|
||||||
|
is VerificationCodeRequestResult.SubmitVerificationCodeRateLimited -> presentGenericError(result)
|
||||||
is VerificationCodeRequestResult.NonNormalizedNumber -> handleNonNormalizedNumberError(result.originalNumber, result.normalizedNumber, fragmentViewModel.mode)
|
is VerificationCodeRequestResult.NonNormalizedNumber -> handleNonNormalizedNumberError(result.originalNumber, result.normalizedNumber, fragmentViewModel.mode)
|
||||||
is VerificationCodeRequestResult.RateLimited -> {
|
is VerificationCodeRequestResult.RateLimited -> {
|
||||||
Log.i(TAG, "Code request rate limited! Next attempt: ${result.timeRemaining.milliseconds}")
|
val timeRemaining = result.timeRemaining?.milliseconds
|
||||||
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, result.timeRemaining.milliseconds.toString()))
|
Log.i(TAG, "Session patch rate limited! Next attempt: $timeRemaining")
|
||||||
|
if (timeRemaining != null) {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, timeRemaining.toString()))
|
||||||
|
} else {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is VerificationCodeRequestResult.TokenNotAccepted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_we_need_to_verify_that_youre_human)) { _, _ -> moveToCaptcha() }
|
is VerificationCodeRequestResult.TokenNotAccepted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_we_need_to_verify_that_youre_human)) { _, _ -> moveToCaptcha() }
|
||||||
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
||||||
|
@ -426,6 +439,23 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleRequestVerificationCodeRateLimited(result: VerificationCodeRequestResult.RequestVerificationCodeRateLimited) {
|
||||||
|
if (result.willBeAbleToRequestAgain) {
|
||||||
|
Log.i(TAG, "New verification code cannot be requested yet but can soon, moving to enter code to show timers")
|
||||||
|
moveToVerificationEntryScreen()
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Unable to request new verification code, prompting to start new session")
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setMessage(R.string.RegistrationActivity_unable_to_connect_to_service)
|
||||||
|
setPositiveButton(R.string.NetworkFailure__retry) { _, _ ->
|
||||||
|
onRegistrationButtonClicked()
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleNonNormalizedNumberError(originalNumber: String, normalizedNumber: String, mode: RegistrationRepository.E164VerificationMode) {
|
private fun handleNonNormalizedNumberError(originalNumber: String, normalizedNumber: String, mode: RegistrationRepository.E164VerificationMode) {
|
||||||
try {
|
try {
|
||||||
val phoneNumber = PhoneNumberUtil.getInstance().parse(normalizedNumber, null)
|
val phoneNumber = PhoneNumberUtil.getInstance().parse(normalizedNumber, null)
|
||||||
|
|
|
@ -148,9 +148,6 @@ class RegistrationLockFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
when (requestResult) {
|
when (requestResult) {
|
||||||
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
||||||
is VerificationCodeRequestResult.RateLimited -> onRateLimited()
|
is VerificationCodeRequestResult.RateLimited -> onRateLimited()
|
||||||
is VerificationCodeRequestResult.AttemptsExhausted -> {
|
|
||||||
findNavController().safeNavigate(RegistrationLockFragmentDirections.actionAccountLocked())
|
|
||||||
}
|
|
||||||
|
|
||||||
is VerificationCodeRequestResult.RegistrationLocked -> {
|
is VerificationCodeRequestResult.RegistrationLocked -> {
|
||||||
Log.i(TAG, "Registration locked response to verify account!")
|
Log.i(TAG, "Registration locked response to verify account!")
|
||||||
|
|
|
@ -302,7 +302,7 @@ object RegistrationRepository {
|
||||||
val result = RegistrationSessionCreationResult.from(registrationSessionResult)
|
val result = RegistrationSessionCreationResult.from(registrationSessionResult)
|
||||||
if (result is RegistrationSessionCreationResult.Success) {
|
if (result is RegistrationSessionCreationResult.Success) {
|
||||||
Log.d(TAG, "Updating registration session and E164 in value store.")
|
Log.d(TAG, "Updating registration session and E164 in value store.")
|
||||||
SignalStore.registration.sessionId = result.getMetadata().body.id
|
SignalStore.registration.sessionId = result.getMetadata().metadata.id
|
||||||
SignalStore.registration.sessionE164 = e164
|
SignalStore.registration.sessionE164 = e164
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,8 +467,8 @@ object RegistrationRepository {
|
||||||
if (receivedPush) {
|
if (receivedPush) {
|
||||||
val challenge = subscriber.challenge
|
val challenge = subscriber.challenge
|
||||||
if (challenge != null) {
|
if (challenge != null) {
|
||||||
Log.w(TAG, "Push challenge token received.")
|
Log.i(TAG, "Push challenge token received.")
|
||||||
return@withContext accountManager.submitPushChallengeToken(sessionCreationResponse.result.body.id, challenge)
|
return@withContext accountManager.submitPushChallengeToken(sessionCreationResponse.result.metadata.id, challenge)
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Push received but challenge token was null.")
|
Log.w(TAG, "Push received but challenge token was null.")
|
||||||
}
|
}
|
||||||
|
@ -489,7 +489,7 @@ object RegistrationRepository {
|
||||||
return 0L
|
return 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
val timestamp: Long = headers.timestamp
|
val timestamp: Long = headers.serverDeliveredTimestamp
|
||||||
return timestamp + deltaSeconds.seconds.inWholeMilliseconds
|
return timestamp + deltaSeconds.seconds.inWholeMilliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,17 +48,17 @@ import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionC
|
||||||
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AlreadyVerified
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AlreadyVerified
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AttemptsExhausted
|
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ChallengeRequired
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ChallengeRequired
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ExternalServiceFailure
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ExternalServiceFailure
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ImpossibleNumber
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ImpossibleNumber
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.InvalidTransportModeFailure
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.InvalidTransportModeFailure
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.MalformedRequest
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.MalformedRequest
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.MustRetry
|
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.NoSuchSession
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.NoSuchSession
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.NonNormalizedNumber
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.NonNormalizedNumber
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RateLimited
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RateLimited
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RegistrationLocked
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RegistrationLocked
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.RequestVerificationCodeRateLimited
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.SubmitVerificationCodeRateLimited
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.Success
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.Success
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.TokenNotAccepted
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.TokenNotAccepted
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.UnknownError
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.UnknownError
|
||||||
|
@ -277,26 +277,26 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for confirming the entered E164.") }
|
val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for confirming the entered E164.") }
|
||||||
|
|
||||||
if (validSession.body.verified) {
|
if (validSession.metadata.verified) {
|
||||||
Log.i(TAG, "Session is already verified, registering account.")
|
Log.i(TAG, "Session is already verified, registering account.")
|
||||||
registerVerifiedSession(context, validSession.body.id)
|
registerVerifiedSession(context, validSession.metadata.id)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validSession.body.allowedToRequestCode) {
|
if (!validSession.metadata.allowedToRequestCode) {
|
||||||
if (System.currentTimeMillis() > (validSession.body.nextVerificationAttempt ?: Int.MAX_VALUE)) {
|
if (System.currentTimeMillis() > (validSession.metadata.nextVerificationAttempt ?: Int.MAX_VALUE)) {
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(registrationCheckpoint = RegistrationCheckpoint.VERIFICATION_CODE_REQUESTED)
|
it.copy(registrationCheckpoint = RegistrationCheckpoint.VERIFICATION_CODE_REQUESTED)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val challenges = validSession.body.requestedInformation
|
val challenges = validSession.metadata.requestedInformation
|
||||||
Log.i(TAG, "Not allowed to request code! Remaining challenges: ${challenges.joinToString()}")
|
Log.i(TAG, "Not allowed to request code! Remaining challenges: ${challenges.joinToString()}")
|
||||||
handleSessionStateResult(context, ChallengeRequired(Challenge.parse(validSession.body.requestedInformation)))
|
handleSessionStateResult(context, ChallengeRequired(Challenge.parse(validSession.metadata.requestedInformation)))
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
requestSmsCodeInternal(context, validSession.body.id, e164)
|
requestSmsCodeInternal(context, validSession.metadata.id, e164)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for requesting an SMS code.") }
|
val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for requesting an SMS code.") }
|
||||||
requestSmsCodeInternal(context, validSession.body.id, e164)
|
requestSmsCodeInternal(context, validSession.metadata.id, e164)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ class RegistrationViewModel : ViewModel() {
|
||||||
Log.d(TAG, "Requesting voice call code…")
|
Log.d(TAG, "Requesting voice call code…")
|
||||||
val codeRequestResponse = RegistrationRepository.requestSmsCode(
|
val codeRequestResponse = RegistrationRepository.requestSmsCode(
|
||||||
context = context,
|
context = context,
|
||||||
sessionId = validSession.body.id,
|
sessionId = validSession.metadata.id,
|
||||||
e164 = e164,
|
e164 = e164,
|
||||||
password = password,
|
password = password,
|
||||||
mode = RegistrationRepository.E164VerificationMode.PHONE_CALL
|
mode = RegistrationRepository.E164VerificationMode.PHONE_CALL
|
||||||
|
@ -397,13 +397,13 @@ class RegistrationViewModel : ViewModel() {
|
||||||
successListener = { networkResult ->
|
successListener = { networkResult ->
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
sessionId = networkResult.body.id,
|
sessionId = networkResult.metadata.id,
|
||||||
nextSmsTimestamp = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextSms),
|
nextSmsTimestamp = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.metadata.nextSms),
|
||||||
nextCallTimestamp = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextCall),
|
nextCallTimestamp = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.metadata.nextCall),
|
||||||
nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.body.nextVerificationAttempt),
|
nextVerificationAttempt = RegistrationRepository.deriveTimestamp(networkResult.headers, networkResult.metadata.nextVerificationAttempt),
|
||||||
allowedToRequestCode = networkResult.body.allowedToRequestCode,
|
allowedToRequestCode = networkResult.metadata.allowedToRequestCode,
|
||||||
challengesRequested = Challenge.parse(networkResult.body.requestedInformation),
|
challengesRequested = Challenge.parse(networkResult.metadata.requestedInformation),
|
||||||
verified = networkResult.body.verified
|
verified = networkResult.metadata.verified
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -430,7 +430,7 @@ class RegistrationViewModel : ViewModel() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a captcha token.") }
|
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a captcha token.") }
|
||||||
Log.d(TAG, "Submitting captcha token…")
|
Log.d(TAG, "Submitting captcha token…")
|
||||||
val captchaSubmissionResult = RegistrationRepository.submitCaptchaToken(context, e164, password, session.body.id, captchaToken)
|
val captchaSubmissionResult = RegistrationRepository.submitCaptchaToken(context, e164, password, session.metadata.id, captchaToken)
|
||||||
Log.d(TAG, "Captcha token submitted.")
|
Log.d(TAG, "Captcha token submitted.")
|
||||||
|
|
||||||
handleSessionStateResult(context, captchaSubmissionResult)
|
handleSessionStateResult(context, captchaSubmissionResult)
|
||||||
|
@ -448,12 +448,12 @@ class RegistrationViewModel : ViewModel() {
|
||||||
Log.d(TAG, "Getting session in order to perform push token verification…")
|
Log.d(TAG, "Getting session in order to perform push token verification…")
|
||||||
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") }
|
val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") }
|
||||||
|
|
||||||
if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) {
|
if (!Challenge.parse(session.metadata.requestedInformation).contains(Challenge.PUSH)) {
|
||||||
return@launch bail { Log.i(TAG, "Push challenge token no longer needed, bailing.") }
|
return@launch bail { Log.i(TAG, "Push challenge token no longer needed, bailing.") }
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Requesting push challenge token…")
|
Log.d(TAG, "Requesting push challenge token…")
|
||||||
val pushSubmissionResult = RegistrationRepository.requestAndVerifyPushToken(context, session.body.id, e164, password)
|
val pushSubmissionResult = RegistrationRepository.requestAndVerifyPushToken(context, session.metadata.id, e164, password)
|
||||||
Log.d(TAG, "Push challenge token submitted.")
|
Log.d(TAG, "Push challenge token submitted.")
|
||||||
handleSessionStateResult(context, pushSubmissionResult)
|
handleSessionStateResult(context, pushSubmissionResult)
|
||||||
}
|
}
|
||||||
|
@ -495,8 +495,6 @@ class RegistrationViewModel : ViewModel() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
is AttemptsExhausted -> Log.i(TAG, "Received AttemptsExhausted.", sessionResult.getCause())
|
|
||||||
|
|
||||||
is ImpossibleNumber -> Log.i(TAG, "Received ImpossibleNumber.", sessionResult.getCause())
|
is ImpossibleNumber -> Log.i(TAG, "Received ImpossibleNumber.", sessionResult.getCause())
|
||||||
|
|
||||||
is NonNormalizedNumber -> Log.i(TAG, "Received NonNormalizedNumber.", sessionResult.getCause())
|
is NonNormalizedNumber -> Log.i(TAG, "Received NonNormalizedNumber.", sessionResult.getCause())
|
||||||
|
@ -509,7 +507,24 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
is MalformedRequest -> Log.i(TAG, "Received MalformedRequest.", sessionResult.getCause())
|
is MalformedRequest -> Log.i(TAG, "Received MalformedRequest.", sessionResult.getCause())
|
||||||
|
|
||||||
is MustRetry -> Log.i(TAG, "Received MustRetry.", sessionResult.getCause())
|
is RequestVerificationCodeRateLimited -> {
|
||||||
|
Log.i(TAG, "Received RequestVerificationCodeRateLimited.", sessionResult.getCause())
|
||||||
|
|
||||||
|
if (sessionResult.willBeAbleToRequestAgain) {
|
||||||
|
store.update {
|
||||||
|
it.copy(
|
||||||
|
nextSmsTimestamp = sessionResult.nextSmsTimestamp,
|
||||||
|
nextCallTimestamp = sessionResult.nextCallTimestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Request verification code rate limit is forever, need to start new session")
|
||||||
|
SignalStore.registration.sessionId = null
|
||||||
|
store.update { RegistrationState() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is SubmitVerificationCodeRateLimited -> Log.i(TAG, "Received SubmitVerificationCodeRateLimited.", sessionResult.getCause())
|
||||||
|
|
||||||
is TokenNotAccepted -> Log.i(TAG, "Received TokenNotAccepted.", sessionResult.getCause())
|
is TokenNotAccepted -> Log.i(TAG, "Received TokenNotAccepted.", sessionResult.getCause())
|
||||||
|
|
||||||
|
@ -527,8 +542,8 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
sessionStateError = sessionResult,
|
inProgress = false,
|
||||||
inProgress = false
|
sessionStateError = sessionResult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -577,8 +592,8 @@ class RegistrationViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
registerAccountError = registrationResult,
|
inProgress = stayInProgress,
|
||||||
inProgress = stayInProgress
|
registerAccountError = registrationResult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -748,7 +763,7 @@ class RegistrationViewModel : ViewModel() {
|
||||||
|
|
||||||
var reglock = registrationLocked
|
var reglock = registrationLocked
|
||||||
|
|
||||||
val sessionId = getOrCreateValidSession(context)?.body?.id ?: return
|
val sessionId = getOrCreateValidSession(context)?.metadata?.id ?: return
|
||||||
val registrationData = getRegistrationData()
|
val registrationData = getRegistrationData()
|
||||||
|
|
||||||
Log.d(TAG, "Submitting verification code…")
|
Log.d(TAG, "Submitting verification code…")
|
||||||
|
@ -818,8 +833,22 @@ class RegistrationViewModel : ViewModel() {
|
||||||
private suspend fun registerVerifiedSession(context: Context, sessionId: String) {
|
private suspend fun registerVerifiedSession(context: Context, sessionId: String) {
|
||||||
Log.v(TAG, "registerVerifiedSession()")
|
Log.v(TAG, "registerVerifiedSession()")
|
||||||
val registrationData = getRegistrationData()
|
val registrationData = getRegistrationData()
|
||||||
val registrationResponse: RegisterAccountResult = RegistrationRepository.registerAccount(context, sessionId, registrationData)
|
val registrationResult: RegisterAccountResult = RegistrationRepository.registerAccount(context = context, sessionId = sessionId, registrationData = registrationData)
|
||||||
handleRegistrationResult(context, registrationData, registrationResponse, false)
|
|
||||||
|
val reglockEnabled = if (registrationResult is RegisterAccountResult.RegistrationLocked) {
|
||||||
|
Log.i(TAG, "Received a registration lock response when trying to register verified session. Retrying with master key.")
|
||||||
|
store.update {
|
||||||
|
it.copy(
|
||||||
|
svr2AuthCredentials = registrationResult.svr2Credentials,
|
||||||
|
svr3AuthCredentials = registrationResult.svr3Credentials
|
||||||
|
)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRegistrationResult(context, registrationData, registrationResult, reglockEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: AccountRegistrationResult, reglockEnabled: Boolean) = withContext(Dispatchers.IO) {
|
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: AccountRegistrationResult, reglockEnabled: Boolean) = withContext(Dispatchers.IO) {
|
||||||
|
|
|
@ -27,6 +27,9 @@ import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle
|
||||||
import org.thoughtcrime.securesms.databinding.FragmentRegistrationEnterCodeBinding
|
import org.thoughtcrime.securesms.databinding.FragmentRegistrationEnterCodeBinding
|
||||||
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
|
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
|
||||||
import org.thoughtcrime.securesms.registration.data.network.RegistrationResult
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationResult
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCheckResult
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCreationResult
|
||||||
|
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
|
||||||
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
|
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
|
||||||
import org.thoughtcrime.securesms.registration.fragments.ContactSupportBottomSheetFragment
|
import org.thoughtcrime.securesms.registration.fragments.ContactSupportBottomSheetFragment
|
||||||
import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView
|
import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView
|
||||||
|
@ -37,6 +40,7 @@ import org.thoughtcrime.securesms.registrationv3.ui.RegistrationViewModel
|
||||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener
|
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The final screen of account registration, where the user enters their verification code.
|
* The final screen of account registration, where the user enters their verification code.
|
||||||
|
@ -44,11 +48,11 @@ import org.thoughtcrime.securesms.util.visible
|
||||||
class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_code) {
|
class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_code) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val TAG = Log.tag(EnterCodeFragment::class.java)
|
||||||
|
|
||||||
private const val BOTTOM_SHEET_TAG = "support_bottom_sheet"
|
private const val BOTTOM_SHEET_TAG = "support_bottom_sheet"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val TAG = Log.tag(EnterCodeFragment::class.java)
|
|
||||||
|
|
||||||
private val sharedViewModel by activityViewModels<RegistrationViewModel>()
|
private val sharedViewModel by activityViewModels<RegistrationViewModel>()
|
||||||
private val fragmentViewModel by viewModels<EnterCodeViewModel>()
|
private val fragmentViewModel by viewModels<EnterCodeViewModel>()
|
||||||
private val bottomSheet = ContactSupportBottomSheetFragment()
|
private val bottomSheet = ContactSupportBottomSheetFragment()
|
||||||
|
@ -116,6 +120,11 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedViewModel.uiState.observe(viewLifecycleOwner) {
|
sharedViewModel.uiState.observe(viewLifecycleOwner) {
|
||||||
|
it.sessionCreationError?.let { error ->
|
||||||
|
handleSessionCreationError(error)
|
||||||
|
sharedViewModel.sessionCreationErrorShown()
|
||||||
|
}
|
||||||
|
|
||||||
it.sessionStateError?.let { error ->
|
it.sessionStateError?.let { error ->
|
||||||
handleSessionErrorResponse(error)
|
handleSessionErrorResponse(error)
|
||||||
sharedViewModel.sessionStateErrorShown()
|
sharedViewModel.sessionStateErrorShown()
|
||||||
|
@ -160,18 +169,65 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSessionCreationError(result: RegistrationSessionResult) {
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
Log.i(TAG, "[sessionCreateError] Handling error response of ${result.javaClass.name}", result.getCause())
|
||||||
|
}
|
||||||
|
when (result) {
|
||||||
|
is RegistrationSessionCheckResult.Success,
|
||||||
|
is RegistrationSessionCreationResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
||||||
|
is RegistrationSessionCreationResult.AttemptsExhausted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_service))
|
||||||
|
is RegistrationSessionCreationResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
|
||||||
|
|
||||||
|
is RegistrationSessionCreationResult.RateLimited -> {
|
||||||
|
val timeRemaining = result.timeRemaining?.milliseconds
|
||||||
|
Log.i(TAG, "Session creation rate limited! Next attempt: $timeRemaining")
|
||||||
|
if (timeRemaining != null) {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, timeRemaining.toString()))
|
||||||
|
} else {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationSessionCreationResult.ServerUnableToParse -> presentGenericError(result)
|
||||||
|
is RegistrationSessionCheckResult.SessionNotFound -> presentGenericError(result)
|
||||||
|
is RegistrationSessionCheckResult.UnknownError,
|
||||||
|
is RegistrationSessionCreationResult.UnknownError -> presentGenericError(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSessionErrorResponse(result: VerificationCodeRequestResult) {
|
private fun handleSessionErrorResponse(result: VerificationCodeRequestResult) {
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
Log.i(TAG, "[sessionError] Handling error response of ${result.javaClass.name}", result.getCause())
|
||||||
|
}
|
||||||
|
|
||||||
when (result) {
|
when (result) {
|
||||||
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
||||||
is VerificationCodeRequestResult.RateLimited -> presentRateLimitedDialog()
|
is VerificationCodeRequestResult.RateLimited -> {
|
||||||
is VerificationCodeRequestResult.AttemptsExhausted -> presentAccountLocked()
|
val timeRemaining = result.timeRemaining?.milliseconds
|
||||||
|
Log.i(TAG, "Session patch rate limited! Next attempt: $timeRemaining")
|
||||||
|
if (timeRemaining != null) {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, timeRemaining.toString()))
|
||||||
|
} else {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later))
|
||||||
|
}
|
||||||
|
}
|
||||||
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
||||||
is VerificationCodeRequestResult.ExternalServiceFailure -> presentSmsGenericError(result)
|
is VerificationCodeRequestResult.ExternalServiceFailure -> presentSmsGenericError(result)
|
||||||
|
is VerificationCodeRequestResult.RequestVerificationCodeRateLimited -> {
|
||||||
|
Log.i(TAG, result.log())
|
||||||
|
handleRequestVerificationCodeRateLimited(result)
|
||||||
|
}
|
||||||
|
is VerificationCodeRequestResult.SubmitVerificationCodeRateLimited -> presentSubmitVerificationCodeRateLimited()
|
||||||
else -> presentGenericError(result)
|
else -> presentGenericError(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRegistrationErrorResponse(result: RegisterAccountResult) {
|
private fun handleRegistrationErrorResponse(result: RegisterAccountResult) {
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
Log.i(TAG, "[registrationError] Handling error response of ${result.javaClass.name}", result.getCause())
|
||||||
|
}
|
||||||
|
|
||||||
when (result) {
|
when (result) {
|
||||||
is RegisterAccountResult.Success -> throw IllegalStateException("Register account error handler called on successful response!")
|
is RegisterAccountResult.Success -> throw IllegalStateException("Register account error handler called on successful response!")
|
||||||
is RegisterAccountResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
is RegisterAccountResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
|
||||||
|
@ -248,6 +304,14 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun presentRemoteErrorDialog(message: String, positiveButtonListener: DialogInterface.OnClickListener? = null) {
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setMessage(message)
|
||||||
|
setPositiveButton(android.R.string.ok, positiveButtonListener)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun presentGenericError(requestResult: RegistrationResult) {
|
private fun presentGenericError(requestResult: RegistrationResult) {
|
||||||
binding.keyboard.displayFailure().addListener(
|
binding.keyboard.displayFailure().addListener(
|
||||||
object : AssertedSuccessListener<Boolean>() {
|
object : AssertedSuccessListener<Boolean>() {
|
||||||
|
@ -263,6 +327,36 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleRequestVerificationCodeRateLimited(result: VerificationCodeRequestResult.RequestVerificationCodeRateLimited) {
|
||||||
|
if (result.willBeAbleToRequestAgain) {
|
||||||
|
Log.i(TAG, "Attempted to request new code too soon, timers should be updated")
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Request for new verification code impossible, need to restart registration")
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ -> popBackStack() }
|
||||||
|
setCancelable(false)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun presentSubmitVerificationCodeRateLimited() {
|
||||||
|
binding.keyboard.displayFailure().addListener(
|
||||||
|
object : AssertedSuccessListener<Boolean>() {
|
||||||
|
override fun onSuccess(result: Boolean?) {
|
||||||
|
Log.w(TAG, "Submit verification code impossible, need to request a new code and restart registration")
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ -> popBackStack() }
|
||||||
|
setCancelable(false)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun popBackStack() {
|
private fun popBackStack() {
|
||||||
sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PUSH_NETWORK_AUDITED)
|
sharedViewModel.setRegistrationCheckpoint(RegistrationCheckpoint.PUSH_NETWORK_AUDITED)
|
||||||
NavHostFragment.findNavController(this).popBackStack()
|
NavHostFragment.findNavController(this).popBackStack()
|
||||||
|
|
|
@ -340,8 +340,13 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
is RegistrationSessionCreationResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
is RegistrationSessionCreationResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
||||||
|
|
||||||
is RegistrationSessionCreationResult.RateLimited -> {
|
is RegistrationSessionCreationResult.RateLimited -> {
|
||||||
Log.i(TAG, "Session creation rate limited! Next attempt: ${result.timeRemaining.milliseconds}")
|
val timeRemaining = result.timeRemaining?.milliseconds
|
||||||
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, result.timeRemaining.milliseconds.toString()))
|
Log.i(TAG, "Session creation rate limited! Next attempt: $timeRemaining")
|
||||||
|
if (timeRemaining != null) {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, timeRemaining.toString()))
|
||||||
|
} else {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is RegistrationSessionCreationResult.ServerUnableToParse -> presentGenericError(result)
|
is RegistrationSessionCreationResult.ServerUnableToParse -> presentGenericError(result)
|
||||||
|
@ -357,7 +362,6 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
}
|
}
|
||||||
when (result) {
|
when (result) {
|
||||||
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
||||||
is VerificationCodeRequestResult.AttemptsExhausted -> presentRateLimitedDialog()
|
|
||||||
is VerificationCodeRequestResult.ChallengeRequired -> handleChallenges(result.challenges)
|
is VerificationCodeRequestResult.ChallengeRequired -> handleChallenges(result.challenges)
|
||||||
is VerificationCodeRequestResult.ExternalServiceFailure -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_sms_provider_error))
|
is VerificationCodeRequestResult.ExternalServiceFailure -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_sms_provider_error))
|
||||||
is VerificationCodeRequestResult.ImpossibleNumber -> {
|
is VerificationCodeRequestResult.ImpossibleNumber -> {
|
||||||
|
@ -380,11 +384,20 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
}
|
}
|
||||||
|
|
||||||
is VerificationCodeRequestResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
is VerificationCodeRequestResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
||||||
is VerificationCodeRequestResult.MustRetry -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
|
is VerificationCodeRequestResult.RequestVerificationCodeRateLimited -> {
|
||||||
|
Log.i(TAG, result.log())
|
||||||
|
handleRequestVerificationCodeRateLimited(result)
|
||||||
|
}
|
||||||
|
is VerificationCodeRequestResult.SubmitVerificationCodeRateLimited -> presentGenericError(result)
|
||||||
is VerificationCodeRequestResult.NonNormalizedNumber -> handleNonNormalizedNumberError(result.originalNumber, result.normalizedNumber, fragmentViewModel.e164VerificationMode)
|
is VerificationCodeRequestResult.NonNormalizedNumber -> handleNonNormalizedNumberError(result.originalNumber, result.normalizedNumber, fragmentViewModel.e164VerificationMode)
|
||||||
is VerificationCodeRequestResult.RateLimited -> {
|
is VerificationCodeRequestResult.RateLimited -> {
|
||||||
Log.i(TAG, "Code request rate limited! Next attempt: ${result.timeRemaining.milliseconds}")
|
val timeRemaining = result.timeRemaining?.milliseconds
|
||||||
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, result.timeRemaining.milliseconds.toString()))
|
Log.i(TAG, "Session patch rate limited! Next attempt: $timeRemaining")
|
||||||
|
if (timeRemaining != null) {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, timeRemaining.toString()))
|
||||||
|
} else {
|
||||||
|
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is VerificationCodeRequestResult.TokenNotAccepted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_we_need_to_verify_that_youre_human)) { _, _ -> moveToCaptcha() }
|
is VerificationCodeRequestResult.TokenNotAccepted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_we_need_to_verify_that_youre_human)) { _, _ -> moveToCaptcha() }
|
||||||
|
@ -438,6 +451,23 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleRequestVerificationCodeRateLimited(result: VerificationCodeRequestResult.RequestVerificationCodeRateLimited) {
|
||||||
|
if (result.willBeAbleToRequestAgain) {
|
||||||
|
Log.i(TAG, "New verification code cannot be requested yet but can soon, moving to enter code to show timers")
|
||||||
|
moveToVerificationEntryScreen()
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Unable to request new verification code, prompting to start new session")
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||||
|
setMessage(R.string.RegistrationActivity_unable_to_connect_to_service)
|
||||||
|
setPositiveButton(R.string.NetworkFailure__retry) { _, _ ->
|
||||||
|
onRegistrationButtonClicked()
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleNonNormalizedNumberError(originalNumber: String, normalizedNumber: String, mode: RegistrationRepository.E164VerificationMode) {
|
private fun handleNonNormalizedNumberError(originalNumber: String, normalizedNumber: String, mode: RegistrationRepository.E164VerificationMode) {
|
||||||
try {
|
try {
|
||||||
val phoneNumber = PhoneNumberUtil.getInstance().parse(normalizedNumber, null)
|
val phoneNumber = PhoneNumberUtil.getInstance().parse(normalizedNumber, null)
|
||||||
|
|
|
@ -148,9 +148,6 @@ class RegistrationLockFragment : LoggingFragment(R.layout.fragment_registration_
|
||||||
when (requestResult) {
|
when (requestResult) {
|
||||||
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
|
||||||
is VerificationCodeRequestResult.RateLimited -> onRateLimited()
|
is VerificationCodeRequestResult.RateLimited -> onRateLimited()
|
||||||
is VerificationCodeRequestResult.AttemptsExhausted -> {
|
|
||||||
findNavController().safeNavigate(RegistrationLockFragmentDirections.actionAccountLocked())
|
|
||||||
}
|
|
||||||
|
|
||||||
is VerificationCodeRequestResult.RegistrationLocked -> {
|
is VerificationCodeRequestResult.RegistrationLocked -> {
|
||||||
Log.i(TAG, "Registration locked response to verify account!")
|
Log.i(TAG, "Registration locked response to verify account!")
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
|
|
||||||
package org.whispersystems.signalservice.api.push.exceptions
|
package org.whispersystems.signalservice.api.push.exceptions
|
||||||
|
|
||||||
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson
|
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We tried to do something on registration endpoints that didn't go well, so now we have to do a challenge. And not a
|
* We tried to do something on registration endpoints that didn't go well, so now we have to do a challenge. And not a
|
||||||
* fun one involving ice buckets.
|
* fun one involving ice buckets.
|
||||||
*/
|
*/
|
||||||
class ChallengeRequiredException(val response: RegistrationSessionMetadataJson) : NonSuccessfulResponseCodeException(409)
|
class ChallengeRequiredException(val response: RegistrationSessionMetadataResponse) : NonSuccessfulResponseCodeException(409)
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.signalservice.api.push.exceptions
|
|
||||||
|
|
||||||
class RegistrationRetryException : NonSuccessfulResponseCodeException(429)
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.signalservice.api.push.exceptions
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate limit exception specific to requesting a verification code for registration.
|
||||||
|
*/
|
||||||
|
class RequestVerificationCodeRateLimitException(
|
||||||
|
val sessionMetadata: RegistrationSessionMetadataResponse
|
||||||
|
) : NonSuccessfulResponseCodeException(429, "User request verification code rate limited")
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.signalservice.api.push.exceptions
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate limit exception specific to submitting verification codes during registration.
|
||||||
|
*/
|
||||||
|
class SubmitVerificationCodeRateLimitException(
|
||||||
|
val sessionMetadata: RegistrationSessionMetadataResponse
|
||||||
|
) : NonSuccessfulResponseCodeException(429, "User submit verification code rate limited")
|
|
@ -106,9 +106,10 @@ import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredExcepti
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.RangeException;
|
import org.whispersystems.signalservice.api.push.exceptions.RangeException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.RegistrationRetryException;
|
import org.whispersystems.signalservice.api.push.exceptions.RequestVerificationCodeRateLimitException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException;
|
import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
|
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.SubmitVerificationCodeRateLimitException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException;
|
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotAssociatedWithAnAccountException;
|
import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotAssociatedWithAnAccountException;
|
||||||
|
@ -437,7 +438,7 @@ public class PushServiceSocket {
|
||||||
|
|
||||||
body.put("client", androidSmsRetriever ? "android-2021-03" : "android");
|
body.put("client", androidSmsRetriever ? "android-2021-03" : "android");
|
||||||
|
|
||||||
try (Response response = makeServiceRequest(path, "POST", jsonRequestBody(JsonUtil.toJson(body)), headers, new RegistrationCodeRequestResponseHandler(), SealedSenderAccess.NONE, false)) {
|
try (Response response = makeServiceRequest(path, "POST", jsonRequestBody(JsonUtil.toJson(body)), headers, new RequestVerificationCodeResponseHandler(), SealedSenderAccess.NONE, false)) {
|
||||||
return parseSessionMetadataResponse(response);
|
return parseSessionMetadataResponse(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,7 +447,7 @@ public class PushServiceSocket {
|
||||||
String path = String.format(VERIFICATION_CODE_PATH, sessionId);
|
String path = String.format(VERIFICATION_CODE_PATH, sessionId);
|
||||||
Map<String, String> body = new HashMap<>();
|
Map<String, String> body = new HashMap<>();
|
||||||
body.put("code", verificationCode);
|
body.put("code", verificationCode);
|
||||||
try (Response response = makeServiceRequest(path, "PUT", jsonRequestBody(JsonUtil.toJson(body)), NO_HEADERS, new RegistrationCodeSubmissionResponseHandler(), SealedSenderAccess.NONE, false)) {
|
try (Response response = makeServiceRequest(path, "PUT", jsonRequestBody(JsonUtil.toJson(body)), NO_HEADERS, new SubmitVerificationCodeResponseHandler(), SealedSenderAccess.NONE, false)) {
|
||||||
return parseSessionMetadataResponse(response);
|
return parseSessionMetadataResponse(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1040,7 +1041,7 @@ public class PushServiceSocket {
|
||||||
public void checkRepeatedUsePreKeys(ServiceIdType serviceIdType, byte[] digest) throws IOException {
|
public void checkRepeatedUsePreKeys(ServiceIdType serviceIdType, byte[] digest) throws IOException {
|
||||||
String body = JsonUtil.toJson(new CheckRepeatedUsedPreKeysRequest(serviceIdType.toString(), digest));
|
String body = JsonUtil.toJson(new CheckRepeatedUsedPreKeysRequest(serviceIdType.toString(), digest));
|
||||||
|
|
||||||
makeServiceRequest(PREKEY_CHECK_PATH, "POST", body, NO_HEADERS, (responseCode, body1) -> {
|
makeServiceRequest(PREKEY_CHECK_PATH, "POST", body, NO_HEADERS, (responseCode, errorBody, getHeader) -> {
|
||||||
// Must override this handling because otherwise code assumes a device mismatch error
|
// Must override this handling because otherwise code assumes a device mismatch error
|
||||||
if (responseCode == 409) {
|
if (responseCode == 409) {
|
||||||
throw new NonSuccessfulResponseCodeException(409);
|
throw new NonSuccessfulResponseCodeException(409);
|
||||||
|
@ -1278,7 +1279,7 @@ public class PushServiceSocket {
|
||||||
"GET",
|
"GET",
|
||||||
null,
|
null,
|
||||||
NO_HEADERS,
|
NO_HEADERS,
|
||||||
(responseCode, body) -> {
|
(responseCode, body, getHeader) -> {
|
||||||
if (responseCode == 404) {
|
if (responseCode == 404) {
|
||||||
throw new UsernameIsNotAssociatedWithAnAccountException();
|
throw new UsernameIsNotAssociatedWithAnAccountException();
|
||||||
}
|
}
|
||||||
|
@ -1301,7 +1302,7 @@ public class PushServiceSocket {
|
||||||
public @NonNull ReserveUsernameResponse reserveUsername(@NonNull List<String> usernameHashes) throws IOException {
|
public @NonNull ReserveUsernameResponse reserveUsername(@NonNull List<String> usernameHashes) throws IOException {
|
||||||
ReserveUsernameRequest reserveUsernameRequest = new ReserveUsernameRequest(usernameHashes);
|
ReserveUsernameRequest reserveUsernameRequest = new ReserveUsernameRequest(usernameHashes);
|
||||||
|
|
||||||
String responseString = makeServiceRequest(RESERVE_USERNAME_PATH, "PUT", JsonUtil.toJson(reserveUsernameRequest), NO_HEADERS, (responseCode, body) -> {
|
String responseString = makeServiceRequest(RESERVE_USERNAME_PATH, "PUT", JsonUtil.toJson(reserveUsernameRequest), NO_HEADERS, (responseCode, body, getHeader) -> {
|
||||||
switch (responseCode) {
|
switch (responseCode) {
|
||||||
case 422: throw new UsernameMalformedException();
|
case 422: throw new UsernameMalformedException();
|
||||||
case 409: throw new UsernameTakenException();
|
case 409: throw new UsernameTakenException();
|
||||||
|
@ -1330,7 +1331,7 @@ public class PushServiceSocket {
|
||||||
Base64.encodeUrlSafeWithoutPadding(link.getEncryptedUsername())
|
Base64.encodeUrlSafeWithoutPadding(link.getEncryptedUsername())
|
||||||
);
|
);
|
||||||
|
|
||||||
String response = makeServiceRequest(CONFIRM_USERNAME_PATH, "PUT", JsonUtil.toJson(confirmUsernameRequest), NO_HEADERS, (responseCode, body) -> {
|
String response = makeServiceRequest(CONFIRM_USERNAME_PATH, "PUT", JsonUtil.toJson(confirmUsernameRequest), NO_HEADERS, (responseCode, body, getHeader) -> {
|
||||||
switch (responseCode) {
|
switch (responseCode) {
|
||||||
case 409:
|
case 409:
|
||||||
throw new UsernameIsNotReservedException();
|
throw new UsernameIsNotReservedException();
|
||||||
|
@ -1392,7 +1393,7 @@ public class PushServiceSocket {
|
||||||
|
|
||||||
public void submitRateLimitPushChallenge(String challenge) throws IOException {
|
public void submitRateLimitPushChallenge(String challenge) throws IOException {
|
||||||
String payload = JsonUtil.toJson(new SubmitPushChallengePayload(challenge));
|
String payload = JsonUtil.toJson(new SubmitPushChallengePayload(challenge));
|
||||||
makeServiceRequest(SUBMIT_RATE_LIMIT_CHALLENGE, "PUT", payload, NO_HEADERS, (responseCode, body) -> {
|
makeServiceRequest(SUBMIT_RATE_LIMIT_CHALLENGE, "PUT", payload, NO_HEADERS, (responseCode, body, getHeader) -> {
|
||||||
if (responseCode == 428) {
|
if (responseCode == 428) {
|
||||||
throw new CaptchaRejectedException();
|
throw new CaptchaRejectedException();
|
||||||
}
|
}
|
||||||
|
@ -1449,7 +1450,7 @@ public class PushServiceSocket {
|
||||||
"POST",
|
"POST",
|
||||||
payload,
|
payload,
|
||||||
NO_HEADERS,
|
NO_HEADERS,
|
||||||
(code, body) -> {
|
(code, body, getHeader) -> {
|
||||||
if (code == 204) throw new NonSuccessfulResponseCodeException(204);
|
if (code == 204) throw new NonSuccessfulResponseCodeException(204);
|
||||||
if (code == 402) {
|
if (code == 402) {
|
||||||
InAppPaymentReceiptCredentialError inAppPaymentReceiptCredentialError;
|
InAppPaymentReceiptCredentialError inAppPaymentReceiptCredentialError;
|
||||||
|
@ -1540,7 +1541,7 @@ public class PushServiceSocket {
|
||||||
"POST",
|
"POST",
|
||||||
payload,
|
payload,
|
||||||
NO_HEADERS,
|
NO_HEADERS,
|
||||||
(code, body) -> {
|
(code, body, getHeader) -> {
|
||||||
if (code == 204) throw new NonSuccessfulResponseCodeException(204);
|
if (code == 204) throw new NonSuccessfulResponseCodeException(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2293,7 +2294,7 @@ public class PushServiceSocket {
|
||||||
Response response = null;
|
Response response = null;
|
||||||
try {
|
try {
|
||||||
response = getServiceConnection(urlFragment, method, body, headers, sealedSenderAccess, doNotAddAuthenticationOrUnidentifiedAccessKey);
|
response = getServiceConnection(urlFragment, method, body, headers, sealedSenderAccess, doNotAddAuthenticationOrUnidentifiedAccessKey);
|
||||||
responseCodeHandler.handle(response.code(), response.body());
|
responseCodeHandler.handle(response.code(), response.body(), response::header);
|
||||||
return validateServiceResponse(response);
|
return validateServiceResponse(response);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (response != null && response.body() != null) {
|
if (response != null && response.body() != null) {
|
||||||
|
@ -2822,16 +2823,12 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface ResponseCodeHandler {
|
private interface ResponseCodeHandler {
|
||||||
void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException;
|
void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException;
|
||||||
|
|
||||||
default void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
|
||||||
handle(responseCode, body);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class EmptyResponseCodeHandler implements ResponseCodeHandler {
|
private static class EmptyResponseCodeHandler implements ResponseCodeHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) { }
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2840,7 +2837,7 @@ public class PushServiceSocket {
|
||||||
*/
|
*/
|
||||||
private static class UnopinionatedResponseCodeHandler implements ResponseCodeHandler {
|
private static class UnopinionatedResponseCodeHandler implements ResponseCodeHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||||
if (responseCode < 200 || responseCode > 299) {
|
if (responseCode < 200 || responseCode > 299) {
|
||||||
String bodyString = null;
|
String bodyString = null;
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
|
@ -2862,7 +2859,7 @@ public class PushServiceSocket {
|
||||||
*/
|
*/
|
||||||
private static class LongPollingResponseCodeHandler implements ResponseCodeHandler {
|
private static class LongPollingResponseCodeHandler implements ResponseCodeHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||||
if (responseCode == 204 || responseCode < 200 || responseCode > 299) {
|
if (responseCode == 204 || responseCode < 200 || responseCode > 299) {
|
||||||
String bodyString = null;
|
String bodyString = null;
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
|
@ -2884,7 +2881,7 @@ public class PushServiceSocket {
|
||||||
*/
|
*/
|
||||||
private static class UnopinionatedBinaryErrorResponseCodeHandler implements ResponseCodeHandler {
|
private static class UnopinionatedBinaryErrorResponseCodeHandler implements ResponseCodeHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||||
if (responseCode < 200 || responseCode > 299) {
|
if (responseCode < 200 || responseCode > 299) {
|
||||||
byte[] bodyBytes = null;
|
byte[] bodyBytes = null;
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
|
@ -2912,27 +2909,22 @@ public class PushServiceSocket {
|
||||||
return JsonUtil.fromJson(response, CredentialResponse.class);
|
return JsonUtil.fromJson(response, CredentialResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final ResponseCodeHandler GROUPS_V2_PUT_RESPONSE_HANDLER = (responseCode, body) -> {
|
private static final ResponseCodeHandler GROUPS_V2_PUT_RESPONSE_HANDLER = (responseCode, body, getHeader) -> {
|
||||||
if (responseCode == 409) throw new GroupExistsException();
|
if (responseCode == 409) throw new GroupExistsException();
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final ResponseCodeHandler GROUPS_V2_GET_CURRENT_HANDLER = (responseCode, body) -> {
|
private static final ResponseCodeHandler GROUPS_V2_GET_CURRENT_HANDLER = (responseCode, body, getHeader) -> {
|
||||||
switch (responseCode) {
|
switch (responseCode) {
|
||||||
case 403: throw new NotInGroupException();
|
case 403: throw new NotInGroupException();
|
||||||
case 404: throw new GroupNotFoundException();
|
case 404: throw new GroupNotFoundException();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final ResponseCodeHandler GROUPS_V2_PATCH_RESPONSE_HANDLER = (responseCode, body) -> {
|
private static final ResponseCodeHandler GROUPS_V2_PATCH_RESPONSE_HANDLER = (responseCode, body, getHeader) -> {
|
||||||
if (responseCode == 400) throw new GroupPatchNotAcceptedException();
|
if (responseCode == 400) throw new GroupPatchNotAcceptedException();
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final ResponseCodeHandler GROUPS_V2_GET_JOIN_INFO_HANDLER = new ResponseCodeHandler() {
|
private static final ResponseCodeHandler GROUPS_V2_GET_JOIN_INFO_HANDLER = new ResponseCodeHandler() {
|
||||||
@Override
|
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException {
|
|
||||||
if (responseCode == 403) throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException {
|
||||||
if (responseCode == 403) {
|
if (responseCode == 403) {
|
||||||
|
@ -3054,7 +3046,7 @@ public class PushServiceSocket {
|
||||||
public GroupJoinInfo getGroupJoinInfo(Optional<byte[]> groupLinkPassword, GroupsV2AuthorizationString authorization)
|
public GroupJoinInfo getGroupJoinInfo(Optional<byte[]> groupLinkPassword, GroupsV2AuthorizationString authorization)
|
||||||
throws NonSuccessfulResponseCodeException, PushNetworkException, IOException, MalformedResponseException
|
throws NonSuccessfulResponseCodeException, PushNetworkException, IOException, MalformedResponseException
|
||||||
{
|
{
|
||||||
String passwordParam = groupLinkPassword.map(org.signal.core.util.Base64::encodeUrlSafeWithoutPadding).orElse("");
|
String passwordParam = groupLinkPassword.map(Base64::encodeUrlSafeWithoutPadding).orElse("");
|
||||||
try (Response response = makeStorageRequest(authorization.toString(),
|
try (Response response = makeStorageRequest(authorization.toString(),
|
||||||
String.format(GROUPSV2_GROUP_JOIN, passwordParam),
|
String.format(GROUPSV2_GROUP_JOIN, passwordParam),
|
||||||
"GET",
|
"GET",
|
||||||
|
@ -3101,7 +3093,7 @@ public class PushServiceSocket {
|
||||||
*/
|
*/
|
||||||
private static class LinkGooglePlayBillingPurchaseTokenResponseCodeHandler implements ResponseCodeHandler {
|
private static class LinkGooglePlayBillingPurchaseTokenResponseCodeHandler implements ResponseCodeHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||||
if (responseCode < 400) {
|
if (responseCode < 400) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3115,7 +3107,7 @@ public class PushServiceSocket {
|
||||||
*/
|
*/
|
||||||
private static class InAppPaymentResponseCodeHandler implements ResponseCodeHandler {
|
private static class InAppPaymentResponseCodeHandler implements ResponseCodeHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||||
if (responseCode < 400) {
|
if (responseCode < 400) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3138,26 +3130,27 @@ public class PushServiceSocket {
|
||||||
private static class RegistrationSessionResponseHandler implements ResponseCodeHandler {
|
private static class RegistrationSessionResponseHandler implements ResponseCodeHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||||
|
|
||||||
if (responseCode == 403) {
|
if (responseCode == 403) {
|
||||||
throw new IncorrectRegistrationRecoveryPasswordException();
|
throw new IncorrectRegistrationRecoveryPasswordException();
|
||||||
} else if (responseCode == 404) {
|
} else if (responseCode == 404) {
|
||||||
throw new NoSuchSessionException();
|
throw new NoSuchSessionException();
|
||||||
} else if (responseCode == 409) {
|
} else if (responseCode == 409) {
|
||||||
RegistrationSessionMetadataJson response;
|
RegistrationSessionMetadataResponse response;
|
||||||
try {
|
try {
|
||||||
response = JsonUtil.fromJson(body.string(), RegistrationSessionMetadataJson.class);
|
response = parseSessionMetadataResponse(body, getHeader);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Unable to read response body.", e);
|
Log.w(TAG, "Unable to read response body.", e);
|
||||||
throw new NonSuccessfulResponseCodeException(409);
|
throw new NonSuccessfulResponseCodeException(409);
|
||||||
}
|
}
|
||||||
if (response.getVerified()) {
|
|
||||||
|
if (response.getMetadata().getVerified()) {
|
||||||
throw new AlreadyVerifiedException();
|
throw new AlreadyVerifiedException();
|
||||||
} else if (response.pushChallengedRequired() || response.captchaRequired()) {
|
} else if (response.getMetadata().pushChallengedRequired() || response.getMetadata().captchaRequired()) {
|
||||||
throw new ChallengeRequiredException(response);
|
throw new ChallengeRequiredException(response);
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Received 409 in reg session handler that is not verified, with required information: " + String.join(", ", response.getRequestedInformation()));
|
Log.i(TAG, "Received 409 in reg session handler that is not verified, with required information: " + String.join(", ", response.getMetadata().getRequestedInformation()));
|
||||||
throw new HttpConflictException();
|
throw new HttpConflictException();
|
||||||
}
|
}
|
||||||
} else if (responseCode == 502) {
|
} else if (responseCode == 502) {
|
||||||
|
@ -3173,12 +3166,13 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
private static class RegistrationCodeRequestResponseHandler implements ResponseCodeHandler {
|
* Error handler used exclusively for dealing with request verification code during registration flow.
|
||||||
|
*/
|
||||||
|
private static class RequestVerificationCodeResponseHandler implements ResponseCodeHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException {
|
||||||
|
|
||||||
if (responseCode == 400) {
|
if (responseCode == 400) {
|
||||||
throw new MalformedRequestException();
|
throw new MalformedRequestException();
|
||||||
} else if (responseCode == 403) {
|
} else if (responseCode == 403) {
|
||||||
|
@ -3186,25 +3180,34 @@ public class PushServiceSocket {
|
||||||
} else if (responseCode == 404) {
|
} else if (responseCode == 404) {
|
||||||
throw new NoSuchSessionException();
|
throw new NoSuchSessionException();
|
||||||
} else if (responseCode == 409) {
|
} else if (responseCode == 409) {
|
||||||
RegistrationSessionMetadataJson response;
|
RegistrationSessionMetadataResponse response;
|
||||||
try {
|
try {
|
||||||
response = JsonUtil.fromJson(body.string(), RegistrationSessionMetadataJson.class);
|
response = parseSessionMetadataResponse(body, getHeader);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Unable to read response body.", e);
|
Log.e(TAG, "Unable to read response body.", e);
|
||||||
throw new NonSuccessfulResponseCodeException(409);
|
throw new NonSuccessfulResponseCodeException(409);
|
||||||
}
|
}
|
||||||
if (response.getVerified()) {
|
|
||||||
|
if (response.getMetadata().getVerified()) {
|
||||||
throw new AlreadyVerifiedException();
|
throw new AlreadyVerifiedException();
|
||||||
} else if (response.pushChallengedRequired() || response.captchaRequired()) {
|
} else if (response.getMetadata().pushChallengedRequired() || response.getMetadata().captchaRequired()) {
|
||||||
throw new ChallengeRequiredException(response);
|
throw new ChallengeRequiredException(response);
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Received 409 in for reg code request that is not verified, with required information: " + String.join(", ", response.getRequestedInformation()));
|
Log.i(TAG, "Received 409 in for reg code request that is not verified, with required information: " + String.join(", ", response.getMetadata().getRequestedInformation()));
|
||||||
throw new HttpConflictException();
|
throw new HttpConflictException();
|
||||||
}
|
}
|
||||||
} else if (responseCode == 418) {
|
} else if (responseCode == 418) {
|
||||||
throw new InvalidTransportModeException();
|
throw new InvalidTransportModeException();
|
||||||
} else if (responseCode == 429) {
|
} else if (responseCode == 429) {
|
||||||
throw new RegistrationRetryException();
|
RegistrationSessionMetadataResponse response;
|
||||||
|
try {
|
||||||
|
response = parseSessionMetadataResponse(body, getHeader);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Unable to read response body.", e);
|
||||||
|
throw new NonSuccessfulResponseCodeException(429);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RequestVerificationCodeRateLimitException(response);
|
||||||
} else if (responseCode == 440) {
|
} else if (responseCode == 440) {
|
||||||
VerificationCodeFailureResponseBody response;
|
VerificationCodeFailureResponseBody response;
|
||||||
try {
|
try {
|
||||||
|
@ -3222,35 +3225,38 @@ public class PushServiceSocket {
|
||||||
private static class PatchRegistrationSessionResponseHandler implements ResponseCodeHandler {
|
private static class PatchRegistrationSessionResponseHandler implements ResponseCodeHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||||
switch (responseCode) {
|
switch (responseCode) {
|
||||||
case 403:
|
case 403:
|
||||||
throw new TokenNotAcceptedException();
|
throw new TokenNotAcceptedException();
|
||||||
case 404:
|
case 404:
|
||||||
throw new NoSuchSessionException();
|
throw new NoSuchSessionException();
|
||||||
case 409:
|
case 409:
|
||||||
RegistrationSessionMetadataJson response;
|
RegistrationSessionMetadataResponse response;
|
||||||
try {
|
try {
|
||||||
response = JsonUtil.fromJson(body.string(), RegistrationSessionMetadataJson.class);
|
response = parseSessionMetadataResponse(body, getHeader);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Unable to read response body.", e);
|
Log.e(TAG, "Unable to read response body.", e);
|
||||||
throw new NonSuccessfulResponseCodeException(409);
|
throw new NonSuccessfulResponseCodeException(409);
|
||||||
}
|
}
|
||||||
if (response.getVerified()) {
|
if (response.getMetadata().getVerified()) {
|
||||||
throw new AlreadyVerifiedException();
|
throw new AlreadyVerifiedException();
|
||||||
} else if (response.pushChallengedRequired() || response.captchaRequired()) {
|
} else if (response.getMetadata().pushChallengedRequired() || response.getMetadata().captchaRequired()) {
|
||||||
throw new ChallengeRequiredException(response);
|
throw new ChallengeRequiredException(response);
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Received 409 for patching reg session that is not verified, with required information: " + String.join(", ", response.getRequestedInformation()));
|
Log.i(TAG, "Received 409 for patching reg session that is not verified, with required information: " + String.join(", ", response.getMetadata().getRequestedInformation()));
|
||||||
throw new HttpConflictException();
|
throw new HttpConflictException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RegistrationCodeSubmissionResponseHandler implements ResponseCodeHandler {
|
/**
|
||||||
|
* Error response handler used exclusively for submitting a verification code during a registration session.
|
||||||
|
*/
|
||||||
|
private static class SubmitVerificationCodeResponseHandler implements ResponseCodeHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
public void handle(int responseCode, ResponseBody body, Function<String, String> getHeader) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||||
|
|
||||||
switch (responseCode) {
|
switch (responseCode) {
|
||||||
case 400:
|
case 400:
|
||||||
|
@ -3274,6 +3280,16 @@ public class PushServiceSocket {
|
||||||
Log.i(TAG, "Received 409 for reg code submission that is not verified, with required information: " + String.join(", ", sessionMetadata.getRequestedInformation()));
|
Log.i(TAG, "Received 409 for reg code submission that is not verified, with required information: " + String.join(", ", sessionMetadata.getRequestedInformation()));
|
||||||
throw new HttpConflictException();
|
throw new HttpConflictException();
|
||||||
}
|
}
|
||||||
|
case 429:
|
||||||
|
RegistrationSessionMetadataResponse response;
|
||||||
|
try {
|
||||||
|
response = parseSessionMetadataResponse(body, getHeader);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Unable to read response body.", e);
|
||||||
|
throw new NonSuccessfulResponseCodeException(429);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SubmitVerificationCodeRateLimitException(response);
|
||||||
case 440:
|
case 440:
|
||||||
VerificationCodeFailureResponseBody codeFailureResponse;
|
VerificationCodeFailureResponseBody codeFailureResponse;
|
||||||
try {
|
try {
|
||||||
|
@ -3289,9 +3305,13 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RegistrationSessionMetadataResponse parseSessionMetadataResponse(Response response) throws IOException {
|
private static RegistrationSessionMetadataResponse parseSessionMetadataResponse(Response response) throws IOException {
|
||||||
|
return parseSessionMetadataResponse(response.body(), response::header);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RegistrationSessionMetadataResponse parseSessionMetadataResponse(ResponseBody body, Function<String, String> getHeader) throws IOException {
|
||||||
long serverDeliveredTimestamp = 0;
|
long serverDeliveredTimestamp = 0;
|
||||||
try {
|
try {
|
||||||
String stringValue = response.header(SERVER_DELIVERED_TIMESTAMP_HEADER);
|
String stringValue = getHeader.apply(SERVER_DELIVERED_TIMESTAMP_HEADER);
|
||||||
stringValue = stringValue != null ? stringValue : "0";
|
stringValue = stringValue != null ? stringValue : "0";
|
||||||
|
|
||||||
serverDeliveredTimestamp = Long.parseLong(stringValue);
|
serverDeliveredTimestamp = Long.parseLong(stringValue);
|
||||||
|
@ -3299,8 +3319,11 @@ public class PushServiceSocket {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
RegistrationSessionMetadataHeaders responseHeaders = new RegistrationSessionMetadataHeaders(serverDeliveredTimestamp);
|
long retryAfterLong = Util.parseLong(getHeader.apply("Retry-After"), -1);
|
||||||
RegistrationSessionMetadataJson responseBody = JsonUtil.fromJson(readBodyString(response), RegistrationSessionMetadataJson.class);
|
Long retryAfter = retryAfterLong != -1 ? TimeUnit.SECONDS.toMillis(retryAfterLong) : null;
|
||||||
|
|
||||||
|
RegistrationSessionMetadataHeaders responseHeaders = new RegistrationSessionMetadataHeaders(serverDeliveredTimestamp, retryAfter);
|
||||||
|
RegistrationSessionMetadataJson responseBody = JsonUtil.fromJson(body.string(), RegistrationSessionMetadataJson.class);
|
||||||
|
|
||||||
return new RegistrationSessionMetadataResponse(responseHeaders, responseBody, null);
|
return new RegistrationSessionMetadataResponse(responseHeaders, responseBody, null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,13 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
*/
|
*/
|
||||||
data class RegistrationSessionMetadataResponse(
|
data class RegistrationSessionMetadataResponse(
|
||||||
val headers: RegistrationSessionMetadataHeaders,
|
val headers: RegistrationSessionMetadataHeaders,
|
||||||
val body: RegistrationSessionMetadataJson,
|
val metadata: RegistrationSessionMetadataJson,
|
||||||
val state: RegistrationSessionState?
|
val state: RegistrationSessionState?
|
||||||
)
|
)
|
||||||
|
|
||||||
data class RegistrationSessionMetadataHeaders(
|
data class RegistrationSessionMetadataHeaders(
|
||||||
val timestamp: Long
|
val serverDeliveredTimestamp: Long,
|
||||||
|
val retryAfterTimestamp: Long? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
data class RegistrationSessionMetadataJson(
|
data class RegistrationSessionMetadataJson(
|
||||||
|
|
|
@ -2,6 +2,8 @@ package org.whispersystems.signalservice.internal.push.exceptions;
|
||||||
|
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
public final class PaymentsRegionException extends NonSuccessfulResponseCodeException {
|
public final class PaymentsRegionException extends NonSuccessfulResponseCodeException {
|
||||||
|
@ -12,7 +14,7 @@ public final class PaymentsRegionException extends NonSuccessfulResponseCodeExce
|
||||||
/**
|
/**
|
||||||
* Promotes a 403 to this exception type.
|
* Promotes a 403 to this exception type.
|
||||||
*/
|
*/
|
||||||
public static void responseCodeHandler(int responseCode, ResponseBody body) throws PaymentsRegionException {
|
public static void responseCodeHandler(int responseCode, ResponseBody body, Function<String, String> getHeader) throws PaymentsRegionException {
|
||||||
if (responseCode == 403) {
|
if (responseCode == 403) {
|
||||||
throw new PaymentsRegionException(responseCode);
|
throw new PaymentsRegionException(responseCode);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue