From 33ab23b636a28ce77cf8cb045a24ec69213248d4 Mon Sep 17 00:00:00 2001 From: Andrew Dryga Date: Tue, 14 Nov 2023 13:02:21 -0600 Subject: [PATCH] Cleanup UX and fix a bunch of TODOs (#2641) This PR cleans up a lot of TODO and some issues I've discovered while fixing them, there are _a few_ UI changes. We show `(you)` next to your name on the actor view page, where `Profile` link goes from the dropdown menu: Screenshot 2023-11-13 at 19 05 35 Relays were way behind Gateways in terms of view code, so I changed them to be exactly the same: Screenshot 2023-11-13 at 18 54 39 We also show authorizations on the Actor page because previously to find "what this user did" you had to go through all user clients individually: Screenshot 2023-11-13 at 18 54 27 I've noticed there is some confusion around sign-in slugs so I added a home page where you can use ID or slug to get the in link (not all the clients will know you need to put that in the URL) and recently used accounts: Screenshot 2023-11-13 at 18 54 06 Buttons to copy the code are more visible now, I've used our accent color but am open to better ideas: Screenshot 2023-11-13 at 19 10 29 When code is copied it's also more visible: Screenshot 2023-11-13 at 19 11 41 We also do not redirect from that page automatically, but the large button becomes green with the text changed: Screenshot 2023-11-13 at 19 12 11 --- elixir/README.md | 6 +- elixir/apps/api/lib/api/gateway/socket.ex | 2 +- elixir/apps/domain/lib/domain/accounts.ex | 9 + .../lib/domain/accounts/account/query.ex | 8 +- elixir/apps/domain/lib/domain/flows.ex | 7 +- .../domain/lib/domain/flows/flow/query.ex | 12 + .../domain/lib/domain/gateways/gateway.ex | 3 +- .../lib/domain/gateways/gateway/changeset.ex | 15 +- .../apps/domain/lib/domain/gateways/group.ex | 3 +- .../lib/domain/gateways/group/changeset.ex | 10 +- .../apps/domain/lib/domain/policies/policy.ex | 4 +- .../lib/domain/policies/policy/query.ex | 12 + ...3333_rename_gateway_groups_name_prefix.exs | 12 + ...017_rename_gateway_name_suffix_to_name.exs | 14 + elixir/apps/domain/priv/repo/seeds.exs | 14 +- elixir/apps/domain/test/domain/flows_test.exs | 29 +- .../apps/domain/test/domain/gateways_test.exs | 56 ++-- .../apps/domain/test/domain/policies_test.exs | 32 +++ .../domain/test/support/fixtures/gateways.ex | 4 +- elixir/apps/web/assets/js/hooks.js | 20 +- elixir/apps/web/lib/web/auth.ex | 43 +++ .../web/lib/web/components/core_components.ex | 74 +++++- .../lib/web/controllers/auth_controller.ex | 44 +--- .../lib/web/controllers/home_controller.ex | 27 ++ .../apps/web/lib/web/controllers/home_html.ex | 81 ++++++ .../web/controllers/redirect_controller.ex | 7 - elixir/apps/web/lib/web/live/actors/edit.ex | 2 +- .../web/live/actors/service_accounts/new.ex | 2 +- elixir/apps/web/lib/web/live/actors/show.ex | 109 +++++++- .../apps/web/lib/web/live/actors/users/new.ex | 4 +- .../lib/web/live/actors/users/new_identity.ex | 4 +- elixir/apps/web/lib/web/live/clients/edit.ex | 2 +- elixir/apps/web/lib/web/live/clients/show.ex | 4 +- elixir/apps/web/lib/web/live/flows/show.ex | 2 +- elixir/apps/web/lib/web/live/gateways/show.ex | 14 +- elixir/apps/web/lib/web/live/groups/edit.ex | 2 +- .../web/lib/web/live/groups/edit_actors.ex | 2 +- elixir/apps/web/lib/web/live/groups/new.ex | 4 +- elixir/apps/web/lib/web/live/groups/show.ex | 2 +- elixir/apps/web/lib/web/live/policies/edit.ex | 2 +- elixir/apps/web/lib/web/live/policies/new.ex | 4 +- elixir/apps/web/lib/web/live/policies/show.ex | 2 +- .../web/lib/web/live/relay_groups/edit.ex | 2 +- .../apps/web/lib/web/live/relay_groups/new.ex | 187 +------------ .../lib/web/live/relay_groups/new_token.ex | 249 ++++++++++++++++++ .../web/lib/web/live/relay_groups/show.ex | 80 +++--- elixir/apps/web/lib/web/live/relays/show.ex | 12 +- .../web/lib/web/live/resources/components.ex | 2 +- .../apps/web/lib/web/live/resources/index.ex | 2 +- .../apps/web/lib/web/live/resources/show.ex | 4 +- .../google_workspace/edit.ex | 2 +- .../google_workspace/new.ex | 2 +- .../google_workspace/show.ex | 4 +- .../identity_providers/openid_connect/edit.ex | 2 +- .../identity_providers/openid_connect/new.ex | 2 +- .../identity_providers/openid_connect/show.ex | 4 +- .../settings/identity_providers/saml/show.ex | 4 +- .../identity_providers/system/show.ex | 4 +- .../web/lib/web/live/{auth => }/sign_in.ex | 2 +- .../lib/web/live/{auth => sign_in}/email.ex | 2 +- elixir/apps/web/lib/web/live/sites/edit.ex | 8 +- elixir/apps/web/lib/web/live/sites/index.ex | 4 +- elixir/apps/web/lib/web/live/sites/new.ex | 13 +- .../apps/web/lib/web/live/sites/new_token.ex | 92 ++++--- elixir/apps/web/lib/web/live/sites/show.ex | 19 +- elixir/apps/web/lib/web/router.ex | 21 +- .../test/web/acceptance/auth/email_test.exs | 2 +- elixir/apps/web/test/web/auth_test.exs | 12 +- .../web/controllers/auth_controller_test.exs | 8 +- .../web/controllers/home_controller_test.exs | 57 ++++ .../web/test/web/live/actors/edit_test.exs | 18 +- .../web/test/web/live/actors/show_test.exs | 64 +++++ .../web/test/web/live/clients/edit_test.exs | 9 +- .../web/test/web/live/clients/show_test.exs | 11 +- .../web/test/web/live/gateways/show_test.exs | 17 +- .../test/web/live/groups/edit_actors_test.exs | 8 +- .../web/test/web/live/groups/edit_test.exs | 9 +- .../web/test/web/live/groups/new_test.exs | 9 +- .../web/test/web/live/groups/show_test.exs | 9 +- .../web/test/web/live/policies/edit_test.exs | 9 +- .../web/test/web/live/policies/show_test.exs | 2 +- .../test/web/live/relay_groups/edit_test.exs | 9 +- .../test/web/live/relay_groups/new_test.exs | 19 +- .../web/live/relay_groups/new_token_test.exs | 49 ++++ .../test/web/live/relay_groups/show_test.exs | 9 +- .../web/test/web/live/relays/show_test.exs | 9 +- .../test/web/live/resources/index_test.exs | 2 +- .../web/test/web/live/resources/show_test.exs | 4 +- .../google_workspace/edit_test.exs | 13 +- .../google_workspace/new_test.exs | 13 +- .../google_workspace/show_test.exs | 9 +- .../openid_connect/edit_test.exs | 13 +- .../openid_connect/new_test.exs | 13 +- .../openid_connect/show_test.exs | 9 +- .../identity_providers/system/show_test.exs | 9 +- .../web/live/{auth => sign_in}/email_test.exs | 2 +- .../test/web/live/{auth => }/sign_in_test.exs | 2 +- .../web/live/{sign_up => }/sign_up_test.exs | 0 .../web/test/web/live/sites/edit_test.exs | 29 +- .../web/test/web/live/sites/index_test.exs | 4 +- .../apps/web/test/web/live/sites/new_test.exs | 16 +- .../test/web/live/sites/new_token_test.exs | 10 +- .../web/test/web/live/sites/show_test.exs | 19 +- rust/connlib/shared/src/lib.rs | 9 +- .../gateway-google-cloud-compute/main.tf | 2 +- 105 files changed, 1327 insertions(+), 622 deletions(-) create mode 100644 elixir/apps/domain/priv/repo/migrations/20231113213333_rename_gateway_groups_name_prefix.exs create mode 100644 elixir/apps/domain/priv/repo/migrations/20231113214017_rename_gateway_name_suffix_to_name.exs create mode 100644 elixir/apps/web/lib/web/controllers/home_controller.ex create mode 100644 elixir/apps/web/lib/web/controllers/home_html.ex delete mode 100644 elixir/apps/web/lib/web/controllers/redirect_controller.ex create mode 100644 elixir/apps/web/lib/web/live/relay_groups/new_token.ex rename elixir/apps/web/lib/web/live/{auth => }/sign_in.ex (99%) rename elixir/apps/web/lib/web/live/{auth => sign_in}/email.ex (99%) create mode 100644 elixir/apps/web/test/web/controllers/home_controller_test.exs create mode 100644 elixir/apps/web/test/web/live/relay_groups/new_token_test.exs rename elixir/apps/web/test/web/live/{auth => sign_in}/email_test.exs (99%) rename elixir/apps/web/test/web/live/{auth => }/sign_in_test.exs (98%) rename elixir/apps/web/test/web/live/{sign_up => }/sign_up_test.exs (100%) 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)" }, {