defmodule ToweropsWeb.Plugs.AgentAuth do @moduledoc """ Plug for authenticating remote agent API requests. Validates Bearer tokens in the Authorization header and assigns the agent token to the connection. Also updates the agent's last_seen_at timestamp asynchronously. """ import Phoenix.Controller import Plug.Conn alias Ecto.Adapters.SQL.Sandbox alias Towerops.Agents @doc """ Initializes the plug with options. """ def init(opts), do: opts @doc """ Validates the agent token from the Authorization header. If valid, assigns :current_agent_token to the connection and updates heartbeat. If invalid or missing, returns 401 Unauthorized and halts the connection. """ def call(conn, _opts) do case get_req_header(conn, "authorization") do ["Bearer " <> token] -> case Agents.verify_agent_token(token) do {:ok, agent_token} -> conn |> assign(:current_agent_token, agent_token) |> update_heartbeat(agent_token) {:error, _} -> unauthorized(conn) end _ -> unauthorized(conn) end end defp update_heartbeat(conn, agent_token) do ip = to_string(:inet_parse.ntoa(conn.remote_ip)) parent = self() Task.start(fn -> # Allow this task to use the test database sandbox if Application.get_env(:towerops, :sql_sandbox) do Sandbox.allow(Towerops.Repo, parent, self()) end Agents.update_agent_token_heartbeat(agent_token.id, ip) end) conn end defp unauthorized(conn) do conn |> put_status(:unauthorized) |> put_view(json: ToweropsWeb.ErrorJSON) |> render(:"401") |> halt() end end