130 lines
3.8 KiB
TypeScript
Executable File
130 lines
3.8 KiB
TypeScript
Executable File
// app/api/auth/discord/callback/route.ts
|
|
|
|
import { cookies } from "next/headers";
|
|
import { redirect } from "next/navigation";
|
|
import { type NextRequest, NextResponse } from "next/server";
|
|
import { stringify } from "yaml";
|
|
import { db } from "@/lib/db"; // your drizzle db instance
|
|
import { endgameDiscord } from "@/lib/db/schema"; // your schema
|
|
|
|
const DISCORD_CLIENT_ID = process.env.DISCORD_CLIENT_ID!;
|
|
const DISCORD_CLIENT_SECRET = process.env.DISCORD_CLIENT_SECRET!;
|
|
const DISCORD_REDIRECT_URI = process.env.DISCORD_REDIRECT_URI!;
|
|
const DISCORD_GUILD_ID = process.env.DISCORD_GUILD_ID!;
|
|
const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN!;
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const searchParams = request.nextUrl.searchParams;
|
|
const cookieStore = await cookies();
|
|
const code = searchParams.get("code");
|
|
|
|
if (!code) {
|
|
if (searchParams.has("error")) redirect("/rubgram");
|
|
return NextResponse.json({ error: "No code provided" }, { status: 400 });
|
|
}
|
|
|
|
try {
|
|
// Exchange code for access token
|
|
const tokenResponse = await fetch("https://discord.com/api/oauth2/token", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
// Authorization: `Basic ${btoa(`${DISCORD_CLIENT_ID}:${DISCORD_CLIENT_SECRET}`)}`,
|
|
},
|
|
body: new URLSearchParams({
|
|
client_id: DISCORD_CLIENT_ID,
|
|
client_secret: DISCORD_CLIENT_SECRET,
|
|
grant_type: "authorization_code",
|
|
code,
|
|
redirect_uri: DISCORD_REDIRECT_URI,
|
|
}),
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
throw new Error(
|
|
`Failed to exchange code for token: ${tokenResponse.status}\n${stringify(await tokenResponse.json())}`,
|
|
);
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
const accessToken = tokenData.access_token;
|
|
|
|
// Get user information
|
|
const userResponse = await fetch("https://discord.com/api/users/@me", {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
},
|
|
});
|
|
|
|
if (!userResponse.ok) {
|
|
throw new Error("Failed to fetch user data");
|
|
}
|
|
|
|
const userData = await userResponse.json();
|
|
|
|
// Check if user is in guild
|
|
const guildMemberResponse = await fetch(
|
|
`https://discord.com/api/users/@me/guilds/${DISCORD_GUILD_ID}/member`,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
},
|
|
},
|
|
);
|
|
|
|
const isInGuild = guildMemberResponse.ok;
|
|
|
|
// If not in guild, add them using bot token
|
|
if (!isInGuild) {
|
|
const addMemberResponse = await fetch(
|
|
`https://discord.com/api/guilds/${DISCORD_GUILD_ID}/members/${userData.id}`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
Authorization: `Bot ${DISCORD_BOT_TOKEN}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
access_token: accessToken,
|
|
}),
|
|
},
|
|
);
|
|
|
|
if (!addMemberResponse.ok) {
|
|
console.error("Failed to add user to guild");
|
|
// Continue anyway - they might join manually
|
|
}
|
|
}
|
|
|
|
// Store/update user in database
|
|
const display = userData.global_name || userData.username;
|
|
|
|
const [{ token }] = await db
|
|
.insert(endgameDiscord)
|
|
.values({
|
|
uid: userData.id,
|
|
display,
|
|
username: userData.username,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: endgameDiscord.uid,
|
|
set: {
|
|
display,
|
|
username: userData.username,
|
|
},
|
|
})
|
|
.returning({ token: endgameDiscord.token });
|
|
|
|
// Redirect to success page or dashboard
|
|
cookieStore.set("discord", token);
|
|
} catch (error) {
|
|
console.error("Discord OAuth error:", (error as Error)?.message || error);
|
|
|
|
return NextResponse.json(
|
|
{ error: "Authentication failed" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
redirect("/rubgram");
|
|
}
|