mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Revert "Remove BootstrapStep.SetupPassphrase and BootstrapStep.ConfirmPassphrase"
This reverts commit 23fa44b6a6a6d34b425e2c1adef4fd2beb9800a7.
This commit is contained in:
parent
957fe189dc
commit
e758ede706
10 changed files with 427 additions and 47 deletions
|
@ -28,6 +28,8 @@ import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
|
|||
import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
||||
import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment
|
||||
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.BootstrapSaveRecoveryKeyFragment
|
||||
import im.vector.riotx.features.crypto.recover.BootstrapSetupRecoveryKeyFragment
|
||||
|
@ -452,6 +454,16 @@ interface FragmentModule {
|
|||
@FragmentKey(GossipingEventsPaperTrailFragment::class)
|
||||
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
|
||||
@IntoMap
|
||||
@FragmentKey(BootstrapWaitingFragment::class)
|
||||
|
|
|
@ -24,13 +24,17 @@ sealed class BootstrapActions : VectorViewModelAction {
|
|||
// Navigation
|
||||
|
||||
object GoBack : BootstrapActions()
|
||||
data class GoToConfirmPassphrase(val passphrase: String) : BootstrapActions()
|
||||
object GoToCompleted : BootstrapActions()
|
||||
object GoToEnterAccountPassword : BootstrapActions()
|
||||
|
||||
object SetupRecoveryKey : BootstrapActions()
|
||||
|
||||
data class DoInitialize(val passphrase: String) : BootstrapActions()
|
||||
object DoInitializeGeneratedKey : 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()
|
||||
object RecoveryKeySaved : BootstrapActions()
|
||||
object Completed : BootstrapActions()
|
||||
|
|
|
@ -132,6 +132,16 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
bootstrapTitleText.text = getString(R.string.upgrade_security)
|
||||
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 -> {
|
||||
bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user))
|
||||
bootstrapTitleText.text = getString(R.string.account_password)
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -66,6 +66,7 @@ interface BootstrapProgressListener {
|
|||
data class Params(
|
||||
val userPasswordAuth: UserPasswordAuth? = null,
|
||||
val progressListener: BootstrapProgressListener? = null,
|
||||
val passphrase: String?,
|
||||
val keySpec: SsssKeySpec? = null
|
||||
)
|
||||
|
||||
|
@ -107,13 +108,24 @@ class BootstrapCrossSigningTask @Inject constructor(
|
|||
)
|
||||
try {
|
||||
keyInfo = awaitCallback {
|
||||
ssssService.generateKey(
|
||||
UUID.randomUUID().toString(),
|
||||
params.keySpec,
|
||||
"ssss_key",
|
||||
EmptyKeySigner(),
|
||||
it
|
||||
)
|
||||
params.passphrase?.let { passphrase ->
|
||||
ssssService.generateKeyWithPassphrase(
|
||||
UUID.randomUUID().toString(),
|
||||
"ssss_key",
|
||||
passphrase,
|
||||
EmptyKeySigner(),
|
||||
null,
|
||||
it
|
||||
)
|
||||
} ?: kotlin.run {
|
||||
ssssService.generateKey(
|
||||
UUID.randomUUID().toString(),
|
||||
params.keySpec,
|
||||
"ssss_key",
|
||||
EmptyKeySigner(),
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (failure: Failure) {
|
||||
return BootstrapResult.FailedToCreateSSSSKey(failure)
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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,11 +51,15 @@ class BootstrapMigrateBackupFragment @Inject constructor(
|
|||
|
||||
override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup
|
||||
|
||||
private val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
|
||||
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
withState(sharedViewModel) {
|
||||
// set initial value (useful when coming back)
|
||||
bootstrapMigrateEditText.setText(it.passphrase ?: "")
|
||||
}
|
||||
bootstrapMigrateEditText.editorActionEvents()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.nulabinc.zxcvbn.Zxcvbn
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
|
@ -43,6 +44,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import java.io.OutputStream
|
||||
|
||||
|
||||
class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: BootstrapViewState,
|
||||
@Assisted val args: BootstrapBottomSheet.Args,
|
||||
|
@ -53,6 +55,8 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
private val reAuthHelper: ReAuthHelper
|
||||
) : VectorViewModel<BootstrapViewState, BootstrapActions, BootstrapViewEvents>(initialState) {
|
||||
|
||||
private val zxcvbn = Zxcvbn()
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: BootstrapViewState, args: BootstrapBottomSheet.Args): BootstrapSharedViewModel
|
||||
|
@ -64,7 +68,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
// need to check if user have an existing keybackup
|
||||
if (args.isNewAccount) {
|
||||
setState {
|
||||
copy(step = BootstrapStep.AccountPassword(false))
|
||||
copy(step = BootstrapStep.SetupPassphrase(false))
|
||||
}
|
||||
} else {
|
||||
setState {
|
||||
|
@ -79,7 +83,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
if (version == null) {
|
||||
// we just resume plain bootstrap
|
||||
setState {
|
||||
copy(step = BootstrapStep.AccountPassword(false))
|
||||
copy(step = BootstrapStep.SetupPassphrase(false))
|
||||
}
|
||||
} else {
|
||||
// we need to get existing backup passphrase/key and convert to SSSS
|
||||
|
@ -108,9 +112,19 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
|
||||
override fun handle(action: BootstrapActions) = withState { state ->
|
||||
when (action) {
|
||||
is BootstrapActions.GoBack -> queryBack()
|
||||
BootstrapActions.TogglePasswordVisibility -> {
|
||||
is BootstrapActions.GoBack -> queryBack()
|
||||
BootstrapActions.TogglePasswordVisibility -> {
|
||||
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 -> {
|
||||
setState {
|
||||
copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
|
||||
|
@ -128,64 +142,118 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
BootstrapActions.SetupRecoveryKey -> {
|
||||
startProcess()
|
||||
}
|
||||
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
|
||||
if (userPassword == null) {
|
||||
setState {
|
||||
copy(
|
||||
passphrase = null,
|
||||
passphraseRepeat = null,
|
||||
step = BootstrapStep.AccountPassword(false)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
setState {
|
||||
copy(
|
||||
passphrase = null,
|
||||
passphraseRepeat = null
|
||||
)
|
||||
}
|
||||
startInitializeFlow(userPassword)
|
||||
}
|
||||
}
|
||||
BootstrapActions.RecoveryKeySaved -> {
|
||||
BootstrapActions.RecoveryKeySaved -> {
|
||||
_viewEvents.post(BootstrapViewEvents.RecoveryKeySaved)
|
||||
setState {
|
||||
copy(step = BootstrapStep.SaveRecoveryKey(true))
|
||||
}
|
||||
}
|
||||
BootstrapActions.Completed -> {
|
||||
BootstrapActions.Completed -> {
|
||||
_viewEvents.post(BootstrapViewEvents.Dismiss)
|
||||
}
|
||||
BootstrapActions.GoToCompleted -> {
|
||||
BootstrapActions.GoToCompleted -> {
|
||||
setState {
|
||||
copy(step = BootstrapStep.DoneSuccess)
|
||||
}
|
||||
}
|
||||
BootstrapActions.SaveReqQueryStarted -> {
|
||||
BootstrapActions.SaveReqQueryStarted -> {
|
||||
setState {
|
||||
copy(recoverySaveFileProcess = Loading())
|
||||
}
|
||||
}
|
||||
is BootstrapActions.SaveKeyToUri -> {
|
||||
is BootstrapActions.SaveKeyToUri -> {
|
||||
saveRecoveryKeyToUri(action.os)
|
||||
}
|
||||
BootstrapActions.SaveReqFailed -> {
|
||||
BootstrapActions.SaveReqFailed -> {
|
||||
setState {
|
||||
copy(recoverySaveFileProcess = Uninitialized)
|
||||
}
|
||||
}
|
||||
BootstrapActions.GoToEnterAccountPassword -> {
|
||||
BootstrapActions.GoToEnterAccountPassword -> {
|
||||
setState {
|
||||
copy(step = BootstrapStep.AccountPassword(false))
|
||||
}
|
||||
}
|
||||
BootstrapActions.HandleForgotBackupPassphrase -> {
|
||||
BootstrapActions.HandleForgotBackupPassphrase -> {
|
||||
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
|
||||
setState {
|
||||
copy(step = BootstrapStep.GetBackupSecretPassForMigration(state.step.isPasswordVisible, true))
|
||||
}
|
||||
} else return@withState
|
||||
}
|
||||
is BootstrapActions.ReAuth -> {
|
||||
is BootstrapActions.ReAuth -> {
|
||||
startInitializeFlow(action.pass)
|
||||
}
|
||||
is BootstrapActions.DoMigrateWithPassphrase -> {
|
||||
is BootstrapActions.DoMigrateWithPassphrase -> {
|
||||
startMigrationFlow(state.step, action.passphrase, null)
|
||||
}
|
||||
is BootstrapActions.DoMigrateWithRecoveryKey -> {
|
||||
is BootstrapActions.DoMigrateWithRecoveryKey -> {
|
||||
startMigrationFlow(state.step, null, action.recoveryKey)
|
||||
}
|
||||
}.exhaustive
|
||||
|
@ -234,6 +302,8 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
if (it is BackupToQuadSMigrationTask.Result.Success) {
|
||||
setState {
|
||||
copy(
|
||||
passphrase = passphrase,
|
||||
passphraseRepeat = passphrase,
|
||||
migrationRecoveryKey = recoveryKey
|
||||
)
|
||||
}
|
||||
|
@ -281,7 +351,6 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
withState { state ->
|
||||
val previousStep = state.step
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val userPasswordAuth = userPassword?.let {
|
||||
UserPasswordAuth(
|
||||
|
@ -296,6 +365,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
Params(
|
||||
userPasswordAuth = userPasswordAuth,
|
||||
progressListener = progressListener,
|
||||
passphrase = state.passphrase,
|
||||
keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } }
|
||||
)
|
||||
) { bootstrapResult ->
|
||||
|
@ -339,7 +409,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(BootstrapViewEvents.ModalError(bootstrapResult.error ?: stringProvider.getString(R.string.matrix_error)))
|
||||
setState {
|
||||
copy(
|
||||
step = previousStep
|
||||
step = BootstrapStep.ConfirmPassphrase(false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -375,12 +445,25 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
// do we let you cancel from here?
|
||||
_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 -> {
|
||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(false))
|
||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
|
||||
}
|
||||
BootstrapStep.Initializing -> {
|
||||
// do we let you cancel from here?
|
||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(false))
|
||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
|
||||
}
|
||||
is BootstrapStep.SaveRecoveryKey,
|
||||
BootstrapStep.DoneSuccess -> {
|
||||
|
@ -393,8 +476,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
|||
// Companion, view model assisted creation
|
||||
// ======================================
|
||||
|
||||
companion object
|
||||
: MvRxViewModelFactory<BootstrapSharedViewModel, BootstrapViewState> {
|
||||
companion object : MvRxViewModelFactory<BootstrapSharedViewModel, BootstrapViewState> {
|
||||
|
||||
override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? {
|
||||
val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
|
|
|
@ -36,25 +36,29 @@ package im.vector.riotx.features.crypto.recover
|
|||
* └───────────────────────────────────┘ │
|
||||
* │ │
|
||||
* │ │
|
||||
* Existing ├─────────No ──────────────────┐
|
||||
* ┌────Keybackup───────┘ KeyBackup │
|
||||
* │ │
|
||||
* │ │
|
||||
* ▼ │
|
||||
* ┌─────────────────────────────────────────┐ │
|
||||
* │BootstrapStep.GetBackupSecretForMigration│ │
|
||||
* └─────────────────────────────────────────┘ │
|
||||
* │ │
|
||||
* │ │
|
||||
* │ is password needed? ─────────────┐
|
||||
* │ │ │
|
||||
* │ ▼ │
|
||||
* Existing ├─────────No ───────┐ │
|
||||
* ┌────Keybackup───────┘ KeyBackup │ │
|
||||
* │ │ │
|
||||
* │ ▼ ▼
|
||||
* ▼ ┌────────────────────────────────────┐
|
||||
* ┌─────────────────────────────────────────┐ │ BootstrapStep.SetupPassphrase │◀─┐
|
||||
* │BootstrapStep.GetBackupSecretForMigration│ └────────────────────────────────────┘ │
|
||||
* └─────────────────────────────────────────┘ │ │
|
||||
* │ │ ┌Back
|
||||
* │ ▼ │
|
||||
* │ ┌────────────────────────────────────┤
|
||||
* │ │ BootstrapStep.ConfirmPassphrase │──┐
|
||||
* │ └────────────────────────────────────┘ │
|
||||
* │ │ │
|
||||
* │ is password needed? │
|
||||
* │ │ │
|
||||
* │ ▼ │
|
||||
* │ ┌────────────────────────────────────┐ │
|
||||
* │ │ BootstrapStep.AccountPassword │ │
|
||||
* │ └────────────────────────────────────┘ │
|
||||
* │ │ │
|
||||
* │ │ │
|
||||
* │ ┌──────────────┘ password not needed (in
|
||||
* │ │ │
|
||||
* │ │ │
|
||||
* │ ┌──────────────────┘ password not needed (in
|
||||
* │ │ memory)
|
||||
* │ │ │
|
||||
* │ ▼ │
|
||||
|
@ -81,6 +85,9 @@ package im.vector.riotx.features.crypto.recover
|
|||
sealed class BootstrapStep {
|
||||
object SetupSecureBackup : 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()
|
||||
object CheckingMigration : BootstrapStep()
|
||||
|
||||
|
|
|
@ -19,13 +19,18 @@ package im.vector.riotx.features.crypto.recover
|
|||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.nulabinc.zxcvbn.Strength
|
||||
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
|
||||
import im.vector.riotx.core.platform.WaitingViewData
|
||||
|
||||
data class BootstrapViewState(
|
||||
val step: BootstrapStep = BootstrapStep.SetupSecureBackup,
|
||||
val step: BootstrapStep = BootstrapStep.SetupPassphrase(false),
|
||||
val passphrase: String? = null,
|
||||
val migrationRecoveryKey: String? = null,
|
||||
val passphraseRepeat: String? = null,
|
||||
val crossSigningInitialization: Async<Unit> = Uninitialized,
|
||||
val passphraseStrength: Async<Strength> = Uninitialized,
|
||||
val passphraseConfirmMatch: Async<Unit> = Uninitialized,
|
||||
val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null,
|
||||
val initializationWaitingViewData: WaitingViewData? = null,
|
||||
val recoverySaveFileProcess: Async<Unit> = Uninitialized
|
||||
|
|
Loading…
Reference in a new issue