- 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
55 lines
1.4 KiB
Elixir
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
|