diff --git a/lib/towerops_web/channels/agent_channel.ex b/lib/towerops_web/channels/agent_channel.ex index d5eedf9f..3a9d6c38 100644 --- a/lib/towerops_web/channels/agent_channel.ex +++ b/lib/towerops_web/channels/agent_channel.ex @@ -33,21 +33,32 @@ defmodule ToweropsWeb.AgentChannel do require Logger @impl true - def join("agent:" <> agent_token_id, _payload, socket) do - # Verify the agent token ID matches the authenticated socket - if socket.assigns.agent_token_id == agent_token_id do - # Update last_seen_at on join - Agents.update_agent_token_heartbeat(agent_token_id, nil, %{}) + def join("agent:" <> _agent_id, %{"token" => token}, socket) do + # Verify agent token from join payload + case Agents.verify_agent_token(token) do + {:ok, agent_token} -> + socket = + socket + |> assign(:agent_token_id, agent_token.id) + |> assign(:organization_id, agent_token.organization_id) - # Send initial job list - send(self(), :send_jobs) + # Update last_seen_at on join + Agents.update_agent_token_heartbeat(agent_token.id, nil, %{}) - {:ok, socket} - else - {:error, %{reason: "unauthorized"}} + # Send initial job list + send(self(), :send_jobs) + + {:ok, socket} + + {:error, _} -> + {:error, %{reason: "unauthorized"}} end end + def join("agent:" <> _agent_id, _payload, _socket) do + {:error, %{reason: "missing token"}} + end + @impl true def handle_info(:send_jobs, socket) do jobs = build_jobs_for_agent(socket.assigns.agent_token_id) diff --git a/lib/towerops_web/channels/agent_socket.ex b/lib/towerops_web/channels/agent_socket.ex index 273b62ba..7185f48e 100644 --- a/lib/towerops_web/channels/agent_socket.ex +++ b/lib/towerops_web/channels/agent_socket.ex @@ -2,7 +2,8 @@ defmodule ToweropsWeb.AgentSocket do @moduledoc """ WebSocket endpoint for remote agent communication. - Agents connect to: ws://server/socket/agent?token= + Agents connect to: ws://server/socket/agent/websocket + Authentication token is sent in the channel join payload. Uses binary Protocol Buffers encoding for all messages. """ @@ -12,23 +13,17 @@ defmodule ToweropsWeb.AgentSocket do channel "agent:*", ToweropsWeb.AgentChannel @impl true - def connect(%{"token" => token}, socket, _connect_info) do - case Towerops.Agents.verify_agent_token(token) do - {:ok, agent_token} -> - socket = - socket - |> assign(:agent_token_id, agent_token.id) - |> assign(:organization_id, agent_token.organization_id) - - {:ok, socket} - - {:error, _} -> - :error - end + def connect(_params, socket, _connect_info) do + # Allow all connections - authentication happens at channel join + {:ok, socket} end - def connect(_, _socket, _connect_info), do: :error - @impl true - def id(socket), do: "agent_socket:#{socket.assigns.agent_token_id}" + def id(socket) do + # Return nil before authentication (channel join will set agent_token_id) + case Map.get(socket.assigns, :agent_token_id) do + nil -> nil + agent_token_id -> "agent_socket:#{agent_token_id}" + end + end end