mirror of
https://github.com/element-hq/element-android
synced 2024-10-26 20:57:20 +03:00
Remove BootstrapStep.SetupPassphrase and BootstrapStep.ConfirmPassphrase
This commit is contained in:
parent
aa2b62976e
commit
8df7797f6d
9 changed files with 47 additions and 426 deletions
|
@ -28,8 +28,6 @@ import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
|
||||||
import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
||||||
import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment
|
import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment
|
||||||
import im.vector.riotx.features.crypto.recover.BootstrapConclusionFragment
|
import im.vector.riotx.features.crypto.recover.BootstrapConclusionFragment
|
||||||
import im.vector.riotx.features.crypto.recover.BootstrapConfirmPassphraseFragment
|
|
||||||
import im.vector.riotx.features.crypto.recover.BootstrapEnterPassphraseFragment
|
|
||||||
import im.vector.riotx.features.crypto.recover.BootstrapMigrateBackupFragment
|
import im.vector.riotx.features.crypto.recover.BootstrapMigrateBackupFragment
|
||||||
import im.vector.riotx.features.crypto.recover.BootstrapSaveRecoveryKeyFragment
|
import im.vector.riotx.features.crypto.recover.BootstrapSaveRecoveryKeyFragment
|
||||||
import im.vector.riotx.features.crypto.recover.BootstrapWaitingFragment
|
import im.vector.riotx.features.crypto.recover.BootstrapWaitingFragment
|
||||||
|
@ -453,16 +451,6 @@ interface FragmentModule {
|
||||||
@FragmentKey(GossipingEventsPaperTrailFragment::class)
|
@FragmentKey(GossipingEventsPaperTrailFragment::class)
|
||||||
fun bindGossipingEventsPaperTrailFragment(fragment: GossipingEventsPaperTrailFragment): Fragment
|
fun bindGossipingEventsPaperTrailFragment(fragment: GossipingEventsPaperTrailFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@FragmentKey(BootstrapEnterPassphraseFragment::class)
|
|
||||||
fun bindBootstrapEnterPassphraseFragment(fragment: BootstrapEnterPassphraseFragment): Fragment
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@FragmentKey(BootstrapConfirmPassphraseFragment::class)
|
|
||||||
fun bindBootstrapConfirmPassphraseFragment(fragment: BootstrapConfirmPassphraseFragment): Fragment
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(BootstrapWaitingFragment::class)
|
@FragmentKey(BootstrapWaitingFragment::class)
|
||||||
|
|
|
@ -24,15 +24,11 @@ sealed class BootstrapActions : VectorViewModelAction {
|
||||||
// Navigation
|
// Navigation
|
||||||
|
|
||||||
object GoBack : BootstrapActions()
|
object GoBack : BootstrapActions()
|
||||||
data class GoToConfirmPassphrase(val passphrase: String) : BootstrapActions()
|
|
||||||
object GoToCompleted : BootstrapActions()
|
object GoToCompleted : BootstrapActions()
|
||||||
object GoToEnterAccountPassword : BootstrapActions()
|
object GoToEnterAccountPassword : BootstrapActions()
|
||||||
|
|
||||||
data class DoInitialize(val passphrase: String) : BootstrapActions()
|
|
||||||
object DoInitializeGeneratedKey : BootstrapActions()
|
object DoInitializeGeneratedKey : BootstrapActions()
|
||||||
object TogglePasswordVisibility : BootstrapActions()
|
object TogglePasswordVisibility : BootstrapActions()
|
||||||
data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions()
|
|
||||||
data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions()
|
|
||||||
data class ReAuth(val pass: String) : BootstrapActions()
|
data class ReAuth(val pass: String) : BootstrapActions()
|
||||||
object RecoveryKeySaved : BootstrapActions()
|
object RecoveryKeySaved : BootstrapActions()
|
||||||
object Completed : BootstrapActions()
|
object Completed : BootstrapActions()
|
||||||
|
|
|
@ -127,16 +127,6 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
bootstrapTitleText.text = getString(R.string.upgrade_security)
|
bootstrapTitleText.text = getString(R.string.upgrade_security)
|
||||||
showFragment(BootstrapWaitingFragment::class, Bundle())
|
showFragment(BootstrapWaitingFragment::class, Bundle())
|
||||||
}
|
}
|
||||||
is BootstrapStep.SetupPassphrase -> {
|
|
||||||
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password))
|
|
||||||
bootstrapTitleText.text = getString(R.string.set_recovery_passphrase, getString(R.string.recovery_passphrase))
|
|
||||||
showFragment(BootstrapEnterPassphraseFragment::class, Bundle())
|
|
||||||
}
|
|
||||||
is BootstrapStep.ConfirmPassphrase -> {
|
|
||||||
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password))
|
|
||||||
bootstrapTitleText.text = getString(R.string.confirm_recovery_passphrase, getString(R.string.recovery_passphrase))
|
|
||||||
showFragment(BootstrapConfirmPassphraseFragment::class, Bundle())
|
|
||||||
}
|
|
||||||
is BootstrapStep.AccountPassword -> {
|
is BootstrapStep.AccountPassword -> {
|
||||||
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user))
|
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user))
|
||||||
bootstrapTitleText.text = getString(R.string.account_password)
|
bootstrapTitleText.text = getString(R.string.account_password)
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.riotx.features.crypto.recover
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import androidx.core.text.toSpannable
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import com.airbnb.mvrx.parentFragmentViewModel
|
|
||||||
import com.airbnb.mvrx.withState
|
|
||||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
|
||||||
import im.vector.riotx.R
|
|
||||||
import im.vector.riotx.core.extensions.hideKeyboard
|
|
||||||
import im.vector.riotx.core.extensions.showPassword
|
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
|
||||||
import im.vector.riotx.core.utils.colorizeMatchingText
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.*
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class BootstrapConfirmPassphraseFragment @Inject constructor(
|
|
||||||
private val colorProvider: ColorProvider
|
|
||||||
) : VectorBaseFragment() {
|
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase
|
|
||||||
|
|
||||||
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
ssss_passphrase_security_progress.isGone = true
|
|
||||||
|
|
||||||
val recPassPhrase = getString(R.string.recovery_passphrase)
|
|
||||||
bootstrapDescriptionText.text = getString(R.string.bootstrap_info_confirm_text, recPassPhrase)
|
|
||||||
.toSpannable()
|
|
||||||
.colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
|
|
||||||
|
|
||||||
ssss_passphrase_enter_edittext.hint = getString(R.string.passphrase_confirm_passphrase)
|
|
||||||
|
|
||||||
withState(sharedViewModel) {
|
|
||||||
// set initial value (useful when coming back)
|
|
||||||
ssss_passphrase_enter_edittext.setText(it.passphraseRepeat ?: "")
|
|
||||||
ssss_passphrase_enter_edittext.requestFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
ssss_passphrase_enter_edittext.editorActionEvents()
|
|
||||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe {
|
|
||||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
|
||||||
submit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.disposeOnDestroyView()
|
|
||||||
|
|
||||||
ssss_passphrase_enter_edittext.textChanges()
|
|
||||||
.subscribe {
|
|
||||||
ssss_passphrase_enter_til.error = null
|
|
||||||
sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: ""))
|
|
||||||
}
|
|
||||||
.disposeOnDestroyView()
|
|
||||||
|
|
||||||
sharedViewModel.observeViewEvents {
|
|
||||||
// when (it) {
|
|
||||||
// is SharedSecureStorageViewEvent.InlineError -> {
|
|
||||||
// ssss_passphrase_enter_til.error = it.message
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
|
|
||||||
bootstrapSubmit.debouncedClicks { submit() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun submit() = withState(sharedViewModel) { state ->
|
|
||||||
if (state.step !is BootstrapStep.ConfirmPassphrase) {
|
|
||||||
return@withState
|
|
||||||
}
|
|
||||||
val passphrase = ssss_passphrase_enter_edittext.text?.toString()
|
|
||||||
when {
|
|
||||||
passphrase.isNullOrBlank() ->
|
|
||||||
ssss_passphrase_enter_til.error = getString(R.string.passphrase_empty_error_message)
|
|
||||||
passphrase != state.passphrase ->
|
|
||||||
ssss_passphrase_enter_til.error = getString(R.string.passphrase_passphrase_does_not_match)
|
|
||||||
else -> {
|
|
||||||
view?.hideKeyboard()
|
|
||||||
sharedViewModel.handle(BootstrapActions.DoInitialize(passphrase))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun invalidate() = withState(sharedViewModel) { state ->
|
|
||||||
if (state.step is BootstrapStep.ConfirmPassphrase) {
|
|
||||||
val isPasswordVisible = state.step.isPasswordVisible
|
|
||||||
ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false)
|
|
||||||
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -68,7 +68,6 @@ interface BootstrapProgressListener {
|
||||||
data class Params(
|
data class Params(
|
||||||
val userPasswordAuth: UserPasswordAuth? = null,
|
val userPasswordAuth: UserPasswordAuth? = null,
|
||||||
val progressListener: BootstrapProgressListener? = null,
|
val progressListener: BootstrapProgressListener? = null,
|
||||||
val passphrase: String?,
|
|
||||||
val keySpec: SsssKeySpec? = null
|
val keySpec: SsssKeySpec? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -105,24 +104,13 @@ class BootstrapCrossSigningTask @Inject constructor(
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
keyInfo = awaitCallback {
|
keyInfo = awaitCallback {
|
||||||
params.passphrase?.let { passphrase ->
|
ssssService.generateKey(
|
||||||
ssssService.generateKeyWithPassphrase(
|
UUID.randomUUID().toString(),
|
||||||
UUID.randomUUID().toString(),
|
params.keySpec,
|
||||||
"ssss_key",
|
"ssss_key",
|
||||||
passphrase,
|
EmptyKeySigner(),
|
||||||
EmptyKeySigner(),
|
it
|
||||||
null,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
} ?: kotlin.run {
|
|
||||||
ssssService.generateKey(
|
|
||||||
UUID.randomUUID().toString(),
|
|
||||||
params.keySpec,
|
|
||||||
"ssss_key",
|
|
||||||
EmptyKeySigner(),
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (failure: Failure) {
|
} catch (failure: Failure) {
|
||||||
return BootstrapResult.FailedToCreateSSSSKey(failure)
|
return BootstrapResult.FailedToCreateSSSSKey(failure)
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.riotx.features.crypto.recover
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import androidx.core.text.toSpannable
|
|
||||||
import com.airbnb.mvrx.parentFragmentViewModel
|
|
||||||
import com.airbnb.mvrx.withState
|
|
||||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
|
||||||
import im.vector.riotx.R
|
|
||||||
import im.vector.riotx.core.extensions.showPassword
|
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
|
||||||
import im.vector.riotx.core.utils.colorizeMatchingText
|
|
||||||
import im.vector.riotx.features.settings.VectorLocale
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.*
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class BootstrapEnterPassphraseFragment @Inject constructor(
|
|
||||||
private val colorProvider: ColorProvider
|
|
||||||
) : VectorBaseFragment() {
|
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase
|
|
||||||
|
|
||||||
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
val recPassPhrase = getString(R.string.recovery_passphrase)
|
|
||||||
bootstrapDescriptionText.text = getString(R.string.bootstrap_info_text, recPassPhrase)
|
|
||||||
.toSpannable()
|
|
||||||
.colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
|
|
||||||
|
|
||||||
ssss_passphrase_enter_edittext.hint = getString(R.string.passphrase_enter_passphrase)
|
|
||||||
withState(sharedViewModel) {
|
|
||||||
// set initial value (useful when coming back)
|
|
||||||
ssss_passphrase_enter_edittext.setText(it.passphrase ?: "")
|
|
||||||
}
|
|
||||||
ssss_passphrase_enter_edittext.editorActionEvents()
|
|
||||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe {
|
|
||||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
|
||||||
submit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.disposeOnDestroyView()
|
|
||||||
|
|
||||||
ssss_passphrase_enter_edittext.textChanges()
|
|
||||||
.subscribe {
|
|
||||||
// ssss_passphrase_enter_til.error = null
|
|
||||||
sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
|
|
||||||
// ssss_passphrase_submit.isEnabled = it.isNotBlank()
|
|
||||||
}
|
|
||||||
.disposeOnDestroyView()
|
|
||||||
|
|
||||||
sharedViewModel.observeViewEvents {
|
|
||||||
// when (it) {
|
|
||||||
// is SharedSecureStorageViewEvent.InlineError -> {
|
|
||||||
// ssss_passphrase_enter_til.error = it.message
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
|
|
||||||
bootstrapSubmit.debouncedClicks { submit() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun submit() = withState(sharedViewModel) { state ->
|
|
||||||
if (state.step !is BootstrapStep.SetupPassphrase) {
|
|
||||||
return@withState
|
|
||||||
}
|
|
||||||
val score = state.passphraseStrength.invoke()?.score
|
|
||||||
val passphrase = ssss_passphrase_enter_edittext.text?.toString()
|
|
||||||
if (passphrase.isNullOrBlank()) {
|
|
||||||
ssss_passphrase_enter_til.error = getString(R.string.passphrase_empty_error_message)
|
|
||||||
} else if (score != 4) {
|
|
||||||
ssss_passphrase_enter_til.error = getString(R.string.passphrase_passphrase_too_weak)
|
|
||||||
} else {
|
|
||||||
sharedViewModel.handle(BootstrapActions.GoToConfirmPassphrase(passphrase))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun invalidate() = withState(sharedViewModel) { state ->
|
|
||||||
if (state.step is BootstrapStep.SetupPassphrase) {
|
|
||||||
val isPasswordVisible = state.step.isPasswordVisible
|
|
||||||
ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false)
|
|
||||||
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
|
|
||||||
|
|
||||||
state.passphraseStrength.invoke()?.let { strength ->
|
|
||||||
val score = strength.score
|
|
||||||
ssss_passphrase_security_progress.strength = score
|
|
||||||
if (score in 1..3) {
|
|
||||||
val hint =
|
|
||||||
strength.feedback?.getWarning(VectorLocale.applicationLocale)?.takeIf { it.isNotBlank() }
|
|
||||||
?: strength.feedback?.getSuggestions(VectorLocale.applicationLocale)?.firstOrNull()
|
|
||||||
if (hint != null && hint != ssss_passphrase_enter_til.error.toString()) {
|
|
||||||
ssss_passphrase_enter_til.error = hint
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ssss_passphrase_enter_til.error = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -51,15 +51,11 @@ class BootstrapMigrateBackupFragment @Inject constructor(
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup
|
override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup
|
||||||
|
|
||||||
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
|
private val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
withState(sharedViewModel) {
|
|
||||||
// set initial value (useful when coming back)
|
|
||||||
bootstrapMigrateEditText.setText(it.passphrase ?: "")
|
|
||||||
}
|
|
||||||
bootstrapMigrateEditText.editorActionEvents()
|
bootstrapMigrateEditText.editorActionEvents()
|
||||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
|
@ -26,8 +26,6 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.nulabinc.zxcvbn.Strength
|
|
||||||
import com.nulabinc.zxcvbn.Zxcvbn
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
@ -49,13 +47,9 @@ import kotlinx.coroutines.launch
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
data class BootstrapViewState(
|
data class BootstrapViewState(
|
||||||
val step: BootstrapStep = BootstrapStep.SetupPassphrase(false),
|
val step: BootstrapStep = BootstrapStep.AccountPassword(false),
|
||||||
val passphrase: String? = null,
|
|
||||||
val migrationRecoveryKey: String? = null,
|
val migrationRecoveryKey: String? = null,
|
||||||
val passphraseRepeat: String? = null,
|
|
||||||
val crossSigningInitialization: Async<Unit> = Uninitialized,
|
val crossSigningInitialization: Async<Unit> = Uninitialized,
|
||||||
val passphraseStrength: Async<Strength> = Uninitialized,
|
|
||||||
val passphraseConfirmMatch: Async<Unit> = Uninitialized,
|
|
||||||
val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null,
|
val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null,
|
||||||
val initializationWaitingViewData: WaitingViewData? = null,
|
val initializationWaitingViewData: WaitingViewData? = null,
|
||||||
val recoverySaveFileProcess: Async<Unit> = Uninitialized
|
val recoverySaveFileProcess: Async<Unit> = Uninitialized
|
||||||
|
@ -71,8 +65,6 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
private val reAuthHelper: ReAuthHelper
|
private val reAuthHelper: ReAuthHelper
|
||||||
) : VectorViewModel<BootstrapViewState, BootstrapActions, BootstrapViewEvents>(initialState) {
|
) : VectorViewModel<BootstrapViewState, BootstrapActions, BootstrapViewEvents>(initialState) {
|
||||||
|
|
||||||
private val zxcvbn = Zxcvbn()
|
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(initialState: BootstrapViewState, args: BootstrapBottomSheet.Args): BootstrapSharedViewModel
|
fun create(initialState: BootstrapViewState, args: BootstrapBottomSheet.Args): BootstrapSharedViewModel
|
||||||
|
@ -84,7 +76,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
// need to check if user have an existing keybackup
|
// need to check if user have an existing keybackup
|
||||||
if (args.isNewAccount) {
|
if (args.isNewAccount) {
|
||||||
setState {
|
setState {
|
||||||
copy(step = BootstrapStep.SetupPassphrase(false))
|
copy(step = BootstrapStep.AccountPassword(false))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setState {
|
setState {
|
||||||
|
@ -99,7 +91,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
// we just resume plain bootstrap
|
// we just resume plain bootstrap
|
||||||
setState {
|
setState {
|
||||||
copy(step = BootstrapStep.SetupPassphrase(false))
|
copy(step = BootstrapStep.AccountPassword(false))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we need to get existing backup passphrase/key and convert to SSSS
|
// we need to get existing backup passphrase/key and convert to SSSS
|
||||||
|
@ -128,19 +120,9 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
override fun handle(action: BootstrapActions) = withState { state ->
|
override fun handle(action: BootstrapActions) = withState { state ->
|
||||||
when (action) {
|
when (action) {
|
||||||
is BootstrapActions.GoBack -> queryBack()
|
is BootstrapActions.GoBack -> queryBack()
|
||||||
BootstrapActions.TogglePasswordVisibility -> {
|
BootstrapActions.TogglePasswordVisibility -> {
|
||||||
when (state.step) {
|
when (state.step) {
|
||||||
is BootstrapStep.SetupPassphrase -> {
|
|
||||||
setState {
|
|
||||||
copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BootstrapStep.ConfirmPassphrase -> {
|
|
||||||
setState {
|
|
||||||
copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BootstrapStep.AccountPassword -> {
|
is BootstrapStep.AccountPassword -> {
|
||||||
setState {
|
setState {
|
||||||
copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
|
copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
|
||||||
|
@ -155,119 +137,64 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is BootstrapActions.DoInitializeGeneratedKey -> {
|
||||||
is BootstrapActions.UpdateCandidatePassphrase -> {
|
|
||||||
val strength = zxcvbn.measure(action.pass)
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
passphrase = action.pass,
|
|
||||||
passphraseStrength = Success(strength)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BootstrapActions.GoToConfirmPassphrase -> {
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
passphrase = action.passphrase,
|
|
||||||
step = BootstrapStep.ConfirmPassphrase(
|
|
||||||
isPasswordVisible = (state.step as? BootstrapStep.SetupPassphrase)?.isPasswordVisible ?: false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BootstrapActions.UpdateConfirmCandidatePassphrase -> {
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
passphraseRepeat = action.pass
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BootstrapActions.DoInitialize -> {
|
|
||||||
if (state.passphrase == state.passphraseRepeat) {
|
|
||||||
val userPassword = reAuthHelper.data
|
|
||||||
if (userPassword == null) {
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
step = BootstrapStep.AccountPassword(false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
startInitializeFlow(userPassword)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
passphraseConfirmMatch = Fail(Throwable(stringProvider.getString(R.string.passphrase_passphrase_does_not_match)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BootstrapActions.DoInitializeGeneratedKey -> {
|
|
||||||
val userPassword = reAuthHelper.data
|
val userPassword = reAuthHelper.data
|
||||||
if (userPassword == null) {
|
if (userPassword == null) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
passphrase = null,
|
|
||||||
passphraseRepeat = null,
|
|
||||||
step = BootstrapStep.AccountPassword(false)
|
step = BootstrapStep.AccountPassword(false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
passphrase = null,
|
|
||||||
passphraseRepeat = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
startInitializeFlow(userPassword)
|
startInitializeFlow(userPassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BootstrapActions.RecoveryKeySaved -> {
|
BootstrapActions.RecoveryKeySaved -> {
|
||||||
_viewEvents.post(BootstrapViewEvents.RecoveryKeySaved)
|
_viewEvents.post(BootstrapViewEvents.RecoveryKeySaved)
|
||||||
setState {
|
setState {
|
||||||
copy(step = BootstrapStep.SaveRecoveryKey(true))
|
copy(step = BootstrapStep.SaveRecoveryKey(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BootstrapActions.Completed -> {
|
BootstrapActions.Completed -> {
|
||||||
_viewEvents.post(BootstrapViewEvents.Dismiss)
|
_viewEvents.post(BootstrapViewEvents.Dismiss)
|
||||||
}
|
}
|
||||||
BootstrapActions.GoToCompleted -> {
|
BootstrapActions.GoToCompleted -> {
|
||||||
setState {
|
setState {
|
||||||
copy(step = BootstrapStep.DoneSuccess)
|
copy(step = BootstrapStep.DoneSuccess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BootstrapActions.SaveReqQueryStarted -> {
|
BootstrapActions.SaveReqQueryStarted -> {
|
||||||
setState {
|
setState {
|
||||||
copy(recoverySaveFileProcess = Loading())
|
copy(recoverySaveFileProcess = Loading())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is BootstrapActions.SaveKeyToUri -> {
|
is BootstrapActions.SaveKeyToUri -> {
|
||||||
saveRecoveryKeyToUri(action.os)
|
saveRecoveryKeyToUri(action.os)
|
||||||
}
|
}
|
||||||
BootstrapActions.SaveReqFailed -> {
|
BootstrapActions.SaveReqFailed -> {
|
||||||
setState {
|
setState {
|
||||||
copy(recoverySaveFileProcess = Uninitialized)
|
copy(recoverySaveFileProcess = Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BootstrapActions.GoToEnterAccountPassword -> {
|
BootstrapActions.GoToEnterAccountPassword -> {
|
||||||
setState {
|
setState {
|
||||||
copy(step = BootstrapStep.AccountPassword(false))
|
copy(step = BootstrapStep.AccountPassword(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BootstrapActions.HandleForgotBackupPassphrase -> {
|
BootstrapActions.HandleForgotBackupPassphrase -> {
|
||||||
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
|
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
|
||||||
setState {
|
setState {
|
||||||
copy(step = BootstrapStep.GetBackupSecretPassForMigration(state.step.isPasswordVisible, true))
|
copy(step = BootstrapStep.GetBackupSecretPassForMigration(state.step.isPasswordVisible, true))
|
||||||
}
|
}
|
||||||
} else return@withState
|
} else return@withState
|
||||||
}
|
}
|
||||||
is BootstrapActions.ReAuth -> {
|
is BootstrapActions.ReAuth -> {
|
||||||
startInitializeFlow(action.pass)
|
startInitializeFlow(action.pass)
|
||||||
}
|
}
|
||||||
is BootstrapActions.DoMigrateWithPassphrase -> {
|
is BootstrapActions.DoMigrateWithPassphrase -> {
|
||||||
startMigrationFlow(state.step, action.passphrase, null)
|
startMigrationFlow(state.step, action.passphrase, null)
|
||||||
}
|
}
|
||||||
is BootstrapActions.DoMigrateWithRecoveryKey -> {
|
is BootstrapActions.DoMigrateWithRecoveryKey -> {
|
||||||
startMigrationFlow(state.step, null, action.recoveryKey)
|
startMigrationFlow(state.step, null, action.recoveryKey)
|
||||||
}
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
|
@ -316,8 +243,6 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
if (it is BackupToQuadSMigrationTask.Result.Success) {
|
if (it is BackupToQuadSMigrationTask.Result.Success) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
passphrase = passphrase,
|
|
||||||
passphraseRepeat = passphrase,
|
|
||||||
migrationRecoveryKey = recoveryKey
|
migrationRecoveryKey = recoveryKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -365,6 +290,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
withState { state ->
|
withState { state ->
|
||||||
|
val previousStep = state.step
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val userPasswordAuth = userPassword?.let {
|
val userPasswordAuth = userPassword?.let {
|
||||||
UserPasswordAuth(
|
UserPasswordAuth(
|
||||||
|
@ -379,7 +305,6 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
Params(
|
Params(
|
||||||
userPasswordAuth = userPasswordAuth,
|
userPasswordAuth = userPasswordAuth,
|
||||||
progressListener = progressListener,
|
progressListener = progressListener,
|
||||||
passphrase = state.passphrase,
|
|
||||||
keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } }
|
keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } }
|
||||||
)
|
)
|
||||||
) { bootstrapResult ->
|
) { bootstrapResult ->
|
||||||
|
@ -423,7 +348,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(BootstrapViewEvents.ModalError(bootstrapResult.error ?: stringProvider.getString(R.string.matrix_error)))
|
_viewEvents.post(BootstrapViewEvents.ModalError(bootstrapResult.error ?: stringProvider.getString(R.string.matrix_error)))
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
step = BootstrapStep.ConfirmPassphrase(false)
|
step = previousStep
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -459,25 +384,12 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
// do we let you cancel from here?
|
// do we let you cancel from here?
|
||||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap())
|
_viewEvents.post(BootstrapViewEvents.SkipBootstrap())
|
||||||
}
|
}
|
||||||
is BootstrapStep.SetupPassphrase -> {
|
|
||||||
// do we let you cancel from here?
|
|
||||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap())
|
|
||||||
}
|
|
||||||
is BootstrapStep.ConfirmPassphrase -> {
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
step = BootstrapStep.SetupPassphrase(
|
|
||||||
isPasswordVisible = (state.step as? BootstrapStep.ConfirmPassphrase)?.isPasswordVisible ?: false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BootstrapStep.AccountPassword -> {
|
is BootstrapStep.AccountPassword -> {
|
||||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
|
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(false))
|
||||||
}
|
}
|
||||||
BootstrapStep.Initializing -> {
|
BootstrapStep.Initializing -> {
|
||||||
// do we let you cancel from here?
|
// do we let you cancel from here?
|
||||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
|
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(false))
|
||||||
}
|
}
|
||||||
is BootstrapStep.SaveRecoveryKey,
|
is BootstrapStep.SaveRecoveryKey,
|
||||||
BootstrapStep.DoneSuccess -> {
|
BootstrapStep.DoneSuccess -> {
|
||||||
|
@ -490,7 +402,8 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
// Companion, view model assisted creation
|
// Companion, view model assisted creation
|
||||||
// ======================================
|
// ======================================
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<BootstrapSharedViewModel, BootstrapViewState> {
|
companion object
|
||||||
|
: MvRxViewModelFactory<BootstrapSharedViewModel, BootstrapViewState> {
|
||||||
|
|
||||||
override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? {
|
||||||
val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
|
|
@ -30,29 +30,25 @@ package im.vector.riotx.features.crypto.recover
|
||||||
* └───────────────────────────────────┘ │
|
* └───────────────────────────────────┘ │
|
||||||
* │ │
|
* │ │
|
||||||
* │ │
|
* │ │
|
||||||
* Existing ├─────────No ───────┐ │
|
* Existing ├─────────No ──────────────────┐
|
||||||
* ┌────Keybackup───────┘ KeyBackup │ │
|
* ┌────Keybackup───────┘ KeyBackup │
|
||||||
* │ │ │
|
* │ │
|
||||||
* │ ▼ ▼
|
* │ │
|
||||||
* ▼ ┌────────────────────────────────────┐
|
* ▼ │
|
||||||
* ┌─────────────────────────────────────────┐ │ BootstrapStep.SetupPassphrase │◀─┐
|
* ┌─────────────────────────────────────────┐ │
|
||||||
* │BootstrapStep.GetBackupSecretForMigration│ └────────────────────────────────────┘ │
|
* │BootstrapStep.GetBackupSecretForMigration│ │
|
||||||
* └─────────────────────────────────────────┘ │ │
|
* └─────────────────────────────────────────┘ │
|
||||||
* │ │ ┌Back
|
* │ │
|
||||||
* │ ▼ │
|
* │ │
|
||||||
* │ ┌────────────────────────────────────┤
|
* │ is password needed? ─────────────┐
|
||||||
* │ │ BootstrapStep.ConfirmPassphrase │──┐
|
* │ │ │
|
||||||
* │ └────────────────────────────────────┘ │
|
* │ ▼ │
|
||||||
* │ │ │
|
|
||||||
* │ is password needed? │
|
|
||||||
* │ │ │
|
|
||||||
* │ ▼ │
|
|
||||||
* │ ┌────────────────────────────────────┐ │
|
* │ ┌────────────────────────────────────┐ │
|
||||||
* │ │ BootstrapStep.AccountPassword │ │
|
* │ │ BootstrapStep.AccountPassword │ │
|
||||||
* │ └────────────────────────────────────┘ │
|
* │ └────────────────────────────────────┘ │
|
||||||
* │ │ │
|
* │ │ │
|
||||||
* │ │ │
|
* │ │ │
|
||||||
* │ ┌──────────────────┘ password not needed (in
|
* │ ┌──────────────┘ password not needed (in
|
||||||
* │ │ memory)
|
* │ │ memory)
|
||||||
* │ │ │
|
* │ │ │
|
||||||
* │ ▼ │
|
* │ ▼ │
|
||||||
|
@ -77,8 +73,6 @@ package im.vector.riotx.features.crypto.recover
|
||||||
*/
|
*/
|
||||||
|
|
||||||
sealed class BootstrapStep {
|
sealed class BootstrapStep {
|
||||||
data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
|
|
||||||
data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
|
|
||||||
data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep()
|
data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep()
|
||||||
object CheckingMigration : BootstrapStep()
|
object CheckingMigration : BootstrapStep()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue