mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
feat(portal): Support search_domain field in Account.Config (#8391)
Introduces a simple `search_domain` field embed into our existing `Accounts.Account.Config` embedded schema. This will be sent to clients to append to single-label DNS queries. UI and API changes will come in subsequent PRs: this one adds field and (lots of) validations only. Related: #8365
This commit is contained in:
@@ -3,6 +3,8 @@ defmodule Domain.Accounts.Config do
|
||||
|
||||
@primary_key false
|
||||
embedded_schema do
|
||||
field :search_domain, :string
|
||||
|
||||
embeds_many :clients_upstream_dns, ClientsUpstreamDNS,
|
||||
primary_key: false,
|
||||
on_replace: :delete do
|
||||
|
||||
@@ -7,16 +7,45 @@ defmodule Domain.Accounts.Config.Changeset do
|
||||
|
||||
def changeset(config \\ %Config{}, attrs) do
|
||||
config
|
||||
|> cast(attrs, [])
|
||||
|> cast(attrs, [:search_domain])
|
||||
|> cast_embed(:clients_upstream_dns,
|
||||
with: &client_upstream_dns_changeset/2,
|
||||
sort_param: :clients_upstream_dns_sort,
|
||||
drop_param: :clients_upstream_dns_drop
|
||||
)
|
||||
|> cast_embed(:notifications, with: ¬ifications_changeset/2)
|
||||
|> validate_search_domain()
|
||||
|> validate_unique_clients_upstream_dns()
|
||||
end
|
||||
|
||||
defp validate_search_domain(changeset) do
|
||||
changeset
|
||||
|> validate_change(:search_domain, fn :search_domain, domain ->
|
||||
cond do
|
||||
domain == nil || domain == "" ->
|
||||
[search_domain: "cannot be empty"]
|
||||
|
||||
String.length(domain) > 255 ->
|
||||
[search_domain: "must not exceed 255 characters"]
|
||||
|
||||
String.starts_with?(domain, ".") || String.ends_with?(domain, ".") ->
|
||||
[search_domain: "must not start or end with a dot"]
|
||||
|
||||
String.contains?(domain, "..") ->
|
||||
[search_domain: "must not contain consecutive dots"]
|
||||
|
||||
!String.match?(domain, ~r/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i) ->
|
||||
[search_domain: "must be a valid fully-qualified domain name"]
|
||||
|
||||
Enum.any?(String.split(domain, "."), &(String.length(&1) > 63)) ->
|
||||
[search_domain: "each label must not exceed 63 characters"]
|
||||
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_unique_clients_upstream_dns(changeset) do
|
||||
with false <- has_errors?(changeset, :clients_upstream_dns),
|
||||
{_data_or_changes, client_upstream_dns} <- fetch_field(changeset, :clients_upstream_dns) do
|
||||
|
||||
@@ -583,6 +583,118 @@ defmodule Domain.AccountsTest do
|
||||
}
|
||||
end
|
||||
|
||||
test "returns error when search_domain is invalid", %{account: account} do
|
||||
attrs = %{
|
||||
config: %{
|
||||
search_domain: "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_account_by_id(account.id, attrs)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
config: %{
|
||||
search_domain: ["must be a valid fully-qualified domain name"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "returns error when search_domain is too long", %{account: account} do
|
||||
attrs = %{
|
||||
config: %{
|
||||
search_domain: String.duplicate("a", 256)
|
||||
}
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_account_by_id(account.id, attrs)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
config: %{
|
||||
search_domain: ["must not exceed 255 characters"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "returns error when search_domain starts with a dot", %{account: account} do
|
||||
attrs = %{
|
||||
config: %{
|
||||
search_domain: ".example.com"
|
||||
}
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_account_by_id(account.id, attrs)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
config: %{
|
||||
search_domain: ["must not start or end with a dot"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "returns error when search_domain ends with a dot", %{account: account} do
|
||||
attrs = %{
|
||||
config: %{
|
||||
search_domain: "example.com."
|
||||
}
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_account_by_id(account.id, attrs)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
config: %{
|
||||
search_domain: ["must not start or end with a dot"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "returns error when search_domain contains consecutive dots", %{account: account} do
|
||||
attrs = %{
|
||||
config: %{
|
||||
search_domain: "example..com"
|
||||
}
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_account_by_id(account.id, attrs)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
config: %{
|
||||
search_domain: ["must not contain consecutive dots"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "returns error when search_domain labels exceed 63 characters", %{account: account} do
|
||||
attrs = %{
|
||||
config: %{
|
||||
search_domain: "a" <> String.duplicate("a", 63) <> ".com"
|
||||
}
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_account_by_id(account.id, attrs)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
config: %{
|
||||
search_domain: ["each label must not exceed 63 characters"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "returns error when search_domain contains invalid characters", %{account: account} do
|
||||
attrs = %{
|
||||
config: %{
|
||||
search_domain: "example.com!"
|
||||
}
|
||||
}
|
||||
|
||||
assert {:error, changeset} = update_account_by_id(account.id, attrs)
|
||||
|
||||
assert errors_on(changeset) == %{
|
||||
config: %{
|
||||
search_domain: ["must be a valid fully-qualified domain name"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "updates account and broadcasts a message", %{account: account} do
|
||||
Bypass.open()
|
||||
|> Domain.Mocks.Stripe.mock_update_customer_endpoint(account)
|
||||
|
||||
Reference in New Issue
Block a user