171 lines · 6.8 KB
import { createAddress } from "../../core/db.ts";
import { buildCommandConfig } from "../../core/config.ts";
import type { SqlExecutor } from "../../core/storage.ts";
import { createMailtmAddress } from "../../receivers/mailtm/address.ts";
import { verifySlackSignature } from "./slack-rest.ts";
import { checkAndIncrement } from "../../core/ratelimit.ts";
import { resolveTorchAllClick, TORCH_ALL_CANCEL } from "../../core/commands.ts";
import { handleSlashCommandRich, TORCH_ALL_ACTION_PREFIX, type SlackSlashCommandPayload } from "./commands.ts";

export interface SlackWebhookEnv {
  DISPOSABLE_DOMAIN?: string;
  // Optional at the type level for the same reason Telegram's bot token is:
  // a Worker not running Slack at all has no reason to set it, but a
  // request that reaches this handler needs a real value or every
  // signature check fails closed.
  SLACK_SIGNING_SECRET?: string;
  [key: string]: unknown;
}

function usesOwnDomain(env: SlackWebhookEnv): boolean {
  return !!env.DISPOSABLE_DOMAIN && env.DISPOSABLE_DOMAIN.trim() !== "";
}

// Slack expects a response within 3 seconds. Every handler is DB-only except
// /cm-new in mail.tm mode, which makes sequential calls to mail.tm; those are
// kept few (cached domain, capped retries) rather than moved to the async
// response_url path. A timeout there can still leave a created address the
// user didn't see; /cm-list shows it.
export async function handleSlackCommandRequest(
  request: Request,
  env: SlackWebhookEnv,
  db: SqlExecutor
): Promise<Response> {
  if (!env.SLACK_SIGNING_SECRET) {
    return new Response("Slack not configured on this Worker", { status: 500 });
  }

  const signature = request.headers.get("X-Slack-Signature");
  const timestamp = request.headers.get("X-Slack-Request-Timestamp");
  if (!signature || !timestamp) {