-
Notifications
You must be signed in to change notification settings - Fork 595
Description
Bug
_updateStore() in internal-session.js only includes the authenticator key in persisted data when this.authenticator is non-empty:
_updateStore() {
let data = this.content;
if (!isEmpty(this.authenticator)) {
set(data, 'authenticated', Object.assign(
{ authenticator: this.authenticator },
data.authenticated || {}
));
}
return this.store.persist(data);
}If this.authenticator is temporarily null due to a race condition (e.g., _clear() runs concurrently with a token refresh that triggers _setup() → _updateStore()), the session is persisted without the authenticator key:
{"authenticated": {"access_token": "...", "refresh_token": "..."}}On the next page load, restore() reads this data, finds no authenticator key, and immediately clears the session — silently logging the user out with zero network requests.
Reproduction
- Have an authenticated session with a valid refresh token
- Trigger a race condition where
_clear()nullsthis.authenticatorwhile_updateStore()is about to persist (e.g., cross-tab logout during a scheduled token refresh) - Close the tab
- Reopen the app — the user is immediately logged out without any token refresh attempt
Root cause
restore() treats missing authenticator key as "no session":
let { authenticator: authenticatorFactory } = restoredContent.authenticated || {};
if (authenticatorFactory) {
// ... restore session
} else {
// clear session ← THIS CLEARS VALID TOKENS
}Suggested fix
Either:
-
Prevent
_updateStore()from persisting whenisAuthenticatedis true butauthenticatoris null — refuse to write corrupt state:_updateStore() { if (this.isAuthenticated && isEmpty(this.authenticator)) { return Promise.resolve(); // skip persist } // ... existing logic }
-
Or preserve the last-known authenticator name so it's always available for persist, even if
this.authenticatoris transiently null.
Versions
- Verified the bug exists in v1.9.2 and the current master (v8.3.0) — the
_updateStore()logic is identical.