fix: keep the destination through a first-time signup

Someone who follows a shared hub project without an account signs up, and the
OAuth callback sends a first-time user to onboarding — dropping the `rd` it
had already read out of localStorage. They finish onboarding in an empty
workspace with no sign of what they came to import, and have to go back to the
hub and click again. That is the path this feature exists for.

The callback now passes `rd` on, and onboarding's two exits honour it instead
of hardcoding `/user/workspaces`. Same-origin relative paths only: `//host` is
a valid URL that leaves the origin while still starting with `/`, so the guard
rejects it rather than bouncing a fresh account off-site.

Nothing changes for a signup without `rd`, which is every existing one.

Gets the user to the wizard with the project in hand; they still pick a
destination on step 1. Having onboarding create the workspace and hand into
step 3 is the larger version, not done here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-08-21 18:05:04 +02:00
co-authored by Claude Opus 5
parent b8c785d0f8
commit eef410014d
2 changed files with 19 additions and 4 deletions
@@ -2,6 +2,7 @@
import { ArrowLeft } from 'lucide-svelte'
import { UserService } from '$lib/gen/services.gen'
import { goto } from '$lib/navigation'
import { page } from '$app/state'
import CenteredModal from '$lib/components/CenteredModal.svelte'
import { Button } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
@@ -16,7 +17,7 @@
Building2,
Twitter,
Youtube,
Bot,
Bot,
MessageCircleCode
} from 'lucide-svelte'
import { sendUserToast } from '$lib/toast'
@@ -96,10 +97,21 @@
sendUserToast('Failed to save information: ' + (error?.body || error?.message || error), true)
} finally {
// do not block users from accessing windmill even if there is an error
goto('/user/workspaces')
goto(onboardingDestination())
}
}
/**
* Where to go once onboarding is done. `/user/workspaces` unless the sign-in carried a
* destination — a hub project import, say — in which case that is what the user came for.
* Same-origin relative paths only, so a crafted `?rd=` cannot bounce them off-site.
*/
function onboardingDestination(): string {
const rd = page.url.searchParams.get('rd')
if (rd && rd.startsWith('/') && !rd.startsWith('//')) return rd
return '/user/workspaces'
}
async function skip() {
isSubmitting = true
try {
@@ -110,7 +122,7 @@
console.error('Error skipping onboarding:', error)
} finally {
// do not block users from accessing windmill even if there is an error
goto('/user/workspaces')
goto(onboardingDestination())
}
}
</script>
@@ -64,7 +64,10 @@
try {
const globalUserInfo = await UserService.globalWhoami()
if (globalUserInfo.first_time_user) {
goto('/user/onboarding')
// `rd` rides along: someone arriving from a shared hub project signs up with a
// destination already in hand, and dropping it here strands them in an empty
// workspace with no sign of what they came to import.
goto(`/user/onboarding${rd ? `?rd=${encodeURIComponent(rd)}` : ''}`)
return
}
} catch (err) {