86 lines
3.4 KiB
TypeScript
86 lines
3.4 KiB
TypeScript
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));
|
|
}
|
|
}
|