From 87b3a5d2f0f3cffe1a19a882985dc1bf05492530 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 26 May 2026 12:19:26 +0200 Subject: [PATCH] Add better logs to track token update failures (#6859) 1. Make some logs use `info` log level instead of `debug`, so they appear in most user's bug reports. 2. Make the anonymized tokens even harder to reverse. 3. Detect when the tokens we should be saving match the current ones, as that's an error. --- .../matrix/impl/RustClientSessionDelegate.kt | 16 ++++++++++++++-- .../matrix/impl/RustMatrixClientFactory.kt | 2 +- .../android/libraries/matrix/impl/util/Token.kt | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt index c776ca8522..a5c69bf831 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt @@ -65,10 +65,22 @@ class RustClientSessionDelegate( // This always runs on a background thread, so we *can* do blocking calls here, although we should avoid doing heavy work override fun saveSessionInKeychain(session: Session) { + Timber.tag(loggerTag.value).i("Saving new session info for user ${session.userId} after a token refresh") runCatchingExceptions { val existingData = runBlocking { sessionStore.getSession(session.userId) } ?: return + + if (existingData.accessToken == session.accessToken) { + Timber.tag(loggerTag.value).e("Access token is the same as the one already stored, this should not happen after a token refresh!") + return + } + + if (existingData.refreshToken == session.refreshToken) { + Timber.tag(loggerTag.value).e("Refresh token is the same as the one already stored, this should not happen after a token refresh!") + return + } + val (anonymizedAccessToken, anonymizedRefreshToken) = session.anonymizedTokens() - Timber.tag(loggerTag.value).d( + Timber.tag(loggerTag.value).i( "Saving new session data with token: access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'. " + "Was token valid: ${existingData.isTokenValid}" ) @@ -79,7 +91,7 @@ class RustClientSessionDelegate( sessionPaths = existingData.getSessionPaths(), ) runBlocking { sessionStore.updateData(newData) } - Timber.tag(loggerTag.value).d("Saved new session data with access token: '$anonymizedAccessToken'.") + Timber.tag(loggerTag.value).i("Saved new session data.") }.onFailure { Timber.tag(loggerTag.value).e(it, "Failed to save new session data.") } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 6757edf16c..c22a8b9454 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -131,7 +131,7 @@ class RustMatrixClientFactory( analyticsService = analyticsService, workManagerScheduler = workManagerScheduler, ).also { - Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'") + Timber.tag("RustMatrixClient").i("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'") } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Token.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Token.kt index 815e134cf2..f5df21008b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Token.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Token.kt @@ -16,7 +16,8 @@ private val sha256 by lazy { MessageDigest.getInstance("SHA-256") } @OptIn(ExperimentalStdlibApi::class) private fun anonymizeToken(token: String): String { - return sha256.digest(token.toByteArray()).toHexString() + // Only keep the first 32 chars (16 bytes) of the hashed token to avoid displaying too much information. + return sha256.digest(token.toByteArray()).toHexString().take(32) } fun SessionData?.anonymizedTokens(): Pair {