mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-18 00:03:11 +00:00
Self-hosted instances that embed their own UI (Windmill as backend, custom
app at the instance root) can't be redirected back to their app after OAuth
login: isValidLogoutRedirect rejects same-origin absolute URLs, so the login
callback falls back to a client-side goto('/') into Windmill's own dashboard.
Same-origin redirects are never open redirects (and toSameOriginRelativePath
already treats them as safe), so accept them.
Also make the test-setup window global configurable so vitest's stubGlobal
can redefine it across the window-stubbing test suites.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
import { get } from 'svelte/store'
|
|
import { hubBaseUrlStore } from './stores'
|
|
|
|
export function isValidLogoutRedirect(url: string): boolean {
|
|
if (url.startsWith('/') && !url.startsWith('//')) {
|
|
return true
|
|
}
|
|
try {
|
|
const parsed = new URL(url)
|
|
// Same-origin absolute URLs are always safe: the user can only be sent
|
|
// back to the site they're already on, which is never an open redirect.
|
|
// Mirrors toSameOriginRelativePath()'s same-origin handling, and lets a
|
|
// self-hosted instance redirect back to its own app after OAuth login.
|
|
if (typeof window !== 'undefined' && parsed.origin === window.location.origin) {
|
|
return true
|
|
}
|
|
const host = parsed.hostname
|
|
if (host === 'windmill.dev' || host.endsWith('.windmill.dev')) {
|
|
return true
|
|
}
|
|
const hubBaseUrl = get(hubBaseUrlStore)
|
|
try {
|
|
const hubHost = new URL(hubBaseUrl).hostname
|
|
if (host === hubHost) {
|
|
return true
|
|
}
|
|
} catch {}
|
|
} catch {}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Reduces a redirect target to a safe same-origin relative path.
|
|
*
|
|
* Returns the path (`/foo?bar#baz`) when the input is either:
|
|
* - already an absolute-but-relative path (`/foo`) that isn't protocol-relative
|
|
* (`//evil.com`) or a back-slash trick (`/\\evil.com`), or
|
|
* - a full URL whose origin matches the current page's origin.
|
|
*
|
|
* Returns `null` for anything else (cross-origin URLs, protocol-relative paths,
|
|
* malformed input). Used to sanitize values flowing into `RelayState`,
|
|
* `localStorage.rd`, and post-login redirects so we don't either lose a valid
|
|
* same-origin deep link or open-redirect into a foreign origin.
|
|
*/
|
|
export function toSameOriginRelativePath(rd: string | null | undefined): string | null {
|
|
if (!rd) return null
|
|
if (rd.length > 2048) return null
|
|
if (hasControlChar(rd)) return null
|
|
if (rd.startsWith('/')) {
|
|
if (rd.startsWith('//') || rd.startsWith('/\\')) return null
|
|
return rd
|
|
}
|
|
if (typeof window === 'undefined') return null
|
|
try {
|
|
const url = new URL(rd, window.location.origin)
|
|
if (url.origin !== window.location.origin) return null
|
|
const path = url.pathname + url.search + url.hash
|
|
return path || '/'
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function hasControlChar(s: string): boolean {
|
|
for (let i = 0; i < s.length; i++) {
|
|
const c = s.charCodeAt(i)
|
|
if (c < 0x20 || c === 0x7f) return true
|
|
}
|
|
return false
|
|
}
|