POST /api/v1/webhooks/agent-release triggers all connected agents to self-update via their existing WebSocket channels. Authenticated with a shared secret (AGENT_WEBHOOK_SECRET env var). - Add ReleaseChecker.invalidate_cache/0 for fresh GitHub fetch - Add Agents.list_updatable_agents/0 (enabled + seen < 10min) - Add Agents.broadcast_mass_update/0 orchestration function - Add WebhookAuth plug with timing-safe secret comparison - Add AgentReleaseWebhookController with :webhook pipeline - Configure AGENT_WEBHOOK_SECRET in dev/test/runtime configs
43 lines
1 KiB
Elixir
43 lines
1 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)
|
|
|
|
if Plug.Crypto.secure_compare(token, secret) do
|
|
conn
|
|
else
|
|
conn
|
|
|> put_status(:unauthorized)
|
|
|> json(%{error: "Invalid webhook secret"})
|
|
|> halt()
|
|
end
|
|
end
|
|
end
|