Browse Source

Improve APIs, add tests

pull/3298/head
Jorge Martín 1 month ago
parent
commit
7fd0ad09dc
  1. 5
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt
  2. 12
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt
  3. 6
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt
  4. 12
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt
  5. 22
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt
  6. 4
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt
  7. 14
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt
  8. 6
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt
  9. 6
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt
  10. 12
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt
  11. 4
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt
  12. 8
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt
  13. 14
      features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt
  14. 119
      features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTests.kt
  15. 96
      features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt
  16. 97
      features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt
  17. 65
      features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTests.kt
  18. 107
      features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTests.kt
  19. 4
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt
  20. 8
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt
  21. 10
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt
  22. 7
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt

5
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt

@ -19,7 +19,6 @@ package io.element.android.features.securebackup.impl.reset
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
@ -46,10 +45,6 @@ class ResetIdentityFlowManager @Inject constructor(
} }
} }
fun currentSessionId(): SessionId {
return matrixClient.sessionId
}
fun getResetHandle(): StateFlow<AsyncData<IdentityResetHandle>> { fun getResetHandle(): StateFlow<AsyncData<IdentityResetHandle>> {
return if (resetHandleFlow.value.isLoading() || resetHandleFlow.value.isSuccess()) { return if (resetHandleFlow.value.isLoading() || resetHandleFlow.value.isSuccess()) {
resetHandleFlow resetHandleFlow

12
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt

@ -34,8 +34,8 @@ import com.bumble.appyx.navmodel.backstack.operation.push
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.securebackup.impl.reset.password.ResetKeyPasswordNode import io.element.android.features.securebackup.impl.reset.password.ResetIdentityPasswordNode
import io.element.android.features.securebackup.impl.reset.root.ResetKeyRootNode import io.element.android.features.securebackup.impl.reset.root.ResetIdentityRootNode
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackView
@ -108,18 +108,18 @@ class ResetIdentityFlowNode @AssistedInject constructor(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
is NavTarget.Root -> { is NavTarget.Root -> {
val callback = object : ResetKeyRootNode.Callback { val callback = object : ResetIdentityRootNode.Callback {
override fun onContinue() { override fun onContinue() {
coroutineScope.startReset() coroutineScope.startReset()
} }
} }
createNode<ResetKeyRootNode>(buildContext, listOf(callback)) createNode<ResetIdentityRootNode>(buildContext, listOf(callback))
} }
is NavTarget.ResetPassword -> { is NavTarget.ResetPassword -> {
val handle = resetIdentityFlowManager.currentHandleFlow.value.dataOrNull() as? IdentityPasswordResetHandle ?: error("No password handle found") val handle = resetIdentityFlowManager.currentHandleFlow.value.dataOrNull() as? IdentityPasswordResetHandle ?: error("No password handle found")
createNode<ResetKeyPasswordNode>( createNode<ResetIdentityPasswordNode>(
buildContext, buildContext,
listOf(ResetKeyPasswordNode.Inputs(resetIdentityFlowManager.currentSessionId(), handle)) listOf(ResetIdentityPasswordNode.Inputs(handle))
) )
} }
is NavTarget.ResetOidc -> { is NavTarget.ResetOidc -> {

6
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordEvent.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt

@ -16,7 +16,7 @@
package io.element.android.features.securebackup.impl.reset.password package io.element.android.features.securebackup.impl.reset.password
sealed interface ResetKeyPasswordEvent { sealed interface ResetIdentityPasswordEvent {
data class Reset(val password: String) : ResetKeyPasswordEvent data class Reset(val password: String) : ResetIdentityPasswordEvent
data object DismissError : ResetKeyPasswordEvent data object DismissError : ResetIdentityPasswordEvent
} }

12
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordNode.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt

@ -21,33 +21,33 @@ import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
@ContributesNode(SessionScope::class) @ContributesNode(SessionScope::class)
class ResetKeyPasswordNode @AssistedInject constructor( class ResetIdentityPasswordNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
private val coroutineDispatchers: CoroutineDispatchers,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
data class Inputs(val userId: UserId, val handle: IdentityPasswordResetHandle) : NodeInputs data class Inputs(val handle: IdentityPasswordResetHandle) : NodeInputs
private val presenter by lazy { private val presenter by lazy {
val inputs = inputs<Inputs>() val inputs = inputs<Inputs>()
ResetKeyPasswordPresenter(inputs.userId, inputs.handle) ResetIdentityPasswordPresenter(inputs.handle, dispatchers = coroutineDispatchers)
} }
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
ResetKeyPasswordView( ResetIdentityPasswordView(
state = state, state = state,
onBack = ::navigateUp onBack = ::navigateUp
) )

22
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordPresenter.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt

@ -24,37 +24,37 @@ import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ResetKeyPasswordPresenter( class ResetIdentityPasswordPresenter(
private val userId: UserId,
private val identityPasswordResetHandle: IdentityPasswordResetHandle, private val identityPasswordResetHandle: IdentityPasswordResetHandle,
) : Presenter<ResetKeyPasswordState> { private val dispatchers: CoroutineDispatchers,
) : Presenter<ResetIdentityPasswordState> {
@Composable @Composable
override fun present(): ResetKeyPasswordState { override fun present(): ResetIdentityPasswordState {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val resetAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) } val resetAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
fun handleEvent(event: ResetKeyPasswordEvent) { fun handleEvent(event: ResetIdentityPasswordEvent) {
when (event) { when (event) {
is ResetKeyPasswordEvent.Reset -> coroutineScope.reset(userId, event.password, resetAction) is ResetIdentityPasswordEvent.Reset -> coroutineScope.reset(event.password, resetAction)
ResetKeyPasswordEvent.DismissError -> resetAction.value = AsyncAction.Uninitialized ResetIdentityPasswordEvent.DismissError -> resetAction.value = AsyncAction.Uninitialized
} }
} }
return ResetKeyPasswordState( return ResetIdentityPasswordState(
resetAction = resetAction.value, resetAction = resetAction.value,
eventSink = ::handleEvent eventSink = ::handleEvent
) )
} }
private fun CoroutineScope.reset(userId: UserId, password: String, action: MutableState<AsyncAction<Unit>>) = launch { private fun CoroutineScope.reset(password: String, action: MutableState<AsyncAction<Unit>>) = launch(dispatchers.io) {
suspend { suspend {
identityPasswordResetHandle.resetPassword(userId, password).getOrThrow() identityPasswordResetHandle.resetPassword(password).getOrThrow()
}.runCatchingUpdatingState(action) }.runCatchingUpdatingState(action)
} }
} }

4
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordState.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt

@ -18,7 +18,7 @@ package io.element.android.features.securebackup.impl.reset.password
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
data class ResetKeyPasswordState( data class ResetIdentityPasswordState(
val resetAction: AsyncAction<Unit>, val resetAction: AsyncAction<Unit>,
val eventSink: (ResetKeyPasswordEvent) -> Unit, val eventSink: (ResetIdentityPasswordEvent) -> Unit,
) )

14
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordView.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt

@ -46,8 +46,8 @@ import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKe
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun ResetKeyPasswordView( fun ResetIdentityPasswordView(
state: ResetKeyPasswordState, state: ResetIdentityPasswordState,
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -63,7 +63,7 @@ fun ResetKeyPasswordView(
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(CommonStrings.action_reset_identity), text = stringResource(CommonStrings.action_reset_identity),
onClick = { state.eventSink(ResetKeyPasswordEvent.Reset(passwordState.value)) }, onClick = { state.eventSink(ResetIdentityPasswordEvent.Reset(passwordState.value)) },
destructive = true, destructive = true,
) )
} }
@ -74,7 +74,7 @@ fun ResetKeyPasswordView(
} else if (state.resetAction.isFailure()) { } else if (state.resetAction.isFailure()) {
ErrorDialog( ErrorDialog(
content = stringResource(CommonStrings.error_unknown), content = stringResource(CommonStrings.error_unknown),
onDismiss = { state.eventSink(ResetKeyPasswordEvent.DismissError) } onDismiss = { state.eventSink(ResetIdentityPasswordEvent.DismissError) }
) )
} }
} }
@ -107,10 +107,10 @@ private fun Content(textFieldState: MutableState<String>) {
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun ResetKeyPasswordViewPreview() { internal fun ResetIdentityPasswordViewPreview() {
ElementPreview { ElementPreview {
ResetKeyPasswordView( ResetIdentityPasswordView(
state = ResetKeyPasswordState( state = ResetIdentityPasswordState(
resetAction = AsyncAction.Uninitialized, resetAction = AsyncAction.Uninitialized,
eventSink = {} eventSink = {}
), ),

6
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootEvent.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt

@ -16,7 +16,7 @@
package io.element.android.features.securebackup.impl.reset.root package io.element.android.features.securebackup.impl.reset.root
sealed interface ResetKeyRootEvent { sealed interface ResetIdentityRootEvent {
data object Continue : ResetKeyRootEvent data object Continue : ResetIdentityRootEvent
data object DismissDialog : ResetKeyRootEvent data object DismissDialog : ResetIdentityRootEvent
} }

6
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootNode.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt

@ -27,7 +27,7 @@ import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class) @ContributesNode(SessionScope::class)
class ResetKeyRootNode @AssistedInject constructor( class ResetIdentityRootNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
@ -35,13 +35,13 @@ class ResetKeyRootNode @AssistedInject constructor(
fun onContinue() fun onContinue()
} }
private val presenter = ResetKeyRootPresenter() private val presenter = ResetIdentityRootPresenter()
private val callback: Callback = plugins.filterIsInstance<Callback>().first() private val callback: Callback = plugins.filterIsInstance<Callback>().first()
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
ResetKeyRootView( ResetIdentityRootView(
state = state, state = state,
onContinue = callback::onContinue, onContinue = callback::onContinue,
onBack = ::navigateUp, onBack = ::navigateUp,

12
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootPresenter.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt

@ -23,19 +23,19 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
class ResetKeyRootPresenter : Presenter<ResetKeyRootState> { class ResetIdentityRootPresenter : Presenter<ResetIdentityRootState> {
@Composable @Composable
override fun present(): ResetKeyRootState { override fun present(): ResetIdentityRootState {
var displayConfirmDialog by remember { mutableStateOf(false) } var displayConfirmDialog by remember { mutableStateOf(false) }
fun handleEvent(event: ResetKeyRootEvent) { fun handleEvent(event: ResetIdentityRootEvent) {
displayConfirmDialog = when (event) { displayConfirmDialog = when (event) {
ResetKeyRootEvent.Continue -> true ResetIdentityRootEvent.Continue -> true
ResetKeyRootEvent.DismissDialog -> false ResetIdentityRootEvent.DismissDialog -> false
} }
} }
return ResetKeyRootState( return ResetIdentityRootState(
displayConfirmationDialog = displayConfirmDialog, displayConfirmationDialog = displayConfirmDialog,
eventSink = ::handleEvent eventSink = ::handleEvent
) )

4
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootState.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt

@ -16,7 +16,7 @@
package io.element.android.features.securebackup.impl.reset.root package io.element.android.features.securebackup.impl.reset.root
data class ResetKeyRootState( data class ResetIdentityRootState(
val displayConfirmationDialog: Boolean, val displayConfirmationDialog: Boolean,
val eventSink: (ResetKeyRootEvent) -> Unit, val eventSink: (ResetIdentityRootEvent) -> Unit,
) )

8
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootStateProvider.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt

@ -18,14 +18,14 @@ package io.element.android.features.securebackup.impl.reset.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
class ResetKeyRootStateProvider : PreviewParameterProvider<ResetKeyRootState> { class ResetIdentityRootStateProvider : PreviewParameterProvider<ResetIdentityRootState> {
override val values: Sequence<ResetKeyRootState> override val values: Sequence<ResetIdentityRootState>
get() = sequenceOf( get() = sequenceOf(
ResetKeyRootState( ResetIdentityRootState(
displayConfirmationDialog = false, displayConfirmationDialog = false,
eventSink = {} eventSink = {}
), ),
ResetKeyRootState( ResetIdentityRootState(
displayConfirmationDialog = true, displayConfirmationDialog = true,
eventSink = {} eventSink = {}
) )

14
features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootView.kt → features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt

@ -43,8 +43,8 @@ import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@Composable @Composable
fun ResetKeyRootView( fun ResetIdentityRootView(
state: ResetKeyRootState, state: ResetIdentityRootState,
onContinue: () -> Unit, onContinue: () -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
) { ) {
@ -58,7 +58,7 @@ fun ResetKeyRootView(
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(id = CommonStrings.action_continue), text = stringResource(id = CommonStrings.action_continue),
onClick = { state.eventSink(ResetKeyRootEvent.Continue) }, onClick = { state.eventSink(ResetIdentityRootEvent.Continue) },
destructive = true, destructive = true,
) )
}, },
@ -71,11 +71,11 @@ fun ResetKeyRootView(
content = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_subtitle), content = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_subtitle),
submitText = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_action), submitText = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_action),
onSubmitClick = { onSubmitClick = {
state.eventSink(ResetKeyRootEvent.DismissDialog) state.eventSink(ResetIdentityRootEvent.DismissDialog)
onContinue() onContinue()
}, },
destructiveSubmit = true, destructiveSubmit = true,
onDismiss = { state.eventSink(ResetKeyRootEvent.DismissDialog) } onDismiss = { state.eventSink(ResetIdentityRootEvent.DismissDialog) }
) )
} }
} }
@ -138,9 +138,9 @@ private fun Content() {
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun ResetKeyRootViewPreview(@PreviewParameter(ResetKeyRootStateProvider::class) state: ResetKeyRootState) { internal fun ResetIdentityRootViewPreview(@PreviewParameter(ResetIdentityRootStateProvider::class) state: ResetIdentityRootState) {
ElementPreview { ElementPreview {
ResetKeyRootView( ResetIdentityRootView(
state = state, state = state,
onContinue = {}, onContinue = {},
onBack = {}, onBack = {},

119
features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTests.kt

@ -0,0 +1,119 @@
/*
* Copyright (c) 2024 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
*
* https://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 io.element.android.features.securebackup.impl.reset
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.encryption.FakeIdentityPasswordResetHandle
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class ResetIdentityFlowManagerTests {
@Test
fun `getResetHandle - emits a reset handle`() = runTest {
val startResetLambda = lambdaRecorder<Result<IdentityResetHandle?>> { Result.success(FakeIdentityPasswordResetHandle()) }
val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda)
val flowManager = createFlowManager(encryptionService = encryptionService)
flowManager.getResetHandle().test {
assertThat(awaitItem().isLoading()).isTrue()
assertThat(awaitItem().isSuccess()).isTrue()
startResetLambda.assertions().isCalledOnce()
}
}
@Test
fun `getResetHandle - om successful handle retrieval returns that same handle`() = runTest {
val startResetLambda = lambdaRecorder<Result<IdentityResetHandle?>> { Result.success(FakeIdentityPasswordResetHandle()) }
val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda)
val flowManager = createFlowManager(encryptionService = encryptionService)
var result: AsyncData.Success<IdentityResetHandle>? = null
flowManager.getResetHandle().test {
assertThat(awaitItem().isLoading()).isTrue()
result = awaitItem() as? AsyncData.Success<IdentityResetHandle>
assertThat(result).isNotNull()
}
flowManager.getResetHandle().test {
assertThat(awaitItem()).isSameInstanceAs(result)
}
}
@Test
fun `getResetHandle - will fail if it receives a null reset handle`() = runTest {
val startResetLambda = lambdaRecorder<Result<IdentityResetHandle?>> { Result.success(null) }
val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda)
val flowManager = createFlowManager(encryptionService = encryptionService)
flowManager.getResetHandle().test {
assertThat(awaitItem().isLoading()).isTrue()
assertThat(awaitItem().isFailure()).isTrue()
startResetLambda.assertions().isCalledOnce()
}
}
@Test
fun `getResetHandle - fails gracefully when receiving an exception from the encryption service`() = runTest {
val startResetLambda = lambdaRecorder<Result<IdentityResetHandle?>> { Result.failure(IllegalStateException("Failure")) }
val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda)
val flowManager = createFlowManager(encryptionService = encryptionService)
flowManager.getResetHandle().test {
assertThat(awaitItem().isLoading()).isTrue()
assertThat(awaitItem().isFailure()).isTrue()
startResetLambda.assertions().isCalledOnce()
}
}
@Test
fun `cancel - resets the state and calls cancel on the reset handle`() = runTest {
val cancelLambda = lambdaRecorder<Unit> { }
val resetHandle = FakeIdentityPasswordResetHandle(cancelLambda = cancelLambda)
val startResetLambda = lambdaRecorder<Result<IdentityResetHandle?>> { Result.success(resetHandle) }
val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda)
val flowManager = createFlowManager(encryptionService = encryptionService)
flowManager.getResetHandle().test {
assertThat(awaitItem().isLoading()).isTrue()
assertThat(awaitItem().isSuccess()).isTrue()
flowManager.cancel()
cancelLambda.assertions().isCalledOnce()
assertThat(awaitItem().isUninitialized()).isTrue()
}
}
private fun TestScope.createFlowManager(
encryptionService: FakeEncryptionService = FakeEncryptionService(),
client: FakeMatrixClient = FakeMatrixClient(encryptionService = encryptionService),
sessionCoroutineScope: CoroutineScope = this,
sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
) = ResetIdentityFlowManager(
matrixClient = client,
sessionCoroutineScope = sessionCoroutineScope,
sessionVerificationService = sessionVerificationService,
)
}

96
features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt

@ -0,0 +1,96 @@
/*
* Copyright (c) 2024 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
*
* https://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 io.element.android.features.securebackup.impl.reset.password
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.encryption.FakeIdentityPasswordResetHandle
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class ResetIdentityPasswordPresenterTest {
@Test
fun `present - initial state`() = runTest {
val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.resetAction.isUninitialized()).isTrue()
}
}
@Test
fun `present - Reset event succeeds`() = runTest {
val resetLambda = lambdaRecorder<String, Result<Unit>> { _ -> Result.success(Unit) }
val resetHandle = FakeIdentityPasswordResetHandle(resetPasswordLambda = resetLambda)
val presenter = createPresenter(identityResetHandle = resetHandle)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(ResetIdentityPasswordEvent.Reset("password"))
assertThat(awaitItem().resetAction.isLoading()).isTrue()
assertThat(awaitItem().resetAction.isSuccess()).isTrue()
}
}
@Test
fun `present - Reset event can fail gracefully`() = runTest {
val resetLambda = lambdaRecorder<String, Result<Unit>> { _ -> Result.failure(IllegalStateException("Failed")) }
val resetHandle = FakeIdentityPasswordResetHandle(resetPasswordLambda = resetLambda)
val presenter = createPresenter(identityResetHandle = resetHandle)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(ResetIdentityPasswordEvent.Reset("password"))
assertThat(awaitItem().resetAction.isLoading()).isTrue()
assertThat(awaitItem().resetAction.isFailure()).isTrue()
}
}
@Test
fun `present - DismissError event resets the state`() = runTest {
val resetLambda = lambdaRecorder<String, Result<Unit>> { _ -> Result.failure(IllegalStateException("Failed")) }
val resetHandle = FakeIdentityPasswordResetHandle(resetPasswordLambda = resetLambda)
val presenter = createPresenter(identityResetHandle = resetHandle)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(ResetIdentityPasswordEvent.Reset("password"))
assertThat(awaitItem().resetAction.isLoading()).isTrue()
assertThat(awaitItem().resetAction.isFailure()).isTrue()
initialState.eventSink(ResetIdentityPasswordEvent.DismissError)
assertThat(awaitItem().resetAction.isUninitialized()).isTrue()
}
}
private fun TestScope.createPresenter(
identityResetHandle: FakeIdentityPasswordResetHandle = FakeIdentityPasswordResetHandle(),
) = ResetIdentityPasswordPresenter(
identityPasswordResetHandle = identityResetHandle,
dispatchers = testCoroutineDispatchers(),
)
}

97
features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt

@ -0,0 +1,97 @@
/*
* Copyright (c) 2024 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
*
* https://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 io.element.android.features.securebackup.impl.reset.password
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressBackKey
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ResetIdentityPasswordViewTest {
@get:Rule
val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `pressing the back HW button invokes the expected callback`() {
ensureCalledOnce {
rule.setResetPasswordView(
ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = {}),
onBack = it,
)
rule.pressBackKey()
}
}
@Test
fun `clicking on the back navigation button invokes the expected callback`() {
ensureCalledOnce {
rule.setResetPasswordView(
ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = {}),
onBack = it,
)
rule.pressBack()
}
}
@Test
fun `clicking 'Reset identity' confirms the reset`() {
val eventsRecorder = EventsRecorder<ResetIdentityPasswordEvent>()
rule.setResetPasswordView(
ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = eventsRecorder),
)
rule.onNodeWithText("Password").performTextInput("A password")
rule.clickOn(CommonStrings.action_reset_identity)
eventsRecorder.assertSingle(ResetIdentityPasswordEvent.Reset("A password"))
}
@Test
fun `clicking OK dismisses the error dialog`() {
val eventsRecorder = EventsRecorder<ResetIdentityPasswordEvent>()
rule.setResetPasswordView(
ResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("A failure")), eventSink = eventsRecorder),
)
rule.clickOn(CommonStrings.action_ok)
eventsRecorder.assertSingle(ResetIdentityPasswordEvent.DismissError)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setResetPasswordView(
state: ResetIdentityPasswordState,
onBack: () -> Unit = EnsureNeverCalled(),
) {
setContent {
ResetIdentityPasswordView(state = state, onBack = onBack)
}
}

65
features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTests.kt

@ -0,0 +1,65 @@
/*
* Copyright (c) 2024 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
*
* https://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 io.element.android.features.securebackup.impl.reset.root
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
class ResetIdentityRootPresenterTests {
@Test
fun `present - initial state`() = runTest {
val presenter = ResetIdentityRootPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.displayConfirmationDialog).isFalse()
}
}
@Test
fun `present - Continue event displays the confirmation dialog`() = runTest {
val presenter = ResetIdentityRootPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(ResetIdentityRootEvent.Continue)
assertThat(awaitItem().displayConfirmationDialog).isTrue()
}
}
@Test
fun `present - DismissDialog event hides the confirmation dialog`() = runTest {
val presenter = ResetIdentityRootPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink(ResetIdentityRootEvent.Continue)
assertThat(awaitItem().displayConfirmationDialog).isTrue()
initialState.eventSink(ResetIdentityRootEvent.DismissDialog)
assertThat(awaitItem().displayConfirmationDialog).isFalse()
}
}
}

107
features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTests.kt

@ -0,0 +1,107 @@
/*
* Copyright (c) 2024 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
*
* https://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 io.element.android.features.securebackup.impl.reset.root
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressBackKey
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class ResetIdentityRootViewTests {
@get:Rule
val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `pressing the back HW button invokes the expected callback`() {
ensureCalledOnce {
rule.setResetRootView(
ResetIdentityRootState(displayConfirmationDialog = false, eventSink = {}),
onBack = it,
)
rule.pressBackKey()
}
}
@Test
fun `clicking on the back navigation button invokes the expected callback`() {
ensureCalledOnce {
rule.setResetRootView(
ResetIdentityRootState(displayConfirmationDialog = false, eventSink = {}),
onBack = it,
)
rule.pressBack()
}
}
@Test
@Config(qualifiers = "h720dp")
fun `clicking Continue displays the confirmation dialog`() {
val eventsRecorder = EventsRecorder<ResetIdentityRootEvent>()
rule.setResetRootView(
ResetIdentityRootState(displayConfirmationDialog = false, eventSink = eventsRecorder),
)
rule.clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle(ResetIdentityRootEvent.Continue)
}
@Test
fun `clicking 'Yes, reset now' confirms the reset`() {
ensureCalledOnce {
rule.setResetRootView(
ResetIdentityRootState(displayConfirmationDialog = true, eventSink = {}),
onContinue = it,
)
rule.clickOn(CommonStrings.screen_reset_encryption_confirmation_alert_action)
}
}
@Test
fun `clicking Cancel dismisses the dialog`() {
val eventsRecorder = EventsRecorder<ResetIdentityRootEvent>()
rule.setResetRootView(
ResetIdentityRootState(displayConfirmationDialog = true, eventSink = eventsRecorder),
)
rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(ResetIdentityRootEvent.DismissDialog)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setResetRootView(
state: ResetIdentityRootState,
onBack: () -> Unit = EnsureNeverCalled(),
onContinue: () -> Unit = EnsureNeverCalled(),
) {
setContent {
ResetIdentityRootView(state = state, onContinue = onContinue, onBack = onBack)
}
}

4
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt

@ -16,7 +16,6 @@
package io.element.android.libraries.matrix.api.encryption package io.element.android.libraries.matrix.api.encryption
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -90,10 +89,9 @@ interface IdentityPasswordResetHandle : IdentityResetHandle {
* This method will block the coroutine it's running on and keep polling indefinitely until either the coroutine is cancelled, the [cancel] method is * This method will block the coroutine it's running on and keep polling indefinitely until either the coroutine is cancelled, the [cancel] method is
* called, or the identity is reset. * called, or the identity is reset.
* *
* @param userId the user id of the user to reset the password for.
* @param password the current password, which will be validated before the process takes place. * @param password the current password, which will be validated before the process takes place.
*/ */
suspend fun resetPassword(userId: UserId, password: String): Result<Unit> suspend fun resetPassword(password: String): Result<Unit>
} }
/** /**

8
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt

@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.encryption
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupState
import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.BackupUploadState
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
@ -55,6 +56,7 @@ internal class RustEncryptionService(
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
) : EncryptionService { ) : EncryptionService {
private val service: Encryption = client.encryption() private val service: Encryption = client.encryption()
private val sessionId = SessionId(client.session().userId)
private val enableRecoveryProgressMapper = EnableRecoveryProgressMapper() private val enableRecoveryProgressMapper = EnableRecoveryProgressMapper()
private val backupUploadStateMapper = BackupUploadStateMapper() private val backupUploadStateMapper = BackupUploadStateMapper()
@ -201,6 +203,10 @@ internal class RustEncryptionService(
} }
override suspend fun startIdentityReset(): Result<IdentityResetHandle?> { override suspend fun startIdentityReset(): Result<IdentityResetHandle?> {
return runCatching { service.resetIdentity()?.let(RustIdentityResetHandleFactory::create)?.getOrNull() } return runCatching {
service.resetIdentity()?.let { handle ->
RustIdentityResetHandleFactory.create(sessionId, handle)
}?.getOrNull()
}
} }
} }

10
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt

@ -25,21 +25,25 @@ import org.matrix.rustcomponents.sdk.AuthDataPasswordDetails
import org.matrix.rustcomponents.sdk.CrossSigningResetAuthType import org.matrix.rustcomponents.sdk.CrossSigningResetAuthType
object RustIdentityResetHandleFactory { object RustIdentityResetHandleFactory {
fun create(identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle): Result<IdentityResetHandle> { fun create(
userId: UserId,
identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle
): Result<IdentityResetHandle> {
return runCatching { return runCatching {
when (val authType = identityResetHandle.authType()) { when (val authType = identityResetHandle.authType()) {
is CrossSigningResetAuthType.Oidc -> RustOidcIdentityResetHandle(identityResetHandle, authType.info.approvalUrl) is CrossSigningResetAuthType.Oidc -> RustOidcIdentityResetHandle(identityResetHandle, authType.info.approvalUrl)
// User interactive authentication (user + password) // User interactive authentication (user + password)
CrossSigningResetAuthType.Uiaa -> RustPasswordIdentityResetHandle(identityResetHandle) CrossSigningResetAuthType.Uiaa -> RustPasswordIdentityResetHandle(userId, identityResetHandle)
} }
} }
} }
} }
class RustPasswordIdentityResetHandle( class RustPasswordIdentityResetHandle(
private val userId: UserId,
private val identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle, private val identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle,
) : IdentityPasswordResetHandle { ) : IdentityPasswordResetHandle {
override suspend fun resetPassword(userId: UserId, password: String): Result<Unit> { override suspend fun resetPassword(password: String): Result<Unit> {
return runCatching { identityResetHandle.reset(AuthData.Password(AuthDataPasswordDetails(userId.value, password))) } return runCatching { identityResetHandle.reset(AuthData.Password(AuthDataPasswordDetails(userId.value, password))) }
} }

7
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt

@ -16,7 +16,6 @@
package io.element.android.libraries.matrix.test.encryption package io.element.android.libraries.matrix.test.encryption
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
@ -35,11 +34,11 @@ class FakeIdentityOidcResetHandle(
} }
class FakeIdentityPasswordResetHandle( class FakeIdentityPasswordResetHandle(
var resetPasswordLambda: (UserId, String) -> Result<Unit> = { _, _ -> error("Not implemented") }, var resetPasswordLambda: (String) -> Result<Unit> = { _ -> error("Not implemented") },
var cancelLambda: () -> Unit = { error("Not implemented") }, var cancelLambda: () -> Unit = { error("Not implemented") },
) : IdentityPasswordResetHandle { ) : IdentityPasswordResetHandle {
override suspend fun resetPassword(userId: UserId, password: String): Result<Unit> { override suspend fun resetPassword(password: String): Result<Unit> {
return resetPasswordLambda(userId, password) return resetPasswordLambda(password)
} }
override suspend fun cancel() { override suspend fun cancel() {

Loading…
Cancel
Save