diff --git a/elixir/README.md b/elixir/README.md index 7f8ffc2dc..49655cd2d 100644 --- a/elixir/README.md +++ b/elixir/README.md @@ -65,7 +65,7 @@ Now you can verify that it's working by connecting to a websocket: # Note: The token value below is an example. The token value you will need is generated and printed out when seeding the database, as described earlier in the document. ❯ export GATEWAY_TOKEN_FROM_SEEDS="SFMyNTY.g2gDaAJtAAAAJDNjZWYwNTY2LWFkZmQtNDhmZS1hMGYxLTU4MDY3OTYwOGY2Zm0AAABAamp0enhSRkpQWkdCYy1vQ1o5RHkyRndqd2FIWE1BVWRwenVScjJzUnJvcHg3NS16bmhfeHBfNWJUNU9uby1yYm4GAJXr4emIAWIAAVGA.jz0s-NohxgdAXeRMjIQ9kLBOyd7CmKXWi2FHY-Op8GM" -❯ websocat --header="User-Agent: iOS/12.7 (iPhone) connlib/0.7.412" "ws://127.0.0.1:13000/gateway/websocket?token=${GATEWAY_TOKEN_FROM_SEEDS}&external_id=thisisrandomandpersistent&name_suffix=kkX1&public_key=kceI60D6PrwOIiGoVz6hD7VYCgD1H57IVQlPJTTieUE=" +❯ websocat --header="User-Agent: iOS/12.7 (iPhone) connlib/0.7.412" "ws://127.0.0.1:13000/gateway/websocket?token=${GATEWAY_TOKEN_FROM_SEEDS}&external_id=thisisrandomandpersistent&name=kkX1&public_key=kceI60D6PrwOIiGoVz6hD7VYCgD1H57IVQlPJTTieUE=" # After this you need to join the `gateway` topic. # For details on this structure see https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html @@ -107,10 +107,10 @@ Now you can verify that it's working by connecting to a websocket: # Panel will only accept token if it's coming with this User-Agent header and from IP 172.28.0.1 ❯ export CLIENT_USER_AGENT="iOS/12.5 (iPhone) connlib/0.7.412" -❯ websocat --header="User-Agent: ${CLIENT_USER_AGENT}" "ws://127.0.0.1:8081/client/websocket?token=${CLIENT_TOKEN_FROM_SEEDS}&external_id=thisisrandomandpersistent&name_suffix=kkX1&public_key=kceI60D6PrwOIiGoVz6hD7VYCgD1H57IVQlPJTTieUE=" +❯ websocat --header="User-Agent: ${CLIENT_USER_AGENT}" "ws://127.0.0.1:8081/client/websocket?token=${CLIENT_TOKEN_FROM_SEEDS}&external_id=thisisrandomandpersistent&name=kkX1&public_key=kceI60D6PrwOIiGoVz6hD7VYCgD1H57IVQlPJTTieUE=" # Here is what you will see in docker logs firezone-api-1 -# firezone-api-1 | {"domain":["elixir"],"erl_level":"info","logging.googleapis.com/sourceLocation":{"file":"lib/phoenix/logger.ex","line":306,"function":"Elixir.Phoenix.Logger.phoenix_socket_connected/4"},"message":"CONNECTED TO API.Client.Socket in 83ms\n Transport: :websocket\n Serializer: Phoenix.Socket.V1.JSONSerializer\n Parameters: %{\"external_id\" => \"thisisrandomandpersistent\", \"name_suffix\" => \"kkX1\", \"public_key\" => \"[FILTERED]\", \"token\" => \"[FILTERED]\"}","severity":"INFO","time":"2023-06-23T21:01:49.566Z"} +# firezone-api-1 | {"domain":["elixir"],"erl_level":"info","logging.googleapis.com/sourceLocation":{"file":"lib/phoenix/logger.ex","line":306,"function":"Elixir.Phoenix.Logger.phoenix_socket_connected/4"},"message":"CONNECTED TO API.Client.Socket in 83ms\n Transport: :websocket\n Serializer: Phoenix.Socket.V1.JSONSerializer\n Parameters: %{\"external_id\" => \"thisisrandomandpersistent\", \"name\" => \"kkX1\", \"public_key\" => \"[FILTERED]\", \"token\" => \"[FILTERED]\"}","severity":"INFO","time":"2023-06-23T21:01:49.566Z"} # After this you need to join the `client` topic and pass a `stamp_secret` in the payload. # For details on this structure see https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html diff --git a/elixir/apps/api/lib/api/gateway/socket.ex b/elixir/apps/api/lib/api/gateway/socket.ex index dc820c1bc..4086d98e0 100644 --- a/elixir/apps/api/lib/api/gateway/socket.ex +++ b/elixir/apps/api/lib/api/gateway/socket.ex @@ -28,7 +28,7 @@ defmodule API.Gateway.Socket do attrs = attrs - |> Map.take(~w[external_id name_suffix public_key]) + |> Map.take(~w[external_id name public_key]) |> Map.put("last_seen_user_agent", user_agent) |> Map.put("last_seen_remote_ip", real_ip) |> Map.put("last_seen_remote_ip_location_region", location_region) diff --git a/elixir/apps/domain/lib/domain/accounts.ex b/elixir/apps/domain/lib/domain/accounts.ex index 616a61cb7..b058fa9be 100644 --- a/elixir/apps/domain/lib/domain/accounts.ex +++ b/elixir/apps/domain/lib/domain/accounts.ex @@ -3,6 +3,15 @@ defmodule Domain.Accounts do alias Domain.Auth alias Domain.Accounts.{Authorizer, Account} + def list_accounts_by_ids(ids) do + if Enum.all?(ids, &Validator.valid_uuid?/1) do + Account.Query.by_id({:in, ids}) + |> Repo.list() + else + {:ok, []} + end + end + def fetch_account_by_id(id, %Auth.Subject{} = subject) do with :ok <- Auth.ensure_has_permissions(subject, Authorizer.view_accounts_permission()), true <- Validator.valid_uuid?(id) do diff --git a/elixir/apps/domain/lib/domain/accounts/account/query.ex b/elixir/apps/domain/lib/domain/accounts/account/query.ex index 8e308b5fa..18d44ba11 100644 --- a/elixir/apps/domain/lib/domain/accounts/account/query.ex +++ b/elixir/apps/domain/lib/domain/accounts/account/query.ex @@ -6,7 +6,13 @@ defmodule Domain.Accounts.Account.Query do # |> where([account: account], is_nil(account.deleted_at)) end - def by_id(queryable \\ all(), id) do + def by_id(queryable \\ all(), id) + + def by_id(queryable, {:in, ids}) do + where(queryable, [account: account], account.id in ^ids) + end + + def by_id(queryable, id) do where(queryable, [account: account], account.id == ^id) end diff --git a/elixir/apps/domain/lib/domain/flows.ex b/elixir/apps/domain/lib/domain/flows.ex index f03b450da..12c5bca03 100644 --- a/elixir/apps/domain/lib/domain/flows.ex +++ b/elixir/apps/domain/lib/domain/flows.ex @@ -1,6 +1,6 @@ defmodule Domain.Flows do alias Domain.{Repo, Validator} - alias Domain.{Auth, Accounts, Clients, Gateways, Resources, Policies} + alias Domain.{Auth, Accounts, Actors, Clients, Gateways, Resources, Policies} alias Domain.Flows.{Authorizer, Flow, Activity} require Ecto.Query @@ -84,6 +84,11 @@ defmodule Domain.Flows do |> list_flows(subject, opts) end + def list_flows_for(%Actors.Actor{} = actor, %Auth.Subject{} = subject, opts) do + Flow.Query.by_actor_id(actor.id) + |> list_flows(subject, opts) + end + def list_flows_for(%Gateways.Gateway{} = gateway, %Auth.Subject{} = subject, opts) do Flow.Query.by_gateway_id(gateway.id) |> list_flows(subject, opts) diff --git a/elixir/apps/domain/lib/domain/flows/flow/query.ex b/elixir/apps/domain/lib/domain/flows/flow/query.ex index 0b8d00a19..7b404fda7 100644 --- a/elixir/apps/domain/lib/domain/flows/flow/query.ex +++ b/elixir/apps/domain/lib/domain/flows/flow/query.ex @@ -25,7 +25,19 @@ defmodule Domain.Flows.Flow.Query do where(queryable, [flows: flows], flows.client_id == ^client_id) end + def by_actor_id(queryable \\ all(), actor_id) do + queryable + |> with_joined_client() + |> where([client: client], client.actor_id == ^actor_id) + end + def by_gateway_id(queryable \\ all(), gateway_id) do where(queryable, [flows: flows], flows.gateway_id == ^gateway_id) end + + def with_joined_client(queryable \\ all()) do + with_named_binding(queryable, :client, fn queryable, binding -> + join(queryable, :inner, [flows: flows], client in assoc(flows, ^binding), as: ^binding) + end) + end end diff --git a/elixir/apps/domain/lib/domain/gateways/gateway.ex b/elixir/apps/domain/lib/domain/gateways/gateway.ex index 2831b3f28..b4bd6f96c 100644 --- a/elixir/apps/domain/lib/domain/gateways/gateway.ex +++ b/elixir/apps/domain/lib/domain/gateways/gateway.ex @@ -4,8 +4,7 @@ defmodule Domain.Gateways.Gateway do schema "gateways" do field :external_id, :string - # TODO: hostname - field :name_suffix, :string + field :name, :string field :public_key, :string diff --git a/elixir/apps/domain/lib/domain/gateways/gateway/changeset.ex b/elixir/apps/domain/lib/domain/gateways/gateway/changeset.ex index 029adf9e1..a15597878 100644 --- a/elixir/apps/domain/lib/domain/gateways/gateway/changeset.ex +++ b/elixir/apps/domain/lib/domain/gateways/gateway/changeset.ex @@ -3,7 +3,7 @@ defmodule Domain.Gateways.Gateway.Changeset do alias Domain.Version alias Domain.Gateways - @upsert_fields ~w[external_id name_suffix public_key + @upsert_fields ~w[external_id name public_key last_seen_user_agent last_seen_remote_ip last_seen_remote_ip_location_region @@ -20,8 +20,8 @@ defmodule Domain.Gateways.Gateway.Changeset do last_seen_version last_seen_at updated_at]a - @update_fields ~w[name_suffix]a - @required_fields ~w[external_id name_suffix public_key + @update_fields ~w[name]a + @required_fields ~w[external_id name public_key last_seen_user_agent last_seen_remote_ip]a # WireGuard base64-encoded string length @@ -35,7 +35,7 @@ defmodule Domain.Gateways.Gateway.Changeset do def upsert(%Gateways.Token{} = token, attrs) do %Gateways.Gateway{} |> cast(attrs, @upsert_fields) - |> put_default_value(:name_suffix, fn -> + |> put_default_value(:name, fn -> Domain.Crypto.random_token(5, encoder: :user_friendly) end) |> changeset() @@ -43,7 +43,6 @@ defmodule Domain.Gateways.Gateway.Changeset do |> validate_base64(:public_key) |> validate_length(:public_key, is: @key_length) |> unique_constraint(:public_key, name: :gateways_account_id_public_key_index) - |> unique_constraint(:name_suffix, name: :gateways_account_id_group_id_name_suffix_index) |> unique_constraint(:ipv4) |> unique_constraint(:ipv6) |> put_change(:last_seen_at, DateTime.utc_now()) @@ -76,9 +75,9 @@ defmodule Domain.Gateways.Gateway.Changeset do defp changeset(changeset) do changeset - |> trim_change(:name_suffix) - |> validate_length(:name_suffix, min: 1, max: 8) - |> unique_constraint(:name_suffix, name: :gateways_group_id_name_suffix_index) + |> trim_change(:name) + |> validate_length(:name, min: 1, max: 8) + |> unique_constraint(:name, name: :gateways_group_id_name_index) |> unique_constraint([:public_key]) |> unique_constraint(:external_id) end diff --git a/elixir/apps/domain/lib/domain/gateways/group.ex b/elixir/apps/domain/lib/domain/gateways/group.ex index d1ab90e32..901f6bd17 100644 --- a/elixir/apps/domain/lib/domain/gateways/group.ex +++ b/elixir/apps/domain/lib/domain/gateways/group.ex @@ -2,8 +2,7 @@ defmodule Domain.Gateways.Group do use Domain, :schema schema "gateway_groups" do - # TODO: name - field :name_prefix, :string + field :name, :string belongs_to :account, Domain.Accounts.Account has_many :gateways, Domain.Gateways.Gateway, foreign_key: :group_id, where: [deleted_at: nil] diff --git a/elixir/apps/domain/lib/domain/gateways/group/changeset.ex b/elixir/apps/domain/lib/domain/gateways/group/changeset.ex index 12b9b5199..518b71ed8 100644 --- a/elixir/apps/domain/lib/domain/gateways/group/changeset.ex +++ b/elixir/apps/domain/lib/domain/gateways/group/changeset.ex @@ -3,7 +3,7 @@ defmodule Domain.Gateways.Group.Changeset do alias Domain.{Auth, Accounts} alias Domain.Gateways - @fields ~w[name_prefix]a + @fields ~w[name]a def create(%Accounts.Account{} = account, attrs, %Auth.Subject{} = subject) do %Gateways.Group{account: account} @@ -35,11 +35,11 @@ defmodule Domain.Gateways.Group.Changeset do defp changeset(%Gateways.Group{} = group, attrs) do group |> cast(attrs, @fields) - |> trim_change(:name_prefix) - |> put_default_value(:name_prefix, &Domain.NameGenerator.generate/0) + |> trim_change(:name) + |> put_default_value(:name, &Domain.NameGenerator.generate/0) |> validate_required(@fields) - |> validate_length(:name_prefix, min: 1, max: 64) - |> unique_constraint(:name_prefix, name: :gateway_groups_account_id_name_prefix_index) + |> validate_length(:name, min: 1, max: 64) + |> unique_constraint(:name, name: :gateway_groups_account_id_name_index) end def delete(%Gateways.Group{} = group) do diff --git a/elixir/apps/domain/lib/domain/policies/policy.ex b/elixir/apps/domain/lib/domain/policies/policy.ex index c4babba2f..7b94adf1a 100644 --- a/elixir/apps/domain/lib/domain/policies/policy.ex +++ b/elixir/apps/domain/lib/domain/policies/policy.ex @@ -4,8 +4,8 @@ defmodule Domain.Policies.Policy do schema "policies" do field :description, :string - belongs_to :actor_group, Domain.Actors.Group - belongs_to :resource, Domain.Resources.Resource + belongs_to :actor_group, Domain.Actors.Group, where: [deleted_at: nil] + belongs_to :resource, Domain.Resources.Resource, where: [deleted_at: nil] belongs_to :account, Domain.Accounts.Account field :created_by, Ecto.Enum, values: ~w[identity]a diff --git a/elixir/apps/domain/lib/domain/policies/policy/query.ex b/elixir/apps/domain/lib/domain/policies/policy/query.ex index 312a90b0d..72b14b288 100644 --- a/elixir/apps/domain/lib/domain/policies/policy/query.ex +++ b/elixir/apps/domain/lib/domain/policies/policy/query.ex @@ -4,6 +4,10 @@ defmodule Domain.Policies.Policy.Query do def all do from(policies in Domain.Policies.Policy, as: :policies) |> where([policies: policies], is_nil(policies.deleted_at)) + |> with_joined_actor_group() + |> where([actor_group: actor_group], is_nil(actor_group.deleted_at)) + |> with_joined_resource() + |> where([resource: resource], is_nil(resource.deleted_at)) end def by_id(queryable \\ all(), id) do @@ -45,6 +49,14 @@ defmodule Domain.Policies.Policy.Query do end) end + def with_joined_resource(queryable \\ all()) do + with_named_binding(queryable, :resource, fn queryable, binding -> + join(queryable, :inner, [policies: policies], resource in assoc(policies, ^binding), + as: ^binding + ) + end) + end + def with_joined_memberships(queryable \\ all()) do queryable |> with_joined_actor_group() diff --git a/elixir/apps/domain/priv/repo/migrations/20231113213333_rename_gateway_groups_name_prefix.exs b/elixir/apps/domain/priv/repo/migrations/20231113213333_rename_gateway_groups_name_prefix.exs new file mode 100644 index 000000000..ef4870415 --- /dev/null +++ b/elixir/apps/domain/priv/repo/migrations/20231113213333_rename_gateway_groups_name_prefix.exs @@ -0,0 +1,12 @@ +defmodule Domain.Repo.Migrations.RenameGatewayGroupsNamePrefix do + use Ecto.Migration + + def change do + rename(table(:gateway_groups), :name_prefix, to: :name) + + execute(""" + ALTER INDEX gateway_groups_account_id_name_prefix_index + RENAME TO gateway_groups_account_id_name_index + """) + end +end diff --git a/elixir/apps/domain/priv/repo/migrations/20231113214017_rename_gateway_name_suffix_to_name.exs b/elixir/apps/domain/priv/repo/migrations/20231113214017_rename_gateway_name_suffix_to_name.exs new file mode 100644 index 000000000..b815fb073 --- /dev/null +++ b/elixir/apps/domain/priv/repo/migrations/20231113214017_rename_gateway_name_suffix_to_name.exs @@ -0,0 +1,14 @@ +defmodule Domain.Repo.Migrations.RenameGatewayNameSuffixToName do + use Ecto.Migration + + def change do + rename(table(:gateways), :name_suffix, to: :name) + + drop( + index(:gateways, [:account_id, :group_id, :name_suffix], + unique: true, + where: "deleted_at IS NULL" + ) + ) + end +end diff --git a/elixir/apps/domain/priv/repo/seeds.exs b/elixir/apps/domain/priv/repo/seeds.exs index 8da5da533..e2a6eb57b 100644 --- a/elixir/apps/domain/priv/repo/seeds.exs +++ b/elixir/apps/domain/priv/repo/seeds.exs @@ -342,7 +342,7 @@ IO.puts("") gateway_group = account |> Gateways.Group.Changeset.create( - %{name_prefix: "mycro-aws-gws", tokens: [%{}]}, + %{name: "mycro-aws-gws", tokens: [%{}]}, admin_subject ) |> Repo.insert!() @@ -359,13 +359,13 @@ gateway_group_token = ) IO.puts("Created gateway groups:") -IO.puts(" #{gateway_group.name_prefix} token: #{Gateways.encode_token!(gateway_group_token)}") +IO.puts(" #{gateway_group.name} token: #{Gateways.encode_token!(gateway_group_token)}") IO.puts("") {:ok, gateway1} = Gateways.upsert_gateway(gateway_group_token, %{ external_id: Ecto.UUID.generate(), - name_suffix: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", + name: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", public_key: :crypto.strong_rand_bytes(32) |> Base.encode64(), last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412", last_seen_remote_ip: %Postgrex.INET{address: {189, 172, 73, 153}} @@ -374,7 +374,7 @@ IO.puts("") {:ok, gateway2} = Gateways.upsert_gateway(gateway_group_token, %{ external_id: Ecto.UUID.generate(), - name_suffix: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", + name: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", public_key: :crypto.strong_rand_bytes(32) |> Base.encode64(), last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412", last_seen_remote_ip: %Postgrex.INET{address: {164, 112, 78, 62}} @@ -384,7 +384,7 @@ for i <- 1..10 do {:ok, _gateway} = Gateways.upsert_gateway(gateway_group_token, %{ external_id: Ecto.UUID.generate(), - name_suffix: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", + name: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", public_key: :crypto.strong_rand_bytes(32) |> Base.encode64(), last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412", last_seen_remote_ip: %Postgrex.INET{address: {164, 112, 78, 62 + i}} @@ -392,14 +392,14 @@ for i <- 1..10 do end IO.puts("Created gateways:") -gateway_name = "#{gateway_group.name_prefix}-#{gateway1.name_suffix}" +gateway_name = "#{gateway_group.name}-#{gateway1.name}" IO.puts(" #{gateway_name}:") IO.puts(" External UUID: #{gateway1.external_id}") IO.puts(" Public Key: #{gateway1.public_key}") IO.puts(" IPv4: #{gateway1.ipv4} IPv6: #{gateway1.ipv6}") IO.puts("") -gateway_name = "#{gateway_group.name_prefix}-#{gateway2.name_suffix}" +gateway_name = "#{gateway_group.name}-#{gateway2.name}" IO.puts(" #{gateway_name}:") IO.puts(" External UUID: #{gateway1.external_id}") IO.puts(" Public Key: #{gateway2.public_key}") diff --git a/elixir/apps/domain/test/domain/flows_test.exs b/elixir/apps/domain/test/domain/flows_test.exs index 95c2d722a..809416b95 100644 --- a/elixir/apps/domain/test/domain/flows_test.exs +++ b/elixir/apps/domain/test/domain/flows_test.exs @@ -273,6 +273,7 @@ defmodule Domain.FlowsTest do describe "list_flows_for/2" do test "returns empty list when there are no flows", %{ + actor: actor, client: client, gateway: gateway, resource: resource, @@ -281,11 +282,13 @@ defmodule Domain.FlowsTest do } do assert list_flows_for(policy, subject) == {:ok, []} assert list_flows_for(resource, subject) == {:ok, []} + assert list_flows_for(actor, subject) == {:ok, []} assert list_flows_for(client, subject) == {:ok, []} assert list_flows_for(gateway, subject) == {:ok, []} end test "does not list flows from other accounts", %{ + actor: actor, client: client, gateway: gateway, resource: resource, @@ -296,12 +299,14 @@ defmodule Domain.FlowsTest do assert list_flows_for(policy, subject) == {:ok, []} assert list_flows_for(resource, subject) == {:ok, []} + assert list_flows_for(actor, subject) == {:ok, []} assert list_flows_for(client, subject) == {:ok, []} assert list_flows_for(gateway, subject) == {:ok, []} end - test "returns all authorized flows", %{ + test "returns all authorized flows for a given entity", %{ account: account, + actor: actor, client: client, gateway: gateway, resource: resource, @@ -320,12 +325,33 @@ defmodule Domain.FlowsTest do assert list_flows_for(policy, subject) == {:ok, [flow]} assert list_flows_for(resource, subject) == {:ok, [flow]} + assert list_flows_for(actor, subject) == {:ok, [flow]} assert list_flows_for(client, subject) == {:ok, [flow]} assert list_flows_for(gateway, subject) == {:ok, [flow]} end + test "does not return authorized flow of other entities", %{ + account: account, + actor: actor, + client: client, + gateway: gateway, + resource: resource, + policy: policy, + subject: subject + } do + other_client = Fixtures.Clients.create_client(account: account) + Fixtures.Flows.create_flow(account: account, client: other_client, subject: subject) + + assert list_flows_for(policy, subject) == {:ok, []} + assert list_flows_for(resource, subject) == {:ok, []} + assert list_flows_for(actor, subject) == {:ok, []} + assert list_flows_for(client, subject) == {:ok, []} + assert list_flows_for(gateway, subject) == {:ok, []} + end + test "returns error when subject has no permission to view flows", %{ client: client, + actor: actor, gateway: gateway, resource: resource, policy: policy, @@ -340,6 +366,7 @@ defmodule Domain.FlowsTest do assert list_flows_for(policy, subject) == expected_error assert list_flows_for(resource, subject) == expected_error assert list_flows_for(client, subject) == expected_error + assert list_flows_for(actor, subject) == expected_error assert list_flows_for(gateway, subject) == expected_error end end diff --git a/elixir/apps/domain/test/domain/gateways_test.exs b/elixir/apps/domain/test/domain/gateways_test.exs index a2b7206bd..7cd7eb352 100644 --- a/elixir/apps/domain/test/domain/gateways_test.exs +++ b/elixir/apps/domain/test/domain/gateways_test.exs @@ -121,7 +121,7 @@ defmodule Domain.GatewaysTest do describe "new_group/0" do test "returns group changeset" do assert %Ecto.Changeset{data: %Gateways.Group{}, changes: changes} = new_group() - assert Map.has_key?(changes, :name_prefix) + assert Map.has_key?(changes, :name) assert Enum.count(changes) == 1 end end @@ -134,31 +134,31 @@ defmodule Domain.GatewaysTest do test "returns error on invalid attrs", %{account: account, subject: subject} do attrs = %{ - name_prefix: String.duplicate("A", 65) + name: String.duplicate("A", 65) } assert {:error, changeset} = create_group(attrs, subject) assert errors_on(changeset) == %{ tokens: ["can't be blank"], - name_prefix: ["should be at most 64 character(s)"] + name: ["should be at most 64 character(s)"] } - Fixtures.Gateways.create_group(account: account, name_prefix: "foo") - attrs = %{name_prefix: "foo", tokens: [%{}]} + Fixtures.Gateways.create_group(account: account, name: "foo") + attrs = %{name: "foo", tokens: [%{}]} assert {:error, changeset} = create_group(attrs, subject) - assert "has already been taken" in errors_on(changeset).name_prefix + assert "has already been taken" in errors_on(changeset).name end test "creates a group", %{subject: subject} do attrs = %{ - name_prefix: "foo", + name: "foo", tokens: [%{}] } assert {:ok, group} = create_group(attrs, subject) assert group.id - assert group.name_prefix == "foo" + assert group.name == "foo" assert group.created_by == :identity assert group.created_by_identity_id == subject.identity.id @@ -190,7 +190,7 @@ defmodule Domain.GatewaysTest do assert changeset = change_group(group, group_attrs) assert changeset.valid? - assert changeset.changes == %{name_prefix: group_attrs.name_prefix} + assert changeset.changes == %{name: group_attrs.name} end end @@ -199,41 +199,41 @@ defmodule Domain.GatewaysTest do subject: subject } do group = Fixtures.Gateways.create_group() - attrs = %{name_prefix: nil} + attrs = %{name: nil} assert {:error, changeset} = update_group(group, attrs, subject) - assert errors_on(changeset) == %{name_prefix: ["can't be blank"]} + assert errors_on(changeset) == %{name: ["can't be blank"]} end test "returns error on invalid attrs", %{account: account, subject: subject} do group = Fixtures.Gateways.create_group(account: account) attrs = %{ - name_prefix: String.duplicate("A", 65) + name: String.duplicate("A", 65) } assert {:error, changeset} = update_group(group, attrs, subject) assert errors_on(changeset) == %{ - name_prefix: ["should be at most 64 character(s)"] + name: ["should be at most 64 character(s)"] } - Fixtures.Gateways.create_group(account: account, name_prefix: "foo") - attrs = %{name_prefix: "foo"} + Fixtures.Gateways.create_group(account: account, name: "foo") + attrs = %{name: "foo"} assert {:error, changeset} = update_group(group, attrs, subject) - assert "has already been taken" in errors_on(changeset).name_prefix + assert "has already been taken" in errors_on(changeset).name end test "updates a group", %{account: account, subject: subject} do group = Fixtures.Gateways.create_group(account: account) attrs = %{ - name_prefix: "foo" + name: "foo" } assert {:ok, group} = update_group(group, attrs, subject) - assert group.name_prefix == "foo" + assert group.name == "foo" end test "returns error when subject has no permission to manage groups", %{ @@ -536,7 +536,7 @@ defmodule Domain.GatewaysTest do assert changeset = change_gateway(gateway, gateway_attrs) assert %Ecto.Changeset{data: %Domain.Gateways.Gateway{}} = changeset - assert changeset.changes == %{name_suffix: gateway_attrs.name_suffix} + assert changeset.changes == %{name: gateway_attrs.name} end end @@ -585,7 +585,7 @@ defmodule Domain.GatewaysTest do assert {:ok, gateway} = upsert_gateway(token, attrs) - assert gateway.name_suffix + assert gateway.name assert gateway.public_key == attrs.public_key assert gateway.token_id == token.id @@ -627,7 +627,7 @@ defmodule Domain.GatewaysTest do assert Repo.aggregate(Gateways.Gateway, :count, :id) == 1 - assert updated_gateway.name_suffix + assert updated_gateway.name assert updated_gateway.last_seen_remote_ip.address == attrs.last_seen_remote_ip assert updated_gateway.last_seen_remote_ip != gateway.last_seen_remote_ip assert updated_gateway.last_seen_user_agent == attrs.last_seen_user_agent @@ -710,11 +710,11 @@ defmodule Domain.GatewaysTest do describe "update_gateway/3" do test "updates gateways", %{account: account, subject: subject} do gateway = Fixtures.Gateways.create_gateway(account: account) - attrs = %{name_suffix: "Foo"} + attrs = %{name: "Foo"} assert {:ok, gateway} = update_gateway(gateway, attrs, subject) - assert gateway.name_suffix == attrs.name_suffix + assert gateway.name == attrs.name end test "does not allow to reset required fields to empty values", %{ @@ -722,24 +722,24 @@ defmodule Domain.GatewaysTest do subject: subject } do gateway = Fixtures.Gateways.create_gateway(account: account) - attrs = %{name_suffix: nil} + attrs = %{name: nil} assert {:error, changeset} = update_gateway(gateway, attrs, subject) - assert errors_on(changeset) == %{name_suffix: ["can't be blank"]} + assert errors_on(changeset) == %{name: ["can't be blank"]} end test "returns error on invalid attrs", %{account: account, subject: subject} do gateway = Fixtures.Gateways.create_gateway(account: account) attrs = %{ - name_suffix: String.duplicate("a", 256) + name: String.duplicate("a", 256) } assert {:error, changeset} = update_gateway(gateway, attrs, subject) assert errors_on(changeset) == %{ - name_suffix: ["should be at most 8 character(s)"] + name: ["should be at most 8 character(s)"] } end @@ -749,7 +749,7 @@ defmodule Domain.GatewaysTest do } do gateway = Fixtures.Gateways.create_gateway(account: account) - fields = Gateways.Gateway.__schema__(:fields) -- [:name_suffix] + fields = Gateways.Gateway.__schema__(:fields) -- [:name] value = -1 for field <- fields do diff --git a/elixir/apps/domain/test/domain/policies_test.exs b/elixir/apps/domain/test/domain/policies_test.exs index 1bf805950..2976d2d3c 100644 --- a/elixir/apps/domain/test/domain/policies_test.exs +++ b/elixir/apps/domain/test/domain/policies_test.exs @@ -91,6 +91,38 @@ defmodule Domain.PoliciesTest do assert list_policies(subject) == {:ok, []} end + test "does not list policies for deleted resources", %{account: account, subject: subject} do + resource = + Fixtures.Resources.create_resource(account: account) + |> Fixtures.Resources.delete_resource() + + actor_group = Fixtures.Actors.create_group(account: account) + + Fixtures.Policies.create_policy( + account: account, + resource: resource, + actor_group: actor_group + ) + + assert list_policies(subject) == {:ok, []} + end + + test "does not list policies for deleted actor groups", %{account: account, subject: subject} do + resource = Fixtures.Resources.create_resource(account: account) + + actor_group = + Fixtures.Actors.create_group(account: account) + |> Fixtures.Actors.delete_group() + + Fixtures.Policies.create_policy( + account: account, + resource: resource, + actor_group: actor_group + ) + + assert list_policies(subject) == {:ok, []} + end + test "returns all policies for account admin subject", %{account: account} do actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) identity = Fixtures.Auth.create_identity(account: account, actor: actor) diff --git a/elixir/apps/domain/test/support/fixtures/gateways.ex b/elixir/apps/domain/test/support/fixtures/gateways.ex index 388f1153a..c0acca920 100644 --- a/elixir/apps/domain/test/support/fixtures/gateways.ex +++ b/elixir/apps/domain/test/support/fixtures/gateways.ex @@ -4,7 +4,7 @@ defmodule Domain.Fixtures.Gateways do def group_attrs(attrs \\ %{}) do Enum.into(attrs, %{ - name_prefix: "group-#{unique_integer()}", + name: "group-#{unique_integer()}", tokens: [%{}] }) end @@ -71,7 +71,7 @@ defmodule Domain.Fixtures.Gateways do def gateway_attrs(attrs \\ %{}) do Enum.into(attrs, %{ external_id: Ecto.UUID.generate(), - name_suffix: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", + name: "gw-#{Domain.Crypto.random_token(5, encoder: :user_friendly)}", public_key: unique_public_key(), last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412", last_seen_remote_ip: %Postgrex.INET{address: {189, 172, 73, 153}}, diff --git a/elixir/apps/web/assets/js/hooks.js b/elixir/apps/web/assets/js/hooks.js index 4cffbcb91..3c806dc1c 100644 --- a/elixir/apps/web/assets/js/hooks.js +++ b/elixir/apps/web/assets/js/hooks.js @@ -13,14 +13,22 @@ Hooks.Copy = { let doc = new DOMParser().parseFromString(inner_html, "text/html"); let text = doc.documentElement.textContent; - let cl = ev.currentTarget.querySelector("[data-icon]").classList + let content = ev.currentTarget.querySelector("[data-content]") + let icon_cl = ev.currentTarget.querySelector("[data-icon]").classList navigator.clipboard.writeText(text).then(() => { - cl.add("hero-clipboard-document-check"); - cl.add("text-green-500"); - cl.remove("hero-clipboard-document"); - cl.remove("text-gray-500"); - }) + icon_cl.add("hero-clipboard-document-check"); + icon_cl.add("text-green-500"); + icon_cl.remove("hero-clipboard-document"); + content.innerHTML = "Copied" + }); + + setTimeout(() => { + icon_cl.remove("hero-clipboard-document-check"); + icon_cl.remove("text-green-500"); + icon_cl.add("hero-clipboard-document"); + content.innerHTML = "Copy" + }, 2000); }); }, } diff --git a/elixir/apps/web/lib/web/auth.ex b/elixir/apps/web/lib/web/auth.ex index c4a9ceaff..7ff426ab1 100644 --- a/elixir/apps/web/lib/web/auth.ex +++ b/elixir/apps/web/lib/web/auth.ex @@ -2,6 +2,18 @@ defmodule Web.Auth do use Web, :verified_routes alias Domain.{Auth, Accounts} + # This is the cookie which will store recent account ids + # that the user has signed in to. + @remember_me_cookie_name "fz_recent_account_ids" + @remember_me_cookie_options [ + sign: true, + max_age: 365 * 24 * 60 * 60, + same_site: "Lax", + secure: true, + http_only: true + ] + @remember_last_account_ids 5 + def signed_in_path(%Auth.Subject{actor: %{type: :account_admin_user}} = subject) do ~p"/#{subject.account}/sites" end @@ -132,6 +144,37 @@ defmodule Web.Auth do |> Plug.Conn.put_session(:preferred_locale, preferred_locale) end + ########################### + ## Controller Helpers + ########################### + + def list_recent_account_ids(conn) do + conn = Plug.Conn.fetch_cookies(conn, signed: [@remember_me_cookie_name]) + + if recent_account_ids = Map.get(conn.cookies, @remember_me_cookie_name) do + {:ok, :erlang.binary_to_term(recent_account_ids, [:safe]), conn} + else + {:ok, [], conn} + end + end + + def update_recent_account_ids(conn, callback) when is_function(callback, 1) do + {:ok, recent_account_ids, conn} = list_recent_account_ids(conn) + + recent_account_ids = + recent_account_ids + |> callback.() + |> Enum.take(@remember_last_account_ids) + |> :erlang.term_to_binary() + + Plug.Conn.put_resp_cookie( + conn, + @remember_me_cookie_name, + recent_account_ids, + @remember_me_cookie_options + ) + end + ########################### ## Plugs ########################### diff --git a/elixir/apps/web/lib/web/components/core_components.ex b/elixir/apps/web/lib/web/components/core_components.ex index f25c7043c..b7f1bf3d2 100644 --- a/elixir/apps/web/lib/web/components/core_components.ex +++ b/elixir/apps/web/lib/web/components/core_components.ex @@ -70,12 +70,23 @@ defmodule Web.CoreComponents do data-copy phx-no-format ><%= render_slot(@inner_block) %> - <.icon name="hero-clipboard-document" data-icon class={~w[ + + + cursor-pointer + rounded + px-2 + focus:ring-4 focus:outline-none + text-white + bg-accent-400 + hover:bg-accent-500 + focus:ring-accent-300 + ]}> + <.icon name="hero-clipboard-document" data-icon class="h-4 w-4" /> + Copy + """ end @@ -486,6 +497,27 @@ defmodule Web.CoreComponents do """ end + def icon(%{name: "spinner"} = assigns) do + ~H""" + + + + + + + """ + end + @doc """ Renders Gravatar img tag. """ @@ -717,6 +749,40 @@ defmodule Web.CoreComponents do """ end + attr :navigate, :string, required: true + attr :connected?, :boolean, required: true + attr :type, :string, required: true + + def initial_connection_status(assigns) do + ~H""" + <.link + class={[ + "mx-4 my-6 h-8", + "flex items-center justify-center", + "font-medium text-sm text-white", + "rounded-full", + (@connected? && "bg-green-500") || "bg-orange-400 cursor-progress" + ]} + navigate={@navigate} + { + if @connected? do + %{} + else + %{"data-confirm" => "Do you want to skip waiting for #{@type} to be connected?"} + end + } + > + + <.icon name="spinner" class="animate-spin h-3.5 w-3.5 mr-1" /> Waiting for connection... + + + + <.icon name="hero-check" class="h-3.5 w-3.5" /> Connected, click to continue + + + """ + end + @doc """ Renders creation timestamp and entity. """ diff --git a/elixir/apps/web/lib/web/controllers/auth_controller.ex b/elixir/apps/web/lib/web/controllers/auth_controller.ex index 7f31db845..0617b34c6 100644 --- a/elixir/apps/web/lib/web/controllers/auth_controller.ex +++ b/elixir/apps/web/lib/web/controllers/auth_controller.ex @@ -3,17 +3,6 @@ defmodule Web.AuthController do alias Web.Auth alias Domain.Auth.Adapters.OpenIDConnect - # This is the cookie which will store recent account ids - # that the user has signed in to. - @remember_me_cookie_name "fz_recent_account_ids" - @remember_me_cookie_options [ - sign: true, - max_age: 365 * 24 * 60 * 60, - same_site: "Lax", - secure: true, - http_only: true - ] - # This is the cookie which will be used to store the # state and code verifier for OpenID Connect IdP's @state_cookie_key_prefix "fz_auth_state_" @@ -306,7 +295,6 @@ defmodule Web.AuthController do Domain.Auth.sign_out(subject.identity, url(~p"/#{account_id_or_slug}?#{redirect_params}")) conn - |> delete_recent_account() |> Auth.sign_out() |> redirect(external: redirect_url) end @@ -319,42 +307,12 @@ defmodule Web.AuthController do |> redirect(to: ~p"/#{account_id_or_slug}?#{redirect_params}") end - defp delete_recent_account(%{assigns: %{subject: subject}} = conn) do - update_recent_accounts(conn, fn recent_account_ids -> - recent_account_ids -- [subject.account.id] - end) - end - defp persist_recent_account(conn, %Domain.Accounts.Account{} = account) do - update_recent_accounts(conn, fn recent_account_ids -> + Auth.update_recent_account_ids(conn, fn recent_account_ids -> [account.id] ++ recent_account_ids end) end - defp update_recent_accounts(conn, callback) when is_function(callback, 1) do - conn = fetch_cookies(conn, signed: [@remember_me_cookie_name]) - - recent_account_ids = - if recent_account_ids = Map.get(conn.cookies, @remember_me_cookie_name) do - :erlang.binary_to_term(recent_account_ids, [:safe]) - else - [] - end - - recent_account_ids = - recent_account_ids - |> callback.() - |> Enum.take(5) - |> :erlang.term_to_binary() - - put_resp_cookie( - conn, - @remember_me_cookie_name, - recent_account_ids, - @remember_me_cookie_options - ) - end - defp take_non_empty_params(map, keys) do map |> Map.take(keys) |> Map.reject(fn {_key, value} -> value in ["", nil] end) end diff --git a/elixir/apps/web/lib/web/controllers/home_controller.ex b/elixir/apps/web/lib/web/controllers/home_controller.ex new file mode 100644 index 000000000..7e4754e3a --- /dev/null +++ b/elixir/apps/web/lib/web/controllers/home_controller.ex @@ -0,0 +1,27 @@ +defmodule Web.HomeController do + use Web, :controller + alias Domain.Accounts + + def home(conn, _params) do + {accounts, conn} = + with {:ok, recent_account_ids, conn} <- Web.Auth.list_recent_account_ids(conn), + {:ok, accounts} <- Accounts.list_accounts_by_ids(recent_account_ids) do + conn = + Web.Auth.update_recent_account_ids(conn, fn _recent_account_ids -> + Enum.map(accounts, & &1.id) + end) + + {accounts, conn} + else + _other -> {[], conn} + end + + conn + |> put_layout(html: {Web.Layouts, :public}) + |> render("home.html", accounts: accounts) + end + + def redirect_to_sign_in(conn, %{"account_id_or_slug" => account_id_or_slug}) do + redirect(conn, to: ~p"/#{account_id_or_slug}") + end +end diff --git a/elixir/apps/web/lib/web/controllers/home_html.ex b/elixir/apps/web/lib/web/controllers/home_html.ex new file mode 100644 index 000000000..1395ab07a --- /dev/null +++ b/elixir/apps/web/lib/web/controllers/home_html.ex @@ -0,0 +1,81 @@ +defmodule Web.HomeHTML do + use Web, :html + + def home(assigns) do + ~H""" +
+
+ <.logo /> + +
+
+

+ Welcome to Firezone +

+ +

+ Recently used accounts +

+ +
+ <.account_button :for={account <- @accounts} account={account} /> +
+ + <.separator if={@accounts != []} /> + + <.form :let={f} for={%{}} action={~p"/"} class="space-y-4 lg:space-y-6"> + <.input + field={f[:account_id_or_slug]} + type="text" + label="Account ID or Slug" + placeholder={~s|As shown in your "Welcome to Firezone" email|} + required + /> + + <.button class="w-full"> + Go to Sign In page + + +

+ Don't have an account? + + Sign up here. + +

+
+
+
+
+ """ + end + + def account_button(assigns) do + ~H""" + + <%= @account.name %> + + """ + end + + def separator(assigns) do + ~H""" +
+
+
or
+
+
+ """ + end +end diff --git a/elixir/apps/web/lib/web/controllers/redirect_controller.ex b/elixir/apps/web/lib/web/controllers/redirect_controller.ex deleted file mode 100644 index 5fe2af21b..000000000 --- a/elixir/apps/web/lib/web/controllers/redirect_controller.ex +++ /dev/null @@ -1,7 +0,0 @@ -defmodule Web.RedirectController do - use Web, :controller - - def home(conn, _params) do - redirect(conn, external: "https://firezone.dev") - end -end diff --git a/elixir/apps/web/lib/web/live/actors/edit.ex b/elixir/apps/web/lib/web/live/actors/edit.ex index f629ae5b4..3981703f7 100644 --- a/elixir/apps/web/lib/web/live/actors/edit.ex +++ b/elixir/apps/web/lib/web/live/actors/edit.ex @@ -75,7 +75,7 @@ defmodule Web.Actors.Edit do attrs = map_actor_form_memberships_attr(attrs) with {:ok, actor} <- Actors.update_actor(socket.assigns.actor, attrs, socket.assigns.subject) do - socket = redirect(socket, to: ~p"/#{socket.assigns.account}/actors/#{actor}") + socket = push_navigate(socket, to: ~p"/#{socket.assigns.account}/actors/#{actor}") {:noreply, socket} else {:error, :unauthorized} -> diff --git a/elixir/apps/web/lib/web/live/actors/service_accounts/new.ex b/elixir/apps/web/lib/web/live/actors/service_accounts/new.ex index 6c887766d..3c79f25a2 100644 --- a/elixir/apps/web/lib/web/live/actors/service_accounts/new.ex +++ b/elixir/apps/web/lib/web/live/actors/service_accounts/new.ex @@ -79,7 +79,7 @@ defmodule Web.Actors.ServiceAccounts.New do socket.assigns.subject ) do socket = - redirect(socket, + push_navigate(socket, to: ~p"/#{socket.assigns.account}/actors/service_accounts/#{actor}/new_identity" ) diff --git a/elixir/apps/web/lib/web/live/actors/show.ex b/elixir/apps/web/lib/web/live/actors/show.ex index ea6df9801..15f0a5859 100644 --- a/elixir/apps/web/lib/web/live/actors/show.ex +++ b/elixir/apps/web/lib/web/live/actors/show.ex @@ -1,7 +1,7 @@ defmodule Web.Actors.Show do use Web, :live_view import Web.Actors.Components - alias Domain.Auth + alias Domain.{Auth, Flows} alias Domain.Actors def mount(%{"id" => id}, _session, socket) do @@ -9,10 +9,21 @@ defmodule Web.Actors.Show do Actors.fetch_actor_by_id(id, socket.assigns.subject, preload: [ identities: [:provider, created_by_identity: [:actor]], - groups: [:provider] + groups: [:provider], + clients: [] ] + ), + {:ok, flows} <- + Flows.list_flows_for(actor, socket.assigns.subject, + preload: [gateway: [:group], client: [], policy: [:resource, :actor_group]] ) do - {:ok, assign(socket, actor: actor), temporary_assigns: [page_title: actor.name]} + {:ok, + assign(socket, + actor: actor, + flows: flows, + page_title: actor.name, + flow_activities_enabled?: Domain.Config.flow_activities_enabled?() + )} else {:error, _reason} -> raise Web.LiveErrors.NotFoundError end @@ -30,6 +41,7 @@ defmodule Web.Actors.Show do <.section> <:title> <%= actor_type(@actor.type) %>: <%= @actor.name %> + (you) <:action> <.edit_button navigate={~p"/#{@account}/actors/#{@actor}/edit"}> @@ -145,6 +157,80 @@ defmodule Web.Actors.Show do + <.section> + <:title>Clients + + <:content> + <.table id="clients" rows={@actor.clients} row_id={&"client-#{&1.id}"}> + <:col :let={client} label="NAME"> + <.link + navigate={~p"/#{@account}/clients/#{client.id}"} + class="font-medium text-blue-600 dark:text-blue-500 hover:underline" + > + <%= client.name %> + + + <:col :let={client} label="STATUS"> + <.connection_status schema={client} /> + + <:empty> +
No clients to display
+
+ Clients are created automatically when user connects to a resource. +
+ + + + + + <.section> + <:title>Authorizations + <:content> + <.table id="flows" rows={@flows} row_id={&"flows-#{&1.id}"}> + <:col :let={flow} label="AUTHORIZED AT"> + <.relative_datetime datetime={flow.inserted_at} /> + + <:col :let={flow} label="EXPIRES AT"> + <.relative_datetime datetime={flow.expires_at} /> + + <:col :let={flow} label="POLICY"> + <.link + navigate={~p"/#{@account}/policies/#{flow.policy_id}"} + class="font-medium text-blue-600 dark:text-blue-500 hover:underline" + > + + + + <:col :let={flow} label="CLIENT (IP)"> + <.link navigate={~p"/#{@account}/clients/#{flow.client_id}"} class={link_style()}> + <%= flow.client.name %> + + (<%= flow.client_remote_ip %>) + + <:col :let={flow} label="GATEWAY (IP)"> + <.link + navigate={~p"/#{@account}/gateways/#{flow.gateway_id}"} + class="font-medium text-blue-600 dark:text-blue-500 hover:underline" + > + <%= flow.gateway.group.name %>-<%= flow.gateway.name %> + + (<%= flow.gateway_remote_ip %>) + + <:col :let={flow} :if={@flow_activities_enabled?} label="ACTIVITY"> + <.link + navigate={~p"/#{@account}/flows/#{flow.id}"} + class="font-medium text-blue-600 dark:text-blue-500 hover:underline" + > + Show + + + <:empty> +
No authorizations to display
+ + + + + <.danger_zone> <:action> <.delete_button @@ -184,7 +270,7 @@ defmodule Web.Actors.Show do def handle_event("delete", _params, socket) do with {:ok, _actor} <- Actors.delete_actor(socket.assigns.actor, socket.assigns.subject) do - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/actors")} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/actors")} else {:error, :cant_delete_the_last_admin} -> {:noreply, put_flash(socket, :error, "You can't delete the last admin of an account.")} @@ -196,7 +282,8 @@ defmodule Web.Actors.Show do actor = %{ actor | identities: socket.assigns.actor.identities, - groups: socket.assigns.actor.groups + groups: socket.assigns.actor.groups, + clients: socket.assigns.actor.clients } socket = @@ -217,7 +304,8 @@ defmodule Web.Actors.Show do actor = %{ actor | identities: socket.assigns.actor.identities, - groups: socket.assigns.actor.groups + groups: socket.assigns.actor.groups, + clients: socket.assigns.actor.clients } socket = @@ -235,11 +323,16 @@ defmodule Web.Actors.Show do {:ok, actor} = Actors.fetch_actor_by_id(socket.assigns.actor.id, socket.assigns.subject, preload: [ - identities: [:provider, created_by_identity: [:actor]], - groups: [] + identities: [:provider, created_by_identity: [:actor]] ] ) + actor = %{ + actor + | groups: socket.assigns.actor.groups, + clients: socket.assigns.actor.clients + } + socket = socket |> put_flash(:info, "Identity was deleted.") diff --git a/elixir/apps/web/lib/web/live/actors/users/new.ex b/elixir/apps/web/lib/web/live/actors/users/new.ex index f404acbb6..254bd81cb 100644 --- a/elixir/apps/web/lib/web/live/actors/users/new.ex +++ b/elixir/apps/web/lib/web/live/actors/users/new.ex @@ -69,7 +69,9 @@ defmodule Web.Actors.Users.New do socket.assigns.subject ) do socket = - redirect(socket, to: ~p"/#{socket.assigns.account}/actors/users/#{actor}/new_identity") + push_navigate(socket, + to: ~p"/#{socket.assigns.account}/actors/users/#{actor}/new_identity" + ) {:noreply, socket} else diff --git a/elixir/apps/web/lib/web/live/actors/users/new_identity.ex b/elixir/apps/web/lib/web/live/actors/users/new_identity.ex index b7cce2848..f4c13101d 100644 --- a/elixir/apps/web/lib/web/live/actors/users/new_identity.ex +++ b/elixir/apps/web/lib/web/live/actors/users/new_identity.ex @@ -102,7 +102,9 @@ defmodule Web.Actors.Users.NewIdentity do attrs, socket.assigns.subject ) do - socket = redirect(socket, to: ~p"/#{socket.assigns.account}/actors/#{socket.assigns.actor}") + socket = + push_navigate(socket, to: ~p"/#{socket.assigns.account}/actors/#{socket.assigns.actor}") + {:noreply, socket} else {:error, changeset} -> diff --git a/elixir/apps/web/lib/web/live/clients/edit.ex b/elixir/apps/web/lib/web/live/clients/edit.ex index 8649ca825..38e73b6e2 100644 --- a/elixir/apps/web/lib/web/live/clients/edit.ex +++ b/elixir/apps/web/lib/web/live/clients/edit.ex @@ -57,7 +57,7 @@ defmodule Web.Clients.Edit do def handle_event("submit", %{"client" => attrs}, socket) do with {:ok, client} <- Clients.update_client(socket.assigns.client, attrs, socket.assigns.subject) do - socket = redirect(socket, to: ~p"/#{socket.assigns.account}/clients/#{client}") + socket = push_navigate(socket, to: ~p"/#{socket.assigns.account}/clients/#{client}") {:noreply, socket} else {:error, changeset} -> diff --git a/elixir/apps/web/lib/web/live/clients/show.ex b/elixir/apps/web/lib/web/live/clients/show.ex index 799f8b3e3..0732a14e2 100644 --- a/elixir/apps/web/lib/web/live/clients/show.ex +++ b/elixir/apps/web/lib/web/live/clients/show.ex @@ -124,7 +124,7 @@ defmodule Web.Clients.Show do navigate={~p"/#{@account}/gateways/#{flow.gateway_id}"} class="font-medium text-blue-600 dark:text-blue-500 hover:underline" > - <%= flow.gateway.group.name_prefix %>-<%= flow.gateway.name_suffix %> + <%= flow.gateway.group.name %>-<%= flow.gateway.name %> (<%= flow.gateway_remote_ip %>) @@ -162,6 +162,6 @@ defmodule Web.Clients.Show do def handle_event("delete", _params, socket) do {:ok, _client} = Clients.delete_client(socket.assigns.client, socket.assigns.subject) - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/clients")} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/clients")} end end diff --git a/elixir/apps/web/lib/web/live/flows/show.ex b/elixir/apps/web/lib/web/live/flows/show.ex index 5710e51f3..87f7fe78e 100644 --- a/elixir/apps/web/lib/web/live/flows/show.ex +++ b/elixir/apps/web/lib/web/live/flows/show.ex @@ -76,7 +76,7 @@ defmodule Web.Flows.Show do <:label>Gateway <:value> <.link navigate={~p"/#{@account}/gateways/#{@flow.gateway_id}"} class={link_style()}> - <%= @flow.gateway.group.name_prefix %>-<%= @flow.gateway.name_suffix %> + <%= @flow.gateway.group.name %>-<%= @flow.gateway.name %>
Remote IP: <%= @flow.gateway_remote_ip %> diff --git a/elixir/apps/web/lib/web/live/gateways/show.ex b/elixir/apps/web/lib/web/live/gateways/show.ex index 1246489f7..a8547d07e 100644 --- a/elixir/apps/web/lib/web/live/gateways/show.ex +++ b/elixir/apps/web/lib/web/live/gateways/show.ex @@ -32,18 +32,18 @@ defmodule Web.Gateways.Show do <.breadcrumbs account={@account}> <.breadcrumb path={~p"/#{@account}/sites"}>Sites <.breadcrumb path={~p"/#{@account}/sites/#{@gateway.group}"}> - <%= @gateway.group.name_prefix %> + <%= @gateway.group.name %> <.breadcrumb path={~p"/#{@account}/sites/#{@gateway.group}?#gateways"}> Gateways <.breadcrumb path={~p"/#{@account}/gateways/#{@gateway}"}> - <%= @gateway.name_suffix %> + <%= @gateway.name %> <.section> <:title> - Gateway: <%= @gateway.name_suffix %> + Gateway: <%= @gateway.name %> <:content> <.vertical_table id="gateway"> @@ -54,13 +54,13 @@ defmodule Web.Gateways.Show do navigate={~p"/#{@account}/sites/#{@gateway.group}"} class="font-bold text-blue-600 dark:text-blue-500 hover:underline" > - <%= @gateway.group.name_prefix %> + <%= @gateway.group.name %> <.vertical_table_row> - <:label>Instance Name - <:value><%= @gateway.name_suffix %> + <:label>Name + <:value><%= @gateway.name %> <.vertical_table_row :if={@todos_enabled?}> <:label>Connectivity @@ -203,7 +203,7 @@ defmodule Web.Gateways.Show do {:ok, _gateway} = Gateways.delete_gateway(socket.assigns.gateway, socket.assigns.subject) socket = - redirect(socket, + push_navigate(socket, to: ~p"/#{socket.assigns.account}/sites/#{socket.assigns.gateway.group}" ) diff --git a/elixir/apps/web/lib/web/live/groups/edit.ex b/elixir/apps/web/lib/web/live/groups/edit.ex index 3d4d8d52a..39643ba30 100644 --- a/elixir/apps/web/lib/web/live/groups/edit.ex +++ b/elixir/apps/web/lib/web/live/groups/edit.ex @@ -58,7 +58,7 @@ defmodule Web.Groups.Edit do def handle_event("submit", %{"group" => attrs}, socket) do with {:ok, group} <- Actors.update_group(socket.assigns.group, attrs, socket.assigns.subject) do - socket = redirect(socket, to: ~p"/#{socket.assigns.account}/groups/#{group}") + socket = push_navigate(socket, to: ~p"/#{socket.assigns.account}/groups/#{group}") {:noreply, socket} else {:error, changeset} -> diff --git a/elixir/apps/web/lib/web/live/groups/edit_actors.ex b/elixir/apps/web/lib/web/live/groups/edit_actors.ex index bd7c4bf86..d70280093 100644 --- a/elixir/apps/web/lib/web/live/groups/edit_actors.ex +++ b/elixir/apps/web/lib/web/live/groups/edit_actors.ex @@ -143,7 +143,7 @@ defmodule Web.Groups.EditActors do with {:ok, group} <- Actors.update_group(socket.assigns.group, attrs, socket.assigns.subject) do - socket = redirect(socket, to: ~p"/#{socket.assigns.account}/groups/#{group}") + socket = push_navigate(socket, to: ~p"/#{socket.assigns.account}/groups/#{group}") {:noreply, socket} else {:error, changeset} -> diff --git a/elixir/apps/web/lib/web/live/groups/new.ex b/elixir/apps/web/lib/web/live/groups/new.ex index b421608c9..75bafff32 100644 --- a/elixir/apps/web/lib/web/live/groups/new.ex +++ b/elixir/apps/web/lib/web/live/groups/new.ex @@ -51,7 +51,9 @@ defmodule Web.Groups.New do def handle_event("submit", %{"group" => attrs}, socket) do with {:ok, group} <- Actors.create_group(attrs, socket.assigns.subject) do - socket = redirect(socket, to: ~p"/#{socket.assigns.account}/groups/#{group}/edit_actors") + socket = + push_navigate(socket, to: ~p"/#{socket.assigns.account}/groups/#{group}/edit_actors") + {:noreply, socket} else {:error, changeset} -> diff --git a/elixir/apps/web/lib/web/live/groups/show.ex b/elixir/apps/web/lib/web/live/groups/show.ex index dc985a131..d1c86ce7c 100644 --- a/elixir/apps/web/lib/web/live/groups/show.ex +++ b/elixir/apps/web/lib/web/live/groups/show.ex @@ -116,6 +116,6 @@ defmodule Web.Groups.Show do def handle_event("delete", _params, socket) do {:ok, _group} = Actors.delete_group(socket.assigns.group, socket.assigns.subject) - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/groups")} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/groups")} end end diff --git a/elixir/apps/web/lib/web/live/policies/edit.ex b/elixir/apps/web/lib/web/live/policies/edit.ex index e8862f00e..4318afb14 100644 --- a/elixir/apps/web/lib/web/live/policies/edit.ex +++ b/elixir/apps/web/lib/web/live/policies/edit.ex @@ -69,7 +69,7 @@ defmodule Web.Policies.Edit do def handle_event("submit", %{"policy" => policy_params}, socket) do with {:ok, policy} <- Policies.update_policy(socket.assigns.policy, policy_params, socket.assigns.subject) do - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/policies/#{policy}")} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/policies/#{policy}")} else {:error, changeset} -> {:noreply, assign(socket, form: to_form(changeset))} diff --git a/elixir/apps/web/lib/web/live/policies/new.ex b/elixir/apps/web/lib/web/live/policies/new.ex index c1b7ea000..5ad32ef7d 100644 --- a/elixir/apps/web/lib/web/live/policies/new.ex +++ b/elixir/apps/web/lib/web/live/policies/new.ex @@ -53,7 +53,7 @@ defmodule Web.Policies.New do type="select" options={ Enum.map(@resources, fn resource -> - group_names = resource.gateway_groups |> Enum.map(& &1.name_prefix) + group_names = resource.gateway_groups |> Enum.map(& &1.name) [ key: "#{resource.name} - #{Enum.join(group_names, ",")}", @@ -103,7 +103,7 @@ defmodule Web.Policies.New do {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/sites/#{site_id}?#resources")} else - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/policies/#{policy}")} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/policies/#{policy}")} end else {:error, %Ecto.Changeset{} = changeset} -> diff --git a/elixir/apps/web/lib/web/live/policies/show.ex b/elixir/apps/web/lib/web/live/policies/show.ex index ad988c150..ef109d962 100644 --- a/elixir/apps/web/lib/web/live/policies/show.ex +++ b/elixir/apps/web/lib/web/live/policies/show.ex @@ -124,7 +124,7 @@ defmodule Web.Policies.Show do <:col :let={flow} label="GATEWAY (IP)"> <.link navigate={~p"/#{@account}/gateways/#{flow.gateway_id}"} class={link_style()}> - <%= flow.gateway.group.name_prefix %>-<%= flow.gateway.name_suffix %> + <%= flow.gateway.group.name %>-<%= flow.gateway.name %> (<%= flow.gateway_remote_ip %>) diff --git a/elixir/apps/web/lib/web/live/relay_groups/edit.ex b/elixir/apps/web/lib/web/live/relay_groups/edit.ex index b0265d033..f825758eb 100644 --- a/elixir/apps/web/lib/web/live/relay_groups/edit.ex +++ b/elixir/apps/web/lib/web/live/relay_groups/edit.ex @@ -59,7 +59,7 @@ defmodule Web.RelayGroups.Edit do def handle_event("submit", %{"group" => attrs}, socket) do with {:ok, group} <- Relays.update_group(socket.assigns.group, attrs, socket.assigns.subject) do - socket = redirect(socket, to: ~p"/#{socket.assigns.account}/relay_groups/#{group}") + socket = push_navigate(socket, to: ~p"/#{socket.assigns.account}/relay_groups/#{group}") {:noreply, socket} else {:error, changeset} -> diff --git a/elixir/apps/web/lib/web/live/relay_groups/new.ex b/elixir/apps/web/lib/web/live/relay_groups/new.ex index 934a4e231..adbafad90 100644 --- a/elixir/apps/web/lib/web/live/relay_groups/new.ex +++ b/elixir/apps/web/lib/web/live/relay_groups/new.ex @@ -4,7 +4,7 @@ defmodule Web.RelayGroups.New do def mount(_params, _session, socket) do changeset = Relays.new_group() - {:ok, assign(socket, form: to_form(changeset), group: nil)} + {:ok, assign(socket, form: to_form(changeset))} end def render(assigns) do @@ -14,15 +14,12 @@ defmodule Web.RelayGroups.New do <.breadcrumb path={~p"/#{@account}/relay_groups/new"}>Add <.section> - <:title :if={is_nil(@group)}> + <:title> Add a new Relay Instance Group - <:title :if={not is_nil(@group)}> - Deploy your Relay Instance - <:content>
- <.form :if={is_nil(@group)} for={@form} phx-change={:change} phx-submit={:submit}> + <.form for={@form} phx-change={:change} phx-submit={:submit}>
<.input @@ -38,71 +35,6 @@ defmodule Web.RelayGroups.New do Save - -
-
- Select deployment method: -
- <.tabs id="deployment-instructions" phx-update="ignore"> - <:tab id="docker-instructions" label="Docker"> -

- Copy-paste this command to your server and replace PUBLIC_IP4_ADDR - and PUBLIC_IP6_ADDR - with your public IP addresses: -

- - <.code_block id="code-sample-docker" class="w-full rounded-b" phx-no-format><%= docker_command(@env) %> - - <:tab id="systemd-instructions" label="Systemd"> -

- 1. Create a systemd unit file with the following content: -

- - <.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo nano /etc/systemd/system/firezone-relay.service - -

- 2. Copy-paste the following content into the file and replace - PUBLIC_IP4_ADDR - and PUBLIC_IP6_ADDR - with your public IP addresses:: -

- - <.code_block id="code-sample-systemd" class="w-full rounded-b" phx-no-format><%= systemd_command(@env) %> - -

- 3. Save by pressing Ctrl+X, then Y, then Enter. -

- -

- 4. Reload systemd configuration: -

- - <.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl daemon-reload - -

- 5. Start the service: -

- - <.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl start firezone-relay - -

- 6. Enable the service to start on boot: -

- - <.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl enable firezone-relay - -

- 7. Check the status of the service: -

- - <.code_block id="code-sample-systemd" class="w-full rounded-b" phx-no-format>sudo systemctl status firezone-relay - - - -
- Waiting for relay connection... -
-
@@ -122,121 +54,10 @@ defmodule Web.RelayGroups.New do with {:ok, group} <- Relays.create_group(attrs, socket.assigns.subject) do - :ok = Relays.subscribe_for_relays_presence_in_group(group) - token = encode_group_token(group) - {:noreply, assign(socket, group: group, env: env(token))} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/relay_groups/#{group}")} else {:error, changeset} -> {:noreply, assign(socket, form: to_form(changeset))} end end - - def handle_info(%Phoenix.Socket.Broadcast{topic: "relay_groups:" <> _account_id}, socket) do - socket = - push_redirect(socket, - to: ~p"/#{socket.assigns.account}/relay_groups/#{socket.assigns.group}" - ) - - {:noreply, socket} - end - - defp version do - vsn = - Application.spec(:domain) - |> Keyword.fetch!(:vsn) - |> List.to_string() - |> Version.parse!() - - "#{vsn.major}.#{vsn.minor}" - end - - defp env(token) do - api_url_override = - if api_url = Domain.Config.get_env(:web, :api_url_override) do - {"FIREZONE_API_URL", api_url} - end - - [ - {"FIREZONE_ID", Ecto.UUID.generate()}, - {"FIREZONE_TOKEN", token}, - {"PUBLIC_IP4_ADDR", "YOU_MUST_SET_THIS_VALUE"}, - {"PUBLIC_IP6_ADDR", "YOU_MUST_SET_THIS_VALUE"}, - api_url_override, - {"RUST_LOG", "warn"}, - {"LOG_FORMAT", "google-cloud"} - ] - |> Enum.reject(&is_nil/1) - end - - defp docker_command(env) do - [ - "docker run -d", - "--restart=unless-stopped", - "--pull=always", - "--health-cmd=\"lsof -i UDP | grep firezone-relay\"", - "--name=firezone-relay", - "--cap-add=NET_ADMIN", - "--sysctl net.ipv4.ip_forward=1", - "--sysctl net.ipv4.conf.all.src_valid_mark=1", - "--sysctl net.ipv6.conf.all.disable_ipv6=0", - "--sysctl net.ipv6.conf.all.forwarding=1", - "--sysctl net.ipv6.conf.default.forwarding=1", - "--device=\"/dev/net/tun:/dev/net/tun\"", - Enum.map(env, fn {key, value} -> "--env #{key}=\"#{value}\"" end), - "--env FIREZONE_HOSTNAME=$(hostname)", - "#{Domain.Config.fetch_env!(:domain, :docker_registry)}/relay:#{version()}" - ] - |> List.flatten() - |> Enum.join(" \\\n ") - end - - defp systemd_command(env) do - """ - [Unit] - Description=Firezone Relay - After=network.target - - [Service] - Type=simple - #{Enum.map_join(env, "\n", fn {key, value} -> "Environment=\"#{key}=#{value}\"" end)} - ExecStartPre=/bin/sh -c ' \\ - remote_version=$(curl -Ls \\ - -H "Accept: application/vnd.github+json" \\ - -H "X-GitHub-Api-Version: 2022-11-28" \\ - https://api.github.com/repos/firezone/firezone/releases/latest | grep -oP '"'"'(?<="tag_name": ")[^"]*'"'"'); \\ - if [ -e /usr/local/bin/firezone-relay ]; then \\ - current_version=$(/usr/local/bin/firezone-relay --version | awk '"'"'{print $NF}'"'"'); \\ - else \\ - current_version=""; \\ - fi; \\ - if [ ! "$current_version" = "$remote_version" ]; then \\ - arch=$(uname -m); \\ - case $arch in \\ - aarch64) \\ - bin_url="https://github.com/firezone/firezone/releases/download/latest/relay-arm64" ;; \\ - armv7l) \\ - bin_url="https://github.com/firezone/firezone/releases/download/latest/relay-arm" ;; \\ - x86_64) \\ - bin_url="https://github.com/firezone/firezone/releases/download/latest/relay-x64" ;; \\ - *) \\ - echo "Unsupported architecture"; \\ - exit 1 ;; \\ - esac; \\ - wget -O /usr/local/bin/firezone-relay $bin_url; \\ - chmod +x /usr/local/bin/firezone-relay; \\ - fi \\ - ' - ExecStartPre=/usr/bin/chmod +x /usr/local/bin/firezone-relay - ExecStart=FIREZONE_HOSTNAME=$(hostname) /usr/local/bin/firezone-relay - Restart=always - RestartSec=3 - - [Install] - WantedBy=multi-user.target - """ - end - - defp encode_group_token(group) do - Relays.encode_token!(hd(group.tokens)) - end end diff --git a/elixir/apps/web/lib/web/live/relay_groups/new_token.ex b/elixir/apps/web/lib/web/live/relay_groups/new_token.ex new file mode 100644 index 000000000..d44cd8eb5 --- /dev/null +++ b/elixir/apps/web/lib/web/live/relay_groups/new_token.ex @@ -0,0 +1,249 @@ +defmodule Web.RelayGroups.NewToken do + use Web, :live_view + alias Domain.Relays + + def mount(%{"id" => id}, _session, socket) do + with {:ok, group} <- Relays.fetch_group_by_id(id, socket.assigns.subject) do + {group, env} = + if connected?(socket) do + {:ok, group} = + Relays.update_group(%{group | tokens: []}, %{tokens: [%{}]}, socket.assigns.subject) + + :ok = Relays.subscribe_for_relays_presence_in_group(group) + + token = Relays.encode_token!(hd(group.tokens)) + {group, env(token)} + else + {group, nil} + end + + {:ok, assign(socket, group: group, env: env, connected?: false)} + else + {:error, _reason} -> raise Web.LiveErrors.NotFoundError + end + end + + def render(assigns) do + ~H""" + <.breadcrumbs account={@account}> + <.breadcrumb path={~p"/#{@account}/relay_groups"}>Relays + <.breadcrumb path={~p"/#{@account}/relay_groups/#{@group}"}> + <%= @group.name %> + + <.breadcrumb path={~p"/#{@account}/relay_groups/#{@group}/new_token"}>Deploy + + + <.section> + <:title> + Deploy your Relay + + <:content> +
+
+ Select deployment method: +
+ <.tabs :if={@env} id="deployment-instructions" phx-update="ignore"> + <:tab id="docker-instructions" label="Docker"> +

+ Copy-paste this command to your server and replace PUBLIC_IP4_ADDR + and PUBLIC_IP6_ADDR + with your public IP addresses: +

+ + <.code_block id="code-sample-docker1" class="w-full rounded-b" phx-no-format><%= docker_command(@env) %> + + <.initial_connection_status + :if={@env} + type="relay" + navigate={~p"/#{@account}/relays/#{@group}"} + connected?={@connected?} + /> + +
+ +

+ Troubleshooting +

+ +

+ Check the container status: +

+ + <.code_block id="code-sample-docker2" class="w-full" phx-no-format>docker ps --filter "name=firezone-relay" + +

+ Check the container logs: +

+ + <.code_block id="code-sample-docker3" class="w-full rounded-b" phx-no-format>docker logs firezone-relay + + <:tab id="systemd-instructions" label="Systemd"> +

+ 1. Create a systemd unit file with the following content: +

+ + <.code_block id="code-sample-systemd1" class="w-full" phx-no-format>sudo nano /etc/systemd/system/firezone-relay.service + +

+ 2. Copy-paste the following content into the file and replace + PUBLIC_IP4_ADDR + and PUBLIC_IP6_ADDR + with your public IP addresses:: +

+ + <.code_block id="code-sample-systemd2" class="w-full rounded-b" phx-no-format><%= systemd_command(@env) %> + +

+ 3. Save by pressing Ctrl+X, then Y, then Enter. +

+ +

+ 4. Reload systemd configuration: +

+ + <.code_block id="code-sample-systemd4" class="w-full" phx-no-format>sudo systemctl daemon-reload + +

+ 5. Start the service: +

+ + <.code_block id="code-sample-systemd5" class="w-full" phx-no-format>sudo systemctl start firezone-relay + +

+ 6. Enable the service to start on boot: +

+ + <.code_block id="code-sample-systemd6" class="w-full" phx-no-format>sudo systemctl enable firezone-relay + + <.initial_connection_status + :if={@env} + type="relay" + navigate={~p"/#{@account}/sites/#{@group}"} + connected?={@connected?} + /> + +
+ +

+ Troubleshooting +

+ +

+ Check the status of the service: +

+ + <.code_block id="code-sample-systemd7" class="w-full rounded-b" phx-no-format>sudo systemctl status firezone-relay + +

+ Check the logs: +

+ + <.code_block id="code-sample-systemd8" class="w-full rounded-b" phx-no-format>sudo journalctl -u firezone-relay.service + + +
+ + + """ + end + + defp version do + vsn = + Application.spec(:domain) + |> Keyword.fetch!(:vsn) + |> List.to_string() + |> Version.parse!() + + "#{vsn.major}.#{vsn.minor}" + end + + defp env(token) do + api_url_override = + if api_url = Domain.Config.get_env(:web, :api_url_override) do + {"FIREZONE_API_URL", api_url} + end + + [ + {"FIREZONE_ID", Ecto.UUID.generate()}, + {"FIREZONE_TOKEN", token}, + {"PUBLIC_IP4_ADDR", "YOU_MUST_SET_THIS_VALUE"}, + {"PUBLIC_IP6_ADDR", "YOU_MUST_SET_THIS_VALUE"}, + api_url_override, + {"RUST_LOG", "warn"}, + {"LOG_FORMAT", "google-cloud"} + ] + |> Enum.reject(&is_nil/1) + end + + defp docker_command(env) do + [ + "docker run -d", + "--restart=unless-stopped", + "--pull=always", + "--health-cmd=\"lsof -i UDP | grep firezone-relay\"", + "--name=firezone-relay", + "--cap-add=NET_ADMIN", + "--sysctl net.ipv4.ip_forward=1", + "--sysctl net.ipv4.conf.all.src_valid_mark=1", + "--sysctl net.ipv6.conf.all.disable_ipv6=0", + "--sysctl net.ipv6.conf.all.forwarding=1", + "--sysctl net.ipv6.conf.default.forwarding=1", + "--device=\"/dev/net/tun:/dev/net/tun\"", + Enum.map(env, fn {key, value} -> "--env #{key}=\"#{value}\"" end), + "--env FIREZONE_NAME=$(hostname)", + "#{Domain.Config.fetch_env!(:domain, :docker_registry)}/relay:#{version()}" + ] + |> List.flatten() + |> Enum.join(" \\\n ") + end + + defp systemd_command(env) do + """ + [Unit] + Description=Firezone Relay + After=network.target + + [Service] + Type=simple + #{Enum.map_join(env, "\n", fn {key, value} -> "Environment=\"#{key}=#{value}\"" end)} + ExecStartPre=/bin/sh -c ' \\ + remote_version=$(curl -Ls \\ + -H "Accept: application/vnd.github+json" \\ + -H "X-GitHub-Api-Version: 2022-11-28" \\ + https://api.github.com/repos/firezone/firezone/releases/latest | grep -oP '"'"'(?<="tag_name": ")[^"]*'"'"'); \\ + if [ -e /usr/local/bin/firezone-relay ]; then \\ + current_version=$(/usr/local/bin/firezone-relay --version | awk '"'"'{print $NF}'"'"'); \\ + else \\ + current_version=""; \\ + fi; \\ + if [ ! "$current_version" = "$remote_version" ]; then \\ + arch=$(uname -m); \\ + case $arch in \\ + aarch64) \\ + bin_url="https://github.com/firezone/firezone/releases/download/latest/relay-arm64" ;; \\ + armv7l) \\ + bin_url="https://github.com/firezone/firezone/releases/download/latest/relay-arm" ;; \\ + x86_64) \\ + bin_url="https://github.com/firezone/firezone/releases/download/latest/relay-x64" ;; \\ + *) \\ + echo "Unsupported architecture"; \\ + exit 1 ;; \\ + esac; \\ + wget -O /usr/local/bin/firezone-relay $bin_url; \\ + chmod +x /usr/local/bin/firezone-relay; \\ + fi \\ + ' + ExecStartPre=/usr/bin/chmod +x /usr/local/bin/firezone-relay + ExecStart=FIREZONE_NAME=$(hostname) /usr/local/bin/firezone-relay + Restart=always + RestartSec=3 + + [Install] + WantedBy=multi-user.target + """ + end + + def handle_info(%Phoenix.Socket.Broadcast{topic: "relay_groups:" <> _group_id}, socket) do + {:noreply, assign(socket, connected?: true)} + end +end diff --git a/elixir/apps/web/lib/web/live/relay_groups/show.ex b/elixir/apps/web/lib/web/live/relay_groups/show.ex index 95fec2fa4..d1b21aea4 100644 --- a/elixir/apps/web/lib/web/live/relay_groups/show.ex +++ b/elixir/apps/web/lib/web/live/relay_groups/show.ex @@ -49,40 +49,46 @@ defmodule Web.RelayGroups.Show do - -
-
-

- Relay Instances -

-
-
-
- <.table id="relays" rows={@group.relays}> - <:col :let={relay} label="INSTANCE"> - <.link - navigate={~p"/#{@account}/relays/#{relay.id}"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - - <%= relay.ipv4 %> - - - <%= relay.ipv6 %> - - - - <:col :let={relay} label="TOKEN CREATED AT"> - <.created_by account={@account} schema={relay.token} /> - - <:col :let={relay} label="STATUS"> - <.connection_status schema={relay} /> - - <:empty> -
No relay instances to display
- - -
+
+ + + + <.section> + <:title>Relays + <:action> + <.add_button navigate={~p"/#{@account}/relay_groups/#{@group}/new_token"}> + Deploy + + + <:content> +
+ <.table id="relays" rows={@group.relays}> + <:col :let={relay} label="INSTANCE"> + <.link + navigate={~p"/#{@account}/relays/#{relay.id}"} + class="font-medium text-blue-600 dark:text-blue-500 hover:underline" + > + + <%= relay.name %> + + + <%= relay.ipv4 %> + + + <%= relay.ipv6 %> + + + + <:col :let={relay} label="TOKEN CREATED AT"> + <.created_by account={@account} schema={relay.token} /> + + <:col :let={relay} label="STATUS"> + <.connection_status schema={relay} /> + + <:empty> +
No relay instances to display
+ +
@@ -103,7 +109,9 @@ defmodule Web.RelayGroups.Show do def handle_info(%Phoenix.Socket.Broadcast{topic: "relay_groups:" <> _account_id}, socket) do socket = - redirect(socket, to: ~p"/#{socket.assigns.account}/relay_groups/#{socket.assigns.group}") + push_navigate(socket, + to: ~p"/#{socket.assigns.account}/relay_groups/#{socket.assigns.group}" + ) {:noreply, socket} end @@ -111,6 +119,6 @@ defmodule Web.RelayGroups.Show do def handle_event("delete", _params, socket) do # TODO: make sure tokens are all deleted too! {:ok, _group} = Relays.delete_group(socket.assigns.group, socket.assigns.subject) - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/relay_groups")} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/relay_groups")} end end diff --git a/elixir/apps/web/lib/web/live/relays/show.ex b/elixir/apps/web/lib/web/live/relays/show.ex index e71c00ab7..5dae48077 100644 --- a/elixir/apps/web/lib/web/live/relays/show.ex +++ b/elixir/apps/web/lib/web/live/relays/show.ex @@ -28,14 +28,14 @@ defmodule Web.Relays.Show do <%= @relay.group.name %> <.breadcrumb path={~p"/#{@account}/relays/#{@relay}"}> - <%= @relay.ipv4 || @relay.ipv6 %> + <%= @relay.name || @relay.ipv4 || @relay.ipv6 %> <.section> <:title> - Relay: - <.intersperse_blocks> + Relay: <%= @relay.name %> + <.intersperse_blocks :if={is_nil(@relay.name)}> <:separator>,  <:item :for={ip <- [@relay.ipv4, @relay.ipv6]} :if={not is_nil(ip)}> @@ -50,6 +50,10 @@ defmodule Web.Relays.Show do <:label>Instance Group Name <:value><%= @relay.group.name %> + <.vertical_table_row> + <:label>Name + <:value><%= @relay.name %> + <.vertical_table_row> <:label>Remote IPv4 <:value> @@ -137,7 +141,7 @@ defmodule Web.Relays.Show do {:ok, _relay} = Relays.delete_relay(socket.assigns.relay, socket.assigns.subject) socket = - redirect(socket, + push_navigate(socket, to: ~p"/#{socket.assigns.account}/relay_groups/#{socket.assigns.relay.group}" ) diff --git a/elixir/apps/web/lib/web/live/resources/components.ex b/elixir/apps/web/lib/web/live/resources/components.ex index f8ca892a3..1961ed37c 100644 --- a/elixir/apps/web/lib/web/live/resources/components.ex +++ b/elixir/apps/web/lib/web/live/resources/components.ex @@ -226,7 +226,7 @@ defmodule Web.Resources.Components do class="font-bold text-blue-600 dark:text-blue-500 hover:underline" target="_blank" > - <%= gateway_group.name_prefix %> + <%= gateway_group.name %>
diff --git a/elixir/apps/web/lib/web/live/resources/index.ex b/elixir/apps/web/lib/web/live/resources/index.ex index 97fd03c55..d870df742 100644 --- a/elixir/apps/web/lib/web/live/resources/index.ex +++ b/elixir/apps/web/lib/web/live/resources/index.ex @@ -57,7 +57,7 @@ defmodule Web.Resources.Index do class="font-medium text-blue-600 dark:text-blue-500 hover:underline" > <.badge type="info"> - <%= gateway_group.name_prefix %> + <%= gateway_group.name %> diff --git a/elixir/apps/web/lib/web/live/resources/show.ex b/elixir/apps/web/lib/web/live/resources/show.ex index b8483795a..3f674023b 100644 --- a/elixir/apps/web/lib/web/live/resources/show.ex +++ b/elixir/apps/web/lib/web/live/resources/show.ex @@ -172,7 +172,7 @@ defmodule Web.Resources.Show do navigate={~p"/#{@account}/sites/#{gateway_group}"} class="font-medium text-blue-600 dark:text-blue-500 hover:underline" > - <%= gateway_group.name_prefix %> + <%= gateway_group.name %> <:empty> @@ -223,7 +223,7 @@ defmodule Web.Resources.Show do navigate={~p"/#{@account}/gateways/#{flow.gateway_id}"} class="font-medium text-blue-600 dark:text-blue-500 hover:underline" > - <%= flow.gateway.group.name_prefix %>-<%= flow.gateway.name_suffix %> + <%= flow.gateway.group.name %>-<%= flow.gateway.name %> (<%= flow.gateway_remote_ip %>) diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/edit.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/edit.ex index 3ef91cdc6..b3e0e0f8d 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/edit.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/edit.ex @@ -54,7 +54,7 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.Edit do with {:ok, provider} <- Auth.update_provider(socket.assigns.provider, attrs, socket.assigns.subject) do socket = - redirect(socket, + push_navigate(socket, to: ~p"/#{socket.assigns.account.id}/settings/identity_providers/google_workspace/#{provider}/redirect" ) diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/new.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/new.ex index bd48ef2ed..000e75d76 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/new.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/new.ex @@ -68,7 +68,7 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.New do with {:ok, provider} <- Auth.create_provider(socket.assigns.account, attrs, socket.assigns.subject) do socket = - redirect(socket, + push_navigate(socket, to: ~p"/#{socket.assigns.account.id}/settings/identity_providers/google_workspace/#{provider}/redirect" ) diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex index 0969b5530..a4366acf9 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex @@ -112,7 +112,9 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.Show do def handle_event("delete", _params, socket) do {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} + + {:noreply, + push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} end def handle_event("enable", _params, socket) do diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/edit.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/edit.ex index 47469216e..be3571a4d 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/edit.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/edit.ex @@ -57,7 +57,7 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Edit do with {:ok, provider} <- Auth.update_provider(socket.assigns.provider, attrs, socket.assigns.subject) do socket = - redirect(socket, + push_navigate(socket, to: ~p"/#{socket.assigns.account.id}/settings/identity_providers/openid_connect/#{provider}/redirect" ) diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/new.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/new.ex index 3ffda8bed..80c42ca5f 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/new.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/new.ex @@ -68,7 +68,7 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.New do with {:ok, provider} <- Auth.create_provider(socket.assigns.account, attrs, socket.assigns.subject) do socket = - redirect(socket, + push_navigate(socket, to: ~p"/#{socket.assigns.account.id}/settings/identity_providers/openid_connect/#{provider}/redirect" ) diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex index 826078e81..d64fb709e 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex @@ -134,7 +134,9 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Show do def handle_event("delete", _params, socket) do {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} + + {:noreply, + push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} end def handle_event("enable", _params, socket) do diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/saml/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/saml/show.ex index 077fcda43..37d8c700f 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/saml/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/saml/show.ex @@ -161,6 +161,8 @@ defmodule Web.Settings.IdentityProviders.SAML.Show do def handle_event("delete", _params, socket) do {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} + + {:noreply, + push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} end end diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex index 7a637abe9..baba8e68e 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex @@ -93,7 +93,9 @@ defmodule Web.Settings.IdentityProviders.System.Show do def handle_event("delete", _params, socket) do {:ok, _provider} = Auth.delete_provider(socket.assigns.provider, socket.assigns.subject) - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} + + {:noreply, + push_navigate(socket, to: ~p"/#{socket.assigns.account}/settings/identity_providers")} end def handle_event("enable", _params, socket) do diff --git a/elixir/apps/web/lib/web/live/auth/sign_in.ex b/elixir/apps/web/lib/web/live/sign_in.ex similarity index 99% rename from elixir/apps/web/lib/web/live/auth/sign_in.ex rename to elixir/apps/web/lib/web/live/sign_in.ex index 8cea0b57a..07ddb0e17 100644 --- a/elixir/apps/web/lib/web/live/auth/sign_in.ex +++ b/elixir/apps/web/lib/web/live/sign_in.ex @@ -1,4 +1,4 @@ -defmodule Web.Auth.SignIn do +defmodule Web.SignIn do use Web, {:live_view, layout: {Web.Layouts, :public}} alias Domain.{Auth, Accounts} diff --git a/elixir/apps/web/lib/web/live/auth/email.ex b/elixir/apps/web/lib/web/live/sign_in/email.ex similarity index 99% rename from elixir/apps/web/lib/web/live/auth/email.ex rename to elixir/apps/web/lib/web/live/sign_in/email.ex index 7e14f2fdc..7dbca4d9a 100644 --- a/elixir/apps/web/lib/web/live/auth/email.ex +++ b/elixir/apps/web/lib/web/live/sign_in/email.ex @@ -1,4 +1,4 @@ -defmodule Web.Auth.Email do +defmodule Web.SignIn.Email do use Web, {:live_view, layout: {Web.Layouts, :public}} def mount( diff --git a/elixir/apps/web/lib/web/live/sites/edit.ex b/elixir/apps/web/lib/web/live/sites/edit.ex index 9665da01d..42cc73f8e 100644 --- a/elixir/apps/web/lib/web/live/sites/edit.ex +++ b/elixir/apps/web/lib/web/live/sites/edit.ex @@ -16,13 +16,13 @@ defmodule Web.Sites.Edit do <.breadcrumbs account={@account}> <.breadcrumb path={~p"/#{@account}/sites"}>Sites <.breadcrumb path={~p"/#{@account}/sites/#{@group}"}> - <%= @group.name_prefix %> + <%= @group.name %> <.breadcrumb path={~p"/#{@account}/sites/#{@group}/edit"}>Edit <.section> - <:title>Edit Site: <%= @group.name_prefix %> + <:title>Edit Site: <%= @group.name %> <:content>
<.form for={@form} phx-change={:change} phx-submit={:submit}> @@ -30,7 +30,7 @@ defmodule Web.Sites.Edit do
<.input label="Name Prefix" - field={@form[:name_prefix]} + field={@form[:name]} placeholder="Name of this Site" required /> @@ -57,7 +57,7 @@ defmodule Web.Sites.Edit do def handle_event("submit", %{"group" => attrs}, socket) do with {:ok, group} <- Gateways.update_group(socket.assigns.group, attrs, socket.assigns.subject) do - socket = redirect(socket, to: ~p"/#{socket.assigns.account}/sites/#{group}") + socket = push_navigate(socket, to: ~p"/#{socket.assigns.account}/sites/#{group}") {:noreply, socket} else {:error, changeset} -> diff --git a/elixir/apps/web/lib/web/live/sites/index.ex b/elixir/apps/web/lib/web/live/sites/index.ex index 9ba8ea9a0..c33b4ac7c 100644 --- a/elixir/apps/web/lib/web/live/sites/index.ex +++ b/elixir/apps/web/lib/web/live/sites/index.ex @@ -36,7 +36,7 @@ defmodule Web.Sites.Index do navigate={~p"/#{@account}/sites/#{group}"} class="font-bold text-blue-600 dark:text-blue-500 hover:underline" > - <%= group.name_prefix %> + <%= group.name %> @@ -92,7 +92,7 @@ defmodule Web.Sites.Index do navigate={~p"/#{@account}/gateways/#{gateway}"} class="font-medium text-blue-600 dark:text-blue-500 hover:underline inline-block" phx-no-format - ><%= gateway.name_suffix %> + ><%= gateway.name %> <:tail :let={count}> diff --git a/elixir/apps/web/lib/web/live/sites/new.ex b/elixir/apps/web/lib/web/live/sites/new.ex index d5b87ef31..742d0d511 100644 --- a/elixir/apps/web/lib/web/live/sites/new.ex +++ b/elixir/apps/web/lib/web/live/sites/new.ex @@ -4,7 +4,7 @@ defmodule Web.Sites.New do def mount(_params, _session, socket) do changeset = Gateways.new_group() - {:ok, assign(socket, form: to_form(changeset), group: nil)} + {:ok, assign(socket, form: to_form(changeset))} end def render(assigns) do @@ -15,20 +15,17 @@ defmodule Web.Sites.New do <.section> - <:title :if={is_nil(@group)}> + <:title> Add a new Site - <:title :if={not is_nil(@group)}> - Deploy your Gateway - <:content>
- <.form :if={is_nil(@group)} for={@form} phx-change={:change} phx-submit={:submit}> + <.form for={@form} phx-change={:change} phx-submit={:submit}>
<.input label="Name Prefix" - field={@form[:name_prefix]} + field={@form[:name]} placeholder="Name of this Site" required /> @@ -58,7 +55,7 @@ defmodule Web.Sites.New do with {:ok, group} <- Gateways.create_group(attrs, socket.assigns.subject) do - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/sites/#{group}")} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/sites/#{group}")} else {:error, changeset} -> {:noreply, assign(socket, form: to_form(changeset))} diff --git a/elixir/apps/web/lib/web/live/sites/new_token.ex b/elixir/apps/web/lib/web/live/sites/new_token.ex index e82fb0e20..d898a0cbf 100644 --- a/elixir/apps/web/lib/web/live/sites/new_token.ex +++ b/elixir/apps/web/lib/web/live/sites/new_token.ex @@ -11,13 +11,13 @@ defmodule Web.Sites.NewToken do :ok = Gateways.subscribe_for_gateways_presence_in_group(group) - token = encode_group_token(group) + token = Gateways.encode_token!(hd(group.tokens)) {group, env(token)} else {group, nil} end - {:ok, assign(socket, group: group, env: env)} + {:ok, assign(socket, group: group, env: env, connected?: false)} else {:error, _reason} -> raise Web.LiveErrors.NotFoundError end @@ -28,16 +28,13 @@ defmodule Web.Sites.NewToken do <.breadcrumbs account={@account}> <.breadcrumb path={~p"/#{@account}/sites"}>Sites <.breadcrumb path={~p"/#{@account}/sites/#{@group}"}> - <%= @group.name_prefix %> + <%= @group.name %> <.breadcrumb path={~p"/#{@account}/sites/#{@group}/new_token"}>Deploy <.section> - <:title :if={is_nil(@group)}> - Add a new Site - - <:title :if={not is_nil(@group)}> + <:title> Deploy your Gateway <:content> @@ -51,20 +48,45 @@ defmodule Web.Sites.NewToken do Copy-paste this command to your server:

- <.code_block id="code-sample-docker" class="w-full rounded-b" phx-no-format><%= docker_command(@env) %> + <.code_block id="code-sample-docker1" class="w-full" phx-no-format><%= docker_command(@env) %> + + <.initial_connection_status + :if={@env} + type="gateway" + navigate={~p"/#{@account}/sites/#{@group}"} + connected?={@connected?} + /> + +
+ +

+ Troubleshooting +

+ +

+ Check the container status: +

+ + <.code_block id="code-sample-docker2" class="w-full" phx-no-format>docker ps --filter "name=firezone-gateway" + +

+ Check the container logs: +

+ + <.code_block id="code-sample-docker3" class="w-full rounded-b" phx-no-format>docker logs firezone-gateway <:tab id="systemd-instructions" label="Systemd">

1. Create a systemd unit file with the following content:

- <.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo nano /etc/systemd/system/firezone-gateway.service + <.code_block id="code-sample-systemd1" class="w-full" phx-no-format>sudo nano /etc/systemd/system/firezone-gateway.service

2. Copy-paste the following content into the file:

- <.code_block id="code-sample-systemd" class="w-full rounded-b" phx-no-format><%= systemd_command(@env) %> + <.code_block id="code-sample-systemd2" class="w-full rounded-b" phx-no-format><%= systemd_command(@env) %>

3. Save by pressing Ctrl+X, then Y, then Enter. @@ -74,31 +96,46 @@ defmodule Web.Sites.NewToken do 4. Reload systemd configuration:

- <.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl daemon-reload + <.code_block id="code-sample-systemd4" class="w-full" phx-no-format>sudo systemctl daemon-reload

5. Start the service:

- <.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl start firezone-gateway + <.code_block id="code-sample-systemd5" class="w-full" phx-no-format>sudo systemctl start firezone-gateway

6. Enable the service to start on boot:

- <.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl enable firezone-gateway + <.code_block id="code-sample-systemd6" class="w-full" phx-no-format>sudo systemctl enable firezone-gateway -

- 7. Check the status of the service: + <.initial_connection_status + :if={@env} + type="gateway" + navigate={~p"/#{@account}/sites/#{@group}"} + connected?={@connected?} + /> + +


+ +

+ Troubleshooting

- <.code_block id="code-sample-systemd" class="w-full rounded-b" phx-no-format>sudo systemctl status firezone-gateway +

+ Check the status of the service: +

+ + <.code_block id="code-sample-systemd7" class="w-full rounded-b" phx-no-format>sudo systemctl status firezone-gateway + +

+ Check the logs: +

+ + <.code_block id="code-sample-systemd8" class="w-full rounded-b" phx-no-format>sudo journalctl -u firezone-gateway.service - -
- Waiting for gateway connection... -
@@ -146,7 +183,7 @@ defmodule Web.Sites.NewToken do "--sysctl net.ipv6.conf.default.forwarding=1", "--device=\"/dev/net/tun:/dev/net/tun\"", Enum.map(env, fn {key, value} -> "--env #{key}=\"#{value}\"" end), - "--env FIREZONE_HOSTNAME=$(hostname)", + "--env FIREZONE_NAME=$(hostname)", "#{Domain.Config.fetch_env!(:domain, :docker_registry)}/gateway:#{version()}" ] |> List.flatten() @@ -190,7 +227,7 @@ defmodule Web.Sites.NewToken do fi \\ ' ExecStartPre=/usr/bin/chmod +x /usr/local/bin/firezone-gateway - ExecStart=FIREZONE_HOSTNAME=$(hostname) /usr/local/bin/firezone-gateway + ExecStart=FIREZONE_NAME=$(hostname) /usr/local/bin/firezone-gateway Restart=always RestartSec=3 @@ -199,14 +236,7 @@ defmodule Web.Sites.NewToken do """ end - defp encode_group_token(group) do - Gateways.encode_token!(hd(group.tokens)) - end - - def handle_info(%Phoenix.Socket.Broadcast{topic: "gateway_groups:" <> _account_id}, socket) do - socket = - redirect(socket, to: ~p"/#{socket.assigns.account}/sites/#{socket.assigns.group}") - - {:noreply, socket} + def handle_info(%Phoenix.Socket.Broadcast{topic: "gateway_groups:" <> _group_id}, socket) do + {:noreply, assign(socket, connected?: true)} end end diff --git a/elixir/apps/web/lib/web/live/sites/show.ex b/elixir/apps/web/lib/web/live/sites/show.ex index 42e0f4bfc..06c5c0f1f 100644 --- a/elixir/apps/web/lib/web/live/sites/show.ex +++ b/elixir/apps/web/lib/web/live/sites/show.ex @@ -11,12 +11,15 @@ defmodule Web.Sites.Show do created_by_identity: [:actor] ] ), - resources = Enum.map(group.connections, & &1.resource), + resources = + group.connections + |> Enum.reject(&is_nil(&1.resource)) + |> Enum.map(& &1.resource), {:ok, resource_actor_groups_peek} <- Resources.peek_resource_actor_groups(resources, 3, socket.assigns.subject) do group = %{ group - | gateways: Enum.sort_by(group.gateways, &{&1.online?, &1.name_suffix}, :desc) + | gateways: Enum.sort_by(group.gateways, &{&1.online?, &1.name}, :desc) } :ok = Gateways.subscribe_for_gateways_presence_in_group(group) @@ -31,13 +34,13 @@ defmodule Web.Sites.Show do <.breadcrumbs account={@account}> <.breadcrumb path={~p"/#{@account}/sites"}>Sites <.breadcrumb path={~p"/#{@account}/sites/#{@group}"}> - <%= @group.name_prefix %> + <%= @group.name %> <.section> <:title> - Site: <%= @group.name_prefix %> + Site: <%= @group.name %> <:action> <.edit_button navigate={~p"/#{@account}/sites/#{@group}/edit"}> @@ -49,7 +52,7 @@ defmodule Web.Sites.Show do <.vertical_table id="group"> <.vertical_table_row> <:label>Name - <:value><%= @group.name_prefix %> + <:value><%= @group.name %> <.vertical_table_row> <:label>Created @@ -76,7 +79,7 @@ defmodule Web.Sites.Show do navigate={~p"/#{@account}/gateways/#{gateway.id}"} class="font-medium text-blue-600 dark:text-blue-500 hover:underline" > - <%= gateway.name_suffix %> + <%= gateway.name %> <:col :let={gateway} label="REMOTE IP"> @@ -217,7 +220,7 @@ defmodule Web.Sites.Show do def handle_info(%Phoenix.Socket.Broadcast{topic: "gateway_groups:" <> _account_id}, socket) do socket = - redirect(socket, to: ~p"/#{socket.assigns.account}/sites/#{socket.assigns.group}") + push_navigate(socket, to: ~p"/#{socket.assigns.account}/sites/#{socket.assigns.group}") {:noreply, socket} end @@ -225,6 +228,6 @@ defmodule Web.Sites.Show do def handle_event("delete", _params, socket) do # TODO: make sure tokens are all deleted too! {:ok, _group} = Gateways.delete_group(socket.assigns.group, socket.assigns.subject) - {:noreply, redirect(socket, to: ~p"/#{socket.assigns.account}/sites")} + {:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/sites")} end end diff --git a/elixir/apps/web/lib/web/router.ex b/elixir/apps/web/lib/web/router.ex index b97a80cdc..85906b484 100644 --- a/elixir/apps/web/lib/web/router.ex +++ b/elixir/apps/web/lib/web/router.ex @@ -22,6 +22,14 @@ defmodule Web.Router do plug :accepts, ["html", "xml"] end + pipeline :home do + plug :accepts, ["html", "xml"] + plug :fetch_session + plug :protect_from_forgery + plug :fetch_live_flash + plug :put_root_layout, {Web.Layouts, :root} + end + pipeline :ensure_authenticated_admin do plug :ensure_authenticated plug :ensure_authenticated_actor_type, :account_admin_user @@ -33,10 +41,16 @@ defmodule Web.Router do get "/config.xml", BrowserController, :config end + scope "/", Web do + pipe_through :home + + get "/", HomeController, :home + post "/", HomeController, :redirect_to_sign_in + end + scope "/", Web do pipe_through :public - get "/", RedirectController, :home get "/healthz", HealthController, :healthz end @@ -61,11 +75,11 @@ defmodule Web.Router do Web.Sandbox, {Web.Auth, :redirect_if_user_is_authenticated} ] do - live "/", Auth.SignIn + live "/", SignIn # Adapter-specific routes ## Email - live "/sign_in/providers/email/:provider_id", Auth.Email + live "/sign_in/providers/email/:provider_id", SignIn.Email end scope "/sign_in/providers/:provider_id" do @@ -137,6 +151,7 @@ defmodule Web.Router do live "/", Index live "/new", New live "/:id/edit", Edit + live "/:id/new_token", NewToken live "/:id", Show end diff --git a/elixir/apps/web/test/web/acceptance/auth/email_test.exs b/elixir/apps/web/test/web/acceptance/auth/email_test.exs index 84d5f2f9e..96d35bbc0 100644 --- a/elixir/apps/web/test/web/acceptance/auth/email_test.exs +++ b/elixir/apps/web/test/web/acceptance/auth/email_test.exs @@ -1,4 +1,4 @@ -defmodule Web.Acceptance.Auth.EmailTest do +defmodule Web.Acceptance.SignIn.EmailTest do use Web.AcceptanceCase, async: true feature "renders success on invalid email to prevent enumeration attacks", %{session: session} do diff --git a/elixir/apps/web/test/web/auth_test.exs b/elixir/apps/web/test/web/auth_test.exs index ba0f78cd5..26beb2192 100644 --- a/elixir/apps/web/test/web/auth_test.exs +++ b/elixir/apps/web/test/web/auth_test.exs @@ -52,10 +52,12 @@ defmodule Web.AuthTest do describe "signed_in_redirect/4" do test "redirects regular users to the platform url", %{conn: conn, user_subject: subject} do redirected_to = conn |> signed_in_redirect(subject, "apple", "foo") |> redirected_to() - assert redirected_to =~ "firezone://handle_client_auth_callback?client_csrf_token=foo" + assert redirected_to =~ "firezone://handle_client_auth_callback" + assert redirected_to =~ "client_csrf_token=foo" redirected_to = conn |> signed_in_redirect(subject, "android", "foo") |> redirected_to() - assert redirected_to =~ "/handle_client_auth_callback?client_csrf_token=foo" + assert redirected_to =~ "/handle_client_auth_callback?" + assert redirected_to =~ "client_csrf_token=foo" end test "redirects regular users to sign in if platform url is missing", %{ @@ -73,10 +75,12 @@ defmodule Web.AuthTest do test "redirects admin user to the platform url", %{conn: conn, admin_subject: subject} do redirected_to = conn |> signed_in_redirect(subject, "apple", "foo") |> redirected_to() - assert redirected_to =~ "firezone://handle_client_auth_callback?client_csrf_token=foo" + assert redirected_to =~ "firezone://handle_client_auth_callback?" + assert redirected_to =~ "client_csrf_token=foo" redirected_to = conn |> signed_in_redirect(subject, "android", "foo") |> redirected_to() - assert redirected_to =~ "/handle_client_auth_callback?client_csrf_token=foo" + assert redirected_to =~ "/handle_client_auth_callback?" + assert redirected_to =~ "client_csrf_token=foo" end test "redirects admin user to the post-login path if platform url is missing", %{ diff --git a/elixir/apps/web/test/web/controllers/auth_controller_test.exs b/elixir/apps/web/test/web/controllers/auth_controller_test.exs index fa96047d7..f149ade99 100644 --- a/elixir/apps/web/test/web/controllers/auth_controller_test.exs +++ b/elixir/apps/web/test/web/controllers/auth_controller_test.exs @@ -1182,9 +1182,6 @@ defmodule Web.AuthControllerTest do assert redirected_to(conn) == url(~p"/#{account}") assert conn.private.plug_session == %{"preferred_locale" => "en_US"} - - assert %{"fz_recent_account_ids" => fz_recent_account_ids} = conn.cookies - assert :erlang.binary_to_term(fz_recent_account_ids) == [] end test "redirects to the IdP sign out page", %{conn: conn} do @@ -1229,7 +1226,7 @@ defmodule Web.AuthControllerTest do assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: ^live_socket_id} end - test "removes current account id from list of recent ones", %{conn: conn} do + test "does not remove current account id from list of recent ones", %{conn: conn} do Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) account = Fixtures.Accounts.create_account() @@ -1270,8 +1267,7 @@ defmodule Web.AuthControllerTest do assert redirected_to(conn) == url(~p"/#{account}") assert conn.private.plug_session == %{"preferred_locale" => "en_US"} - assert %{"fz_recent_account_ids" => fz_recent_account_ids} = conn.cookies - assert :erlang.binary_to_term(fz_recent_account_ids) == [] + assert %{"fz_recent_account_ids" => ^signed_state} = conn.cookies end test "works even if user is already logged out", %{conn: conn} do diff --git a/elixir/apps/web/test/web/controllers/home_controller_test.exs b/elixir/apps/web/test/web/controllers/home_controller_test.exs new file mode 100644 index 000000000..939fbcd3b --- /dev/null +++ b/elixir/apps/web/test/web/controllers/home_controller_test.exs @@ -0,0 +1,57 @@ +defmodule Web.HomeControllerTest do + use Web.ConnCase, async: true + + describe "home/2" do + test "renders the form to find the account sign in page", %{conn: conn} do + conn = get(conn, ~p"/") + html = response(conn, 200) + + assert html =~ "Account ID or Slug" + assert html =~ "Go to Sign In page" + end + + test "renders recently used account", %{conn: conn} do + accounts = [ + Fixtures.Accounts.create_account(), + Fixtures.Accounts.create_account() + ] + + conn = get(conn, ~p"/") + html = response(conn, 200) + + for account <- accounts do + refute html =~ account.name + refute html =~ ~p"/#{account.slug}" + end + + account_ids = + accounts + |> Enum.map(& &1.id) + |> :erlang.term_to_binary() + + %{resp_cookies: %{"fz_recent_account_ids" => %{value: value}}} = + %{build_conn() | secret_key_base: Web.Endpoint.config(:secret_key_base)} + |> put_resp_cookie("fz_recent_account_ids", account_ids, sign: true, secure: true) + + conn = + build_conn() + |> put_req_cookie("fz_recent_account_ids", value) + |> get(~p"/") + + html = response(conn, 200) + + for account <- accounts do + assert html =~ account.name + assert html =~ ~p"/#{account.slug}" + end + end + end + + describe "redirect_to_sign_in/2" do + test "redirects to the sign in page", %{conn: conn} do + id = Ecto.UUID.generate() + conn = post(conn, ~p"/", %{"account_id_or_slug" => id}) + assert redirected_to(conn) == ~p"/#{id}" + end + end +end diff --git a/elixir/apps/web/test/web/live/actors/edit_test.exs b/elixir/apps/web/test/web/live/actors/edit_test.exs index 2a9966990..33e0a6e06 100644 --- a/elixir/apps/web/test/web/live/actors/edit_test.exs +++ b/elixir/apps/web/test/web/live/actors/edit_test.exs @@ -197,10 +197,11 @@ defmodule Web.Live.Actors.EditTest do |> authorize_conn(identity) |> live(~p"/#{account}/actors/#{actor}/edit") - assert lv - |> form("form", actor: attrs) - |> render_submit() == - {:error, {:redirect, %{to: ~p"/#{account}/actors/#{actor}"}}} + lv + |> form("form", actor: attrs) + |> render_submit() + + assert_redirected(lv, ~p"/#{account}/actors/#{actor}") assert actor = Repo.get_by(Domain.Actors.Actor, id: actor.id) |> Repo.preload(:memberships) assert actor.name == attrs.name @@ -375,10 +376,11 @@ defmodule Web.Live.Actors.EditTest do |> authorize_conn(identity) |> live(~p"/#{account}/actors/#{actor}/edit") - assert lv - |> form("form", actor: attrs) - |> render_submit() == - {:error, {:redirect, %{to: ~p"/#{account}/actors/#{actor}"}}} + lv + |> form("form", actor: attrs) + |> render_submit() + + assert_redirected(lv, ~p"/#{account}/actors/#{actor}") assert actor = Repo.get_by(Domain.Actors.Actor, id: actor.id) |> Repo.preload(:memberships) assert actor.name == attrs.name diff --git a/elixir/apps/web/test/web/live/actors/show_test.exs b/elixir/apps/web/test/web/live/actors/show_test.exs index 6cef3f6fe..6058acf9f 100644 --- a/elixir/apps/web/test/web/live/actors/show_test.exs +++ b/elixir/apps/web/test/web/live/actors/show_test.exs @@ -46,6 +46,45 @@ defmodule Web.Live.Actors.ShowTest do assert breadcrumbs =~ actor.name end + test "renders logs table", %{ + conn: conn + } do + account = Fixtures.Accounts.create_account() + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + identity = Fixtures.Auth.create_identity(account: account, actor: actor) + client = Fixtures.Clients.create_client(account: account, actor: actor) + + flow = + Fixtures.Flows.create_flow( + account: account, + client: client + ) + + flow = Repo.preload(flow, [:client, gateway: [:group], policy: [:actor_group, :resource]]) + + {:ok, lv, _html} = + conn + |> authorize_conn(identity) + |> live(~p"/#{account}/actors/#{actor}") + + [row] = + lv + |> element("#flows") + |> render() + |> table_to_map() + + assert row["authorized at"] + assert row["expires at"] + assert row["policy"] =~ flow.policy.actor_group.name + assert row["policy"] =~ flow.policy.resource.name + + assert row["client (ip)"] == + "#{flow.client.name} (#{client.last_seen_remote_ip})" + + assert row["gateway (ip)"] == + "#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)" + end + describe "users" do setup do account = Fixtures.Accounts.create_account() @@ -59,6 +98,30 @@ defmodule Web.Live.Actors.ShowTest do } end + test "renders (you) next to subject actor title", %{ + account: account, + actor: actor, + identity: identity, + conn: conn + } do + {:ok, _lv, html} = + conn + |> authorize_conn(identity) + |> live(~p"/#{account}/actors/#{actor}") + + assert html =~ "(you)" + + other_actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + identity = Fixtures.Auth.create_identity(account: account, actor: other_actor) + + {:ok, _lv, html} = + conn + |> authorize_conn(identity) + |> live(~p"/#{account}/actors/#{actor}") + + refute html =~ "(you)" + end + test "renders actor details", %{ account: account, actor: actor, @@ -73,6 +136,7 @@ defmodule Web.Live.Actors.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/actors/#{actor}") + assert html =~ actor.name assert html =~ "User" table = diff --git a/elixir/apps/web/test/web/live/clients/edit_test.exs b/elixir/apps/web/test/web/live/clients/edit_test.exs index 04adc59e2..b770a8a3d 100644 --- a/elixir/apps/web/test/web/live/clients/edit_test.exs +++ b/elixir/apps/web/test/web/live/clients/edit_test.exs @@ -144,10 +144,11 @@ defmodule Web.Live.Clients.EditTest do |> authorize_conn(identity) |> live(~p"/#{account}/clients/#{client}/edit") - assert lv - |> form("form", client: attrs) - |> render_submit() == - {:error, {:redirect, %{to: ~p"/#{account}/clients/#{client}"}}} + lv + |> form("form", client: attrs) + |> render_submit() + + assert_redirected(lv, ~p"/#{account}/clients/#{client}") assert client = Repo.get_by(Domain.Clients.Client, id: client.id) assert client.name == attrs.name diff --git a/elixir/apps/web/test/web/live/clients/show_test.exs b/elixir/apps/web/test/web/live/clients/show_test.exs index 9394a4bf6..f1c151089 100644 --- a/elixir/apps/web/test/web/live/clients/show_test.exs +++ b/elixir/apps/web/test/web/live/clients/show_test.exs @@ -144,7 +144,7 @@ defmodule Web.Live.Clients.ShowTest do assert row["policy"] =~ flow.policy.resource.name assert row["gateway (ip)"] == - "#{flow.gateway.group.name_prefix}-#{flow.gateway.name_suffix} (189.172.73.153)" + "#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)" end test "allows editing clients", %{ @@ -176,10 +176,11 @@ defmodule Web.Live.Clients.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/clients/#{client}") - assert lv - |> element("button", "Delete Client") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/clients"}}} + lv + |> element("button", "Delete Client") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/clients") assert Repo.get(Domain.Clients.Client, client.id).deleted_at end diff --git a/elixir/apps/web/test/web/live/gateways/show_test.exs b/elixir/apps/web/test/web/live/gateways/show_test.exs index 06a564fa6..381141d57 100644 --- a/elixir/apps/web/test/web/live/gateways/show_test.exs +++ b/elixir/apps/web/test/web/live/gateways/show_test.exs @@ -62,8 +62,8 @@ defmodule Web.Live.Gateways.ShowTest do assert item = Floki.find(html, "[aria-label='Breadcrumb']") breadcrumbs = String.trim(Floki.text(item)) assert breadcrumbs =~ "Sites" - assert breadcrumbs =~ gateway.group.name_prefix - assert breadcrumbs =~ gateway.name_suffix + assert breadcrumbs =~ gateway.group.name + assert breadcrumbs =~ gateway.name end test "renders gateway details", %{ @@ -83,8 +83,8 @@ defmodule Web.Live.Gateways.ShowTest do |> render() |> vertical_table_to_map() - assert table["site"] =~ gateway.group.name_prefix - assert table["instance name"] =~ gateway.name_suffix + assert table["site"] =~ gateway.group.name + assert table["name"] =~ gateway.name assert table["last seen"] assert table["last seen remote ip"] =~ to_string(gateway.last_seen_remote_ip) assert table["status"] =~ "Offline" @@ -162,10 +162,11 @@ defmodule Web.Live.Gateways.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/gateways/#{gateway}") - assert lv - |> element("button", "Delete Gateway") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/sites/#{gateway.group}"}}} + lv + |> element("button", "Delete Gateway") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/sites/#{gateway.group}") assert Repo.get(Domain.Gateways.Gateway, gateway.id).deleted_at end diff --git a/elixir/apps/web/test/web/live/groups/edit_actors_test.exs b/elixir/apps/web/test/web/live/groups/edit_actors_test.exs index cb87bfc09..a16ebdb6b 100644 --- a/elixir/apps/web/test/web/live/groups/edit_actors_test.exs +++ b/elixir/apps/web/test/web/live/groups/edit_actors_test.exs @@ -211,9 +211,11 @@ defmodule Web.Live.Groups.EditActorsTest do |> element("#actor-#{service_account.id} button", "Add") |> render_click() - assert lv - |> element("button", "Save") - |> render_click() == {:error, {:redirect, %{to: ~p"/#{account}/groups/#{group}"}}} + lv + |> element("button", "Save") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/groups/#{group}") group = Repo.preload(group, :actors, force: true) group_actor_ids = Enum.map(group.actors, & &1.id) diff --git a/elixir/apps/web/test/web/live/groups/edit_test.exs b/elixir/apps/web/test/web/live/groups/edit_test.exs index aba88110e..ecbc2d725 100644 --- a/elixir/apps/web/test/web/live/groups/edit_test.exs +++ b/elixir/apps/web/test/web/live/groups/edit_test.exs @@ -167,10 +167,11 @@ defmodule Web.Live.Groups.EditTest do |> authorize_conn(identity) |> live(~p"/#{account}/groups/#{group}/edit") - assert lv - |> form("form", group: attrs) - |> render_submit() == - {:error, {:redirect, %{to: ~p"/#{account}/groups/#{group}"}}} + lv + |> form("form", group: attrs) + |> render_submit() + + assert_redirected(lv, ~p"/#{account}/groups/#{group}") assert group = Repo.get_by(Domain.Actors.Group, id: group.id) assert group.name == attrs.name diff --git a/elixir/apps/web/test/web/live/groups/new_test.exs b/elixir/apps/web/test/web/live/groups/new_test.exs index eb61e8b16..901f163a1 100644 --- a/elixir/apps/web/test/web/live/groups/new_test.exs +++ b/elixir/apps/web/test/web/live/groups/new_test.exs @@ -118,14 +118,13 @@ defmodule Web.Live.Groups.NewTest do |> authorize_conn(identity) |> live(~p"/#{account}/groups/new") - result = - lv - |> form("form", group: attrs) - |> render_submit() + lv + |> form("form", group: attrs) + |> render_submit() assert group = Repo.get_by(Domain.Actors.Group, name: attrs.name) - assert result == {:error, {:redirect, %{to: ~p"/#{account}/groups/#{group}/edit_actors"}}} + assert_redirected(lv, ~p"/#{account}/groups/#{group}/edit_actors") assert group.name == attrs.name refute group.provider_id diff --git a/elixir/apps/web/test/web/live/groups/show_test.exs b/elixir/apps/web/test/web/live/groups/show_test.exs index 57efb11f8..eecc8e018 100644 --- a/elixir/apps/web/test/web/live/groups/show_test.exs +++ b/elixir/apps/web/test/web/live/groups/show_test.exs @@ -275,10 +275,11 @@ defmodule Web.Live.Groups.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/groups/#{group}") - assert lv - |> element("button", "Delete Group") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/groups"}}} + lv + |> element("button", "Delete Group") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/groups") assert Repo.get(Domain.Actors.Group, group.id).deleted_at end diff --git a/elixir/apps/web/test/web/live/policies/edit_test.exs b/elixir/apps/web/test/web/live/policies/edit_test.exs index 3b4d45a2d..f87321241 100644 --- a/elixir/apps/web/test/web/live/policies/edit_test.exs +++ b/elixir/apps/web/test/web/live/policies/edit_test.exs @@ -146,10 +146,11 @@ defmodule Web.Live.Policies.EditTest do |> authorize_conn(identity) |> live(~p"/#{account}/policies/#{policy}/edit") - assert lv - |> form("form", policy: attrs) - |> render_submit() == - {:error, {:redirect, %{to: ~p"/#{account}/policies/#{policy}"}}} + lv + |> form("form", policy: attrs) + |> render_submit() + + assert_redirected(lv, ~p"/#{account}/policies/#{policy}") assert policy = Repo.get_by(Domain.Policies.Policy, id: policy.id) assert policy.description == attrs.description diff --git a/elixir/apps/web/test/web/live/policies/show_test.exs b/elixir/apps/web/test/web/live/policies/show_test.exs index 743571a1d..0a9775bd4 100644 --- a/elixir/apps/web/test/web/live/policies/show_test.exs +++ b/elixir/apps/web/test/web/live/policies/show_test.exs @@ -157,7 +157,7 @@ defmodule Web.Live.Policies.ShowTest do assert row["client, actor (ip)"] =~ to_string(flow.client_remote_ip) assert row["gateway (ip)"] =~ - "#{flow.gateway.group.name_prefix}-#{flow.gateway.name_suffix} (189.172.73.153)" + "#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)" end test "allows deleting policy", %{ diff --git a/elixir/apps/web/test/web/live/relay_groups/edit_test.exs b/elixir/apps/web/test/web/live/relay_groups/edit_test.exs index 7375e644a..7b1594abb 100644 --- a/elixir/apps/web/test/web/live/relay_groups/edit_test.exs +++ b/elixir/apps/web/test/web/live/relay_groups/edit_test.exs @@ -143,10 +143,11 @@ defmodule Web.Live.RelayGroups.EditTest do |> authorize_conn(identity) |> live(~p"/#{account}/relay_groups/#{group}/edit") - assert lv - |> form("form", group: attrs) - |> render_submit() == - {:error, {:redirect, %{to: ~p"/#{account}/relay_groups/#{group}"}}} + lv + |> form("form", group: attrs) + |> render_submit() + + assert_redirected(lv, ~p"/#{account}/relay_groups/#{group}") assert group = Repo.get_by(Domain.Relays.Group, id: group.id) assert group.name == attrs.name diff --git a/elixir/apps/web/test/web/live/relay_groups/new_test.exs b/elixir/apps/web/test/web/live/relay_groups/new_test.exs index 9105b6511..14a5a9937 100644 --- a/elixir/apps/web/test/web/live/relay_groups/new_test.exs +++ b/elixir/apps/web/test/web/live/relay_groups/new_test.exs @@ -113,22 +113,11 @@ defmodule Web.Live.RelayGroups.NewTest do |> authorize_conn(identity) |> live(~p"/#{account}/relay_groups/new") - html = - lv - |> form("form", group: attrs) - |> render_submit() + lv + |> form("form", group: attrs) + |> render_submit() - assert html =~ "Select deployment method" - assert html =~ "FIREZONE_TOKEN=" - assert html =~ "docker run" - assert html =~ "Waiting for relay connection..." - - token = Regex.run(~r/FIREZONE_TOKEN="([^ ]+)"/, html) |> List.last() - assert {:ok, _token} = Domain.Relays.authorize_relay(token) - - group = Repo.get_by(Domain.Relays.Group, name: attrs.name) |> Repo.preload(:tokens) - relay = Fixtures.Relays.create_relay(account: account, group: group) - Domain.Relays.connect_relay(relay, "foo") + group = Repo.get_by(Domain.Relays.Group, name: attrs.name) assert assert_redirect(lv, ~p"/#{account}/relay_groups/#{group}") end diff --git a/elixir/apps/web/test/web/live/relay_groups/new_token_test.exs b/elixir/apps/web/test/web/live/relay_groups/new_token_test.exs new file mode 100644 index 000000000..036def1a1 --- /dev/null +++ b/elixir/apps/web/test/web/live/relay_groups/new_token_test.exs @@ -0,0 +1,49 @@ +defmodule Web.Live.RelayGroups.NewTokenTest do + use Web.ConnCase, async: true + + setup do + account = Fixtures.Accounts.create_account() + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + identity = Fixtures.Auth.create_identity(account: account, actor: actor) + group = Fixtures.Relays.create_group(account: account) + + %{ + account: account, + actor: actor, + identity: identity, + group: group + } + end + + test "creates a new group on valid attrs and redirects when relay is connected", %{ + account: account, + identity: identity, + group: group, + conn: conn + } do + {:ok, lv, html} = + conn + |> authorize_conn(identity) + |> live(~p"/#{account}/relay_groups/#{group}/new_token") + + assert html =~ "Select deployment method" + assert html =~ "FIREZONE_TOKEN=" + assert html =~ "PUBLIC_IP4_ADDR=" + assert html =~ "PUBLIC_IP6_ADDR=" + assert html =~ "docker run" + assert html =~ "Waiting for connection..." + + assert Regex.run(~r/FIREZONE_ID=([^ ]+)/, html) |> List.last() + token = Regex.run(~r/FIREZONE_TOKEN=([^ ]+)/, html) |> List.last() |> String.trim(""") + + :ok = Domain.Relays.subscribe_for_relays_presence_in_group(group) + relay = Fixtures.Relays.create_relay(account: account, group: group) + assert {:ok, _token} = Domain.Relays.authorize_relay(token) + Domain.Relays.connect_relay(relay, "foo") + + assert_receive %Phoenix.Socket.Broadcast{topic: "relay_groups:" <> _group_id} + + assert element(lv, "#deployment-instructions") + |> render() =~ "Connected, click to continue" + end +end diff --git a/elixir/apps/web/test/web/live/relay_groups/show_test.exs b/elixir/apps/web/test/web/live/relay_groups/show_test.exs index 50bb1d57e..22bf347f9 100644 --- a/elixir/apps/web/test/web/live/relay_groups/show_test.exs +++ b/elixir/apps/web/test/web/live/relay_groups/show_test.exs @@ -164,10 +164,11 @@ defmodule Web.Live.RelayGroups.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/relay_groups/#{group}") - assert lv - |> element("button", "Delete") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/relay_groups"}}} + lv + |> element("button", "Delete") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/relay_groups") assert Repo.get(Domain.Relays.Group, group.id).deleted_at end diff --git a/elixir/apps/web/test/web/live/relays/show_test.exs b/elixir/apps/web/test/web/live/relays/show_test.exs index b33546ddb..7d617d2cb 100644 --- a/elixir/apps/web/test/web/live/relays/show_test.exs +++ b/elixir/apps/web/test/web/live/relays/show_test.exs @@ -126,10 +126,11 @@ defmodule Web.Live.Relays.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/relays/#{relay}") - assert lv - |> element("button", "Delete Relay") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/relay_groups/#{relay.group}"}}} + lv + |> element("button", "Delete Relay") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/relay_groups/#{relay.group}") assert Repo.get(Domain.Relays.Relay, relay.id).deleted_at end diff --git a/elixir/apps/web/test/web/live/resources/index_test.exs b/elixir/apps/web/test/web/live/resources/index_test.exs index cfc0c5d63..86a4a7077 100644 --- a/elixir/apps/web/test/web/live/resources/index_test.exs +++ b/elixir/apps/web/test/web/live/resources/index_test.exs @@ -77,7 +77,7 @@ defmodule Web.Live.Resources.IndexTest do Enum.each(resource_rows, fn row -> assert row["name"] =~ resource.name assert row["address"] =~ resource.address - assert row["sites"] =~ group.name_prefix + assert row["sites"] =~ group.name assert row["authorized groups"] == "None, create a Policy to grant access." end) end diff --git a/elixir/apps/web/test/web/live/resources/show_test.exs b/elixir/apps/web/test/web/live/resources/show_test.exs index a2a4fcbe5..46ae27de4 100644 --- a/elixir/apps/web/test/web/live/resources/show_test.exs +++ b/elixir/apps/web/test/web/live/resources/show_test.exs @@ -197,7 +197,7 @@ defmodule Web.Live.Resources.ShowTest do |> table_to_map() for gateway_group <- gateway_groups do - assert gateway_group["name"] =~ group.name_prefix + assert gateway_group["name"] =~ group.name # TODO: check that status is being rendered end end @@ -234,7 +234,7 @@ defmodule Web.Live.Resources.ShowTest do assert row["policy"] =~ flow.policy.resource.name assert row["gateway (ip)"] == - "#{flow.gateway.group.name_prefix}-#{flow.gateway.name_suffix} (189.172.73.153)" + "#{flow.gateway.group.name}-#{flow.gateway.name} (189.172.73.153)" assert row["client, actor (ip)"] =~ flow.client.name assert row["client, actor (ip)"] =~ "owned by #{flow.client.actor.name}" diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/edit_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/edit_test.exs index 50843e4ea..29b98df85 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/edit_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/edit_test.exs @@ -99,16 +99,13 @@ defmodule Web.Live.Settings.IdentityProviders.GoogleWorkspace.EditTest do } ) - result = render_submit(form) + render_submit(form) assert provider = Repo.get_by(Domain.Auth.Provider, name: provider_attrs.name) - assert result == - {:error, - {:redirect, - %{ - to: - ~p"/#{account.id}/settings/identity_providers/google_workspace/#{provider}/redirect" - }}} + assert_redirected( + lv, + ~p"/#{account.id}/settings/identity_providers/google_workspace/#{provider}/redirect" + ) assert provider.name == provider_attrs.name assert provider.adapter == :google_workspace diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/new_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/new_test.exs index b3ac69eda..512958649 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/new_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/new_test.exs @@ -86,16 +86,13 @@ defmodule Web.Live.Settings.IdentityProviders.GoogleWorkspace.NewTest do } ) - result = render_submit(form) + render_submit(form) assert provider = Repo.get_by(Domain.Auth.Provider, name: provider_attrs.name) - assert result == - {:error, - {:redirect, - %{ - to: - ~p"/#{account.id}/settings/identity_providers/google_workspace/#{provider}/redirect" - }}} + assert_redirected( + lv, + ~p"/#{account.id}/settings/identity_providers/google_workspace/#{provider}/redirect" + ) assert provider.name == provider_attrs.name assert provider.adapter == :google_workspace diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs index 166746ab3..74b41f4dd 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs @@ -215,10 +215,11 @@ defmodule Web.Live.Settings.IdentityProviders.GoogleWorkspace.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/settings/identity_providers/google_workspace/#{provider}") - assert lv - |> element("button", "Delete Identity Provider") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/settings/identity_providers"}}} + lv + |> element("button", "Delete Identity Provider") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/settings/identity_providers") assert Repo.get(Domain.Auth.Provider, provider.id).deleted_at end diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/edit_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/edit_test.exs index 2c06c48f4..00eab3a35 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/edit_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/edit_test.exs @@ -94,16 +94,13 @@ defmodule Web.Live.Settings.IdentityProviders.OpenIDConnect.EditTest do } ) - result = render_submit(form) + render_submit(form) assert provider = Repo.get_by(Domain.Auth.Provider, name: provider_attrs.name) - assert result == - {:error, - {:redirect, - %{ - to: - ~p"/#{account.id}/settings/identity_providers/openid_connect/#{provider}/redirect" - }}} + assert_redirected( + lv, + ~p"/#{account.id}/settings/identity_providers/openid_connect/#{provider}/redirect" + ) assert provider.name == provider_attrs.name assert provider.adapter == :openid_connect diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/new_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/new_test.exs index 214d33555..4d8b92e74 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/new_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/new_test.exs @@ -88,16 +88,13 @@ defmodule Web.Live.Settings.IdentityProviders.OpenIDConnect.NewTest do } ) - result = render_submit(form) + render_submit(form) assert provider = Repo.get_by(Domain.Auth.Provider, name: provider_attrs.name) - assert result == - {:error, - {:redirect, - %{ - to: - ~p"/#{account.id}/settings/identity_providers/openid_connect/#{provider}/redirect" - }}} + assert_redirected( + lv, + ~p"/#{account.id}/settings/identity_providers/openid_connect/#{provider}/redirect" + ) assert provider.name == provider_attrs.name assert provider.adapter == :openid_connect diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs index 6e365f51f..dd0df3e45 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs @@ -154,10 +154,11 @@ defmodule Web.Live.Settings.IdentityProviders.OpenIDConnect.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/settings/identity_providers/openid_connect/#{provider}") - assert lv - |> element("button", "Delete Identity Provider") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/settings/identity_providers"}}} + lv + |> element("button", "Delete Identity Provider") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/settings/identity_providers") assert Repo.get(Domain.Auth.Provider, provider.id).deleted_at end diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/system/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/system/show_test.exs index d3c210395..488bf5858 100644 --- a/elixir/apps/web/test/web/live/settings/identity_providers/system/show_test.exs +++ b/elixir/apps/web/test/web/live/settings/identity_providers/system/show_test.exs @@ -145,10 +145,11 @@ defmodule Web.Live.Settings.IdentityProviders.System.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/settings/identity_providers/system/#{provider}") - assert lv - |> element("button", "Delete Identity Provider") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/settings/identity_providers"}}} + lv + |> element("button", "Delete Identity Provider") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/settings/identity_providers") assert Repo.get(Domain.Auth.Provider, provider.id).deleted_at end diff --git a/elixir/apps/web/test/web/live/auth/email_test.exs b/elixir/apps/web/test/web/live/sign_in/email_test.exs similarity index 99% rename from elixir/apps/web/test/web/live/auth/email_test.exs rename to elixir/apps/web/test/web/live/sign_in/email_test.exs index 2755a3d8e..35ab3016b 100644 --- a/elixir/apps/web/test/web/live/auth/email_test.exs +++ b/elixir/apps/web/test/web/live/sign_in/email_test.exs @@ -1,4 +1,4 @@ -defmodule Web.Auth.EmailTest do +defmodule Web.SignIn.EmailTest do use Web.ConnCase, async: true setup do diff --git a/elixir/apps/web/test/web/live/auth/sign_in_test.exs b/elixir/apps/web/test/web/live/sign_in_test.exs similarity index 98% rename from elixir/apps/web/test/web/live/auth/sign_in_test.exs rename to elixir/apps/web/test/web/live/sign_in_test.exs index e2cc86dc9..bcecadc48 100644 --- a/elixir/apps/web/test/web/live/auth/sign_in_test.exs +++ b/elixir/apps/web/test/web/live/sign_in_test.exs @@ -1,4 +1,4 @@ -defmodule Web.Auth.SignInTest do +defmodule Web.SignInTest do use Web.ConnCase, async: true test "renders active providers on the page", %{conn: conn} do diff --git a/elixir/apps/web/test/web/live/sign_up/sign_up_test.exs b/elixir/apps/web/test/web/live/sign_up_test.exs similarity index 100% rename from elixir/apps/web/test/web/live/sign_up/sign_up_test.exs rename to elixir/apps/web/test/web/live/sign_up_test.exs diff --git a/elixir/apps/web/test/web/live/sites/edit_test.exs b/elixir/apps/web/test/web/live/sites/edit_test.exs index 8f2a34f94..600b4cfec 100644 --- a/elixir/apps/web/test/web/live/sites/edit_test.exs +++ b/elixir/apps/web/test/web/live/sites/edit_test.exs @@ -59,7 +59,7 @@ defmodule Web.Live.Sites.EditTest do assert item = Floki.find(html, "[aria-label='Breadcrumb']") breadcrumbs = String.trim(Floki.text(item)) assert breadcrumbs =~ "Sites" - assert breadcrumbs =~ group.name_prefix + assert breadcrumbs =~ group.name assert breadcrumbs =~ "Edit" end @@ -77,7 +77,7 @@ defmodule Web.Live.Sites.EditTest do form = form(lv, "form") assert find_inputs(form) == [ - "group[name_prefix]" + "group[name]" ] end @@ -96,14 +96,14 @@ defmodule Web.Live.Sites.EditTest do lv |> form("form", group: attrs) - |> validate_change(%{group: %{name_prefix: String.duplicate("a", 256)}}, fn form, _html -> + |> validate_change(%{group: %{name: String.duplicate("a", 256)}}, fn form, _html -> assert form_validation_errors(form) == %{ - "group[name_prefix]" => ["should be at most 64 character(s)"] + "group[name]" => ["should be at most 64 character(s)"] } end) - |> validate_change(%{group: %{name_prefix: ""}}, fn form, _html -> + |> validate_change(%{group: %{name: ""}}, fn form, _html -> assert form_validation_errors(form) == %{ - "group[name_prefix]" => ["can't be blank"] + "group[name]" => ["can't be blank"] } end) end @@ -115,7 +115,7 @@ defmodule Web.Live.Sites.EditTest do conn: conn } do other_group = Fixtures.Gateways.create_group(account: account) - attrs = %{name_prefix: other_group.name_prefix} + attrs = %{name: other_group.name} {:ok, lv, _html} = conn @@ -126,7 +126,7 @@ defmodule Web.Live.Sites.EditTest do |> form("form", group: attrs) |> render_submit() |> form_validation_errors() == %{ - "group[name_prefix]" => ["has already been taken"] + "group[name]" => ["has already been taken"] } end @@ -136,19 +136,20 @@ defmodule Web.Live.Sites.EditTest do group: group, conn: conn } do - attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name_prefix]) + attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name]) {:ok, lv, _html} = conn |> authorize_conn(identity) |> live(~p"/#{account}/sites/#{group}/edit") - assert lv - |> form("form", group: attrs) - |> render_submit() == - {:error, {:redirect, %{to: ~p"/#{account}/sites/#{group}"}}} + lv + |> form("form", group: attrs) + |> render_submit() + + assert_redirected(lv, ~p"/#{account}/sites/#{group}") assert group = Repo.get_by(Domain.Gateways.Group, id: group.id) - assert group.name_prefix == attrs.name_prefix + assert group.name == attrs.name end end diff --git a/elixir/apps/web/test/web/live/sites/index_test.exs b/elixir/apps/web/test/web/live/sites/index_test.exs index 89b5221fb..085bc911b 100644 --- a/elixir/apps/web/test/web/live/sites/index_test.exs +++ b/elixir/apps/web/test/web/live/sites/index_test.exs @@ -76,8 +76,8 @@ defmodule Web.Live.Sites.IndexTest do |> table_to_map() assert row == %{ - "site" => group.name_prefix, - "gateways" => gateway.name_suffix, + "site" => group.name, + "gateways" => gateway.name, "resources" => resource.name } end diff --git a/elixir/apps/web/test/web/live/sites/new_test.exs b/elixir/apps/web/test/web/live/sites/new_test.exs index e4986fe15..88d218b4e 100644 --- a/elixir/apps/web/test/web/live/sites/new_test.exs +++ b/elixir/apps/web/test/web/live/sites/new_test.exs @@ -55,7 +55,7 @@ defmodule Web.Live.Sites.NewTest do form = form(lv, "form") assert find_inputs(form) == [ - "group[name_prefix]" + "group[name]" ] end @@ -64,7 +64,7 @@ defmodule Web.Live.Sites.NewTest do identity: identity, conn: conn } do - attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name_prefix]) + attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name]) {:ok, lv, _html} = conn @@ -73,9 +73,9 @@ defmodule Web.Live.Sites.NewTest do lv |> form("form", group: attrs) - |> validate_change(%{group: %{name_prefix: String.duplicate("a", 256)}}, fn form, _html -> + |> validate_change(%{group: %{name: String.duplicate("a", 256)}}, fn form, _html -> assert form_validation_errors(form) == %{ - "group[name_prefix]" => ["should be at most 64 character(s)"] + "group[name]" => ["should be at most 64 character(s)"] } end) end @@ -86,7 +86,7 @@ defmodule Web.Live.Sites.NewTest do conn: conn } do other_gateway = Fixtures.Gateways.create_group(account: account) - attrs = %{name_prefix: other_gateway.name_prefix} + attrs = %{name: other_gateway.name} {:ok, lv, _html} = conn @@ -97,7 +97,7 @@ defmodule Web.Live.Sites.NewTest do |> form("form", group: attrs) |> render_submit() |> form_validation_errors() == %{ - "group[name_prefix]" => ["has already been taken"] + "group[name]" => ["has already been taken"] } end @@ -106,7 +106,7 @@ defmodule Web.Live.Sites.NewTest do identity: identity, conn: conn } do - attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name_prefix]) + attrs = Fixtures.Gateways.group_attrs() |> Map.take([:name]) {:ok, lv, _html} = conn @@ -118,7 +118,7 @@ defmodule Web.Live.Sites.NewTest do |> render_submit() group = - Repo.get_by(Domain.Gateways.Group, name_prefix: attrs.name_prefix) + Repo.get_by(Domain.Gateways.Group, name: attrs.name) |> Repo.preload(:tokens) assert assert_redirect(lv, ~p"/#{account}/sites/#{group}") diff --git a/elixir/apps/web/test/web/live/sites/new_token_test.exs b/elixir/apps/web/test/web/live/sites/new_token_test.exs index e3d654f2b..8097619a7 100644 --- a/elixir/apps/web/test/web/live/sites/new_token_test.exs +++ b/elixir/apps/web/test/web/live/sites/new_token_test.exs @@ -29,15 +29,19 @@ defmodule Web.Live.Sites.NewTokenTest do assert html =~ "Select deployment method" assert html =~ "FIREZONE_TOKEN=" assert html =~ "docker run" - assert html =~ "Waiting for gateway connection..." + assert html =~ "Waiting for connection..." assert Regex.run(~r/FIREZONE_ID=([^ ]+)/, html) |> List.last() token = Regex.run(~r/FIREZONE_TOKEN=([^ ]+)/, html) |> List.last() |> String.trim(""") - assert {:ok, _token} = Domain.Gateways.authorize_gateway(token) + :ok = Domain.Gateways.subscribe_for_gateways_presence_in_group(group) gateway = Fixtures.Gateways.create_gateway(account: account, group: group) + assert {:ok, _token} = Domain.Gateways.authorize_gateway(token) Domain.Gateways.connect_gateway(gateway) - assert assert_redirect(lv, ~p"/#{account}/sites/#{group}") + assert_receive %Phoenix.Socket.Broadcast{topic: "gateway_groups:" <> _group_id} + + assert element(lv, "#deployment-instructions") + |> render() =~ "Connected, click to continue" end end diff --git a/elixir/apps/web/test/web/live/sites/show_test.exs b/elixir/apps/web/test/web/live/sites/show_test.exs index 7a39aca35..6f183bac2 100644 --- a/elixir/apps/web/test/web/live/sites/show_test.exs +++ b/elixir/apps/web/test/web/live/sites/show_test.exs @@ -64,7 +64,7 @@ defmodule Web.Live.Sites.ShowTest do assert item = Floki.find(html, "[aria-label='Breadcrumb']") breadcrumbs = String.trim(Floki.text(item)) assert breadcrumbs =~ "Sites" - assert breadcrumbs =~ group.name_prefix + assert breadcrumbs =~ group.name end test "allows editing gateway groups", %{ @@ -102,7 +102,7 @@ defmodule Web.Live.Sites.ShowTest do |> render() |> vertical_table_to_map() - assert table["name"] =~ group.name_prefix + assert table["name"] =~ group.name assert table["created"] =~ actor.name end @@ -123,7 +123,7 @@ defmodule Web.Live.Sites.ShowTest do |> element("#gateways") |> render() |> table_to_map() - |> with_table_row("instance", gateway.name_suffix, fn row -> + |> with_table_row("instance", gateway.name, fn row -> assert row["token created at"] =~ actor.name assert row["status"] =~ "Offline" end) @@ -147,7 +147,7 @@ defmodule Web.Live.Sites.ShowTest do |> element("#gateways") |> render() |> table_to_map() - |> with_table_row("instance", gateway.name_suffix, fn row -> + |> with_table_row("instance", gateway.name, fn row -> assert gateway.last_seen_remote_ip assert row["remote ip"] =~ to_string(gateway.last_seen_remote_ip) assert row["status"] =~ "Online" @@ -181,7 +181,7 @@ defmodule Web.Live.Sites.ShowTest do Enum.each(resource_rows, fn row -> assert row["name"] =~ resource.name assert row["address"] =~ resource.address - assert row["sites"] =~ group.name_prefix + assert row["sites"] =~ group.name assert row["authorized groups"] == "None, create a Policy to grant access." end) end @@ -264,10 +264,11 @@ defmodule Web.Live.Sites.ShowTest do |> authorize_conn(identity) |> live(~p"/#{account}/sites/#{group}") - assert lv - |> element("button", "Delete") - |> render_click() == - {:error, {:redirect, %{to: ~p"/#{account}/sites"}}} + lv + |> element("button", "Delete") + |> render_click() + + assert_redirected(lv, ~p"/#{account}/sites") assert Repo.get(Domain.Gateways.Group, group.id).deleted_at end diff --git a/rust/connlib/shared/src/lib.rs b/rust/connlib/shared/src/lib.rs index 06e9733d5..dc039b8c9 100644 --- a/rust/connlib/shared/src/lib.rs +++ b/rust/connlib/shared/src/lib.rs @@ -36,7 +36,8 @@ pub fn login_url( device_id: String, ) -> Result<(Url, StaticSecret)> { let private_key = StaticSecret::random_from_rng(rand::rngs::OsRng); - let name_suffix: String = thread_rng() + // FIXME: read FIREZONE_NAME from env (eg. for gateways) and use system hostname by default + let name: String = thread_rng() .sample_iter(&Alphanumeric) .take(8) .map(char::from) @@ -52,7 +53,7 @@ pub fn login_url( }, &Key(PublicKey::from(&private_key).to_bytes()), &external_id, - &name_suffix, + &name, )?; Ok((url, private_key)) @@ -103,7 +104,7 @@ fn get_websocket_path( mode: &str, public_key: &Key, external_id: &str, - name_suffix: &str, + name: &str, ) -> Result { set_ws_scheme(&mut api_url)?; @@ -120,7 +121,7 @@ fn get_websocket_path( query_pairs.append_pair("token", secret.expose_secret()); query_pairs.append_pair("public_key", &public_key.to_string()); query_pairs.append_pair("external_id", external_id); - query_pairs.append_pair("name_suffix", name_suffix); + query_pairs.append_pair("name", name); } Ok(api_url) diff --git a/terraform/modules/gateway-google-cloud-compute/main.tf b/terraform/modules/gateway-google-cloud-compute/main.tf index 24bcd685d..bb61118fa 100644 --- a/terraform/modules/gateway-google-cloud-compute/main.tf +++ b/terraform/modules/gateway-google-cloud-compute/main.tf @@ -54,7 +54,7 @@ locals { value = "$(hostname)" }, { - name = "FIREZONE_HOSTNAME" + name = "FIREZONE_NAME" value = "$(hostname)" }, {