mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
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: <img width="1728" alt="Screenshot 2023-11-13 at 19 05 35" src="https://github.com/firezone/firezone/assets/1877644/f52b2531-e3be-4d3a-a587-4f9f54ca2c49"> Relays were way behind Gateways in terms of view code, so I changed them to be exactly the same: <img width="1728" alt="Screenshot 2023-11-13 at 18 54 39" src="https://github.com/firezone/firezone/assets/1877644/a9f0905d-80d2-4e91-a744-c4baf7ad4a7c"> 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: <img width="1728" alt="Screenshot 2023-11-13 at 18 54 27" src="https://github.com/firezone/firezone/assets/1877644/02ada445-e175-427e-99de-f9fa5bdd5aab"> 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: <img width="1728" alt="Screenshot 2023-11-13 at 18 54 06" src="https://github.com/firezone/firezone/assets/1877644/ccfb9198-ed1f-4b3e-a26f-b76bab24243c"> Buttons to copy the code are more visible now, I've used our accent color but am open to better ideas: <img width="1728" alt="Screenshot 2023-11-13 at 19 10 29" src="https://github.com/firezone/firezone/assets/1877644/a2c0658e-1003-409b-b5ad-d5d3ade60a10"> When code is copied it's also more visible: <img width="699" alt="Screenshot 2023-11-13 at 19 11 41" src="https://github.com/firezone/firezone/assets/1877644/62e793d2-d760-4aa7-9a42-92a6bbfcbf52"> We also do not redirect from that page automatically, but the large button becomes green with the text changed: <img width="660" alt="Screenshot 2023-11-13 at 19 12 11" src="https://github.com/firezone/firezone/assets/1877644/780dcde3-8018-4405-91e5-984288431ec1">
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}},
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
###########################
|
||||
|
||||
@@ -70,12 +70,23 @@ defmodule Web.CoreComponents do
|
||||
data-copy
|
||||
phx-no-format
|
||||
><%= render_slot(@inner_block) %></code>
|
||||
<.icon name="hero-clipboard-document" data-icon class={~w[
|
||||
|
||||
<span class={~w[
|
||||
absolute bottom-1 right-1
|
||||
h-5 w-5
|
||||
text-gray-400
|
||||
transition
|
||||
text-gray-500 group-hover:text-white
|
||||
]} />
|
||||
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" />
|
||||
<span data-content>Copy</span>
|
||||
</span>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
@@ -486,6 +497,27 @@ defmodule Web.CoreComponents do
|
||||
"""
|
||||
end
|
||||
|
||||
def icon(%{name: "spinner"} = assigns) do
|
||||
~H"""
|
||||
<svg
|
||||
class={["inline-block", @class]}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{@rest}
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
|
||||
</circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
"""
|
||||
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
|
||||
}
|
||||
>
|
||||
<span :if={not @connected?}>
|
||||
<.icon name="spinner" class="animate-spin h-3.5 w-3.5 mr-1" /> Waiting for connection...
|
||||
</span>
|
||||
|
||||
<span :if={@connected?}>
|
||||
<.icon name="hero-check" class="h-3.5 w-3.5" /> Connected, click to continue
|
||||
</span>
|
||||
</.link>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders creation timestamp and entity.
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
27
elixir/apps/web/lib/web/controllers/home_controller.ex
Normal file
27
elixir/apps/web/lib/web/controllers/home_controller.ex
Normal file
@@ -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
|
||||
81
elixir/apps/web/lib/web/controllers/home_html.ex
Normal file
81
elixir/apps/web/lib/web/controllers/home_html.ex
Normal file
@@ -0,0 +1,81 @@
|
||||
defmodule Web.HomeHTML do
|
||||
use Web, :html
|
||||
|
||||
def home(assigns) do
|
||||
~H"""
|
||||
<section class="bg-gray-50 dark:bg-gray-900">
|
||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto lg:py-0">
|
||||
<.logo />
|
||||
|
||||
<div class="w-full col-span-6 mx-auto bg-white rounded shadow dark:bg-gray-800 md:mt-0 sm:max-w-lg xl:p-0">
|
||||
<div class="p-6 space-y-4 lg:space-y-6 sm:p-8">
|
||||
<h1 class="text-xl text-center font-bold leading-tight tracking-tight text-gray-900 sm:text-2xl dark:text-white">
|
||||
Welcome to Firezone
|
||||
</h1>
|
||||
|
||||
<h3
|
||||
:if={@accounts != []}
|
||||
class="text-m font-bold leading-tight tracking-tight text-gray-900 sm:text-xl dark:text-white"
|
||||
>
|
||||
Recently used accounts
|
||||
</h3>
|
||||
|
||||
<div :if={@accounts != []} class="space-y-3 items-center">
|
||||
<.account_button :for={account <- @accounts} account={account} />
|
||||
</div>
|
||||
|
||||
<.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
|
||||
</.button>
|
||||
</.form>
|
||||
<p :if={Domain.Config.sign_up_enabled?()} class="py-2">
|
||||
Don't have an account?
|
||||
<a href={~p"/sign_up"} class="font-medium text-blue-600 hover:text-blue-500">
|
||||
Sign up here.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
"""
|
||||
end
|
||||
|
||||
def account_button(assigns) do
|
||||
~H"""
|
||||
<a href={~p"/#{@account}"} class={~w[
|
||||
w-full inline-flex items-center justify-center py-2.5 px-5
|
||||
bg-white rounded
|
||||
text-sm font-medium text-gray-900
|
||||
focus:outline-none
|
||||
border border-gray-200
|
||||
hover:bg-gray-100 hover:text-gray-900
|
||||
focus:z-10 focus:ring-4 focus:ring-gray-200
|
||||
dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400
|
||||
dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700]}>
|
||||
<%= @account.name %>
|
||||
</a>
|
||||
"""
|
||||
end
|
||||
|
||||
def separator(assigns) do
|
||||
~H"""
|
||||
<div class="flex items-center">
|
||||
<div class="w-full h-0.5 bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div class="px-5 text-center text-gray-500 dark:text-gray-400">or</div>
|
||||
<div class="w-full h-0.5 bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
defmodule Web.RedirectController do
|
||||
use Web, :controller
|
||||
|
||||
def home(conn, _params) do
|
||||
redirect(conn, external: "https://firezone.dev")
|
||||
end
|
||||
end
|
||||
@@ -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} ->
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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) %>: <span class="font-bold"><%= @actor.name %></span>
|
||||
<span :if={@actor.id == @subject.actor.id} class="text-gray-400">(you)</span>
|
||||
</:title>
|
||||
<:action>
|
||||
<.edit_button navigate={~p"/#{@account}/actors/#{@actor}/edit"}>
|
||||
@@ -145,6 +157,80 @@ defmodule Web.Actors.Show do
|
||||
</:content>
|
||||
</.section>
|
||||
|
||||
<.section>
|
||||
<:title>Clients</:title>
|
||||
|
||||
<: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 %>
|
||||
</.link>
|
||||
</:col>
|
||||
<:col :let={client} label="STATUS">
|
||||
<.connection_status schema={client} />
|
||||
</:col>
|
||||
<:empty>
|
||||
<div class="text-center text-slate-500 p-4">No clients to display</div>
|
||||
<div class="text-center text-slate-500 mb-4">
|
||||
Clients are created automatically when user connects to a resource.
|
||||
</div>
|
||||
</:empty>
|
||||
</.table>
|
||||
</:content>
|
||||
</.section>
|
||||
|
||||
<.section>
|
||||
<:title>Authorizations</:title>
|
||||
<:content>
|
||||
<.table id="flows" rows={@flows} row_id={&"flows-#{&1.id}"}>
|
||||
<:col :let={flow} label="AUTHORIZED AT">
|
||||
<.relative_datetime datetime={flow.inserted_at} />
|
||||
</:col>
|
||||
<:col :let={flow} label="EXPIRES AT">
|
||||
<.relative_datetime datetime={flow.expires_at} />
|
||||
</:col>
|
||||
<: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"
|
||||
>
|
||||
<Web.Policies.Components.policy_name policy={flow.policy} />
|
||||
</.link>
|
||||
</:col>
|
||||
<:col :let={flow} label="CLIENT (IP)">
|
||||
<.link navigate={~p"/#{@account}/clients/#{flow.client_id}"} class={link_style()}>
|
||||
<%= flow.client.name %>
|
||||
</.link>
|
||||
(<%= flow.client_remote_ip %>)
|
||||
</:col>
|
||||
<: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 %>
|
||||
</.link>
|
||||
(<%= flow.gateway_remote_ip %>)
|
||||
</:col>
|
||||
<: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
|
||||
</.link>
|
||||
</:col>
|
||||
<:empty>
|
||||
<div class="text-center text-slate-500 p-4">No authorizations to display</div>
|
||||
</:empty>
|
||||
</.table>
|
||||
</:content>
|
||||
</.section>
|
||||
|
||||
<.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.")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -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 %>
|
||||
</.link>
|
||||
(<%= flow.gateway_remote_ip %>)
|
||||
</:col>
|
||||
@@ -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
|
||||
|
||||
@@ -76,7 +76,7 @@ defmodule Web.Flows.Show do
|
||||
<:label>Gateway</:label>
|
||||
<: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 %>
|
||||
</.link>
|
||||
<div>
|
||||
Remote IP: <%= @flow.gateway_remote_ip %>
|
||||
|
||||
@@ -32,18 +32,18 @@ defmodule Web.Gateways.Show do
|
||||
<.breadcrumbs account={@account}>
|
||||
<.breadcrumb path={~p"/#{@account}/sites"}>Sites</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/sites/#{@gateway.group}"}>
|
||||
<%= @gateway.group.name_prefix %>
|
||||
<%= @gateway.group.name %>
|
||||
</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/sites/#{@gateway.group}?#gateways"}>
|
||||
Gateways
|
||||
</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/gateways/#{@gateway}"}>
|
||||
<%= @gateway.name_suffix %>
|
||||
<%= @gateway.name %>
|
||||
</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
<.section>
|
||||
<:title>
|
||||
Gateway: <code><%= @gateway.name_suffix %></code>
|
||||
Gateway: <code><%= @gateway.name %></code>
|
||||
</:title>
|
||||
<: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 %>
|
||||
</.link>
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Instance Name</:label>
|
||||
<:value><%= @gateway.name_suffix %></:value>
|
||||
<:label>Name</:label>
|
||||
<:value><%= @gateway.name %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row :if={@todos_enabled?}>
|
||||
<:label>Connectivity</:label>
|
||||
@@ -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}"
|
||||
)
|
||||
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))}
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -124,7 +124,7 @@ defmodule Web.Policies.Show do
|
||||
</:col>
|
||||
<: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 %>
|
||||
</.link>
|
||||
(<%= flow.gateway_remote_ip %>)
|
||||
</:col>
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -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</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
<.section>
|
||||
<:title :if={is_nil(@group)}>
|
||||
<:title>
|
||||
Add a new Relay Instance Group
|
||||
</:title>
|
||||
<:title :if={not is_nil(@group)}>
|
||||
Deploy your Relay Instance
|
||||
</:title>
|
||||
<:content>
|
||||
<div class="py-8 px-4 mx-auto max-w-2xl lg:py-16">
|
||||
<.form :if={is_nil(@group)} for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
<.input
|
||||
@@ -38,71 +35,6 @@ defmodule Web.RelayGroups.New do
|
||||
Save
|
||||
</.submit_button>
|
||||
</.form>
|
||||
|
||||
<div :if={not is_nil(@group)}>
|
||||
<div class="text-xl mb-2">
|
||||
Select deployment method:
|
||||
</div>
|
||||
<.tabs id="deployment-instructions" phx-update="ignore">
|
||||
<:tab id="docker-instructions" label="Docker">
|
||||
<p class="pl-4 mb-2">
|
||||
Copy-paste this command to your server and replace <code>PUBLIC_IP4_ADDR</code>
|
||||
and <code>PUBLIC_IP6_ADDR</code>
|
||||
with your public IP addresses:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-docker" class="w-full rounded-b" phx-no-format><%= docker_command(@env) %></.code_block>
|
||||
</:tab>
|
||||
<:tab id="systemd-instructions" label="Systemd">
|
||||
<p class="pl-4 mb-2">
|
||||
1. Create a systemd unit file with the following content:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo nano /etc/systemd/system/firezone-relay.service</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
2. Copy-paste the following content into the file and replace
|
||||
<code>PUBLIC_IP4_ADDR</code>
|
||||
and <code>PUBLIC_IP6_ADDR</code>
|
||||
with your public IP addresses::
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full rounded-b" phx-no-format><%= systemd_command(@env) %></.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
3. Save by pressing <kbd>Ctrl</kbd>+<kbd>X</kbd>, then <kbd>Y</kbd>, then <kbd>Enter</kbd>.
|
||||
</p>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
4. Reload systemd configuration:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl daemon-reload</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
5. Start the service:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl start firezone-relay</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
6. Enable the service to start on boot:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl enable firezone-relay</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
7. Check the status of the service:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full rounded-b" phx-no-format>sudo systemctl status firezone-relay</.code_block>
|
||||
</:tab>
|
||||
</.tabs>
|
||||
|
||||
<div class="mt-4 animate-pulse text-center">
|
||||
Waiting for relay connection...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</:content>
|
||||
</.section>
|
||||
@@ -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
|
||||
|
||||
249
elixir/apps/web/lib/web/live/relay_groups/new_token.ex
Normal file
249
elixir/apps/web/lib/web/live/relay_groups/new_token.ex
Normal file
@@ -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>
|
||||
<.breadcrumb path={~p"/#{@account}/relay_groups/#{@group}"}>
|
||||
<%= @group.name %>
|
||||
</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/relay_groups/#{@group}/new_token"}>Deploy</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Deploy your Relay
|
||||
</:title>
|
||||
<:content>
|
||||
<div class="py-8 px-4 mx-auto max-w-2xl lg:py-16">
|
||||
<div class="text-xl mb-2">
|
||||
Select deployment method:
|
||||
</div>
|
||||
<.tabs :if={@env} id="deployment-instructions" phx-update="ignore">
|
||||
<:tab id="docker-instructions" label="Docker">
|
||||
<p class="pl-4 mb-2">
|
||||
Copy-paste this command to your server and replace <code>PUBLIC_IP4_ADDR</code>
|
||||
and <code>PUBLIC_IP6_ADDR</code>
|
||||
with your public IP addresses:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-docker1" class="w-full rounded-b" phx-no-format><%= docker_command(@env) %></.code_block>
|
||||
|
||||
<.initial_connection_status
|
||||
:if={@env}
|
||||
type="relay"
|
||||
navigate={~p"/#{@account}/relays/#{@group}"}
|
||||
connected?={@connected?}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<p class="pl-4 mb-2 mt-4 text-xl font-semibold">
|
||||
Troubleshooting
|
||||
</p>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
Check the container status:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-docker2" class="w-full" phx-no-format>docker ps --filter "name=firezone-relay"</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
Check the container logs:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-docker3" class="w-full rounded-b" phx-no-format>docker logs firezone-relay</.code_block>
|
||||
</:tab>
|
||||
<:tab id="systemd-instructions" label="Systemd">
|
||||
<p class="pl-4 mb-2">
|
||||
1. Create a systemd unit file with the following content:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd1" class="w-full" phx-no-format>sudo nano /etc/systemd/system/firezone-relay.service</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
2. Copy-paste the following content into the file and replace
|
||||
<code>PUBLIC_IP4_ADDR</code>
|
||||
and <code>PUBLIC_IP6_ADDR</code>
|
||||
with your public IP addresses::
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd2" class="w-full rounded-b" phx-no-format><%= systemd_command(@env) %></.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
3. Save by pressing <kbd>Ctrl</kbd>+<kbd>X</kbd>, then <kbd>Y</kbd>, then <kbd>Enter</kbd>.
|
||||
</p>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
4. Reload systemd configuration:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd4" class="w-full" phx-no-format>sudo systemctl daemon-reload</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
5. Start the service:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd5" class="w-full" phx-no-format>sudo systemctl start firezone-relay</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
6. Enable the service to start on boot:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd6" class="w-full" phx-no-format>sudo systemctl enable firezone-relay</.code_block>
|
||||
|
||||
<.initial_connection_status
|
||||
:if={@env}
|
||||
type="relay"
|
||||
navigate={~p"/#{@account}/sites/#{@group}"}
|
||||
connected?={@connected?}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<p class="pl-4 mb-2 mt-4 text-xl font-semibold">
|
||||
Troubleshooting
|
||||
</p>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
Check the status of the service:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd7" class="w-full rounded-b" phx-no-format>sudo systemctl status firezone-relay</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
Check the logs:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd8" class="w-full rounded-b" phx-no-format>sudo journalctl -u firezone-relay.service</.code_block>
|
||||
</:tab>
|
||||
</.tabs>
|
||||
</div>
|
||||
</:content>
|
||||
</.section>
|
||||
"""
|
||||
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
|
||||
@@ -49,40 +49,46 @@ defmodule Web.RelayGroups.Show do
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
</.vertical_table>
|
||||
<!-- Relays table -->
|
||||
<div class="grid grid-cols-1 p-4 xl:grid-cols-3 xl:gap-4 dark:bg-gray-900">
|
||||
<div class="col-span-full mb-4 xl:mb-2">
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">
|
||||
Relay Instances
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative overflow-x-auto">
|
||||
<.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"
|
||||
>
|
||||
<code :if={relay.ipv4} class="block text-xs">
|
||||
<%= relay.ipv4 %>
|
||||
</code>
|
||||
<code :if={relay.ipv6} class="block text-xs">
|
||||
<%= relay.ipv6 %>
|
||||
</code>
|
||||
</.link>
|
||||
</:col>
|
||||
<:col :let={relay} label="TOKEN CREATED AT">
|
||||
<.created_by account={@account} schema={relay.token} />
|
||||
</:col>
|
||||
<:col :let={relay} label="STATUS">
|
||||
<.connection_status schema={relay} />
|
||||
</:col>
|
||||
<:empty>
|
||||
<div class="text-center text-slate-500 p-4">No relay instances to display</div>
|
||||
</:empty>
|
||||
</.table>
|
||||
</div>
|
||||
</div>
|
||||
</:content>
|
||||
</.section>
|
||||
|
||||
<.section>
|
||||
<:title>Relays</:title>
|
||||
<:action>
|
||||
<.add_button navigate={~p"/#{@account}/relay_groups/#{@group}/new_token"}>
|
||||
Deploy
|
||||
</.add_button>
|
||||
</:action>
|
||||
<:content>
|
||||
<div class="relative overflow-x-auto">
|
||||
<.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"
|
||||
>
|
||||
<code :if={relay.name} class="block text-xs">
|
||||
<%= relay.name %>
|
||||
</code>
|
||||
<code :if={relay.ipv4} class="block text-xs">
|
||||
<%= relay.ipv4 %>
|
||||
</code>
|
||||
<code :if={relay.ipv6} class="block text-xs">
|
||||
<%= relay.ipv6 %>
|
||||
</code>
|
||||
</.link>
|
||||
</:col>
|
||||
<:col :let={relay} label="TOKEN CREATED AT">
|
||||
<.created_by account={@account} schema={relay.token} />
|
||||
</:col>
|
||||
<:col :let={relay} label="STATUS">
|
||||
<.connection_status schema={relay} />
|
||||
</:col>
|
||||
<:empty>
|
||||
<div class="text-center text-slate-500 p-4">No relay instances to display</div>
|
||||
</:empty>
|
||||
</.table>
|
||||
</div>
|
||||
</:content>
|
||||
</.section>
|
||||
@@ -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
|
||||
|
||||
@@ -28,14 +28,14 @@ defmodule Web.Relays.Show do
|
||||
<%= @relay.group.name %>
|
||||
</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/relays/#{@relay}"}>
|
||||
<%= @relay.ipv4 || @relay.ipv6 %>
|
||||
<%= @relay.name || @relay.ipv4 || @relay.ipv6 %>
|
||||
</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Relay:
|
||||
<.intersperse_blocks>
|
||||
Relay: <span :if={@relay.name}><%= @relay.name %></span>
|
||||
<.intersperse_blocks :if={is_nil(@relay.name)}>
|
||||
<:separator>, </: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</:label>
|
||||
<:value><%= @relay.group.name %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value><%= @relay.name %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Remote IPv4</:label>
|
||||
<: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}"
|
||||
)
|
||||
|
||||
|
||||
@@ -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 %>
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 %>
|
||||
</.badge>
|
||||
</.link>
|
||||
</:col>
|
||||
|
||||
@@ -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 %>
|
||||
</.link>
|
||||
</:col>
|
||||
<: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 %>
|
||||
</.link>
|
||||
(<%= flow.gateway_remote_ip %>)
|
||||
</:col>
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule Web.Auth.Email do
|
||||
defmodule Web.SignIn.Email do
|
||||
use Web, {:live_view, layout: {Web.Layouts, :public}}
|
||||
|
||||
def mount(
|
||||
@@ -16,13 +16,13 @@ defmodule Web.Sites.Edit do
|
||||
<.breadcrumbs account={@account}>
|
||||
<.breadcrumb path={~p"/#{@account}/sites"}>Sites</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/sites/#{@group}"}>
|
||||
<%= @group.name_prefix %>
|
||||
<%= @group.name %>
|
||||
</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/sites/#{@group}/edit"}>Edit</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
<.section>
|
||||
<:title>Edit Site: <code><%= @group.name_prefix %></code></:title>
|
||||
<:title>Edit Site: <code><%= @group.name %></code></:title>
|
||||
<:content>
|
||||
<div class="py-8 px-4 mx-auto max-w-2xl lg:py-16">
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
@@ -30,7 +30,7 @@ defmodule Web.Sites.Edit do
|
||||
<div>
|
||||
<.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} ->
|
||||
|
||||
@@ -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 %>
|
||||
</.link>
|
||||
</:col>
|
||||
|
||||
@@ -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 %></.link>
|
||||
><%= gateway.name %></.link>
|
||||
</:item>
|
||||
|
||||
<:tail :let={count}>
|
||||
|
||||
@@ -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
|
||||
</.breadcrumbs>
|
||||
|
||||
<.section>
|
||||
<:title :if={is_nil(@group)}>
|
||||
<:title>
|
||||
Add a new Site
|
||||
</:title>
|
||||
<:title :if={not is_nil(@group)}>
|
||||
Deploy your Gateway
|
||||
</:title>
|
||||
<:content>
|
||||
<div class="py-8 px-4 mx-auto max-w-2xl lg:py-16">
|
||||
<.form :if={is_nil(@group)} for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
<.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))}
|
||||
|
||||
@@ -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>
|
||||
<.breadcrumb path={~p"/#{@account}/sites/#{@group}"}>
|
||||
<%= @group.name_prefix %>
|
||||
<%= @group.name %>
|
||||
</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/sites/#{@group}/new_token"}>Deploy</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
<.section>
|
||||
<:title :if={is_nil(@group)}>
|
||||
Add a new Site
|
||||
</:title>
|
||||
<:title :if={not is_nil(@group)}>
|
||||
<:title>
|
||||
Deploy your Gateway
|
||||
</:title>
|
||||
<:content>
|
||||
@@ -51,20 +48,45 @@ defmodule Web.Sites.NewToken do
|
||||
Copy-paste this command to your server:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-docker" class="w-full rounded-b" phx-no-format><%= docker_command(@env) %></.code_block>
|
||||
<.code_block id="code-sample-docker1" class="w-full" phx-no-format><%= docker_command(@env) %></.code_block>
|
||||
|
||||
<.initial_connection_status
|
||||
:if={@env}
|
||||
type="gateway"
|
||||
navigate={~p"/#{@account}/sites/#{@group}"}
|
||||
connected?={@connected?}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<p class="pl-4 mb-2 mt-4 text-xl font-semibold">
|
||||
Troubleshooting
|
||||
</p>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
Check the container status:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-docker2" class="w-full" phx-no-format>docker ps --filter "name=firezone-gateway"</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
Check the container logs:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-docker3" class="w-full rounded-b" phx-no-format>docker logs firezone-gateway</.code_block>
|
||||
</:tab>
|
||||
<:tab id="systemd-instructions" label="Systemd">
|
||||
<p class="pl-4 mb-2">
|
||||
1. Create a systemd unit file with the following content:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo nano /etc/systemd/system/firezone-gateway.service</.code_block>
|
||||
<.code_block id="code-sample-systemd1" class="w-full" phx-no-format>sudo nano /etc/systemd/system/firezone-gateway.service</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
2. Copy-paste the following content into the file:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full rounded-b" phx-no-format><%= systemd_command(@env) %></.code_block>
|
||||
<.code_block id="code-sample-systemd2" class="w-full rounded-b" phx-no-format><%= systemd_command(@env) %></.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
3. Save by pressing <kbd>Ctrl</kbd>+<kbd>X</kbd>, then <kbd>Y</kbd>, then <kbd>Enter</kbd>.
|
||||
@@ -74,31 +96,46 @@ defmodule Web.Sites.NewToken do
|
||||
4. Reload systemd configuration:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl daemon-reload</.code_block>
|
||||
<.code_block id="code-sample-systemd4" class="w-full" phx-no-format>sudo systemctl daemon-reload</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
5. Start the service:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl start firezone-gateway</.code_block>
|
||||
<.code_block id="code-sample-systemd5" class="w-full" phx-no-format>sudo systemctl start firezone-gateway</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
6. Enable the service to start on boot:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full" phx-no-format>sudo systemctl enable firezone-gateway</.code_block>
|
||||
<.code_block id="code-sample-systemd6" class="w-full" phx-no-format>sudo systemctl enable firezone-gateway</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
7. Check the status of the service:
|
||||
<.initial_connection_status
|
||||
:if={@env}
|
||||
type="gateway"
|
||||
navigate={~p"/#{@account}/sites/#{@group}"}
|
||||
connected?={@connected?}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<p class="pl-4 mb-2 mt-4 text-xl font-semibold">
|
||||
Troubleshooting
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd" class="w-full rounded-b" phx-no-format>sudo systemctl status firezone-gateway</.code_block>
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
Check the status of the service:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd7" class="w-full rounded-b" phx-no-format>sudo systemctl status firezone-gateway</.code_block>
|
||||
|
||||
<p class="pl-4 mb-2 mt-4">
|
||||
Check the logs:
|
||||
</p>
|
||||
|
||||
<.code_block id="code-sample-systemd8" class="w-full rounded-b" phx-no-format>sudo journalctl -u firezone-gateway.service</.code_block>
|
||||
</:tab>
|
||||
</.tabs>
|
||||
|
||||
<div :if={@env} class="mt-4 animate-pulse text-center">
|
||||
Waiting for gateway connection...
|
||||
</div>
|
||||
</div>
|
||||
</:content>
|
||||
</.section>
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
<.breadcrumb path={~p"/#{@account}/sites/#{@group}"}>
|
||||
<%= @group.name_prefix %>
|
||||
<%= @group.name %>
|
||||
</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Site: <code><%= @group.name_prefix %></code>
|
||||
Site: <code><%= @group.name %></code>
|
||||
</:title>
|
||||
<: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</:label>
|
||||
<:value><%= @group.name_prefix %></:value>
|
||||
<:value><%= @group.name %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Created</:label>
|
||||
@@ -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 %>
|
||||
</.link>
|
||||
</:col>
|
||||
<: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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", %{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", %{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule Web.Auth.EmailTest do
|
||||
defmodule Web.SignIn.EmailTest do
|
||||
use Web.ConnCase, async: true
|
||||
|
||||
setup do
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user