towerops/lib/towerops_web/plugs/webhook_auth.ex
Graham McIntire 3632580d1b fix: address security and reliability findings from bugs.md review
- Client IP: only trust X-Forwarded-For from RFC 1918 proxy IPs
- Webhook auth: handle nil/blank secret with controlled error, not 500 crash
- Sudo redirect: reuse validated return_path? from login to prevent open redirect
- Map live: remove redundant inline script (ensureLeaflet hook handles loading)
- Bang calls: convert crash-prone exact matches to case in QR live and API controllers
2026-05-11 18:54:12 -05:00

55 lines
1.4 KiB
Elixir

defmodule ToweropsWeb.Plugs.WebhookAuth do
@moduledoc """
Plug for authenticating webhook requests using a shared secret.
Expects the Authorization header to contain a Bearer token matching
the configured `agent_webhook_secret`.
Returns 401 Unauthorized if:
- No Authorization header is present
- Token does not match the configured secret
"""
import Phoenix.Controller, only: [json: 2]
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] ->
verify_secret(conn, token)
_ ->
conn
|> put_status(:unauthorized)
|> json(%{error: "Missing or invalid Authorization header"})
|> halt()
end
end
defp verify_secret(conn, token) do
secret = Application.get_env(:towerops, :agent_webhook_secret)
cond do
is_nil(secret) or secret == "" ->
require Logger
Logger.error("agent_webhook_secret not configured — rejecting webhook request")
conn
|> put_status(:internal_server_error)
|> json(%{error: "Webhook authentication not configured"})
|> halt()
Plug.Crypto.secure_compare(token, secret) ->
conn
true ->
conn
|> put_status(:unauthorized)
|> json(%{error: "Invalid webhook secret"})
|> halt()
end
end
end