Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure to save the tokens the Client might return when its session is restored #3378

Merged
merged 3 commits into from
Sep 4, 2024

Conversation

jmartinesp
Copy link
Member

@jmartinesp jmartinesp commented Sep 2, 2024

Content

When we're restoring a Rust Client object with some session data, the client might automatically refresh the tokens it uses. Usually, this would get reported by the ClientDelegate methods, but since that's only added after the session is restored, the update doesn't trigger the delegate callback and the new tokens aren't stored locally. This means next time we restore the session we'd be using discarded credentials and the server will log us out.

I'm creating a new RustClientSessionDelegate that implements both:

  • ClientSessionDelegate: used by iOS mainly up until now, it's passed to the ClientBuilder so it will be able to handle token refreshes while the Client is restoring the session, solving the problem above.
  • ClientDelegate: what we were using for handling token refreshes and refresh errors that should end in a logout. Since the previous delegate is handling token refreshes now, we only need the logout part.

Motivation and context

Found some promising explanation in a rageshake.

This might fix #3274.

Tests

This is quite difficult to test since it depends on the access token expiration time, which is different in each homeserver and also you need some luck (or bad luck) to make it fail, so even if it doesn't fail in some hours or days the fix might not do anything.

I think the best we can do is include it, then check if there haven't been any recent reports of spontaneous logouts.

When I tried to test it, I did the next:

  • Set up an OIDC account, log in, verify, etc., and have a room in common that will trigger notifications.
  • Wait until a token refresh happens, then kill the app by swiping them from the recents menu (this will kill the app but not prevent notifications from being received).
  • Wait until the access token expiry date happens and don't touch the app in the meantime.
  • Send a message that will trigger a notification in the OIDC session.
  • Check the logs, the restoration of the client and the refresh of the tokens should happen in that order and there should be no issues.

However, I'm not 100% certain this is the right way to reproduce the previous error.

Tested devices

  • Physical
  • Emulator
  • OS version(s): 14

Checklist

  • Changes have been tested on an Android device or Android emulator with API 23
  • UI change has been tested on both light and dark themes
  • Accessibility has been taken into account. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#accessibility
  • Pull request is based on the develop branch
  • Pull request title will be used in the release note, it clearly define what will change for the user
  • Pull request includes screenshots or videos if containing UI changes
  • Pull request includes a sign off
  • You've made a self review of your PR

@jmartinesp jmartinesp added the PR-Bugfix For bug fix label Sep 2, 2024
@jmartinesp jmartinesp requested a review from a team as a code owner September 2, 2024 12:08
@jmartinesp jmartinesp requested review from bmarty and removed request for a team September 2, 2024 12:08
Copy link

codecov bot commented Sep 2, 2024

Codecov Report

Attention: Patch coverage is 83.33333% with 1 line in your changes missing coverage. Please review.

Project coverage is 82.59%. Comparing base (ae3fd89) to head (8be90e2).
Report is 5 commits behind head on develop.

Files with missing lines Patch % Lines
...droid/features/logout/impl/DefaultLogoutUseCase.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #3378   +/-   ##
========================================
  Coverage    82.59%   82.59%           
========================================
  Files         1693     1693           
  Lines        39646    39646           
  Branches      4822     4822           
========================================
  Hits         32745    32745           
  Misses        5216     5216           
  Partials      1685     1685           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

github-actions bot commented Sep 2, 2024

📱 Scan the QR code below to install the build (arm64 only) for this PR.
QR code
If you can't scan the QR code you can install the build via this link: https://i.diawi.com/RHSSjq

Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but maybe a better fix is to invoke client.restoreSession(sessionData.toSession()) after creating the RustMatrixClient instance? So move the line 67 into this also block? So in this case, the callback didRefreshTokens() will be invoked, since the clientDelegate will be set?

@jmartinesp
Copy link
Member Author

I don't think we can because RustMatrixClient needs a SyncService parameter that we can only get once the session is restored. We'd have to maybe lazily initialize it, and make sure that doesn't break any existing behaviours.

In any case, I think we'll implement a fix in the Rust SDK instead. I'm leaving the PR as a draft until either that solution is ready or we decide a client side one might be better.

@jmartinesp jmartinesp marked this pull request as draft September 2, 2024 14:41
@jmartinesp jmartinesp force-pushed the fix/jme/tokens-refresh-while-restoring-client branch from 315d811 to 34846e2 Compare September 2, 2024 16:43
private val updateTokensDispatcher = coroutineDispatchers.io.limitedParallelism(1)

/** This [RustMatrixClient] needs to be set up as soon as possible so `didReceiveAuthError` can work properly. */
var client: RustMatrixClient? = null
Copy link
Member Author

@jmartinesp jmartinesp Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate that this is needed, but I don't see any better way to centralise handling the token operations in a single point without being able to access the client for a logout.

Refreshed tokens on client restoration might not have been stored to disk if the token refresh happened before `RustMatrixClient` was built and the `ClientDelegate` was set in it.

Using `ClientSessionDelegate` should ensure the tokens refreshed callback is called at any point in time.
@jmartinesp
Copy link
Member Author

In any case, I think we'll implement a fix in the Rust SDK instead. I'm leaving the PR as a draft until either that solution is ready or we decide a client side one might be better.

Well, this is no longer true. We saw we could reuse the same flow as iOS with ClientSessionDelegate. This part probably needs a refactor in the SDK, but it's probably not the time to tackle it.

@jmartinesp jmartinesp force-pushed the fix/jme/tokens-refresh-while-restoring-client branch from 06e1ce9 to 57b88ec Compare September 3, 2024 09:36
@jmartinesp jmartinesp marked this pull request as ready for review September 3, 2024 11:55
Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is something missing, see the comment: #3378 (comment)

@jmartinesp jmartinesp force-pushed the fix/jme/tokens-refresh-while-restoring-client branch from 26d81dd to 8be90e2 Compare September 3, 2024 15:49
Copy link

sonarcloud bot commented Sep 3, 2024

Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update, all my tests are passing now!

Hopefully no more wild sign out!

override suspend fun logout(ignoreSdkError: Boolean, forced: Boolean): String? = simulateLongTask {
return logoutLambda(ignoreSdkError, forced)
override suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean): String? = simulateLongTask {
return logoutLambda(ignoreSdkError, userInitiated)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a swap in the parameters, this is not a big deal, but it can be confusing. Also when userInitiated is false, ignoreSdkError does not have sense.

Maybe we should create a small sealed interface to handle this:

    sealed interface LogoutParameter {
        data class UserInitiated(val ignoreSdkError: Boolean) : LogoutParameter
        data object ServerInitiated : LogoutParameter
    }

Not a blocker for this PR though, I can do it later, just sharing the idea here :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add this in a separate PR then? So I can merge this and add it to the current release.

@bmarty bmarty added the Run-Maestro Starts a Maestro Cloud session to run integration tests label Sep 4, 2024
@github-actions github-actions bot removed the Run-Maestro Starts a Maestro Cloud session to run integration tests label Sep 4, 2024
@jmartinesp jmartinesp merged commit 2c8b0d0 into develop Sep 4, 2024
28 of 29 checks passed
@jmartinesp jmartinesp deleted the fix/jme/tokens-refresh-while-restoring-client branch September 4, 2024 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR-Bugfix For bug fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Logged out without apparent reason
2 participants