57 lines
1.5 KiB
Elixir
57 lines
1.5 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
|
|
|
|
require Logger
|
|
|
|
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 == "" ->
|
|
Logger.error("agent_webhook_secret not configured — rejecting webhook request")
|
|
|
|
# Log the real reason server-side; don't echo it to the caller —
|
|
# otherwise an attacker can probe for unconfigured endpoints.
|
|
conn
|
|
|> put_status(:internal_server_error)
|
|
|> json(%{error: "Internal server error"})
|
|
|> halt()
|
|
|
|
Plug.Crypto.secure_compare(token, secret) ->
|
|
conn
|
|
|
|
true ->
|
|
conn
|
|
|> put_status(:unauthorized)
|
|
|> json(%{error: "Invalid webhook secret"})
|
|
|> halt()
|
|
end
|
|
end
|
|
end
|