From b2bc5b93900495e7d2ec7e3ac13486c206787806 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 3 Jul 2026 03:30:00 +0000 Subject: [PATCH] Harden getSiteOrigin to use request headers (x-forwarded-host/host) and safe production fallback; remove duplicate getOrigin helpers in routes to prevent localhost:3003 redirect_uri after Google login --- app/api/auth/callback/google/route.ts | 18 +++--------------- app/api/auth/google/route.ts | 15 ++------------- infrastructure/auth/google-oauth.ts | 17 +++++++++-------- 3 files changed, 14 insertions(+), 36 deletions(-) diff --git a/app/api/auth/callback/google/route.ts b/app/api/auth/callback/google/route.ts index f85e51b..280f198 100644 --- a/app/api/auth/callback/google/route.ts +++ b/app/api/auth/callback/google/route.ts @@ -9,6 +9,7 @@ import { fetchGoogleUserFromCode, isGoogleOAuthConfigured, verifyOAuthState, + getSiteOrigin, } from '@/infrastructure/auth/google-oauth'; import { getOAuthReturnCookieName, @@ -20,7 +21,7 @@ export const dynamic = 'force-dynamic'; const OAUTH_STATE_COOKIE = 'shahi_google_oauth_state'; function redirectToLogin(error: string, returnTo?: string, req?: Request) { - const loginUrl = new URL('/login', getOrigin(req)); + const loginUrl = new URL('/login', getSiteOrigin(req)); loginUrl.searchParams.set('tab', 'customer'); loginUrl.searchParams.set('error', error); if (returnTo && returnTo !== '/account') { @@ -29,19 +30,6 @@ function redirectToLogin(error: string, returnTo?: string, req?: Request) { return NextResponse.redirect(loginUrl); } -function getOrigin(req?: Request): string { - if (req) { - try { - const url = new URL(req.url); - if (process.env.NODE_ENV === 'production' && url.protocol === 'http:') { - url.protocol = 'https:'; - } - return url.origin; - } catch {} - } - return process.env.NEXT_PUBLIC_SITE_URL ?? 'https://shahikitchen.se'; -} - export async function GET(request: Request) { if (!isGoogleOAuthConfigured()) { return redirectToLogin('google_not_configured', undefined, request); @@ -82,7 +70,7 @@ export async function GET(request: Request) { getCustomerSessionCookieOptions(), ); - const response = NextResponse.redirect(new URL(returnTo, getOrigin(request))); + const response = NextResponse.redirect(new URL(returnTo, getSiteOrigin(request))); response.headers.set('Cache-Control', 'no-store'); return response; } \ No newline at end of file diff --git a/app/api/auth/google/route.ts b/app/api/auth/google/route.ts index b5b2531..f6715ea 100644 --- a/app/api/auth/google/route.ts +++ b/app/api/auth/google/route.ts @@ -4,6 +4,7 @@ import { buildGoogleAuthUrl, createOAuthState, isGoogleOAuthConfigured, + getSiteOrigin, } from '@/infrastructure/auth/google-oauth'; import { getOAuthReturnCookieName, @@ -16,7 +17,7 @@ const OAUTH_STATE_COOKIE = 'shahi_google_oauth_state'; export async function GET(request: Request) { if (!isGoogleOAuthConfigured()) { - return NextResponse.redirect(new URL('/login?tab=customer&error=google_not_configured', getOrigin(request))); + return NextResponse.redirect(new URL('/login?tab=customer&error=google_not_configured', getSiteOrigin(request))); } const url = new URL(request.url); @@ -42,15 +43,3 @@ export async function GET(request: Request) { return NextResponse.redirect(buildGoogleAuthUrl(state, request)); } -function getOrigin(req?: Request): string { - if (req) { - try { - const url = new URL(req.url); - if (process.env.NODE_ENV === 'production' && url.protocol === 'http:') { - url.protocol = 'https:'; - } - return url.origin; - } catch {} - } - return process.env.NEXT_PUBLIC_SITE_URL ?? 'https://shahikitchen.se'; -} \ No newline at end of file diff --git a/infrastructure/auth/google-oauth.ts b/infrastructure/auth/google-oauth.ts index f032b1c..6928884 100644 --- a/infrastructure/auth/google-oauth.ts +++ b/infrastructure/auth/google-oauth.ts @@ -9,22 +9,23 @@ export function isGoogleOAuthConfigured(): boolean { } export function getSiteOrigin(req?: Request): string { + const configured = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://shahikitchen.se'; if (req) { try { const h = req.headers; - const host = h.get('x-forwarded-host') || h.get('host'); - let proto = h.get('x-forwarded-proto') || 'https'; - if (proto.includes(',')) proto = proto.split(',')[0].trim(); + let host = h.get('x-forwarded-host') || h.get('host'); if (host) { - // ensure https in prod - if (process.env.NODE_ENV === 'production' && proto === 'http') { - proto = 'https'; + host = host.split(':')[0]; // strip port + // Only use www variant if explicitly requested; otherwise always canonical production domain + // This prevents localhost, IP, or internal ports from leaking into redirect_uri + if (host === 'www.shahikitchen.se') { + return 'https://www.shahikitchen.se'; } - return `${proto}://${host}`; + return configured; } } catch {} } - return process.env.NEXT_PUBLIC_SITE_URL ?? 'https://shahikitchen.se'; + return configured; } export function getGoogleRedirectUri(req?: Request): string {