diff --git a/ui/homepage/app/login/LoginContent.tsx b/ui/homepage/app/login/LoginContent.tsx index 51b60e1..3a970d4 100644 --- a/ui/homepage/app/login/LoginContent.tsx +++ b/ui/homepage/app/login/LoginContent.tsx @@ -61,6 +61,47 @@ export default function LoginContent({ children }: LoginContentProps) { const loginUrlRef = useRef(loginUrl) + const deriveSameOriginLoginFallback = useCallback((url: string): string | undefined => { + if (typeof window === 'undefined') { + return undefined + } + + try { + const currentOrigin = window.location.origin + const parsed = new URL(url, currentOrigin) + + if (parsed.origin === currentOrigin) { + const relative = `${parsed.pathname}${parsed.search}${parsed.hash}` || '/api/auth/login' + return relative + } + + const localHostnames = new Set(['localhost', '127.0.0.1', '[::1]']) + const parsedHostname = parsed.hostname.toLowerCase() + const browserHostname = window.location.hostname.toLowerCase() + + const parsedIsLocal = localHostnames.has(parsedHostname) + const browserIsLocal = localHostnames.has(browserHostname) + + if (!browserIsLocal && parsedIsLocal) { + const relative = `${parsed.pathname}${parsed.search}${parsed.hash}` || '/api/auth/login' + return relative + } + + if ( + window.location.protocol === 'https:' && + parsed.protocol === 'http:' && + parsedHostname === browserHostname + ) { + parsed.protocol = 'https:' + return parsed.toString() + } + } catch (error) { + console.warn('Failed to derive same-origin login fallback', error) + } + + return undefined + }, []) + useEffect(() => { loginUrlRef.current = loginUrl }, [loginUrl]) @@ -148,20 +189,32 @@ export default function LoginContent({ children }: LoginContentProps) { try { response = await fetch(usedUrl, requestPayload) } catch (primaryError) { - const httpsPattern = /^https:/i - if (httpsPattern.test(usedUrl)) { - const insecureUrl = usedUrl.replace(httpsPattern, 'http:') - + const sameOriginFallback = deriveSameOriginLoginFallback(usedUrl) + if (sameOriginFallback && sameOriginFallback !== usedUrl) { try { - response = await fetch(insecureUrl, requestPayload) - loginUrlRef.current = insecureUrl - usedUrl = insecureUrl + response = await fetch(sameOriginFallback, requestPayload) + loginUrlRef.current = sameOriginFallback + usedUrl = sameOriginFallback } catch (fallbackError) { - console.error('Primary login request failed, insecure fallback also failed', fallbackError) + console.error('Primary login request failed, same-origin fallback also failed', fallbackError) throw fallbackError } } else { - throw primaryError + const httpsPattern = /^https:/i + if (httpsPattern.test(usedUrl)) { + const insecureUrl = usedUrl.replace(httpsPattern, 'http:') + + try { + response = await fetch(insecureUrl, requestPayload) + loginUrlRef.current = insecureUrl + usedUrl = insecureUrl + } catch (fallbackError) { + console.error('Primary login request failed, insecure fallback also failed', fallbackError) + throw fallbackError + } + } else { + throw primaryError + } } } diff --git a/ui/homepage/app/register/RegisterContent.tsx b/ui/homepage/app/register/RegisterContent.tsx index 2fccdd2..4449a9a 100644 --- a/ui/homepage/app/register/RegisterContent.tsx +++ b/ui/homepage/app/register/RegisterContent.tsx @@ -118,6 +118,26 @@ function coerceRegisterUrlOverride(rawValue: string | undefined | null, accountS } } +function deriveSameOriginFallback(url: string): string | undefined { + if (typeof window === 'undefined') { + return undefined + } + + try { + const currentOrigin = window.location.origin + const parsed = new URL(url, currentOrigin) + const preferred = preferSameOrigin(ensureHttpsForSameHost(parsed.toString())) + + if (preferred !== url) { + return preferred + } + } catch (error) { + console.warn('Failed to derive same-origin fallback for register URL', error) + } + + return undefined +} + export default function RegisterContent() { const { language } = useLanguage() const t = translations[language].auth.register @@ -257,20 +277,32 @@ export default function RegisterContent() { try { response = await fetch(usedUrl, requestPayload) } catch (primaryError) { - const httpsPattern = /^https:/i - if (httpsPattern.test(usedUrl)) { - const insecureUrl = usedUrl.replace(httpsPattern, 'http:') - + const sameOriginFallback = deriveSameOriginFallback(usedUrl) + if (sameOriginFallback && sameOriginFallback !== usedUrl) { try { - response = await fetch(insecureUrl, requestPayload) - registerUrlRef.current = insecureUrl - usedUrl = insecureUrl + response = await fetch(sameOriginFallback, requestPayload) + registerUrlRef.current = sameOriginFallback + usedUrl = sameOriginFallback } catch (fallbackError) { - console.error('Primary register request failed, insecure fallback also failed', fallbackError) + console.error('Primary register request failed, same-origin fallback also failed', fallbackError) throw fallbackError } } else { - throw primaryError + const httpsPattern = /^https:/i + if (httpsPattern.test(usedUrl)) { + const insecureUrl = usedUrl.replace(httpsPattern, 'http:') + + try { + response = await fetch(insecureUrl, requestPayload) + registerUrlRef.current = insecureUrl + usedUrl = insecureUrl + } catch (fallbackError) { + console.error('Primary register request failed, insecure fallback also failed', fallbackError) + throw fallbackError + } + } else { + throw primaryError + } } }