71 lines · 2.5 KB
import { verifyKey } from "discord-interactions";
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import type { CommandConfig } from "../adapters/discord/config.ts";
import { handleInteraction, type CreateAddressFn, type DiscordInteraction } from "../adapters/discord/interactions.ts";
import type { SqlExecutor } from "../core/storage.ts";

// Serves Discord's /interactions webhook. Identical regardless of receiver
// (SMTP, mail.tm), since it only ever talks to the command layer, never to
// however mail happens to arrive.
export function startHttpServer(
  httpPort: number,
  discordPublicKey: string,
  db: SqlExecutor,
  createAddressFn: CreateAddressFn,
  commandConfig: CommandConfig
) {
  const server = createServer((req: IncomingMessage, res: ServerResponse) => {
    if (req.method !== "POST" || req.url !== "/interactions") {
      res.writeHead(404).end("not found");
      return;
    }

    const chunks: Buffer[] = [];
    req.on("data", (chunk: Buffer) => chunks.push(chunk));
    req.on("end", () => {
      void (async () => {
        const rawBody = Buffer.concat(chunks);
        const signature = req.headers["x-signature-ed25519"];
        const timestamp = req.headers["x-signature-timestamp"];

        if (typeof signature !== "string" || typeof timestamp !== "string") {
          res.writeHead(401).end("missing signature headers");
          return;
        }

        const isValid = await verifyKey(rawBody, signature, timestamp, discordPublicKey);
        if (!isValid) {
          res.writeHead(401).end("invalid request signature");
          return;
        }