diff --git a/frontend/src/lib/components/ForkWorkspaceBanner.svelte b/frontend/src/lib/components/ForkWorkspaceBanner.svelte
index c37370bcb0..9ab0dfa744 100644
--- a/frontend/src/lib/components/ForkWorkspaceBanner.svelte
+++ b/frontend/src/lib/components/ForkWorkspaceBanner.svelte
@@ -1,5 +1,5 @@
-{#if isFork}
+{#if showBanner}
diff --git a/frontend/src/lib/stores.ts b/frontend/src/lib/stores.ts
index 3761e291dd..0058cc8dd0 100644
--- a/frontend/src/lib/stores.ts
+++ b/frontend/src/lib/stores.ts
@@ -17,6 +17,11 @@ import { DEFAULT_HUB_BASE_URL } from './hub'
import type { DbManagerUriState } from './components/dbManagerDrawerModel.svelte'
export interface UserExt {
+ // Workspace this membership was fetched for. `$workspaceStore` flips
+ // synchronously on a switch while the new `whoami` is still in flight, so a
+ // consumer whose behavior depends on the role must compare this against the
+ // active workspace rather than read a role that still describes the previous one.
+ workspace_id: string
email: string
name?: string
username: string
diff --git a/frontend/src/lib/user.ts b/frontend/src/lib/user.ts
index aaca893636..cbeb0ccccf 100644
--- a/frontend/src/lib/user.ts
+++ b/frontend/src/lib/user.ts
@@ -4,15 +4,16 @@ import type { UserExt } from './stores.js'
export async function getUserExt(workspace: string): Promise {
try {
const user = await UserService.whoami({ workspace })
- return mapUserToUserExt(user)
+ return mapUserToUserExt(user, workspace)
} catch (error) {
return undefined
}
}
-function mapUserToUserExt(user: User): UserExt {
+function mapUserToUserExt(user: User, workspace: string): UserExt {
const ext: UserExt = {
...user,
+ workspace_id: workspace,
groups: user.groups!,
pgroups: user.groups!.map((x) => `g/${x}`)
}
diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte
index a88184ed62..ecb7b6c39b 100644
--- a/frontend/src/routes/(root)/(logged)/+layout.svelte
+++ b/frontend/src/routes/(root)/(logged)/+layout.svelte
@@ -307,6 +307,13 @@
}
}
const user = await getUserExt(workspace)
+ // Every workspace change starts a fetch without cancelling the one before it,
+ // so a slow response can land after a faster one for the workspace the user
+ // has since moved to. The store must describe the active workspace: letting a
+ // superseded response write would leave every role gate reading the one we left.
+ if ($workspaceStore !== workspace) {
+ return
+ }
if (!deepEqual(user, $userStore)) {
userStore.set(user)
}
@@ -628,8 +635,15 @@
timeout = undefined
} else if (!u) {
timeout = setTimeout(async () => {
- if (!$userStore && $workspaceStore) {
- $userStore = await getUserExt($workspaceStore)
+ const ws = $workspaceStore
+ if (!$userStore && ws) {
+ const user = await getUserExt(ws)
+ // Recovers the workspace that was left without a role. A switch
+ // mid-flight has already started the fetch for the new one, so this
+ // answer describes the workspace we left.
+ if ($workspaceStore === ws) {
+ $userStore = user
+ }
}
}, 5000)
}
diff --git a/frontend/src/routes/(root)/+layout.svelte b/frontend/src/routes/(root)/+layout.svelte
index 6fbffda21d..1f7a813eac 100644
--- a/frontend/src/routes/(root)/+layout.svelte
+++ b/frontend/src/routes/(root)/+layout.svelte
@@ -130,10 +130,18 @@
if ($userStore) {
console.log(`Welcome back ${$userStore.username} to ${$workspaceStore}`)
} else {
- $userStore = await getUserExt($workspaceStore)
- if (!$userStore) {
+ const ws = $workspaceStore
+ const user = await getUserExt(ws)
+ // A switch mid-flight means this answers for the workspace we left, and
+ // that switch has already started the fetch answering for the active
+ // one: neither this role nor its failure describes where we are now.
+ if ($workspaceStore !== ws) {
+ return
+ }
+ if (!user) {
throw Error('Not logged in')
}
+ $userStore = user
}
} else {
if (
@@ -240,7 +248,11 @@
if (workspace && user) {
const newUser = await getUserExt(workspace)
- if (!deepEqual(newUser, $userStore)) {
+ // Refreshes the workspace that was active when the tick started; a
+ // switch mid-flight makes this answer describe the one we left.
+ if ($workspaceStore !== workspace) {
+ console.debug('workspace changed during user refresh, dropping')
+ } else if (!deepEqual(newUser, $userStore)) {
userStore.set(newUser)
console.info('refreshed user')
} else {