feat: Share one session across tabs (M2-11052,M2-11002) - #2251
feat: Share one session across tabs (M2-11052,M2-11002)#2251sricharan-varanasi wants to merge 41 commits into
Conversation
50e94ba to
b8cc745
Compare
32f1efd to
ce42957
Compare
b8cc745 to
8504546
Compare
|
This pull request is automatically being deployed by Amplify Hosting (learn more). |
There was a problem hiding this comment.
@sricharan-varanasi This is big set of changes and looks really good overall. Just a few comments: 3 that I think could tighten the security, and 2 ACTIVITY_EVENTS comments that could slightly reduce how often the event handler fires.
| if (Date.now() - lastActivityAt < idleTimeoutMs) return; | ||
|
|
||
| authStorage.clear(); | ||
| clearSessionState(); |
There was a problem hiding this comment.
[Important] Also clear sessionStorage like we do in Auth.reducer.ts?
sessionStorage.clear();|
|
||
| const { idleTimeoutMs } = resolveSessionConfig(); | ||
| if (Date.now() - lastActivityAt < idleTimeoutMs) return; | ||
|
|
There was a problem hiding this comment.
[Important] Also invalidate tokens on the server like we do in useLogout.ts?
await deleteAccessTokenApi();await deleteRefreshTokenApi();There was a problem hiding this comment.
Fixed in 46a02ca . It now calls deleteRefreshTokenApi() before clearing, guarded on the
refresh token's own exp so it only fires while the server would still accept it.
clearStaleSession now calls deleteRefreshTokenApi() before clearing local storage, so the
server ends the session too. logout2 revokes the whole token family, so the access token goes
with it - one call covers both.
The call is guarded on the refresh token's exp, so we only ask the server when the token is
actually still valid. If it has already expired there's nothing to revoke and we skip it.
| export const ACTIVITY_THROTTLE_MS = 5 * MS_IN_SEC; | ||
|
|
||
| export const ACTIVITY_EVENTS = [ | ||
| 'mousemove', |
There was a problem hiding this comment.
s/mousemove/pointermove/? pointermove should capture movement from mouse, touch, or pen.
There was a problem hiding this comment.
Fixed in 46a6f63 . Switched to pointermove, which covers mouse, touch and pen in one.
| 'keydown', | ||
| 'scroll', | ||
| 'wheel', | ||
| 'touchstart', |
There was a problem hiding this comment.
Remove touchstart? touchstart should be captured by pointerdown.
There was a problem hiding this comment.
Fixed in 46a6f63 . Dropped both touchstart and mousedown for pointerdown.
| if (!accessToken || !refreshToken) { | ||
| throw new Error('Access token refresh failed.'); | ||
| } | ||
|
|
There was a problem hiding this comment.
[Important] Do we need a check that the user hasn’t logged out while awaiting the refreshed token?
divbzero
left a comment
There was a problem hiding this comment.
The latest 9 commits look solid 👍 just one typo from the design. Thank you @sricharan-varanasi!
| "sept": "September", | ||
| "serverConfiguration": "Server Configuration", | ||
| "serverStatusConfigured": "Server Status: Connected.", | ||
| "sessionTimeoutDescription": "You haven't done anything in this window for awhile. If you don't make a choice, we'll log you out in {{countdown}}.", |
There was a problem hiding this comment.
s/awhile/a while/ — I think this was a typo in the design.
There was a problem hiding this comment.
Fixed in cbef683 . Corrected in both app-en.json .
📝 Description
🔗 Jira Ticket M2-11052
🔗 Jira Ticket M2-11002
#2249 moved tokens into one shared, encrypted slot. But
react-secure-storagereads from a snapshot taken when the page loads, so a tab still can't see what another tab wrote.That leaves three problems:
Tabs now talk over a
BroadcastChannel.Changes include:
Changes include:
The main bug - a background tab logging out the tab you're actually using - is fixed by one line. The idle clock is shared now, so the timer just re-reads it instead of firing blind.
🪤 Peer Testing
Needs
enableSessionKeepAliveon locally,REACT_APP_IDLE_TIMEOUT_MIN=3,REACT_APP_REFRESH_LEAD_SEC=20.Use
chrome://discards(oredge://discards) → Freeze for the frozen-tab steps.Two tabs. Work in tab A past the idle timeout, leave tab B in the background
Expected outcome: neither logs out
Two tabs. Wait for a rotation, then click something in tab B
Expected outcome: one refresh call across the browser, and tab B doesn't 401
Log out in tab A
Expected outcome: tab B goes to the login page straight away
Edit something in the builder, then idle out
Expected outcome: soft-lock login, no unsaved-changes modal
Edit something in the builder, then log out from the account menu
Expected outcome: the save/discard prompt still appears
Freeze a tab, rotate a few times elsewhere, then focus it
Expected outcome: it catches up, no 401
Freeze tab B, log out in tab A, focus tab B
Expected outcome: tab B tears down on focus
Leave tab A on the login page, sign in on tab B
Expected outcome: tab A moves to the dashboard. Close tab B first and tab A reloads once instead
Flag off
Expected outcome: no channel traffic, no idle logout, same as feat: Move tokens to local storage (M2-11055) #2249 alone
✏️ Notes
session-storage-local(feat: Move tokens to local storage (M2-11055) #2249), which sits onsession-keep-alive(feat: keep session alive on Admin ( M2 - 11001 ) #2246). Merge in order.✅ Checklist
Functionality
Testing
Security & Data Privacy
Logging/Monitoring
Performance
Readability
Change Safety
Backend changes are backwards compatible with old clients, or it is well known they are not and a deployment/rollout plan is in place. This include backend changes being compatible with old mobile app versions, as well as applet versioning within Curious.Destructive database migrations are rolled out in stages. For example, renaming a column means adding a new column and migrating the existing data to that columns in one deployment. Then monitoring to ensure that field isn't used, and finally removing that old column in a separate deployment.