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
18 changes: 10 additions & 8 deletions app/oauth/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export async function GET(request: NextRequest) {
const state = searchParams.get("state");
const error = searchParams.get("error");
const errorDescription = searchParams.get("error_description");
// Use configured base URL when behind a reverse proxy, falling back to request origin for local dev.
const baseUrl = process.env.APP_BASE_URL ?? request.nextUrl.origin;

// Handle OAuth errors
if (error) {
Expand All @@ -27,14 +29,14 @@ export async function GET(request: NextRequest) {
errorDescription,
});
return NextResponse.redirect(
new URL(`/login?error=${encodeURIComponent(errorDescription || error)}`, request.url),
new URL(`/login?error=${encodeURIComponent(errorDescription || error)}`, baseUrl),
);
}

if (!code || !state) {
logger.error("Missing code or state in OAuth callback");
return NextResponse.redirect(
new URL("/login?error=Missing+authorization+code", request.url),
new URL("/login?error=Missing+authorization+code", baseUrl),
);
}

Expand All @@ -51,20 +53,20 @@ export async function GET(request: NextRequest) {
storedState: storedState ? "[present]" : "[missing]",
});
return NextResponse.redirect(
new URL("/login?error=Invalid+state", request.url),
new URL("/login?error=Invalid+state", baseUrl),
);
}

if (!codeVerifier) {
logger.error("Missing code verifier in OAuth callback");
return NextResponse.redirect(
new URL("/login?error=Missing+code+verifier", request.url),
new URL("/login?error=Missing+code+verifier", baseUrl),
);
}

try {
// Determine the redirect URI (must match what was used in the authorization request)
const redirectUri = `${process.env.APP_BASE_URL}/oauth/callback`;
const redirectUri = `${baseUrl}/oauth/callback`;

// Exchange authorization code for tokens
const tokens = await exchangeCodeForTokens(code, codeVerifier, redirectUri);
Expand Down Expand Up @@ -99,7 +101,7 @@ export async function GET(request: NextRequest) {
email: claims.email,
});
return NextResponse.redirect(
new URL("/login?error=Failed+to+create+user", request.url),
new URL("/login?error=Failed+to+create+user", baseUrl),
);
}

Expand All @@ -125,7 +127,7 @@ export async function GET(request: NextRequest) {
idpUserId: claims.sub,
});
return NextResponse.redirect(
new URL("/login?error=Account+is+deactivated", request.url),
new URL("/login?error=Account+is+deactivated", baseUrl),
);
}

Expand Down Expand Up @@ -168,7 +170,7 @@ export async function GET(request: NextRequest) {
code: code ? "[present]" : "[missing]",
});
return NextResponse.redirect(
new URL("/login?error=Authentication+failed", request.url),
new URL("/login?error=Authentication+failed", baseUrl),
);
}
}
9 changes: 5 additions & 4 deletions app/oauth/login/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ import { logger } from "@/lib/logger";
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const returnUrl = searchParams.get("returnUrl") || "/";
// Use configured base URL when behind a reverse proxy (Cloudflare Tunnel / Tailscale),
// falling back to request origin for local development.
const baseUrl = process.env.APP_BASE_URL ?? request.nextUrl.origin;

try {
// Generate PKCE values
const state = generateRandomString(32);
const codeVerifier = generateRandomString(64);
const codeChallenge = await generateCodeChallenge(codeVerifier);

// Determine redirect URI from the configured base URL, not the request origin,
// since the app runs behind a reverse proxy (Cloudflare Tunnel / Tailscale).
const redirectUri = `${process.env.APP_BASE_URL}/oauth/callback`;
const redirectUri = `${baseUrl}/oauth/callback`;

// Store state and code verifier in cookies for validation in callback
const cookieStore = await cookies();
Expand Down Expand Up @@ -64,7 +65,7 @@ export async function GET(request: NextRequest) {
} catch (err) {
logger.error("Failed to initiate OAuth login", err as Error);
return NextResponse.redirect(
new URL("/login?error=Failed+to+start+login", request.url),
new URL("/login?error=Failed+to+start+login", baseUrl),
);
}
}
Loading