diff --git a/api/discord/interactions.ts b/api/discord/interactions.ts index 93a5ff49..2f00f58b 100644 --- a/api/discord/interactions.ts +++ b/api/discord/interactions.ts @@ -1,10 +1,31 @@ import { VercelRequest, VercelResponse } from "@vercel/node"; import { createVerify } from "crypto"; +export const config = { + api: { + bodyParser: { + raw: { + type: "application/json", + }, + }, + }, +}; + export default function handler(req: VercelRequest, res: VercelResponse) { - // Only accept POST requests + // Set CORS headers for Discord + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); + res.setHeader( + "Access-Control-Allow-Headers", + "Content-Type, x-signature-ed25519, x-signature-timestamp" + ); + + if (req.method === "OPTIONS") { + return res.status(200).end(); + } + if (req.method !== "POST") { - res.setHeader("Allow", "POST"); + res.setHeader("Allow", "POST, OPTIONS"); return res.status(405).json({ error: "Method not allowed" }); } @@ -13,29 +34,33 @@ export default function handler(req: VercelRequest, res: VercelResponse) { const timestamp = req.headers["x-signature-timestamp"] as string; const publicKey = process.env.DISCORD_PUBLIC_KEY; - console.log("[Discord] Interaction received at", new Date().toISOString()); + console.log("[Discord] Interaction received"); + console.log("[Discord] Has signature:", !!signature); + console.log("[Discord] Has timestamp:", !!timestamp); + console.log("[Discord] Has public key:", !!publicKey); if (!publicKey) { console.error("[Discord] DISCORD_PUBLIC_KEY not set"); - return res.status(401).json({ error: "Server not configured" }); + return res.status(500).json({ error: "Server not configured" }); } if (!signature || !timestamp) { - console.error( - "[Discord] Missing headers - signature:", - !!signature, - "timestamp:", - !!timestamp, - ); + console.error("[Discord] Missing required headers"); return res.status(401).json({ error: "Invalid request" }); } - // Get raw body + // Get raw body - Vercel sends it as Buffer with raw: true config const rawBody = - typeof req.body === "string" ? req.body : JSON.stringify(req.body); + req.body instanceof Buffer + ? req.body.toString("utf8") + : typeof req.body === "string" + ? req.body + : JSON.stringify(req.body); // Verify signature const message = `${timestamp}${rawBody}`; + console.log("[Discord] Verifying signature for message length:", message.length); + const signatureBuffer = Buffer.from(signature, "hex"); const verifier = createVerify("ed25519"); verifier.update(message); @@ -46,23 +71,31 @@ export default function handler(req: VercelRequest, res: VercelResponse) { return res.status(401).json({ error: "Invalid signature" }); } - const interaction = - typeof req.body === "string" ? JSON.parse(req.body) : req.body; - console.log("[Discord] Valid interaction type:", interaction.type); + console.log("[Discord] Signature verified successfully"); + + let interaction; + try { + interaction = typeof rawBody === "string" ? JSON.parse(rawBody) : rawBody; + } catch { + console.error("[Discord] Failed to parse JSON body"); + return res.status(400).json({ error: "Invalid JSON" }); + } + + console.log("[Discord] Interaction type:", interaction?.type); // Discord sends a PING to verify the endpoint - if (interaction.type === 1) { + if (interaction?.type === 1) { console.log("[Discord] ✓ PING verified"); - return res.json({ type: 1 }); + return res.status(200).json({ type: 1 }); } // For all other interactions, acknowledge them - return res.json({ + return res.status(200).json({ type: 4, data: { content: "Interaction received" }, }); } catch (err: any) { - console.error("[Discord] Error:", err?.message); + console.error("[Discord] Error:", err?.message, err?.stack); return res.status(500).json({ error: "Server error" }); } }