Fix dashboard loading bug

This commit is contained in:
Konstantin Wohlwend 2026-03-29 12:49:40 -07:00
parent 71e6562e6f
commit ea62e70f44
3 changed files with 70 additions and 7 deletions

View File

@ -8,7 +8,7 @@ import { useMemo } from 'react';
import { SignIn, SignUp, StackServerApp } from "..";
import { useStackApp } from "../lib/hooks";
import { HandlerUrls, StackClientApp, stackAppInternalsSymbol } from "../lib/stack-app";
import { resolveUnknownHandlerPathFallbackUrl } from "../lib/stack-app/url-targets";
import { isLocalHandlerUrlTarget, resolveUnknownHandlerPathFallbackUrl } from "../lib/stack-app/url-targets";
import { AccountSettings } from "./account-settings";
import { CliAuthConfirmation } from "./cli-auth-confirm";
import { EmailVerification } from "./email-verification";
@ -260,11 +260,11 @@ export function StackHandlerClient(props: BaseHandlerProps & Partial<RouteProps>
if (isCrossDomainLocalOauthCallback) {
return;
}
const urlObject = new URL(url, placeholderOrigin);
const isHandlerPathTarget = urlObject.pathname === handlerPath || urlObject.pathname.startsWith(`${handlerPath}/`);
const isLocalHandlerTarget = typeof window === "undefined"
? isHandlerPathTarget
: urlObject.origin === window.location.origin && isHandlerPathTarget;
const isLocalHandlerTarget = isLocalHandlerUrlTarget({
targetUrl: url,
handlerPath,
currentOrigin: typeof window === "undefined" ? undefined : window.location.origin,
});
if (isLocalHandlerTarget) {
return;
}

View File

@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { resolveHandlerUrls, resolveUnknownHandlerPathFallbackUrl } from "./url-targets";
import { isLocalHandlerUrlTarget, resolveHandlerUrls, resolveUnknownHandlerPathFallbackUrl } from "./url-targets";
describe("handler URL targets", () => {
afterEach(() => {
@ -112,3 +112,37 @@ describe("handler URL targets", () => {
})).toThrowError(/\{projectId\} and \{hostedPath\}/);
});
});
describe("isLocalHandlerUrlTarget", () => {
it("treats relative handler URLs as local targets", () => {
expect(isLocalHandlerUrlTarget({
targetUrl: "/handler/sign-in",
handlerPath: "/handler",
currentOrigin: "http://p91.localhost:9101",
})).toBe(true);
});
it("treats same-origin absolute handler URLs as local targets", () => {
expect(isLocalHandlerUrlTarget({
targetUrl: "http://p91.localhost:9101/handler/sign-in",
handlerPath: "/handler",
currentOrigin: "http://p91.localhost:9101",
})).toBe(true);
});
it("treats cross-origin absolute handler URLs as non-local targets", () => {
expect(isLocalHandlerUrlTarget({
targetUrl: "https://project-id.built-with-stack-auth.com/handler/sign-in",
handlerPath: "/handler",
currentOrigin: "http://p91.localhost:9101",
})).toBe(false);
});
it("treats non-handler paths as non-local targets", () => {
expect(isLocalHandlerUrlTarget({
targetUrl: "/projects",
handlerPath: "/handler",
currentOrigin: "http://p91.localhost:9101",
})).toBe(false);
});
});

View File

@ -6,6 +6,8 @@ import { DefaultHandlerUrlTarget, HandlerPageUrls, HandlerUrlOptions, HandlerUrl
const defaultHostedHandlerDomainSuffix = ".built-with-stack-auth.com";
const hostedHandlerProjectIdPlaceholder = "{projectId}";
const hostedHandlerPathPlaceholder = "{hostedPath}";
const localUrlPlaceholderOrigin = "http://example.com";
const schemePrefixRegex = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
type CustomPagePrompt = {
title: string,
@ -216,6 +218,33 @@ export const getHostedHandlerUrl = (options: { projectId: string, pagePath: stri
return new URL(templateFilled).toString();
};
const isRelativeUrlString = (url: string): boolean => {
if (url.startsWith("//")) {
return false;
}
return !schemePrefixRegex.test(url);
};
export const isLocalHandlerUrlTarget = (options: {
targetUrl: string,
handlerPath: string,
currentOrigin?: string,
}): boolean => {
const urlObject = new URL(options.targetUrl, localUrlPlaceholderOrigin);
const isHandlerPathTarget = urlObject.pathname === options.handlerPath
|| urlObject.pathname.startsWith(`${options.handlerPath}/`);
if (!isHandlerPathTarget) {
return false;
}
// On server we only have path information, so treat matching handler paths as local.
if (options.currentOrigin == null) {
return true;
}
return isRelativeUrlString(options.targetUrl) || urlObject.origin === options.currentOrigin;
};
const resolveUrlTarget = (options: {
target: HandlerUrlTarget,
fallbackPath: string,