11f5ca83ec
Defense-in-depth against header injection if the post-login redirect target ever reaches a context that doesn't re-encode it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
41 lines
1.5 KiB
TypeScript
41 lines
1.5 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server";
|
|
import { AUTH_ENABLED } from "@/lib/supabase/config";
|
|
import { createClient } from "@/lib/supabase/server";
|
|
|
|
// Only allow same-origin relative paths. Rejects `//evil.com`, `/\evil.com`,
|
|
// and absolute URLs that would otherwise turn `${origin}${next}` into an
|
|
// open redirect (CWE-601).
|
|
function safeNext(raw: string | null): string {
|
|
if (!raw || !raw.startsWith("/")) return "/";
|
|
if (raw.startsWith("//") || raw.startsWith("/\\")) return "/";
|
|
// Reject control chars (CR/LF etc.) — defense-in-depth against header
|
|
// injection if `next` ever reaches a context that doesn't re-encode it.
|
|
for (let i = 0; i < raw.length; i++) {
|
|
const code = raw.charCodeAt(i);
|
|
if (code < 0x20 || code === 0x7f) return "/";
|
|
}
|
|
return raw;
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const { searchParams, origin } = request.nextUrl;
|
|
|
|
// Auth not configured: nothing can legitimately hit this route, so just
|
|
// bounce home instead of constructing a Supabase client from blank env vars.
|
|
if (!AUTH_ENABLED) {
|
|
return NextResponse.redirect(`${origin}/`);
|
|
}
|
|
|
|
const code = searchParams.get("code");
|
|
const next = safeNext(searchParams.get("next"));
|
|
|
|
if (code) {
|
|
const supabase = await createClient();
|
|
const { error } = await supabase.auth.exchangeCodeForSession(code);
|
|
if (!error) {
|
|
return NextResponse.redirect(`${origin}${next}`);
|
|
}
|
|
}
|
|
return NextResponse.redirect(`${origin}/?auth_error=1`);
|
|
}
|