Skip to content

Commit 95ce404

Browse files
authored
Merge pull request #5372 from element-hq/feature/bma/fixLogoutUseCase
When logging out from Pin code screen, logout from all the sessions.
2 parents b5e5350 + 6f06e6a commit 95ce404

File tree

6 files changed

+146
-20
lines changed

6 files changed

+146
-20
lines changed

features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class PinUnlockPresenter(
174174

175175
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncAction<Unit>>) = launch {
176176
suspend {
177-
logoutUseCase.logout(ignoreSdkError = true)
177+
logoutUseCase.logoutAll(ignoreSdkError = true)
178178
}.runCatchingUpdatingState(signOutAction)
179179
}
180180
}

features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutUseCase.kt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,12 @@
88
package io.element.android.features.logout.api
99

1010
/**
11-
* Used to trigger a log out of the current user from any part of the app.
11+
* Used to trigger a log out of the current user(s) from any part of the app.
1212
*/
1313
interface LogoutUseCase {
1414
/**
15-
* Log out the current user and then perform any needed cleanup tasks.
15+
* Log out the current user(s) and then perform any needed cleanup tasks.
1616
* @param ignoreSdkError if true, the SDK error will be ignored and the user will be logged out anyway.
1717
*/
18-
suspend fun logout(ignoreSdkError: Boolean)
19-
20-
interface Factory {
21-
fun create(sessionId: String): LogoutUseCase
22-
}
18+
suspend fun logoutAll(ignoreSdkError: Boolean)
2319
}

features/logout/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@ dependencies {
3939
testCommonDependencies(libs, true)
4040
testImplementation(projects.libraries.matrix.test)
4141
testImplementation(projects.libraries.featureflag.test)
42+
testImplementation(projects.libraries.sessionStorage.test)
4243
}

features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutUseCase.kt

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,31 @@ import dev.zacsweers.metro.ContributesBinding
1212
import dev.zacsweers.metro.Inject
1313
import io.element.android.features.logout.api.LogoutUseCase
1414
import io.element.android.libraries.matrix.api.MatrixClientProvider
15-
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
15+
import io.element.android.libraries.matrix.api.core.SessionId
16+
import io.element.android.libraries.sessionstorage.api.SessionStore
17+
import timber.log.Timber
1618

1719
@ContributesBinding(AppScope::class)
1820
@Inject
1921
class DefaultLogoutUseCase(
20-
private val authenticationService: MatrixAuthenticationService,
22+
private val sessionStore: SessionStore,
2123
private val matrixClientProvider: MatrixClientProvider,
2224
) : LogoutUseCase {
23-
override suspend fun logout(ignoreSdkError: Boolean) {
24-
val currentSession = authenticationService.getLatestSessionId()
25-
if (currentSession != null) {
26-
matrixClientProvider.getOrRestore(currentSession)
27-
.getOrThrow()
28-
.logout(userInitiated = true, ignoreSdkError = true)
29-
} else {
30-
error("No session to sign out")
31-
}
25+
override suspend fun logoutAll(ignoreSdkError: Boolean) {
26+
sessionStore.getAllSessions()
27+
.map { sessionData ->
28+
SessionId(sessionData.userId)
29+
}
30+
.forEach { sessionId ->
31+
Timber.d("Logging out sessionId: $sessionId")
32+
matrixClientProvider.getOrRestore(sessionId).fold(
33+
onSuccess = { client ->
34+
client.logout(userInitiated = true, ignoreSdkError = ignoreSdkError)
35+
},
36+
onFailure = { error ->
37+
Timber.e(error, "Failed to get or restore MatrixClient for sessionId: $sessionId")
38+
}
39+
)
40+
}
3241
}
3342
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
@file:OptIn(ExperimentalCoroutinesApi::class)
9+
10+
package io.element.android.features.logout.impl
11+
12+
import io.element.android.libraries.matrix.test.A_USER_ID
13+
import io.element.android.libraries.matrix.test.A_USER_ID_2
14+
import io.element.android.libraries.matrix.test.FakeMatrixClient
15+
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
16+
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
17+
import io.element.android.libraries.sessionstorage.test.aSessionData
18+
import io.element.android.tests.testutils.lambda.lambdaRecorder
19+
import io.element.android.tests.testutils.lambda.value
20+
import kotlinx.coroutines.ExperimentalCoroutinesApi
21+
import kotlinx.coroutines.test.runTest
22+
import org.junit.Test
23+
24+
class DefaultLogoutUseCaseTest {
25+
@Test
26+
fun `test logout from one session`() = runTest {
27+
val logoutLambda1 = lambdaRecorder<Boolean, Boolean, Unit> { _, _ -> }
28+
val client1 = FakeMatrixClient(A_USER_ID).apply {
29+
logoutLambda = logoutLambda1
30+
}
31+
val sut = DefaultLogoutUseCase(
32+
sessionStore = InMemorySessionStore(
33+
initialList = listOf(
34+
aSessionData(sessionId = A_USER_ID.value),
35+
)
36+
),
37+
matrixClientProvider = FakeMatrixClientProvider(
38+
getClient = { sessionId ->
39+
when (sessionId) {
40+
A_USER_ID -> Result.success(client1)
41+
else -> error("Unexpected sessionId")
42+
}
43+
}
44+
),
45+
)
46+
sut.logoutAll(ignoreSdkError = true)
47+
logoutLambda1.assertions().isCalledOnce().with(value(true), value(true))
48+
}
49+
50+
@Test
51+
fun `test logout from several sessions`() = runTest {
52+
val logoutLambda1 = lambdaRecorder<Boolean, Boolean, Unit> { _, _ -> }
53+
val logoutLambda2 = lambdaRecorder<Boolean, Boolean, Unit> { _, _ -> }
54+
val client1 = FakeMatrixClient(A_USER_ID).apply {
55+
logoutLambda = logoutLambda1
56+
}
57+
val client2 = FakeMatrixClient(A_USER_ID_2).apply {
58+
logoutLambda = logoutLambda2
59+
}
60+
val sut = DefaultLogoutUseCase(
61+
sessionStore = InMemorySessionStore(
62+
initialList = listOf(
63+
aSessionData(sessionId = A_USER_ID.value),
64+
aSessionData(sessionId = A_USER_ID_2.value),
65+
)
66+
),
67+
matrixClientProvider = FakeMatrixClientProvider(
68+
getClient = { sessionId ->
69+
when (sessionId) {
70+
A_USER_ID -> Result.success(client1)
71+
A_USER_ID_2 -> Result.success(client2)
72+
else -> error("Unexpected sessionId")
73+
}
74+
}
75+
),
76+
)
77+
sut.logoutAll(ignoreSdkError = true)
78+
logoutLambda1.assertions().isCalledOnce().with(value(true), value(true))
79+
logoutLambda2.assertions().isCalledOnce().with(value(true), value(true))
80+
}
81+
82+
@Test
83+
fun `test logout session not found is ignored`() = runTest {
84+
val sut = DefaultLogoutUseCase(
85+
sessionStore = InMemorySessionStore(
86+
initialList = listOf(
87+
aSessionData(sessionId = A_USER_ID.value),
88+
)
89+
),
90+
matrixClientProvider = FakeMatrixClientProvider(
91+
getClient = { sessionId ->
92+
when (sessionId) {
93+
A_USER_ID -> Result.failure(Exception("Session not found"))
94+
else -> error("Unexpected sessionId")
95+
}
96+
}
97+
),
98+
)
99+
sut.logoutAll(ignoreSdkError = true)
100+
// No error
101+
}
102+
103+
@Test
104+
fun `test logout no sessions`() = runTest {
105+
val sut = DefaultLogoutUseCase(
106+
sessionStore = InMemorySessionStore(
107+
initialList = emptyList()
108+
),
109+
matrixClientProvider = FakeMatrixClientProvider(
110+
getClient = { sessionId ->
111+
when (sessionId) {
112+
else -> error("Unexpected sessionId")
113+
}
114+
}
115+
),
116+
)
117+
sut.logoutAll(ignoreSdkError = true)
118+
// No error
119+
}
120+
}

features/logout/test/src/main/kotlin/io/element/android/features/logout/test/FakeLogoutUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import io.element.android.tests.testutils.simulateLongTask
1414
class FakeLogoutUseCase(
1515
var logoutLambda: (Boolean) -> Unit = { lambdaError() }
1616
) : LogoutUseCase {
17-
override suspend fun logout(ignoreSdkError: Boolean) = simulateLongTask {
17+
override suspend fun logoutAll(ignoreSdkError: Boolean) = simulateLongTask {
1818
logoutLambda(ignoreSdkError)
1919
}
2020
}

0 commit comments

Comments
 (0)