diff --git a/apps/web/app/(auth)/login/new/page.tsx b/apps/web/app/(auth)/login/new/page.tsx index 82d0fd48..6272cd96 100644 --- a/apps/web/app/(auth)/login/new/page.tsx +++ b/apps/web/app/(auth)/login/new/page.tsx @@ -17,6 +17,7 @@ import { motion } from "motion/react" import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { Logo } from "@ui/assets/Logo" +import { resolveAuthRedirectUrl } from "@/lib/url-helpers" function isMcpOAuthAuthorizeContext(sp: Pick): boolean { return sp.get("response_type") === "code" && Boolean(sp.get("client_id")) @@ -113,9 +114,25 @@ export default function LoginPage() { if (sessionPending) return if (!sessionData?.session) return const sp = new URLSearchParams(oauthQueryForResume) - if (!isMcpOAuthAuthorizeContext(sp)) return - window.location.assign(buildMcpAuthorizeResumeUrl(sp)) - }, [sessionPending, sessionData?.session, oauthQueryForResume]) + if (isMcpOAuthAuthorizeContext(sp)) { + window.location.assign(buildMcpAuthorizeResumeUrl(sp)) + return + } + const redirectUrl = params.get("redirect") + if (redirectUrl) { + window.location.assign( + resolveAuthRedirectUrl(redirectUrl, window.location.origin).toString(), + ) + return + } + router.replace("/") + }, [ + sessionPending, + sessionData?.session, + oauthQueryForResume, + params, + router, + ]) // Get redirect URL from query params const redirectUrl = params.get("redirect") @@ -128,17 +145,7 @@ export default function LoginPage() { return buildMcpAuthorizeResumeUrl(params) } - let finalUrl: URL - - if (redirectUrl) { - try { - finalUrl = new URL(redirectUrl, origin) - } catch { - finalUrl = new URL(origin) - } - } else { - finalUrl = new URL(origin) - } + const finalUrl = resolveAuthRedirectUrl(redirectUrl, origin) finalUrl.searchParams.set("extension-auth-success", "true") return finalUrl.toString() diff --git a/apps/web/lib/url-helpers.ts b/apps/web/lib/url-helpers.ts index 832b8722..cfd57209 100644 --- a/apps/web/lib/url-helpers.ts +++ b/apps/web/lib/url-helpers.ts @@ -1,3 +1,47 @@ +const PROXY_LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]) + +/** Reconstruct the browser-facing URL when running behind portless (or similar). */ +export function getPublicRequestUrl(request: Request): URL { + const internal = new URL(request.url) + const forwardedHost = request.headers + .get("x-forwarded-host") + ?.split(",")[0] + ?.trim() + if (forwardedHost) { + const proto = request.headers.get("x-forwarded-proto") || "https" + return new URL( + `${proto}://${forwardedHost}${internal.pathname}${internal.search}`, + ) + } + const portlessUrl = process.env.PORTLESS_URL + if (portlessUrl) { + try { + const base = new URL(portlessUrl) + return new URL(`${base.origin}${internal.pathname}${internal.search}`) + } catch {} + } + return internal +} + +/** Map portless proxy localhost redirects back to the current public origin. */ +export function resolveAuthRedirectUrl( + redirectUrl: string | null, + origin: string, +): URL { + const fallback = new URL(origin) + if (!redirectUrl) return fallback + try { + const target = new URL(redirectUrl) + if (PROXY_LOCAL_HOSTS.has(target.hostname)) { + return new URL(`${target.pathname}${target.search}`, origin) + } + if (target.origin === origin) return target + return fallback + } catch { + return fallback + } +} + /** * Validates if a string is a valid URL. */ diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 0473e315..4715c571 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -1,14 +1,22 @@ import { getSessionCookie } from "better-auth/cookies" import { NextResponse } from "next/server" +import { getPublicRequestUrl } from "@/lib/url-helpers" + +function getAuthSessionCookie(request: Request): string | null { + return ( + getSessionCookie(request) ?? + getSessionCookie(request, { cookiePrefix: "better-auth-dev" }) + ) +} export default async function proxy(request: Request) { console.debug("[PROXY] === PROXY START ===") - const url = new URL(request.url) + const url = getPublicRequestUrl(request) console.debug("[PROXY] Path:", url.pathname) console.debug("[PROXY] Method:", request.method) - const sessionCookie = getSessionCookie(request) + const sessionCookie = getAuthSessionCookie(request) console.debug("[PROXY] Session cookie exists:", !!sessionCookie) // Always allow access to login and waitlist pages @@ -40,9 +48,9 @@ export default async function proxy(request: Request) { console.debug( "[PROXY] No session cookie and not on public path, redirecting to /login", ) - const url = new URL("/login", request.url) - url.searchParams.set("redirect", request.url) - return NextResponse.redirect(url) + const loginUrl = new URL("/login", url.origin) + loginUrl.searchParams.set("redirect", url.toString()) + return NextResponse.redirect(loginUrl) } // TEMPORARILY DISABLED: Waitlist check