From 8f04a3f684506e569ce3c0caddbdd93c027ef135 Mon Sep 17 00:00:00 2001 From: Graham McIntie Date: Sat, 14 Feb 2026 16:31:54 -0600 Subject: [PATCH] Fix credo: extract PagerDuty HMAC verification to reduce nesting --- .../api/v1/pagerduty_webhook_controller.ex | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/lib/towerops_web/controllers/api/v1/pagerduty_webhook_controller.ex b/lib/towerops_web/controllers/api/v1/pagerduty_webhook_controller.ex index d5883abb..fa50c0e1 100644 --- a/lib/towerops_web/controllers/api/v1/pagerduty_webhook_controller.ex +++ b/lib/towerops_web/controllers/api/v1/pagerduty_webhook_controller.ex @@ -52,36 +52,39 @@ defmodule ToweropsWeb.Api.V1.PagerdutyWebhookController do webhook_secret = get_in(integration.credentials, ["webhook_secret"]) if is_nil(webhook_secret) or webhook_secret == "" do - # No webhook secret configured — accept without verification - # (user hasn't set up the V3 webhook extension yet) :ok else - signatures = - conn - |> Plug.Conn.get_req_header("x-pagerduty-signature") - |> Enum.flat_map(&String.split(&1, ",")) - |> Enum.map(&String.trim/1) - - expected = - :hmac - |> :crypto.mac(:sha256, webhook_secret, raw_body) - |> Base.encode16(case: :lower) - - if Enum.any?(signatures, fn sig -> - case sig do - "v1=" <> hex -> Plug.Crypto.secure_compare(hex, expected) - _ -> false - end - end) do - :ok - else - Logger.warning("PagerDuty webhook signature mismatch for org #{integration.organization_id}") - - {:error, :invalid_signature} - end + check_pagerduty_hmac(conn, raw_body, webhook_secret, integration.organization_id) end end + defp check_pagerduty_hmac(conn, raw_body, secret, org_id) do + signatures = + conn + |> Plug.Conn.get_req_header("x-pagerduty-signature") + |> Enum.flat_map(&String.split(&1, ",")) + |> Enum.map(&String.trim/1) + + expected = + :hmac + |> :crypto.mac(:sha256, secret, raw_body) + |> Base.encode16(case: :lower) + + if signature_matches?(signatures, expected) do + :ok + else + Logger.warning("PagerDuty webhook signature mismatch for org #{org_id}") + {:error, :invalid_signature} + end + end + + defp signature_matches?(signatures, expected) do + Enum.any?(signatures, fn + "v1=" <> hex -> Plug.Crypto.secure_compare(hex, expected) + _ -> false + end) + end + defp process_webhook(%{"event" => %{"event_type" => event_type, "data" => data}}, organization_id) do case event_type do "incident.resolved" ->