mirror of
https://github.com/element-hq/element-android
synced 2024-10-27 05:06:59 +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.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.BootstrapSetupRecoveryKeyFragment
|
import im.vector.riotx.features.crypto.recover.BootstrapSetupRecoveryKeyFragment
|
||||||
|
@ -452,6 +454,16 @@ 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,13 +24,17 @@ 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()
|
||||||
|
|
||||||
object SetupRecoveryKey : BootstrapActions()
|
object SetupRecoveryKey : 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()
|
||||||
|
|
|
@ -132,6 +132,16 @@ 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)
|
||||||
|
|
|
@ -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(
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,13 +108,24 @@ class BootstrapCrossSigningTask @Inject constructor(
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
keyInfo = awaitCallback {
|
keyInfo = awaitCallback {
|
||||||
ssssService.generateKey(
|
params.passphrase?.let { passphrase ->
|
||||||
UUID.randomUUID().toString(),
|
ssssService.generateKeyWithPassphrase(
|
||||||
params.keySpec,
|
UUID.randomUUID().toString(),
|
||||||
"ssss_key",
|
"ssss_key",
|
||||||
EmptyKeySigner(),
|
passphrase,
|
||||||
it
|
EmptyKeySigner(),
|
||||||
)
|
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)
|
||||||
|
|
|
@ -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
|
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?) {
|
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())
|
||||||
|
|
|
@ -24,6 +24,7 @@ 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.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
|
||||||
|
@ -43,6 +44,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
|
||||||
class BootstrapSharedViewModel @AssistedInject constructor(
|
class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: BootstrapViewState,
|
@Assisted initialState: BootstrapViewState,
|
||||||
@Assisted val args: BootstrapBottomSheet.Args,
|
@Assisted val args: BootstrapBottomSheet.Args,
|
||||||
|
@ -53,6 +55,8 @@ 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
|
||||||
|
@ -64,7 +68,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.AccountPassword(false))
|
copy(step = BootstrapStep.SetupPassphrase(false))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setState {
|
setState {
|
||||||
|
@ -79,7 +83,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.AccountPassword(false))
|
copy(step = BootstrapStep.SetupPassphrase(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
|
||||||
|
@ -108,9 +112,19 @@ 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))
|
||||||
|
@ -128,64 +142,118 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
BootstrapActions.SetupRecoveryKey -> {
|
BootstrapActions.SetupRecoveryKey -> {
|
||||||
startProcess()
|
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
|
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
|
||||||
|
@ -234,6 +302,8 @@ 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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -281,7 +351,6 @@ 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(
|
||||||
|
@ -296,6 +365,7 @@ 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 ->
|
||||||
|
@ -339,7 +409,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 = previousStep
|
step = BootstrapStep.ConfirmPassphrase(false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,12 +445,25 @@ 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(false))
|
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
|
||||||
}
|
}
|
||||||
BootstrapStep.Initializing -> {
|
BootstrapStep.Initializing -> {
|
||||||
// do we let you cancel from here?
|
// do we let you cancel from here?
|
||||||
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(false))
|
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
|
||||||
}
|
}
|
||||||
is BootstrapStep.SaveRecoveryKey,
|
is BootstrapStep.SaveRecoveryKey,
|
||||||
BootstrapStep.DoneSuccess -> {
|
BootstrapStep.DoneSuccess -> {
|
||||||
|
@ -393,8 +476,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||||
// Companion, view model assisted creation
|
// Companion, view model assisted creation
|
||||||
// ======================================
|
// ======================================
|
||||||
|
|
||||||
companion object
|
companion object : MvRxViewModelFactory<BootstrapSharedViewModel, BootstrapViewState> {
|
||||||
: 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()
|
||||||
|
|
|
@ -36,25 +36,29 @@ 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)
|
||||||
* │ │ │
|
* │ │ │
|
||||||
* │ ▼ │
|
* │ ▼ │
|
||||||
|
@ -81,6 +85,9 @@ package im.vector.riotx.features.crypto.recover
|
||||||
sealed class BootstrapStep {
|
sealed class BootstrapStep {
|
||||||
object SetupSecureBackup : 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()
|
data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep()
|
||||||
object CheckingMigration : BootstrapStep()
|
object CheckingMigration : BootstrapStep()
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,18 @@ package im.vector.riotx.features.crypto.recover
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import com.nulabinc.zxcvbn.Strength
|
||||||
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
|
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
|
||||||
import im.vector.riotx.core.platform.WaitingViewData
|
import im.vector.riotx.core.platform.WaitingViewData
|
||||||
|
|
||||||
data class BootstrapViewState(
|
data class BootstrapViewState(
|
||||||
val step: BootstrapStep = BootstrapStep.SetupSecureBackup,
|
val step: BootstrapStep = BootstrapStep.SetupPassphrase(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
|
||||||
|
|
Loading…
Reference in a new issue