diff --git a/api/discord/interactions.ts b/api/discord/interactions.ts index ba195ed5..bbb2363b 100644 --- a/api/discord/interactions.ts +++ b/api/discord/interactions.ts @@ -1,31 +1,10 @@ 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) { - // 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, OPTIONS"); return res.status(405).json({ error: "Method not allowed" }); } @@ -34,71 +13,55 @@ 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"); - 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(500).json({ error: "Server not configured" }); + if (!signature || !timestamp || !publicKey) { + console.error("[Discord] Missing signature, timestamp, or public key"); + return res.status(401).json({ error: "Unauthorized" }); } - if (!signature || !timestamp) { - console.error("[Discord] Missing required headers"); - return res.status(401).json({ error: "Invalid request" }); + // Reconstruct raw body exactly as Discord sent it + let rawBody: string; + if (typeof req.body === "string") { + rawBody = req.body; + } else { + rawBody = JSON.stringify(req.body); } - // Get raw body - Vercel sends it as Buffer with raw: true config - const rawBody = - req.body instanceof Buffer - ? req.body.toString("utf8") - : typeof req.body === "string" - ? req.body - : JSON.stringify(req.body); - - // Verify signature + // Create message for signature verification 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); - const isValid = verifier.verify(publicKey, signatureBuffer); - - if (!isValid) { - console.error("[Discord] Signature verification failed"); - return res.status(401).json({ error: "Invalid signature" }); - } - - console.log("[Discord] Signature verified successfully"); - - let interaction; + // Verify Discord's signature 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" }); + const signatureBuffer = Buffer.from(signature, "hex"); + const verifier = createVerify("ed25519"); + verifier.update(message); + const isValid = verifier.verify(publicKey, signatureBuffer); + + if (!isValid) { + console.error("[Discord] Signature verification failed"); + return res.status(401).json({ error: "Invalid signature" }); + } + } catch (err: any) { + console.error("[Discord] Signature verification error:", err.message); + return res.status(401).json({ error: "Signature error" }); } - console.log("[Discord] Interaction type:", interaction?.type); + // Parse interaction + const interaction = JSON.parse(rawBody); - // Discord sends a PING to verify the endpoint - if (interaction?.type === 1) { + // Respond to PING with type 1 + if (interaction.type === 1) { console.log("[Discord] ✓ PING verified"); return res.status(200).json({ type: 1 }); } - // For all other interactions, acknowledge them + // Handle other interaction types + console.log("[Discord] Interaction type:", interaction.type); return res.status(200).json({ type: 4, - data: { content: "Interaction received" }, + data: { content: "Pong!" }, }); } catch (err: any) { - console.error("[Discord] Error:", err?.message, err?.stack); - return res.status(500).json({ error: "Server error" }); + console.error("[Discord] Error:", err?.message); + return res.status(500).json({ error: "Internal server error" }); } }