From 3c779c6b64a7210b14d3a5f68ddc5a150333a899 Mon Sep 17 00:00:00 2001 From: Nikos Papadakis Date: Fri, 23 Jun 2023 10:05:10 +0300 Subject: [PATCH] backend: register server api --- backend/lib/prymn/servers.ex | 31 ++++++++++------- backend/lib/prymn/servers/server.ex | 13 +++++--- .../prymn_web/components/core_components.ex | 2 +- .../controllers/server_controller.ex | 19 +++++++++++ .../lib/prymn_web/live/server_live/index.ex | 12 ++++--- .../live/server_live/index.html.heex | 13 +++++--- .../prymn_web/live/server_live/show.html.heex | 33 +++++++++++-------- backend/lib/prymn_web/router.ex | 9 ++--- .../20230609164352_create_servers.exs | 10 +++--- 9 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 backend/lib/prymn_web/controllers/server_controller.ex diff --git a/backend/lib/prymn/servers.ex b/backend/lib/prymn/servers.ex index d6d5bef..42aeff0 100644 --- a/backend/lib/prymn/servers.ex +++ b/backend/lib/prymn/servers.ex @@ -38,24 +38,33 @@ defmodule Prymn.Servers do def get_server!(id), do: Repo.get!(Server, id) @doc """ - Creates a server. - - ## Examples - - iex> create_server(%{field: value}) - {:ok, %Server{}} - - iex> create_server(%{field: bad_value}) - {:error, %Ecto.Changeset{}} + Start a new server connection with the app. """ def create_server(attrs \\ %{}) do - # FIXME: Maybe use a cryptographically secure token (if UUID v4 is not one)? - %Server{connection_token: Ecto.UUID.generate()} + # Create a unique registration token + %Server{registration_token: :crypto.strong_rand_bytes(16)} |> Server.changeset(attrs) |> Repo.insert() end + @doc """ + Registers a server using a registration token. + """ + def register_server(token, public_ip) do + # TODO: Validate public ip + + case Base.decode64(token) do + {:ok, token} -> + from(s in Server, where: s.registration_token == ^token, select: s) + |> Repo.one() + |> update_server(%{public_ip: public_ip}) + + :error -> + {:error, "token is not a valid base64 encoded string"} + end + end + @doc """ Updates a server. diff --git a/backend/lib/prymn/servers/server.ex b/backend/lib/prymn/servers/server.ex index 21b22ac..ae3dc11 100644 --- a/backend/lib/prymn/servers/server.ex +++ b/backend/lib/prymn/servers/server.ex @@ -4,10 +4,13 @@ defmodule Prymn.Servers.Server do schema "servers" do field :name, :string - field :ipv4, :string - field :ipv6, :string + field :public_ip, :string field :provider, Ecto.Enum, values: [:Hetzner, :Custom] - field :connection_token, Ecto.UUID, redact: true + field :registration_token, :binary, redact: true + + field :connection_status, Ecto.Enum, + values: [:awaiting, :connecting, :installing, :connected, :disconnected], + default: :awaiting timestamps() end @@ -15,8 +18,8 @@ defmodule Prymn.Servers.Server do @doc false def changeset(server, attrs) do server - |> cast(attrs, [:name, :provider]) - |> validate_required([:name, :provider, :connection_token]) + |> cast(attrs, [:name, :provider, :registration_token]) + |> validate_required([:name, :provider]) |> validate_inclusion(:provider, [:Custom], message: "Provider not available (yet)") end end diff --git a/backend/lib/prymn_web/components/core_components.ex b/backend/lib/prymn_web/components/core_components.ex index f9c99ca..c3830e6 100644 --- a/backend/lib/prymn_web/components/core_components.ex +++ b/backend/lib/prymn_web/components/core_components.ex @@ -477,7 +477,7 @@ defmodule PrymnWeb.CoreComponents do class="relative w-min p-0 hover:cursor-pointer" phx-click={@row_click && @row_click.(row)} > -
+
<%= @row_indicator.(row) %>
diff --git a/backend/lib/prymn_web/controllers/server_controller.ex b/backend/lib/prymn_web/controllers/server_controller.ex new file mode 100644 index 0000000..e0b5e92 --- /dev/null +++ b/backend/lib/prymn_web/controllers/server_controller.ex @@ -0,0 +1,19 @@ +defmodule PrymnWeb.ServerController do + use PrymnWeb, :controller + + alias Prymn.Servers + + @doc """ + Used by clients to request a new server connection to the prymn backend + validating their registration token. + """ + def register(conn, %{"token" => token, "ip" => ip}) do + case Servers.register_server(token, ip) do + {:ok, _server} -> + json(conn, %{"connected" => true}) + + {:error, error} -> + raise inspect(error) + end + end +end diff --git a/backend/lib/prymn_web/live/server_live/index.ex b/backend/lib/prymn_web/live/server_live/index.ex index 99ddd39..d896183 100644 --- a/backend/lib/prymn_web/live/server_live/index.ex +++ b/backend/lib/prymn_web/live/server_live/index.ex @@ -24,11 +24,13 @@ defmodule PrymnWeb.ServerLive.Index do end @impl true - def handle_info({:connect, server}, socket) do - # TODO: Connect the new server: - # For custom connections: will need to generate a new prompt for the user to execute - # For API connections: the "prompt" will be automatic, but same execution server-side - {:noreply, stream_insert(socket, :servers, server)} + def handle_info({:connect, %Servers.Server{} = server}, socket) do + socket = + if server.provider == :Custom, + do: push_navigate(socket, to: ~p"/servers/#{server}"), + else: stream_insert(socket, :servers, server) + + {:noreply, socket} end @impl true diff --git a/backend/lib/prymn_web/live/server_live/index.html.heex b/backend/lib/prymn_web/live/server_live/index.html.heex index 3f38d52..d10c240 100644 --- a/backend/lib/prymn_web/live/server_live/index.html.heex +++ b/backend/lib/prymn_web/live/server_live/index.html.heex @@ -12,15 +12,20 @@ rows={@streams.servers} row_click={fn {_id, server} -> JS.navigate(~p"/servers/#{server}") end} row_indicator={ - fn {_id, _server} -> - ~H(Connecting) + fn + {_id, %Servers.Server{connection_status: :awaiting}} -> + ~H(Awaiting connection) + + {_id, %Servers.Server{connection_status: :connecting}} -> + ~H(Connecting) + + {_id, %Servers.Server{connection_status: :connected}} -> + ~H(Connected) end } indicator_label="Status" > <:col :let={{_id, server}} label="Name"><%= server.name %> - <:col :let={{_id, server}} label="IPv4"><%= server.ipv4 || "N/A" %> - <:col :let={{_id, server}} label="IPv6"><%= server.ipv6 || "N/A" %> <:action :let={{id, server}}> <.link phx-click={JS.push("delete", value: %{id: server.id}) |> hide("##{id}")} diff --git a/backend/lib/prymn_web/live/server_live/show.html.heex b/backend/lib/prymn_web/live/server_live/show.html.heex index 604a5f9..a21038a 100644 --- a/backend/lib/prymn_web/live/server_live/show.html.heex +++ b/backend/lib/prymn_web/live/server_live/show.html.heex @@ -1,18 +1,25 @@ <.header> - <%= @server.name %> - <:subtitle> - IPv4: <%= @server.ipv4 || "Not available" %> - IPv6: <%= @server.ipv6 %> - - <:actions> - <.button> - New site - - + Server <%= @server.name %> -
-

Sites

-
+
+

+ Connect to your server using root credentials and execute the following command: +

+
+ + # + + your command here that contains token: <%= Base.encode64(@server.registration_token) %> + + + +
+
<.back navigate={~p"/servers"}>Back to servers diff --git a/backend/lib/prymn_web/router.ex b/backend/lib/prymn_web/router.ex index 5cb50c4..0eaff59 100644 --- a/backend/lib/prymn_web/router.ex +++ b/backend/lib/prymn_web/router.ex @@ -25,10 +25,11 @@ defmodule PrymnWeb.Router do live "/servers/:id", ServerLive.Show end - # Other scopes may use custom stacks. - # scope "/api", PrymnWeb do - # pipe_through :api - # end + scope "/api/v1", PrymnWeb do + pipe_through :api + + post "/servers/register", ServerController, :register + end # Enable LiveDashboard and Swoosh mailbox preview in development if Application.compile_env(:prymn, :dev_routes) do diff --git a/backend/priv/repo/migrations/20230609164352_create_servers.exs b/backend/priv/repo/migrations/20230609164352_create_servers.exs index 93e046b..b097bad 100644 --- a/backend/priv/repo/migrations/20230609164352_create_servers.exs +++ b/backend/priv/repo/migrations/20230609164352_create_servers.exs @@ -2,14 +2,16 @@ defmodule Prymn.Repo.Migrations.CreateServers do use Ecto.Migration def change do - create table(:servers) do + create table("servers") do add :name, :string - add :ipv4, :string - add :ipv6, :string + add :public_ip, :string add :provider, :string - add :connection_token, :binary + add :connection_status, :string + add :registration_token, :binary timestamps() end + + create index("servers", [:registration_token], unique: true) end end