diff --git a/lib/birdy_chat/dispatcher.ex b/lib/birdy_chat/dispatcher.ex index 31e134e..b47c823 100644 --- a/lib/birdy_chat/dispatcher.ex +++ b/lib/birdy_chat/dispatcher.ex @@ -7,19 +7,24 @@ defmodule BirdyChat.Dispatcher do end def send_to_remote(%{server: server, to: to} = message) do - {name, base_url} = + {_name, base_url} = BirdyChat.Identity.peers() |> Enum.find(fn {name, _url} -> name == server end) api_url = base_url <> "/api/internal" token = Phoenix.Token.sign(BirdyChatWeb.Endpoint, "serverAuth", to) - result = + {_request, result} = Req.new(url: api_url, retry: false, method: :post) |> Req.merge(req_opts()) |> Req.Request.put_new_header("authorization", token) |> Req.merge(json: message) |> Req.Request.run_request() + + # Handle more when you encounter errors + case result do + %Req.Response{status: 201} -> :ok + end end def req_opts do diff --git a/lib/birdy_chat_web/channels/server_channel.ex b/lib/birdy_chat_web/channels/server_channel.ex deleted file mode 100644 index 009aa1b..0000000 --- a/lib/birdy_chat_web/channels/server_channel.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule BirdyChatWeb.ServerChannel do - use BirdyChatWeb, :channel - - @impl true - def join("server:" <> server_id, payload, socket) do - if authorised?(payload, server_id) do - {:ok, socket} - else - {:error, %{reason: "unauthorised"}} - end - end - - @impl true - def handle_in("new_message", {:binary, message}, socket) do - result = :erlang.binary_to_term(message, [:safe]) - - # TODO: Add validation that this is the right server. - case BirdyChat.Message.validate(result) do - {:ok, changeset} -> - case BirdyChat.MessageWriter.write(changeset.changes) do - :ok -> - {:reply, {:ok, {:binary, message}}, socket} - end - end - end - - # This is how we send messages - def broadcast!(peer, message) do - channel = "server:#{peer}" - encoded_message = :erlang.term_to_binary(message) - BirdyChatWeb.Endpoint.broadcast!(channel, "new_message", {:binary, encoded_message}) - end - - # Simple token-based authentication. Servers should use the same Phoenix secret key so they will - # have the basis for generating tokens. - defp authorised?(%{"token" => token}, server_id) do - case Phoenix.Token.verify(BirdyChatWeb.Endpoint, "serverAuth", token, max_age: 86400) do - {:ok, id} -> id == server_id - {:error, :invalid} -> false - end - end -end diff --git a/lib/birdy_chat_web/channels/server_socket.ex b/lib/birdy_chat_web/channels/server_socket.ex deleted file mode 100644 index 8843d1b..0000000 --- a/lib/birdy_chat_web/channels/server_socket.ex +++ /dev/null @@ -1,44 +0,0 @@ -defmodule BirdyChatWeb.ServerSocket do - use Phoenix.Socket - - # A Socket handler - # - # It's possible to control the websocket connection and - # assign values that can be accessed by your channel topics. - - ## Channels - - channel "server:*", BirdyChatWeb.ServerChannel - - # Socket params are passed from the client and can - # be used to verify and authenticate a user. After - # verification, you can put default assigns into - # the socket that will be set for all channels, ie - # - # {:ok, assign(socket, :user_id, verified_user_id)} - # - # To deny connection, return `:error` or `{:error, term}`. To control the - # response the client receives in that case, [define an error handler in the - # websocket - # configuration](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#socket/3-websocket-configuration). - # - # See `Phoenix.Token` documentation for examples in - # performing token verification on connect. - @impl true - def connect(_params, socket, _connect_info) do - {:ok, socket} - end - - # Socket IDs are topics that allow you to identify all sockets for a given user: - # - # def id(socket), do: "user_socket:#{socket.assigns.user_id}" - # - # Would allow you to broadcast a "disconnect" event and terminate - # all active sockets and channels for a given user: - # - # Elixir.BirdyChatWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) - # - # Returning `nil` makes this socket anonymous. - @impl true - def id(socket), do: "user_socket:#{socket.assigns.server_id}" -end diff --git a/lib/birdy_chat_web/endpoint.ex b/lib/birdy_chat_web/endpoint.ex index 972f7ee..72a9ccf 100644 --- a/lib/birdy_chat_web/endpoint.ex +++ b/lib/birdy_chat_web/endpoint.ex @@ -15,10 +15,6 @@ defmodule BirdyChatWeb.Endpoint do websocket: [connect_info: [session: @session_options]], longpoll: [connect_info: [session: @session_options]] - socket "/socket", BirdyChatWeb.ServerSocket, - websocket: true, - longpoll: false - # Serve at "/" the static files from "priv/static" directory. # # When code reloading is disabled (e.g., in production), diff --git a/test/birdy_chat_web/api/messages_test.exs b/test/birdy_chat_web/api/messages_test.exs index cdd1328..4ecdd2c 100644 --- a/test/birdy_chat_web/api/messages_test.exs +++ b/test/birdy_chat_web/api/messages_test.exs @@ -1,8 +1,6 @@ defmodule BirdyChatWeb.Api.MessagesTest do use BirdyChatWeb.ConnCase, async: true - import Phoenix.ChannelTest - setup %{conn: conn} do url = ~p"/api/messages" @@ -17,18 +15,6 @@ defmodule BirdyChatWeb.Api.MessagesTest do end describe "POST /api/messages to other server" do - setup do - server_id = "test2" - token = Phoenix.Token.sign(BirdyChatWeb.Endpoint, "serverAuth", server_id) - - {:ok, _, socket} = - BirdyChatWeb.ServerSocket - |> socket("server_socket", %{server_id: server_id}) - |> subscribe_and_join(BirdyChatWeb.ServerChannel, "server:#{server_id}", %{token: token}) - - %{socket: socket} - end - test "returns error when a peer is unknown", %{conn: conn, url: url} do message = %{from: "test1-user", to: "fake-user", message: "123"} @@ -41,7 +27,7 @@ defmodule BirdyChatWeb.Api.MessagesTest do assert result == expected_result end - test "broadcasts message to websocket", %{conn: conn, url: url} do + test "send message to the other server via HTTP", %{conn: conn, url: url} do message = %{from: "test1-user", to: "test2-user", message: "123"} string_message = %{ @@ -52,6 +38,26 @@ defmodule BirdyChatWeb.Api.MessagesTest do "server" => "test2" } + Req.Test.expect(BirdyChat.Dispatcher, fn conn -> + expected_body_params = %{ + "from" => "test1-user", + "message" => "123", + "routing" => "remote", + "server" => "test2", + "to" => "test2-user" + } + + {"authorization", token} = + Enum.find(conn.req_headers, fn {key, _v} -> key == "authorization" end) + + {:ok, "test2-user"} = + Phoenix.Token.verify(BirdyChatWeb.Endpoint, "serverAuth", token, max_age: 1200) + + assert conn.body_params == expected_body_params + resp = Jason.encode!(expected_body_params) + Plug.Conn.send_resp(conn, :created, resp) + end) + payload = Jason.encode!(message) conn = post(conn, url, payload) assert result = json_response(conn, :created) diff --git a/test/birdy_chat_web/channels/server_channel_test.exs b/test/birdy_chat_web/channels/server_channel_test.exs deleted file mode 100644 index e57613d..0000000 --- a/test/birdy_chat_web/channels/server_channel_test.exs +++ /dev/null @@ -1,81 +0,0 @@ -defmodule BirdyChatWeb.ServerChannelTest do - use BirdyChatWeb.ChannelCase, async: true - - setup do - server_id = "test2" - token = Phoenix.Token.sign(BirdyChatWeb.Endpoint, "serverAuth", server_id) - - path = Application.app_dir(:birdy_chat, ["priv", "messages", "test-user.txt"]) - on_exit(fn -> File.rm(path) end) - - {:ok, _, socket} = - BirdyChatWeb.ServerSocket - |> socket("server_socket", %{server_id: server_id}) - |> subscribe_and_join(BirdyChatWeb.ServerChannel, "server:#{server_id}", %{token: token}) - - %{socket: socket} - end - - describe "authorization" do - test "requires a signed token to join a channel" do - server_id = "test2" - token = Phoenix.Token.sign(BirdyChatWeb.Endpoint, "fakeAuth", server_id) - - result = - BirdyChatWeb.ServerSocket - |> socket("server_socket", %{server_id: server_id}) - |> subscribe_and_join(BirdyChatWeb.ServerChannel, "server:#{server_id}", %{token: token}) - - assert result == {:error, %{reason: "unauthorised"}} - end - - test "requires server id in token and in channel to match" do - server_id = "test2" - token = Phoenix.Token.sign(BirdyChatWeb.Endpoint, "serverAuth", server_id) - - result = - BirdyChatWeb.ServerSocket - |> socket("server_socket", %{server_id: server_id}) - |> subscribe_and_join(BirdyChatWeb.ServerChannel, "server:fake", %{token: token}) - - assert result == {:error, %{reason: "unauthorised"}} - end - end - - describe "new_message" do - test "receives message from other server and saves it locally", %{socket: socket} do - message = %{from: "test2-user", to: "test-user", message: "123"} - bin = :erlang.term_to_binary(message) - ref = push(socket, "new_message", {:binary, bin}) - assert_reply ref, :ok, {:binary, ^bin} - - path = Application.app_dir(:birdy_chat, ["priv", "messages", "test-user.txt"]) - contents = File.read!(path) - assert contents == "test2-user: 123\n" - end - - test "blows up when sent payload is not safe to decode", %{socket: socket} do - # This unsafe binary is taken from erlang docs directly: - bin = <<131, 119, 8, "tjenixen">> - Process.flag(:trap_exit, true) - push(socket, "new_message", {:binary, bin}) - - assert_receive {:EXIT, _pid, - {:badarg, - [ - {:erlang, :binary_to_term, - [<<131, 119, 8, 116, 106, 101, 110, 105, 120, 101, 110>>, [:safe]], - [error_info: %{module: :erl_erts_errors}]}, - {BirdyChatWeb.ServerChannel, :handle_in, 3, - [file: ~c"lib/birdy_chat_web/channels/server_channel.ex", line: 15]}, - {Phoenix.Channel.Server, :handle_info, 2, - [file: ~c"lib/phoenix/channel/server.ex", line: 332]}, - {:gen_server, :try_handle_info, 3, - [file: ~c"gen_server.erl", line: 2434]}, - {:gen_server, :handle_msg, 3, [file: ~c"gen_server.erl", line: 2420]}, - {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 333]} - ]}}, - 100 - end - end -end