mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-03 08:02:19 +00:00
fix(frontend): keep the session when the persisted workspace is stale (#11344)
* fix(frontend): keep the session when the persisted workspace is stale A single-use login link signs a different account in while `workspace` in session/localStorage still names the previous account's workspace. `loadUser` read that workspace, got no membership back, threw `Not logged in` and logged the brand-new session out, landing on `/user/login?rd=...`. A missing membership says nothing about the session, so forget the workspace and continue down the no-workspace path, which logs out only when `globalWhoami` shows the session itself is gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(frontend): confirm the session before the workspace-picker redirect `loadWithoutWorkspace` fired the `/user/workspaces?rd=…` navigation before awaiting `globalWhoami`, so when the session turned out to be gone the logout read whichever URL the race had left in `page.url` and carried the picker as its `rd`. Ask first, then redirect. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(frontend): stop the loadUser comments overclaiming what they know Neither comment can promise what it stated: `getUserExt` collapses every failure into `undefined`, so the branch cannot tell a real non-membership from a transient one, and `loadWithoutWorkspace` throws on any `globalWhoami` rejection rather than only on a dead session. Say what each call actually answers about. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e8c3f9514e
commit
2e5f6d0e76
@@ -107,12 +107,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
clearWorkspaceFromStorage()
|
||||
} catch (e) {
|
||||
console.error('Could not clear workspace storage during deleted-workspace recovery', e)
|
||||
}
|
||||
workspaceStore.set(undefined)
|
||||
forgetWorkspace()
|
||||
sendUserToast(
|
||||
`Workspace ${workspaceId} is no longer available, please pick a workspace.`,
|
||||
'warning'
|
||||
@@ -121,6 +116,39 @@
|
||||
return true
|
||||
}
|
||||
|
||||
// Storage is what a reload reads the workspace back from, so dropping only the store
|
||||
// leaves the same dead id waiting for the next load (see getWorkspaceFromStorage).
|
||||
function forgetWorkspace() {
|
||||
try {
|
||||
clearWorkspaceFromStorage()
|
||||
} catch (e) {
|
||||
console.error('Could not clear workspace storage', e)
|
||||
}
|
||||
workspaceStore.set(undefined)
|
||||
}
|
||||
|
||||
// Throws on any `globalWhoami` rejection, which is the caller's cue to log out. The picker
|
||||
// redirect waits on that answer: navigating first leaves the logout's `rd` pointing at the
|
||||
// picker rather than at where the user was headed.
|
||||
async function loadWithoutWorkspace() {
|
||||
let user = await UserService.globalWhoami()
|
||||
noteSessionEmail(user.email)
|
||||
console.log(`Welcome back ${user.email}`)
|
||||
if (
|
||||
(!page.url.pathname.startsWith('/user/') || page.url.pathname.startsWith('/user/cli')) &&
|
||||
// The MCP consent page carries its own workspace picker, so it is left to
|
||||
// run without one. Nothing sets `$userStore` on this branch, which is why
|
||||
// that page must stay outside the (logged) layout — see its `@(root)` name.
|
||||
!page.url.pathname.startsWith(`${base}/oauth/mcp_authorize`) &&
|
||||
// The hub import wizard asks for the destination itself, and may end in a
|
||||
// workspace that does not exist yet — bouncing it to the picker would
|
||||
// force the very choice it exists to make.
|
||||
!page.url.pathname.startsWith(`${base}/projects/import`)
|
||||
) {
|
||||
goto(`/user/workspaces?rd=${encodeURIComponent(page.url.href.replace(page.url.origin, ''))}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUser() {
|
||||
try {
|
||||
await refreshSuperadmin()
|
||||
@@ -141,29 +169,18 @@
|
||||
return
|
||||
}
|
||||
if (!user) {
|
||||
throw Error('Not logged in')
|
||||
// The persisted workspace outlives the session that chose it: a login link
|
||||
// signs a different account in while storage still names a workspace that
|
||||
// account is not a member of. This lookup answers about the workspace, never
|
||||
// about the session, so throwing here would log the new session out blind.
|
||||
forgetWorkspace()
|
||||
await loadWithoutWorkspace()
|
||||
return
|
||||
}
|
||||
$userStore = user
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(!page.url.pathname.startsWith('/user/') || page.url.pathname.startsWith('/user/cli')) &&
|
||||
// The MCP consent page carries its own workspace picker, so it is left to
|
||||
// run without one. Nothing sets `$userStore` on this branch, which is why
|
||||
// that page must stay outside the (logged) layout — see its `@(root)` name.
|
||||
!page.url.pathname.startsWith(`${base}/oauth/mcp_authorize`) &&
|
||||
// The hub import wizard asks for the destination itself, and may end in a
|
||||
// workspace that does not exist yet — bouncing it to the picker would
|
||||
// force the very choice it exists to make.
|
||||
!page.url.pathname.startsWith(`${base}/projects/import`)
|
||||
) {
|
||||
goto(
|
||||
`/user/workspaces?rd=${encodeURIComponent(page.url.href.replace(page.url.origin, ''))}`
|
||||
)
|
||||
}
|
||||
let user = await UserService.globalWhoami()
|
||||
noteSessionEmail(user.email)
|
||||
console.log(`Welcome back ${user.email}`)
|
||||
await loadWithoutWorkspace()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
Reference in New Issue
Block a user