fix: keep the web session when a resume fails without refusing the token

Merged
GG
13h ago
+142-0

Proposed changes

Users are logged out of the web client when the server restarts or is redeployed.

Meteor's accounts client logs out after any error from the resume login it sends in three cases: on page load, after every reconnect, and when the stored token changes in another tab. During a restart the server can answer with errors that say nothing about the token:

  • internal errors (a Mongo disruption, a login validation hook throwing);
  • too-many-requests;
  • error-login-blocked-for-ip.

Any of these was enough to discard a valid session.

The session now only ends when the server refuses the token:

  • client/lib/isLoginTokenRefused.ts decides what counts as a refusal. That is an unknown token ("You've been logged out by the server"), an expired token ("Your session has expired", including the form the service broker rebuilds), or an account that may not log in (error-user-is-not-activated, error-user-has-no-roles, error-app-user-is-not-allowed-to-login).
  • client/meteor/overrides/keepSessionOnTransientResumeErrors.ts keeps the stored token after any other resume failure and retries the resume with a backoff (1s, doubling up to 60s). It covers the page-load resume too, which accounts-base starts before the app's overrides load. A logout the user asks for (Meteor.logout / Accounts.logout) always goes through.

Issue(s)

https://rocketchat.atlassian.net/browse/ARCH-2581

Steps to test or reproduce

  1. Log in to a monolith instance and keep the tab open.
  2. Make the resume login fail with a non-auth error, then reconnect. For example, restart the server while MongoDB is briefly unavailable, or enable Block_Multiple_Failed_Logins_By_Ip with a low threshold.
  3. Before: the tab lands on the login page and Meteor.loginToken is gone from localStorage. After: the token stays, the console shows [accounts] kept the session after a login failure that does not refuse the token, and the session comes back once the server answers.
  4. Revoke the token instead (e.g. log out the session from another device, or wait for it to expire). The tab is logged out as before.

client/lib/isLoginTokenRefused.spec.ts covers the refused and the transient errors.

Further comments

Found during the same investigation and left for separate PRs:

  • The REST client clears credentials on any 401 from a mutating endpoint (e.g. rooms.hide answers 401 for a room the user can't access).
  • force_logout ignores the session id, so logging out another device logs out this tab.
  • In microservices, the DDP streamer can answer a login with an empty successful result while the account service isn't registered.

Summary by CodeRabbit

  • Bug Fixes
    • Users remain signed in when session-resume attempts encounter temporary errors.
    • Session resumption now retries automatically with increasing delays.
    • Users are logged out only when the server rejects the session token.
    • Successful reconnection restores normal session handling, while explicit logout continues to work as expected.