Implement user verification (#4294)

* Add support for starting verification of a user

* Add support for replying to incoming user verification requests

* Add reset recovery key button and previews to `ChooseSelfVerificationModeView`

* Add 'Profile' item in room details screen

* Update screenshots

* Remove `showDeviceVerifiedScreen` parameter from `NavTarget.UseAnotherDevice`

* Allow exiting the FTUE flow, which will close the app. The previous state will be restored when the app is reopened.

* When outgoing verification fails, move to the `Canceled` state. Then, when resetting the state machine state also reset the verification service.

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa 2025-03-10 11:20:17 +01:00 committed by GitHub
parent 2ce1b17dae
commit f73c0e42a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
145 changed files with 1662 additions and 830 deletions

View file

@ -8,13 +8,14 @@
package io.element.android.libraries.matrix.impl.verification
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.matrix.api.verification.VerificationRequest
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
@ -50,6 +51,8 @@ class RustSessionVerificationService(
isSyncServiceReady: Flow<Boolean>,
private val sessionCoroutineScope: CoroutineScope,
) : SessionVerificationService, SessionVerificationControllerDelegate {
private var currentVerificationRequest: VerificationRequest? = null
private val encryptionService: Encryption = client.encryption()
private lateinit var verificationController: SessionVerificationController
@ -88,10 +91,8 @@ class RustSessionVerificationService(
verificationStatus == SessionVerifiedStatus.NotVerified
}
private var isOwnVerification = true
override fun didReceiveVerificationRequest(details: RustSessionVerificationRequestDetails) {
listener?.onIncomingSessionRequest(details.map())
listener?.onIncomingSessionRequest(details.toVerificationRequest(UserId(client.userId())))
}
private var listener: SessionVerificationServiceListener? = null
@ -111,9 +112,16 @@ class RustSessionVerificationService(
this.listener = listener
}
override suspend fun requestVerification() = tryOrFail {
override suspend fun requestCurrentSessionVerification() = tryOrFail {
initVerificationControllerIfNeeded()
verificationController.requestDeviceVerification()
currentVerificationRequest = VerificationRequest.Outgoing.CurrentSession
}
override suspend fun requestUserVerification(userId: UserId) = tryOrFail {
initVerificationControllerIfNeeded()
verificationController.requestUserVerification(userId.value)
currentVerificationRequest = VerificationRequest.Outgoing.User(userId)
}
override suspend fun cancelVerification() = tryOrFail {
@ -130,16 +138,16 @@ class RustSessionVerificationService(
verificationController.startSasVerification()
}
override suspend fun acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) = tryOrFail {
isOwnVerification = false
override suspend fun acknowledgeVerificationRequest(verificationRequest: VerificationRequest.Incoming) = tryOrFail {
initVerificationControllerIfNeeded()
verificationController.acknowledgeVerificationRequest(
senderId = details.senderId.value,
flowId = details.flowId.value,
senderId = verificationRequest.details.senderProfile.userId.value,
flowId = verificationRequest.details.flowId.value,
)
}
override suspend fun acceptVerificationRequest() = tryOrFail {
Timber.d("Accepting incoming verification request")
verificationController.acceptVerificationRequest()
}
@ -183,7 +191,7 @@ class RustSessionVerificationService(
}
}
.onSuccess {
if (isOwnVerification) {
if (currentVerificationRequest is VerificationRequest.Outgoing.CurrentSession) {
// Try waiting for the final recovery state for better UX, but don't block the verification state on it
tryOrNull {
withTimeout(10.seconds) {
@ -215,7 +223,7 @@ class RustSessionVerificationService(
// end-region
override suspend fun reset(cancelAnyPendingVerificationAttempt: Boolean) {
isOwnVerification = true
currentVerificationRequest = null
if (isReady.value && cancelAnyPendingVerificationAttempt) {
// Cancel any pending verification attempt
tryOrNull { verificationController.cancelVerification() }

View file

@ -11,12 +11,28 @@ import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.FlowId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
import io.element.android.libraries.matrix.api.verification.VerificationRequest
import org.matrix.rustcomponents.sdk.SessionVerificationRequestDetails as RustSessionVerificationRequestDetails
import org.matrix.rustcomponents.sdk.UserProfile as RustUserProfile
fun RustSessionVerificationRequestDetails.map() = SessionVerificationRequestDetails(
senderId = UserId(senderProfile.userId),
senderProfile = senderProfile.map(),
flowId = FlowId(flowId),
deviceId = DeviceId(deviceId),
displayName = senderProfile.displayName,
firstSeenTimestamp = firstSeenTimestamp.toLong(),
)
fun RustUserProfile.map() = SessionVerificationRequestDetails.SenderProfile(
userId = UserId(userId),
displayName = displayName,
avatarUrl = avatarUrl,
)
fun RustSessionVerificationRequestDetails.toVerificationRequest(currentUserId: UserId): VerificationRequest.Incoming {
val details = map()
return if (currentUserId == details.senderProfile.userId) {
VerificationRequest.Incoming.OtherSession(details)
} else {
VerificationRequest.Incoming.User(details)
}
}