import { NextRequest, NextResponse } from "next/server"; import { cookies } from "next/headers"; import { checkBetaServer } from "@/lib/discord"; export const dynamic = 'force-dynamic'; export async function GET(request: NextRequest) { try { const searchParams = request.nextUrl.searchParams; const code = searchParams.get("code"); const state = searchParams.get("state"); const guildId = searchParams.get("guild_id"); const error = searchParams.get("error"); if (error) { return NextResponse.redirect(new URL("/dashboard?error=access_denied", request.url)); } if (!code || !state || !guildId) { return NextResponse.redirect(new URL("/dashboard?error=invalid_request", request.url)); } const cookieStore = await cookies(); const storedState = cookieStore.get("oauth_invite_state")?.value; const storedGuildId = cookieStore.get("oauth_invite_guild")?.value; // 1. Verify State if (!storedState || state !== storedState) { return NextResponse.redirect(new URL("/dashboard?error=state_mismatch", request.url)); } // 2. Verify Guild ID Match (Optional but recommended extra layer) if (storedGuildId && guildId !== storedGuildId) { return NextResponse.redirect(new URL("/dashboard?error=guild_mismatch", request.url)); } // 3. CRITICAL: Check Beta Server Eligibility // We only exchange the code (and thus add the bot) if this check passes. const [isBeta] = await checkBetaServer([guildId]); if (!isBeta) { console.warn(`Blocked attempt to add bot to non-beta server: ${guildId}`); return NextResponse.redirect(new URL("/dashboard?error=not_beta_server", request.url)); } // 4. Exchange Code for Token (Finalize Bot Join) const appUrl = process.env.APP_URL; if (!appUrl) { console.error("APP_URL env var is not set"); return NextResponse.redirect(new URL("/dashboard?error=config_error", request.url)); } const redirectUri = `${appUrl}/api/oauth/callback`; const tokenResponse = await fetch("https://discord.com/api/oauth2/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ client_id: process.env.AUTH_DISCORD_ID as string, client_secret: process.env.AUTH_DISCORD_SECRET as string, grant_type: "authorization_code", code: code, redirect_uri: redirectUri, }), }); if (!tokenResponse.ok) { const errorText = await tokenResponse.text(); console.error("Failed to exchange token:", errorText); return NextResponse.redirect(new URL("/dashboard?error=token_exchange_failed", request.url)); } // Clean up cookies cookieStore.delete("oauth_invite_state"); cookieStore.delete("oauth_invite_guild"); // Success! return NextResponse.redirect(new URL(`/dashboard?success=bot_added&guild_id=${guildId}`, request.url)); } catch (error) { console.error("Callback handler error:", error); return NextResponse.redirect(new URL("/dashboard?error=internal_server_error", request.url)); } }