From 8120a09a0c71efdeecf86f73fc067d8131b63cc7 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 3 Jul 2026 03:06:44 +0000 Subject: [PATCH] Make Google OAuth redirect_uri dynamic based on request origin to support both shahikitchen.se and www.shahikitchen.se --- app/api/auth/callback/google/route.ts | 27 ++++++++++++++++++--------- app/api/auth/google/route.ts | 15 ++++++++++++--- infrastructure/auth/google-oauth.ts | 23 +++++++++++++++++------ 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/app/api/auth/callback/google/route.ts b/app/api/auth/callback/google/route.ts index a8e6d41..7084e85 100644 --- a/app/api/auth/callback/google/route.ts +++ b/app/api/auth/callback/google/route.ts @@ -19,8 +19,8 @@ export const dynamic = 'force-dynamic'; const OAUTH_STATE_COOKIE = 'shahi_google_oauth_state'; -function redirectToLogin(error: string, returnTo?: string) { - const loginUrl = new URL('/login', getOrigin()); +function redirectToLogin(error: string, returnTo?: string, req?: Request) { + const loginUrl = new URL('/login', getOrigin(req)); loginUrl.searchParams.set('tab', 'customer'); loginUrl.searchParams.set('error', error); if (returnTo && returnTo !== '/account') { @@ -29,13 +29,22 @@ function redirectToLogin(error: string, returnTo?: string) { return NextResponse.redirect(loginUrl); } -function getOrigin(): string { +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 ?? 'http://localhost:3000'; } export async function GET(request: Request) { if (!isGoogleOAuthConfigured()) { - return redirectToLogin('google_not_configured'); + return redirectToLogin('google_not_configured', undefined, request); } const url = new URL(request.url); @@ -48,21 +57,21 @@ export async function GET(request: Request) { cookieStore.delete(getOAuthReturnCookieName()); if (oauthError) { - return redirectToLogin('google_denied', returnTo); + return redirectToLogin('google_denied', returnTo, request); } const storedState = cookieStore.get(OAUTH_STATE_COOKIE)?.value; cookieStore.delete(OAUTH_STATE_COOKIE); if (!verifyOAuthState(state) || state !== storedState) { - return redirectToLogin('invalid_state', returnTo); + return redirectToLogin('invalid_state', returnTo, request); } if (!code) { - return redirectToLogin('missing_code', returnTo); + return redirectToLogin('missing_code', returnTo, request); } - const user = await fetchGoogleUserFromCode(code); + const user = await fetchGoogleUserFromCode(code, request); if (!user) { return redirectToLogin('google_failed', returnTo); } @@ -73,7 +82,7 @@ export async function GET(request: Request) { getCustomerSessionCookieOptions(), ); - const response = NextResponse.redirect(new URL(returnTo, getOrigin())); + const response = NextResponse.redirect(new URL(returnTo, getOrigin(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 31c8e9a..f2384e6 100644 --- a/app/api/auth/google/route.ts +++ b/app/api/auth/google/route.ts @@ -16,7 +16,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())); + return NextResponse.redirect(new URL('/login?tab=customer&error=google_not_configured', getOrigin(request))); } const url = new URL(request.url); @@ -39,9 +39,18 @@ export async function GET(request: Request) { maxAge: 600, }); - return NextResponse.redirect(buildGoogleAuthUrl(state)); + return NextResponse.redirect(buildGoogleAuthUrl(state, request)); } -function getOrigin(): string { +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 ?? 'http://localhost:3000'; } \ No newline at end of file diff --git a/infrastructure/auth/google-oauth.ts b/infrastructure/auth/google-oauth.ts index 2cfcb42..7390815 100644 --- a/infrastructure/auth/google-oauth.ts +++ b/infrastructure/auth/google-oauth.ts @@ -8,12 +8,22 @@ export function isGoogleOAuthConfigured(): boolean { return Boolean(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET); } -export function getSiteOrigin(): string { +export function getSiteOrigin(req?: Request): string { + if (req) { + try { + const url = new URL(req.url); + // In production behind nginx, prefer https even if internal request is http + if (process.env.NODE_ENV === 'production' && url.protocol === 'http:') { + url.protocol = 'https:'; + } + return url.origin; + } catch {} + } return process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'; } -export function getGoogleRedirectUri(): string { - return `${getSiteOrigin()}/api/auth/callback/google`; +export function getGoogleRedirectUri(req?: Request): string { + return `${getSiteOrigin(req)}/api/auth/callback/google`; } function getOAuthStateSecret(): string { @@ -38,10 +48,10 @@ export function verifyOAuthState(state: string | null | undefined): boolean { return timingSafeEqual(sigBuffer, expectedBuffer); } -export function buildGoogleAuthUrl(state: string): string { +export function buildGoogleAuthUrl(state: string, req?: Request): string { const params = new URLSearchParams({ client_id: process.env.GOOGLE_CLIENT_ID!, - redirect_uri: getGoogleRedirectUri(), + redirect_uri: getGoogleRedirectUri(req), response_type: 'code', scope: 'openid email profile', access_type: 'online', @@ -65,6 +75,7 @@ interface GoogleUserInfo { export async function fetchGoogleUserFromCode( code: string, + req?: Request, ): Promise<{ email: string; name?: string } | null> { const tokenResponse = await fetch(GOOGLE_TOKEN_URL, { method: 'POST', @@ -73,7 +84,7 @@ export async function fetchGoogleUserFromCode( code, client_id: process.env.GOOGLE_CLIENT_ID!, client_secret: process.env.GOOGLE_CLIENT_SECRET!, - redirect_uri: getGoogleRedirectUri(), + redirect_uri: getGoogleRedirectUri(req), grant_type: 'authorization_code', }), });