birdy_chat/lib/birdy_chat_web/api/server/internal/controller.ex

49 lines
1.9 KiB
Elixir

defmodule BirdyChatWeb.Api.Server.Internal.Controller do
@moduledoc """
A controller for handling inter-server communication. It started off with using Erlang term
format instead of JSON as communication language but then I removed it for the following
reasons:
1. The messages are mostly binaries anyway, there is no big efficiency gain from skipping JSON.
2. Testing JSON is much easier than testing erlang term format.
3. Erlang term format can give an illusion of extra security but unless the transport is HTTPS
then the communication is still inherently unsafe.
4. Erlang term format is difficult to handle for unfamiliar developers, you need to remember
about safe conversion to avoid atom exhaustion attacks or sending an `rm -rf /` function over
the wire.
The endpoint is protected by simple authentication that requires the secret key of all servers
being the same. It is good enough for a demo, but for any real application it would need to be
reconsidered. HTTPS would be a non-negotiable requirement for any user-facing deployment.
"""
use BirdyChatWeb, :controller
def create(conn, params) do
with true <- authorised?(conn.req_headers, params),
{:ok, changeset} <- BirdyChat.Message.validate_for_inter_server_use(params),
:ok <- BirdyChat.MessageWriter.write(changeset.changes) do
conn
|> put_status(:created)
|> render(:create, message: changeset.changes)
else
_any ->
conn
|> put_status(:forbidden)
|> render(:error, message: "Unauthorised")
end
end
defp authorised?(headers, %{"from" => from}) do
case Enum.find(headers, fn {key, _value} -> key == "authorization" end) do
nil ->
false
{"authorization", token} ->
case Phoenix.Token.verify(BirdyChatWeb.Endpoint, "serverAuth", token, max_age: 1200) do
{:ok, id} -> id == from
{:error, :invalid} -> false
end
end
end
end