diff --git a/lib/chronoscope/application.ex b/lib/chronoscope/application.ex index 1f35d3d..b984f28 100644 --- a/lib/chronoscope/application.ex +++ b/lib/chronoscope/application.ex @@ -23,6 +23,7 @@ defmodule Chronoscope.Application do {Registry, [keys: :unique, name: Chronoscope.Gemini.Registry]}, # Start to serve requests, typically the last entry ChronoscopeWeb.Endpoint, + # Initialize clients for the main LiveView Chronoscope.ViewActivator ] diff --git a/lib/chronoscope/view_activator.ex b/lib/chronoscope/view_activator.ex index 54b7fb5..5ca763a 100644 --- a/lib/chronoscope/view_activator.ex +++ b/lib/chronoscope/view_activator.ex @@ -33,6 +33,7 @@ defmodule Chronoscope.ViewActivator do @nts_file |> File.stream!() |> Stream.map(&String.trim/1) + |> Stream.filter(&(&1 != "")) |> Stream.map(&Parse.parse_nts_server/1) |> Enum.to_list() |> tap(fn server -> Enum.each(server, &NTS.auto_refresh/1) end) diff --git a/lib/chronoscope_web/controllers/api/v1/gemini/connection_controller.ex b/lib/chronoscope_web/controllers/api/v1/gemini/connection_controller.ex index 39d8861..208b4b0 100644 --- a/lib/chronoscope_web/controllers/api/v1/gemini/connection_controller.ex +++ b/lib/chronoscope_web/controllers/api/v1/gemini/connection_controller.ex @@ -13,7 +13,7 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do def get(conn, %{"host" => host, "port" => port, "path" => path}) do try do - handle_get(conn, %{host: host, port: String.to_integer(port), path: path}) + handle_get(conn, %{host: String.trim(host), port: String.to_integer(port), path: path}) rescue ArgumentError -> bad_request_response(conn, "invalid port") end @@ -25,20 +25,24 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do def get(conn, %{"host" => host, "port" => port}) do try do - handle_get(conn, %{host: host, port: String.to_integer(port), path: @default_path}) + handle_get(conn, %{host: String.trim(host), port: String.to_integer(port), path: @default_path}) rescue ArgumentError -> bad_request_response(conn, "invalid port") end end def get(conn, %{"host" => host}) do - handle_get(conn, %{host: host, port: @default_port, path: @default_path}) + handle_get(conn, %{host: String.trim(host), port: @default_port, path: @default_path}) end def get(conn, _params) do bad_request_response(conn, "missing host") end + defp handle_get(conn, %{host: ""}) do + bad_request_response(conn, "empty host") + end + defp handle_get(conn, %{host: host, port: port, path: path}) when port > 0 and port < 65536 do case connect(host, port, path) do {:ok, response} -> diff --git a/lib/chronoscope_web/controllers/api/v1/nts/key_establishment_controller.ex b/lib/chronoscope_web/controllers/api/v1/nts/key_establishment_controller.ex index 7fa4944..3c5cc5c 100644 --- a/lib/chronoscope_web/controllers/api/v1/nts/key_establishment_controller.ex +++ b/lib/chronoscope_web/controllers/api/v1/nts/key_establishment_controller.ex @@ -12,20 +12,24 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentController do def get(conn, %{"host" => host, "port" => port}) do try do - handle_get(conn, %{host: host, port: String.to_integer(port)}) + handle_get(conn, %{host: String.trim(host), port: String.to_integer(port)}) rescue ArgumentError -> bad_request_response(conn, "invalid port") end end def get(conn, %{"host" => host}) do - handle_get(conn, %{host: host, port: @default_port}) + handle_get(conn, %{host: String.trim(host), port: @default_port}) end def get(conn, _params) do bad_request_response(conn, "missing host") end + defp handle_get(conn, %{host: ""}) do + bad_request_response(conn, "empty host") + end + defp handle_get(conn, %{host: host, port: port}) when port > 0 and port < 65536 do case key_establishment_response(host, port) do {:ok, response} -> diff --git a/lib/chronoscope_web/live/index_live.ex b/lib/chronoscope_web/live/index_live.ex index 1b72ddb..7e7fb8d 100644 --- a/lib/chronoscope_web/live/index_live.ex +++ b/lib/chronoscope_web/live/index_live.ex @@ -3,26 +3,33 @@ defmodule ChronoscopeWeb.IndexLive do alias Chronoscope.NTS alias Chronoscope.NTS.KeyEstablishmentResponse + alias Chronoscope.ViewActivator @topic Application.compile_env(:chronoscope, :nts_topic) def mount(_params, _session, socket) do ChronoscopeWeb.Endpoint.subscribe(@topic) - {:ok, assign(socket, %{clients: client_list()})} + {:ok, assign(socket, %{servers: server_list(), clients: client_list()})} end def handle_info(%{topic: @topic, event: "key-exchange", payload: client}, socket) do - {:noreply, update(socket, :clients, &update_client(&1, client))} + if client.server in socket.assigns.servers do + {:noreply, update(socket, :clients, &update_client(&1, client))} + else + {:noreply, socket} + end end defp update_client(client_list, client) do - # todo - use a map instead of list for fast lookups? Enum.map(client_list, &if(client.server == &1.server, do: client, else: &1)) end + defp server_list() do + GenServer.call(ViewActivator, :get_nts_servers) + end + defp client_list() do - Chronoscope.ViewActivator - |> GenServer.call(:get_nts_servers) + server_list() |> NTS.list_clients() end end diff --git a/test/chronoscope_web/controllers/api/v1/gemini/connection_controller_test.exs b/test/chronoscope_web/controllers/api/v1/gemini/connection_controller_test.exs index e932087..c0a00d3 100644 --- a/test/chronoscope_web/controllers/api/v1/gemini/connection_controller_test.exs +++ b/test/chronoscope_web/controllers/api/v1/gemini/connection_controller_test.exs @@ -17,6 +17,42 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionControllerTest do assert %{"error" => "missing host"} == response end + test "requires a host name value", %{conn: conn} do + response1 = + conn + |> get(~p"/api/v1/gemini/connect?host=") + |> json_response(400) + + response2 = + conn + |> get(~p"/api/v1/gemini/connect?host=&port=1966") + |> json_response(400) + + assert %{"error" => "empty host"} == response1 + assert %{"error" => "empty host"} == response2 + end + + test "requires a non-blank host name", %{conn: conn} do + response1 = + conn + |> get(~p"/api/v1/gemini/connect?host=%20%20") + |> json_response(400) + + response2 = + conn + |> get(~p"/api/v1/gemini/connect?host=%20%20&port=1966") + |> json_response(400) + + response3 = + conn + |> get(~p"/api/v1/gemini/connect?host=%20%20&port=1966&path=/test") + |> json_response(400) + + assert %{"error" => "empty host"} == response1 + assert %{"error" => "empty host"} == response2 + assert %{"error" => "empty host"} == response3 + end + test "uses the given host name", %{conn: conn} do GeminiMock |> expect(:connect, fn "localhost", 1965, "/" -> {:ok, %{status: :ok}} end) diff --git a/test/chronoscope_web/controllers/api/v1/nts/key_establishment_controller_test.exs b/test/chronoscope_web/controllers/api/v1/nts/key_establishment_controller_test.exs index 0c88fc6..a360003 100644 --- a/test/chronoscope_web/controllers/api/v1/nts/key_establishment_controller_test.exs +++ b/test/chronoscope_web/controllers/api/v1/nts/key_establishment_controller_test.exs @@ -17,6 +17,36 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do assert %{"error" => "missing host"} == response end + test "requires a host name value", %{conn: conn} do + response1 = + conn + |> get(~p"/api/v1/nts/key-establishment?host=") + |> json_response(400) + + response2 = + conn + |> get(~p"/api/v1/nts/key-establishment?host=&port=4444") + |> json_response(400) + + assert %{"error" => "empty host"} == response1 + assert %{"error" => "empty host"} == response2 + end + + test "requires a non-blank host name", %{conn: conn} do + response1 = + conn + |> get(~p"/api/v1/nts/key-establishment?host=%20%20") + |> json_response(400) + + response2 = + conn + |> get(~p"/api/v1/nts/key-establishment?host=%20%20&port=4444") + |> json_response(400) + + assert %{"error" => "empty host"} == response1 + assert %{"error" => "empty host"} == response2 + end + test "uses the given host name", %{conn: conn} do NTSMock |> expect(:key_establishment, fn "localhost", 4460 -> {:ok, %{status: :ok}} end)