defmodule BerrypodWeb.Plugs.VerifyPrintfulWebhook do @moduledoc """ Verifies Printful webhook requests using a shared secret token. Checks the `webhook_secret` stored in the Printful provider connection config against the `X-PF-Webhook-Token` header (or `token` query param as fallback). Can be upgraded to HMAC signature verification once the exact Printful signing format is confirmed. Expects raw body cached in conn.assigns[:raw_body] (via CacheRawBody). """ import Plug.Conn require Logger alias Berrypod.Products def init(opts), do: opts def call(conn, _opts) do with {:ok, token} <- get_token(conn), {:ok, secret} <- get_webhook_secret(), :ok <- verify_token(token, secret) do conn else {:error, reason} -> Logger.warning("Printful webhook verification failed: #{reason}") conn |> put_resp_content_type("application/json") |> send_resp(401, Jason.encode!(%{error: "Invalid token"})) |> halt() end end defp get_token(conn) do # Check header first, then query param case get_req_header(conn, "x-pf-webhook-token") do [token] when token != "" -> {:ok, token} _ -> case conn.query_params["token"] || conn.params["token"] do token when is_binary(token) and token != "" -> {:ok, token} _ -> {:error, :missing_token} end end end defp get_webhook_secret do case Products.get_provider_connection_by_type("printful") do %{config: %{"webhook_secret" => secret}} when is_binary(secret) and secret != "" -> {:ok, secret} _ -> {:error, :no_webhook_secret} end end defp verify_token(token, secret) do if Plug.Crypto.secure_compare(token, secret) do :ok else {:error, :token_mismatch} end end end