Merge pull request #1541 from Molecule-AI/fix/auth-redirect-loop
fix(auth): break infinite redirect loop on /cp/auth/login
This commit is contained in:
commit
26c4565308
@ -29,6 +29,11 @@ export function AuthGate({ children }: { children: ReactNode }) {
|
||||
setState({ kind: "anonymous", skipRedirect: true });
|
||||
return;
|
||||
}
|
||||
// Never gate /cp/auth/* paths — these ARE the login pages.
|
||||
if (typeof window !== "undefined" && window.location.pathname.startsWith("/cp/auth/")) {
|
||||
setState({ kind: "anonymous", skipRedirect: true });
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
fetchSession()
|
||||
.then((s) => {
|
||||
|
||||
@ -47,7 +47,12 @@ describe("redirectToLogin", () => {
|
||||
const href = "https://acme.moleculesai.app/dashboard";
|
||||
Object.defineProperty(window, "location", {
|
||||
writable: true,
|
||||
value: { href },
|
||||
value: {
|
||||
href,
|
||||
pathname: "/dashboard",
|
||||
hostname: "acme.moleculesai.app",
|
||||
protocol: "https:",
|
||||
},
|
||||
});
|
||||
redirectToLogin("sign-in");
|
||||
// href now holds the redirect target. encodeURIComponent(href) must
|
||||
@ -61,7 +66,12 @@ describe("redirectToLogin", () => {
|
||||
it("uses signup path for sign-up screenHint", () => {
|
||||
Object.defineProperty(window, "location", {
|
||||
writable: true,
|
||||
value: { href: "https://acme.moleculesai.app/" },
|
||||
value: {
|
||||
href: "https://acme.moleculesai.app/",
|
||||
pathname: "/",
|
||||
hostname: "acme.moleculesai.app",
|
||||
protocol: "https:",
|
||||
},
|
||||
});
|
||||
redirectToLogin("sign-up");
|
||||
expect((window.location as unknown as { href: string }).href).toContain("/cp/auth/signup");
|
||||
|
||||
@ -38,6 +38,13 @@ async function request<T>(
|
||||
credentials: "include",
|
||||
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS),
|
||||
});
|
||||
if (res.status === 401) {
|
||||
// Session expired or credentials lost — redirect to login once.
|
||||
// Import dynamically to avoid circular dependency with auth.ts.
|
||||
const { redirectToLogin } = await import("./auth");
|
||||
redirectToLogin("sign-in");
|
||||
throw new Error("Session expired — redirecting to login");
|
||||
}
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`API ${method} ${path}: ${res.status} ${text}`);
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
* can surface them.
|
||||
*/
|
||||
import { PLATFORM_URL } from "./api";
|
||||
import { SaaSHostSuffix } from "./tenant";
|
||||
|
||||
export interface Session {
|
||||
user_id: string;
|
||||
@ -17,6 +18,18 @@ export interface Session {
|
||||
// Base path prefix for auth endpoints on the control plane.
|
||||
const AUTH_BASE = "/cp/auth";
|
||||
|
||||
// Auth UI lives on the "app" subdomain (app.moleculesai.app), NOT on
|
||||
// tenant subdomains (hongmingwang.moleculesai.app). Tenant subdomains
|
||||
// proxy to EC2 platform which has no auth routes.
|
||||
function getAuthOrigin(): string {
|
||||
if (typeof window === "undefined") return PLATFORM_URL;
|
||||
const host = window.location.hostname;
|
||||
if (host.endsWith(SaaSHostSuffix)) {
|
||||
return `${window.location.protocol}//app${SaaSHostSuffix}`;
|
||||
}
|
||||
return PLATFORM_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetchSession probes /cp/auth/me with the session cookie (credentials:
|
||||
* include mandatory cross-origin). Returns the Session on 200, null on
|
||||
@ -44,8 +57,13 @@ export async function fetchSession(): Promise<Session | null> {
|
||||
*/
|
||||
export function redirectToLogin(screenHint: "sign-up" | "sign-in" = "sign-in"): void {
|
||||
if (typeof window === "undefined") return;
|
||||
// Guard against infinite redirect loop: if we're already on the login
|
||||
// page, don't redirect again (each redirect double-encodes return_to
|
||||
// until the URL exceeds header limits → 431).
|
||||
if (window.location.pathname.startsWith("/cp/auth/")) return;
|
||||
const returnTo = window.location.href;
|
||||
const path = screenHint === "sign-up" ? "signup" : "login";
|
||||
const dest = `${PLATFORM_URL}${AUTH_BASE}/${path}?return_to=${encodeURIComponent(returnTo)}`;
|
||||
const authOrigin = getAuthOrigin();
|
||||
const dest = `${authOrigin}${AUTH_BASE}/${path}?return_to=${encodeURIComponent(returnTo)}`;
|
||||
window.location.href = dest;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user