Skip to content

Commit 59e72fc

Browse files
bmartyclaude
andcommitted
Stop deleting the session data of a logged in account on the next login
sessionPaths keeps pointing at the directories of the account which has just been logged in, and it was never reset. Adding another account calls setHomeserver(), whose rotateSessionPath() starts by deleting the directories of the previous sessionPaths, so the file and cache directories of the first account were wiped. finalizeClientCreation() now forgets the session paths once the login succeeded. It is done there rather than in clear() on purpose: after a failed attempt the directories are orphaned and must still be deleted by the rotateSessionPath() of the next attempt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 579ec9b commit 59e72fc

3 files changed

Lines changed: 89 additions & 5 deletions

File tree

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,10 @@ class RustMatrixAuthenticationService(
386386
newMatrixClientObservers.forEach { it.invoke(matrixClient) }
387387
sessionStore.addSession(sessionData)
388388

389+
// The session paths now hold the data of the account which has just been logged in, so forget
390+
// them: they must not be deleted by the rotateSessionPath() of the next login attempt.
391+
sessionPaths = null
392+
389393
return SessionId(sessionData.userId)
390394
}
391395

libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
1515
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
1616
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
1717
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
18+
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider
1819
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
1920
import io.element.android.libraries.sessionstorage.api.SessionStore
2021
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
@@ -52,6 +53,7 @@ fun TestScope.createRustMatrixClientFactory(
5253
),
5354
clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(),
5455
workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(),
56+
sqliteStoreBuilderProvider: SqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
5557
) = RustMatrixClientFactory(
5658
cacheDirectory = cacheDirectory,
5759
appCoroutineScope = backgroundScope,
@@ -64,7 +66,7 @@ fun TestScope.createRustMatrixClientFactory(
6466
featureFlagService = FakeFeatureFlagService(),
6567
timelineEventFilterFactory = FakeTimelineEventFilterFactory(),
6668
clientBuilderProvider = clientBuilderProvider,
67-
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
69+
sqliteStoreBuilderProvider = sqliteStoreBuilderProvider,
6870
workManagerScheduler = workManagerScheduler,
6971
clientBuilderEnterpriseHook = FakeClientBuilderEnterpriseHook(),
7072
)

libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.google.common.truth.Truth.assertThat
1212
import io.element.android.features.enterprise.api.EnterpriseService
1313
import io.element.android.features.enterprise.test.FakeEnterpriseService
1414
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
15+
import io.element.android.libraries.matrix.api.paths.SessionPaths
1516
import io.element.android.libraries.matrix.impl.ClientBuilderProvider
1617
import io.element.android.libraries.matrix.impl.FakeClientBuilderProvider
1718
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
@@ -22,6 +23,10 @@ import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiHomeserver
2223
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiLoginWithQrCodeHandler
2324
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiQrCodeData
2425
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
26+
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilder
27+
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
28+
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilder
29+
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider
2530
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
2631
import io.element.android.libraries.matrix.test.A_USER_ID
2732
import io.element.android.libraries.matrix.test.auth.FakeOAuthRedirectUrlProvider
@@ -33,13 +38,18 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
3338
import io.element.android.tests.testutils.testCoroutineDispatchers
3439
import kotlinx.coroutines.test.TestScope
3540
import kotlinx.coroutines.test.runTest
41+
import org.junit.Rule
3642
import org.junit.Test
43+
import org.junit.rules.TemporaryFolder
3744
import org.matrix.rustcomponents.sdk.Client
3845
import org.matrix.rustcomponents.sdk.ClientBuilder
3946
import org.matrix.rustcomponents.sdk.HumanQrLoginException
4047
import java.io.File
4148

4249
class RustMatrixAuthenticationServiceTest {
50+
@get:Rule
51+
val temporaryFolder = TemporaryFolder()
52+
4353
@Test
4454
fun `setHomeserver is successful`() = runTest {
4555
val sut = createRustMatrixAuthenticationService(
@@ -177,21 +187,89 @@ class RustMatrixAuthenticationServiceTest {
177187
override fun provide(): ClientBuilder = FakeFfiClientBuilder(buildResult = clients[index++])
178188
}
179189

190+
@Test
191+
fun `a new login does not delete the session data of the account which has just been logged in`() = runTest {
192+
val storeBuilderProvider = SessionDirectoryCreatingSqliteStoreBuilderProvider()
193+
val sut = createRustMatrixAuthenticationService(
194+
clientBuilderProvider = FakeSequentialClientBuilderProvider(
195+
{ aLoginFakeFfiClient() },
196+
{ FakeFfiClient(withUtdHook = {}) },
197+
{ aLoginFakeFfiClient() },
198+
),
199+
sessionPathsFactory = SessionPathsFactory(temporaryFolder.newFolder("base"), temporaryFolder.newFolder("cache")),
200+
sqliteStoreBuilderProvider = storeBuilderProvider,
201+
)
202+
203+
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
204+
assertThat(sut.login("alice", "password").getOrNull()).isEqualTo(A_USER_ID)
205+
val loggedInSessionPaths = storeBuilderProvider.providedSessionPaths.first()
206+
207+
// Adding another account rotates the session paths, which must not touch the previous account.
208+
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
209+
210+
assertThat(loggedInSessionPaths.fileDirectory.exists()).isTrue()
211+
assertThat(loggedInSessionPaths.cacheDirectory.exists()).isTrue()
212+
}
213+
214+
@Test
215+
fun `a new login deletes the session data of a previous failed login attempt`() = runTest {
216+
val storeBuilderProvider = SessionDirectoryCreatingSqliteStoreBuilderProvider()
217+
val sut = createRustMatrixAuthenticationService(
218+
clientBuilderProvider = FakeSequentialClientBuilderProvider(
219+
{ aLoginFakeFfiClient() },
220+
{ throw IllegalStateException("Failed to build the client of the session") },
221+
{ aLoginFakeFfiClient() },
222+
),
223+
sessionPathsFactory = SessionPathsFactory(temporaryFolder.newFolder("base"), temporaryFolder.newFolder("cache")),
224+
sqliteStoreBuilderProvider = storeBuilderProvider,
225+
)
226+
227+
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
228+
assertThat(sut.login("alice", "password").isFailure).isTrue()
229+
val abandonedSessionPaths = storeBuilderProvider.providedSessionPaths.first()
230+
231+
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
232+
233+
assertThat(abandonedSessionPaths.fileDirectory.exists()).isFalse()
234+
assertThat(abandonedSessionPaths.cacheDirectory.exists()).isFalse()
235+
}
236+
237+
private fun aLoginFakeFfiClient() = FakeFfiClient(
238+
homeserverLoginDetailsResult = { FakeFfiHomeserverLoginDetails() },
239+
loginResult = { _, _ -> },
240+
)
241+
242+
/**
243+
* A [SqliteStoreBuilderProvider] which creates the session directories, like the SDK does when it
244+
* opens its stores, and records them so that a test can assert on their lifecycle.
245+
*/
246+
private class SessionDirectoryCreatingSqliteStoreBuilderProvider : SqliteStoreBuilderProvider {
247+
val providedSessionPaths = mutableListOf<SessionPaths>()
248+
249+
override fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder {
250+
sessionPaths.fileDirectory.mkdirs()
251+
sessionPaths.cacheDirectory.mkdirs()
252+
providedSessionPaths.add(sessionPaths)
253+
return FakeSqliteStoreBuilder()
254+
}
255+
}
256+
180257
private fun TestScope.createRustMatrixAuthenticationService(
181258
sessionStore: SessionStore = InMemorySessionStore(updateUserProfileResult = { _, _, _ -> }),
182259
clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(),
183260
enterpriseService: EnterpriseService = FakeEnterpriseService(),
261+
sessionPathsFactory: SessionPathsFactory = SessionPathsFactory(File("/base"), File("/cache")),
262+
sqliteStoreBuilderProvider: SqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
184263
): RustMatrixAuthenticationService {
185-
val baseDirectory = File("/base")
186-
val cacheDirectory = File("/cache")
187264
val rustMatrixClientFactory = createRustMatrixClientFactory(
188-
cacheDirectory = cacheDirectory,
265+
cacheDirectory = File("/cache"),
189266
sessionStore = sessionStore,
190267
clientBuilderProvider = clientBuilderProvider,
191268
workManagerScheduler = FakeWorkManagerScheduler(submitLambda = {}),
269+
sqliteStoreBuilderProvider = sqliteStoreBuilderProvider,
192270
)
193271
return RustMatrixAuthenticationService(
194-
sessionPathsFactory = SessionPathsFactory(baseDirectory, cacheDirectory),
272+
sessionPathsFactory = sessionPathsFactory,
195273
coroutineDispatchers = testCoroutineDispatchers(),
196274
sessionStore = sessionStore,
197275
rustMatrixClientFactory = rustMatrixClientFactory,

0 commit comments

Comments
 (0)