Skip to content

fix(web): don't strand a second account in the previous user's org - #5524

Open
pedrofrxncx wants to merge 1 commit into
mainfrom
claude/fix-no-access-redirect
Open

fix(web): don't strand a second account in the previous user's org#5524
pedrofrxncx wants to merge 1 commit into
mainfrom
claude/fix-no-access-redirect

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Problem

Signing in with a different account on a browser that already used Studio lands on No access instead of that account's own org:

studio.decocms.com/ferragens-floresta → "You don't have access to Ferragens Floresta."

studio:last-org-slug and studio:last-location are browser-global, but they describe one principal's history. A second account (or a session that expired and was replaced) inherits them, and cold entry (/) redirects straight into an org it can't access.

The gate's existing self-heal didn't catch it: it bounced back to / only when lastOrgSlug still matched the org, while the redirect that actually lands users there now comes from lastLocation (checked first in homeRoute) — and orgLayout.beforeLoad overwrites lastLocation with the current org on arrival, so it can't answer "did we send them here?" either.

Changes

  • Principal-scope the restore state. studio:last-user-id records who owns the two keys; the shell calls claimRestoreStateFor(userId) as soon as the session resolves and wipes them if it's somebody else. Same pattern as the already user-scoped org cache (readCachedOrg). Sign-out clears them too (clearPersistedQueryCache, whose contract is already "next user starts clean").
  • Fix the recovery. homeRoute marks its restore-driven redirects (per-tab sessionStorage); OrgAccessGate reads the marker. A restored org the user can't access → back to /, which now resolves their default org. A deliberate visit to a foreign org (shared link) still shows the screen with a working "Go to home".
  • Clear both keys, not just the slug, when landing on an archived org or deleting the org you're in — lastLocation pointing at either bounced the user right back.

Testing

  • apps/web/src/lib/last-location.test.ts — principal scoping, marker matching, and that a re-render gets the same marker answer (so StrictMode can't turn a bounce into the dead-end screen).
  • bun test apps/web/src/lib/ green (195 pass); full bun test apps/web has fewer failures than main in this environment (61 vs 68 — the pre-existing ones are unrelated).
  • bun run fmt, bun run lint (0 errors), bun run check pass.

The query-persist test's window stub moved to Object.defineProperty: happy-dom makes window.localStorage a readonly accessor, so the plain assignment threw whenever another test file registered happy-dom first.

Manual check

Sign in as A, visit an org only A can access, sign out, sign in as B → B lands on B's own org.


Summary by cubic

Fixes “No access” loops when signing in with a different account by scoping restore state to the current user and tracking restore-driven redirects. Also clears stale state on sign-out, org deletion, and archived orgs to avoid bouncing back.

  • Bug Fixes
    • Principal-scoped restore state with studio:last-user-id; claimRestoreStateFor(userId) clears stale lastLocation/lastOrgSlug as soon as the session resolves. Sign-out also clears it via clearPersistedQueryCache.
    • homeRoute marks restore redirects per-tab (markRestoreRedirect), and OrgAccessGate consumes them (consumeRestoreRedirect) to send users back to / only for restore-driven arrivals without access; direct shared links still show the gate.
    • Replaced ad-hoc slug clearing with clearRestoreState() when deleting an org or landing on an archived org.
    • Added tests for principal scoping and the per-tab marker (stable across re-reads); fixed window.localStorage stubbing using Object.defineProperty.

Written for commit 82eac49. Summary will update on new commits.

Review in cubic

@pedrofrxncx
pedrofrxncx enabled auto-merge (squash) July 31, 2026 19:39
@pedrofrxncx
pedrofrxncx disabled auto-merge July 31, 2026 19:44
`studio:last-org-slug` and `studio:last-location` are browser-global, so
signing in with a different account inherits the previous one's last org:
cold entry ("/") redirects there and dead-ends on "No access".

- Scope the restore state to a principal (`studio:last-user-id`): the shell
  drops it as soon as the session resolves as somebody else, so the next "/"
  resolves the new user's own default org. Also cleared on sign-out.
- Fix the gate's recovery. It bounced to "/" only when `lastOrgSlug` still
  matched the org, but the redirect that lands users there now comes from
  `lastLocation` — and `orgLayout.beforeLoad` overwrites that with the current
  org on arrival, so it can't answer "did we send them here?" either. The home
  loader now marks its restore redirects (per-tab sessionStorage) and the gate
  reads that marker, so a restored org the user can't access sends them home
  while a deliberate visit to a foreign org still shows the screen.
- Clear both keys (not just the slug) when landing on an archived org or
  deleting the current one — `lastLocation` pointing at either bounced back.

Unit tests for the principal scoping and the marker (including that a
re-render gets the same answer, so StrictMode can't turn a bounce into the
dead-end screen). The `query-persist` test's window stub had to move to
`defineProperty`: happy-dom makes `window.localStorage` readonly, so the
plain assignment threw whenever another file registered it first.
@pedrofrxncx
pedrofrxncx force-pushed the claude/fix-no-access-redirect branch from 76b79cb to 82eac49 Compare July 31, 2026 19:52
@pedrofrxncx
pedrofrxncx enabled auto-merge (squash) July 31, 2026 19:52
@viktormarinho

Copy link
Copy Markdown
Contributor

curious, instead of doing this:

 lastOrgSlug: () => `studio:last-org-slug`,
 lastLocation: () => `studio:last-location`,
 /** Principal that owns the two keys above (see claimRestoreStateFor). */
 lastUserId: () => `studio:last-user-id`,

Couldn't we just remove that "lastUserId" and key by user id the others making me keep caches for all accounts i have?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants