Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 21 additions & 14 deletions apps/web/app/(auth)/login/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<URLSearchParams, "get">): boolean {
return sp.get("response_type") === "code" && Boolean(sp.get("client_id"))
Expand Down Expand Up @@ -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")
Expand All @@ -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()
Expand Down
44 changes: 44 additions & 0 deletions apps/web/lib/url-helpers.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
Expand Down
18 changes: 13 additions & 5 deletions apps/web/middleware.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading