diff --git a/elixir/apps/domain/lib/domain/auth.ex b/elixir/apps/domain/lib/domain/auth.ex
index 95a2ef0e0..235efb27d 100644
--- a/elixir/apps/domain/lib/domain/auth.ex
+++ b/elixir/apps/domain/lib/domain/auth.ex
@@ -35,7 +35,8 @@ defmodule Domain.Auth do
true <- Validator.valid_uuid?(id) do
{preload, _opts} = Keyword.pop(opts, :preload, [])
- Provider.Query.by_id(id)
+ Provider.Query.all()
+ |> Provider.Query.by_id(id)
|> Authorizer.for_subject(Provider, subject)
|> Repo.fetch()
|> case do
diff --git a/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex b/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex
index 9c39e14f8..812c7e92a 100644
--- a/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex
+++ b/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex
@@ -33,7 +33,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do
def identity_changeset(%Provider{} = _provider, %Ecto.Changeset{} = changeset) do
changeset
|> Domain.Validator.trim_change(:provider_identifier)
- |> Ecto.Changeset.put_change(:provider_state, %{})
+ |> Domain.Validator.copy_change(:provider_virtual_state, :provider_state)
|> Ecto.Changeset.put_change(:provider_virtual_state, %{})
end
diff --git a/elixir/apps/domain/lib/domain/auth/provider/changeset.ex b/elixir/apps/domain/lib/domain/auth/provider/changeset.ex
index 80135d7a4..6fa31d83c 100644
--- a/elixir/apps/domain/lib/domain/auth/provider/changeset.ex
+++ b/elixir/apps/domain/lib/domain/auth/provider/changeset.ex
@@ -68,7 +68,7 @@ defmodule Domain.Auth.Provider.Changeset do
def enable_provider(%Provider{} = provider) do
provider
|> change()
- |> put_default_value(:disabled_at, nil)
+ |> put_change(:disabled_at, nil)
end
def delete_provider(%Provider{} = provider) do
diff --git a/elixir/apps/domain/lib/domain/policies.ex b/elixir/apps/domain/lib/domain/policies.ex
index 1c961aa9b..161b7ae4c 100644
--- a/elixir/apps/domain/lib/domain/policies.ex
+++ b/elixir/apps/domain/lib/domain/policies.ex
@@ -70,6 +70,22 @@ defmodule Domain.Policies do
end
end
+ def disable_policy(%Policy{} = policy, %Auth.Subject{} = subject) do
+ with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do
+ Policy.Query.by_id(policy.id)
+ |> Authorizer.for_subject(subject)
+ |> Repo.fetch_and_update(with: &Policy.Changeset.disable(&1, subject))
+ end
+ end
+
+ def enable_policy(%Policy{} = policy, %Auth.Subject{} = subject) do
+ with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_policies_permission()) do
+ Policy.Query.by_id(policy.id)
+ |> Authorizer.for_subject(subject)
+ |> Repo.fetch_and_update(with: &Policy.Changeset.enable/1)
+ end
+ end
+
def delete_policy(%Policy{} = policy, %Auth.Subject{} = subject) do
required_permissions =
{:one_of, [Authorizer.manage_policies_permission()]}
diff --git a/elixir/apps/domain/lib/domain/policies/policy.ex b/elixir/apps/domain/lib/domain/policies/policy.ex
index c4babba2f..8bedfa1b9 100644
--- a/elixir/apps/domain/lib/domain/policies/policy.ex
+++ b/elixir/apps/domain/lib/domain/policies/policy.ex
@@ -11,6 +11,7 @@ defmodule Domain.Policies.Policy do
field :created_by, Ecto.Enum, values: ~w[identity]a
belongs_to :created_by_identity, Domain.Auth.Identity
+ field :disabled_at, :utc_datetime_usec
field :deleted_at, :utc_datetime_usec
timestamps()
end
diff --git a/elixir/apps/domain/lib/domain/policies/policy/changeset.ex b/elixir/apps/domain/lib/domain/policies/policy/changeset.ex
index 98dca1bee..8c64ffa43 100644
--- a/elixir/apps/domain/lib/domain/policies/policy/changeset.ex
+++ b/elixir/apps/domain/lib/domain/policies/policy/changeset.ex
@@ -24,6 +24,18 @@ defmodule Domain.Policies.Policy.Changeset do
|> changeset()
end
+ def disable(%Policy{} = policy, %Auth.Subject{}) do
+ policy
+ |> change()
+ |> put_default_value(:disabled_at, DateTime.utc_now())
+ end
+
+ def enable(%Policy{} = policy) do
+ policy
+ |> change()
+ |> put_change(:disabled_at, nil)
+ end
+
def delete(%Policy{} = policy) do
policy
|> change()
diff --git a/elixir/apps/domain/priv/repo/migrations/20231201165608_add_policies_disabled_fields.exs b/elixir/apps/domain/priv/repo/migrations/20231201165608_add_policies_disabled_fields.exs
new file mode 100644
index 000000000..153f29431
--- /dev/null
+++ b/elixir/apps/domain/priv/repo/migrations/20231201165608_add_policies_disabled_fields.exs
@@ -0,0 +1,9 @@
+defmodule Domain.Repo.Migrations.AddPoliciesDisabledFields do
+ use Ecto.Migration
+
+ def change do
+ alter table(:policies) do
+ add(:disabled_at, :utc_datetime_usec)
+ end
+ end
+end
diff --git a/elixir/apps/domain/test/domain/auth/adapters/google_workspace_test.exs b/elixir/apps/domain/test/domain/auth/adapters/google_workspace_test.exs
index fd48d5f0f..6ec1265cc 100644
--- a/elixir/apps/domain/test/domain/auth/adapters/google_workspace_test.exs
+++ b/elixir/apps/domain/test/domain/auth/adapters/google_workspace_test.exs
@@ -22,8 +22,17 @@ defmodule Domain.Auth.Adapters.GoogleWorkspaceTest do
end
test "puts default provider state", %{provider: provider, changeset: changeset} do
+ changeset =
+ Ecto.Changeset.put_change(changeset, :provider_virtual_state, %{
+ "userinfo" => %{"email" => "foo@example.com"}
+ })
+
assert %Ecto.Changeset{} = changeset = identity_changeset(provider, changeset)
- assert changeset.changes == %{provider_virtual_state: %{}}
+
+ assert changeset.changes == %{
+ provider_virtual_state: %{},
+ provider_state: %{"userinfo" => %{"email" => "foo@example.com"}}
+ }
end
test "trims provider identifier", %{provider: provider, changeset: changeset} do
diff --git a/elixir/apps/domain/test/domain/auth/adapters/openid_connect_test.exs b/elixir/apps/domain/test/domain/auth/adapters/openid_connect_test.exs
index 7640306b7..390bf3035 100644
--- a/elixir/apps/domain/test/domain/auth/adapters/openid_connect_test.exs
+++ b/elixir/apps/domain/test/domain/auth/adapters/openid_connect_test.exs
@@ -22,8 +22,17 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do
end
test "puts default provider state", %{provider: provider, changeset: changeset} do
+ changeset =
+ Ecto.Changeset.put_change(changeset, :provider_virtual_state, %{
+ "userinfo" => %{"email" => "foo@example.com"}
+ })
+
assert %Ecto.Changeset{} = changeset = identity_changeset(provider, changeset)
- assert changeset.changes == %{provider_state: %{}, provider_virtual_state: %{}}
+
+ assert changeset.changes == %{
+ provider_virtual_state: %{},
+ provider_state: %{"userinfo" => %{"email" => "foo@example.com"}}
+ }
end
test "trims provider identifier", %{provider: provider, changeset: changeset} do
diff --git a/elixir/apps/domain/test/domain/auth_test.exs b/elixir/apps/domain/test/domain/auth_test.exs
index 23c7c11b4..7afcd3507 100644
--- a/elixir/apps/domain/test/domain/auth_test.exs
+++ b/elixir/apps/domain/test/domain/auth_test.exs
@@ -75,11 +75,12 @@ defmodule Domain.AuthTest do
assert fetch_provider_by_id("foo", subject) == {:error, :not_found}
end
- test "returns error when provider is deleted", %{account: account, subject: subject} do
+ test "returns deleted provider", %{account: account, subject: subject} do
provider = Fixtures.Auth.create_userpass_provider(account: account)
{:ok, _provider} = delete_provider(provider, subject)
- assert fetch_provider_by_id(provider.id, subject) == {:error, :not_found}
+ assert {:ok, fetched_provider} = fetch_provider_by_id(provider.id, subject)
+ assert fetched_provider.id == provider.id
end
test "returns provider", %{account: account, subject: subject} do
@@ -877,11 +878,12 @@ defmodule Domain.AuthTest do
subject: subject,
provider: provider
} do
- assert {:ok, provider} = enable_provider(provider, subject)
assert provider.disabled_at
+ assert {:ok, provider} = enable_provider(provider, subject)
+ assert is_nil(provider.disabled_at)
assert provider = Repo.get(Auth.Provider, provider.id)
- assert provider.disabled_at
+ assert is_nil(provider.disabled_at)
end
test "does not do anything when an provider is enabled twice", %{
diff --git a/elixir/apps/domain/test/domain/policies_test.exs b/elixir/apps/domain/test/domain/policies_test.exs
index de69daae6..66dfff8b5 100644
--- a/elixir/apps/domain/test/domain/policies_test.exs
+++ b/elixir/apps/domain/test/domain/policies_test.exs
@@ -341,6 +341,117 @@ defmodule Domain.PoliciesTest do
end
end
+ describe "disable_policy/2" do
+ setup context do
+ policy =
+ Fixtures.Policies.create_policy(
+ account: context.account,
+ subject: context.subject
+ )
+
+ Map.put(context, :policy, policy)
+ end
+
+ test "disables a given policy", %{
+ account: account,
+ subject: subject,
+ policy: policy
+ } do
+ other_policy = Fixtures.Policies.create_policy(account: account)
+
+ assert {:ok, policy} = disable_policy(policy, subject)
+ assert policy.disabled_at
+
+ assert policy = Repo.get(Policies.Policy, policy.id)
+ assert policy.disabled_at
+
+ assert other_policy = Repo.get(Policies.Policy, other_policy.id)
+ assert is_nil(other_policy.disabled_at)
+ end
+
+ test "does not do anything when an policy is disabled twice", %{
+ subject: subject,
+ account: account
+ } do
+ policy = Fixtures.Policies.create_policy(account: account)
+ assert {:ok, _policy} = disable_policy(policy, subject)
+ assert {:ok, policy} = disable_policy(policy, subject)
+ assert {:ok, _policy} = disable_policy(policy, subject)
+ end
+
+ test "does not allow to disable policies in other accounts", %{
+ subject: subject
+ } do
+ policy = Fixtures.Policies.create_policy()
+ assert disable_policy(policy, subject) == {:error, :not_found}
+ end
+
+ test "returns error when subject can not disable policies", %{
+ subject: subject,
+ policy: policy
+ } do
+ subject = Fixtures.Auth.remove_permissions(subject)
+
+ assert disable_policy(policy, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Policies.Authorizer.manage_policies_permission()]]}}
+ end
+ end
+
+ describe "enable_policy/2" do
+ setup context do
+ policy =
+ Fixtures.Policies.create_policy(
+ account: context.account,
+ subject: context.subject
+ )
+
+ {:ok, policy} = disable_policy(policy, context.subject)
+
+ Map.put(context, :policy, policy)
+ end
+
+ test "enables a given policy", %{
+ subject: subject,
+ policy: policy
+ } do
+ assert {:ok, policy} = enable_policy(policy, subject)
+ assert is_nil(policy.disabled_at)
+
+ assert policy = Repo.get(Policies.Policy, policy.id)
+ assert is_nil(policy.disabled_at)
+ end
+
+ test "does not do anything when an policy is enabled twice", %{
+ subject: subject,
+ policy: policy
+ } do
+ assert {:ok, _policy} = enable_policy(policy, subject)
+ assert {:ok, policy} = enable_policy(policy, subject)
+ assert {:ok, _policy} = enable_policy(policy, subject)
+ end
+
+ test "does not allow to enable policies in other accounts", %{
+ subject: subject
+ } do
+ policy = Fixtures.Policies.create_policy()
+ assert enable_policy(policy, subject) == {:error, :not_found}
+ end
+
+ test "returns error when subject can not enable policies", %{
+ subject: subject,
+ policy: policy
+ } do
+ subject = Fixtures.Auth.remove_permissions(subject)
+
+ assert enable_policy(policy, subject) ==
+ {:error,
+ {:unauthorized,
+ [missing_permissions: [Policies.Authorizer.manage_policies_permission()]]}}
+ end
+ end
+
describe "delete_policy/2" do
setup context do
policy =
diff --git a/elixir/apps/web/assets/js/hooks.js b/elixir/apps/web/assets/js/hooks.js
index 6a44cf0d8..45a226ec7 100644
--- a/elixir/apps/web/assets/js/hooks.js
+++ b/elixir/apps/web/assets/js/hooks.js
@@ -15,6 +15,18 @@ Hooks.Tabs = {
}
}
+Hooks.Refocus = {
+ mounted() {
+ this.el.addEventListener("click", (ev) => {
+ ev.preventDefault();
+ let target_id = ev.currentTarget.getAttribute("data-refocus");
+ let el = document.getElementById(target_id);
+ if (document.activeElement === el) return;
+ el.focus();
+ });
+ }
+}
+
Hooks.Copy = {
mounted() {
this.el.addEventListener("click", (ev) => {
@@ -31,14 +43,14 @@ Hooks.Copy = {
icon_cl.add("hero-clipboard-document-check");
icon_cl.add("text-green-500");
icon_cl.remove("hero-clipboard-document");
- content.innerHTML = "Copied"
+ if (content) { 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"
+ if (content) { content.innerHTML = "Copy" }
}, 2000);
});
},
diff --git a/elixir/apps/web/assets/package.json b/elixir/apps/web/assets/package.json
index 028140d5c..bc3b37c2d 100644
--- a/elixir/apps/web/assets/package.json
+++ b/elixir/apps/web/assets/package.json
@@ -1,6 +1,6 @@
{
"dependencies": {
"@fontsource/source-sans-pro": "^5.0.8",
- "flowbite": "^2.1.1"
+ "flowbite": "^2.2.0"
}
}
diff --git a/elixir/apps/web/assets/pnpm-lock.yaml b/elixir/apps/web/assets/pnpm-lock.yaml
index 2e2f8b18a..14270a826 100644
--- a/elixir/apps/web/assets/pnpm-lock.yaml
+++ b/elixir/apps/web/assets/pnpm-lock.yaml
@@ -9,8 +9,8 @@ dependencies:
specifier: ^5.0.8
version: 5.0.8
flowbite:
- specifier: ^2.1.1
- version: 2.1.1
+ specifier: ^2.2.0
+ version: 2.2.0
packages:
@@ -22,8 +22,8 @@ packages:
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
dev: false
- /flowbite@2.1.1:
- resolution: {integrity: sha512-FkTwNXlfWRXUhSsiE9D4bEqLN8ywdunW2qOz1z7gN+Co7h9EQIJf/Fr4xgq1NJPFa+6MeMf08QLzviU7LqB/rQ==}
+ /flowbite@2.2.0:
+ resolution: {integrity: sha512-Eq0qWz4a5nlxaGuUcspzpu+8Ny0A7lKEJEKcuPpkdSoF8tWjbKeuVVgKk8/q10ZE9bhXh1GHBXdCsRxu0LZTNQ==}
dependencies:
'@popperjs/core': 2.11.8
mini-svg-data-uri: 1.4.4
diff --git a/elixir/apps/web/lib/web/auth.ex b/elixir/apps/web/lib/web/auth.ex
index 7ff426ab1..9aaca9310 100644
--- a/elixir/apps/web/lib/web/auth.ex
+++ b/elixir/apps/web/lib/web/auth.ex
@@ -70,6 +70,8 @@ defmodule Web.Auth do
client_auth_token: client_token,
client_csrf_token: client_csrf_token,
actor_name: subject.actor.name,
+ account_slug: subject.account.slug,
+ account_name: subject.account.name,
identity_provider_identifier: subject.identity.provider_identifier
}
|> Enum.reject(&is_nil(elem(&1, 1)))
diff --git a/elixir/apps/web/lib/web/components/core_components.ex b/elixir/apps/web/lib/web/components/core_components.ex
index 1aab8de38..14cb29adf 100644
--- a/elixir/apps/web/lib/web/components/core_components.ex
+++ b/elixir/apps/web/lib/web/components/core_components.ex
@@ -90,6 +90,31 @@ defmodule Web.CoreComponents do
"""
end
+ @doc """
+ Render an inlined copy-paste button to the right of the content block.
+
+ ## Examples
+
+ <.copy id="foo">
+ The lazy brown fox jumped over the quick dog.
+
+ """
+ attr :id, :string, required: true
+ attr :class, :string, default: ""
+ slot :inner_block, required: true
+ attr :rest, :global
+
+ def copy(assigns) do
+ ~H"""
+
+ <%= render_slot(@inner_block) %>
+
+ <.icon name="hero-clipboard-document" data-icon class="h-4 w-4" />
+
+
+ """
+ end
+
@doc """
Render a tabs toggle container and its content.
diff --git a/elixir/apps/web/lib/web/components/form_components.ex b/elixir/apps/web/lib/web/components/form_components.ex
index 055e9ba5d..6fc44f870 100644
--- a/elixir/apps/web/lib/web/components/form_components.ex
+++ b/elixir/apps/web/lib/web/components/form_components.ex
@@ -23,6 +23,7 @@ defmodule Web.FormComponents do
attr :id, :any, default: nil
attr :name, :any
attr :label, :string, default: nil
+ attr :prefix, :string, default: nil
attr :value, :any
attr :value_id, :any,
@@ -204,7 +205,51 @@ defmodule Web.FormComponents do
"""
end
- # All other inputs text, datetime-local, url, password, etc. are handled here...
+ def input(%{type: "text", prefix: prefix} = assigns) when not is_nil(prefix) do
+ ~H"""
+
+ <.label :if={not is_nil(@label)} for={@id}><%= @label %>
+
+ <.error :for={msg <- @errors} data-validation-error-for={@name}><%= msg %>
+
+ """
+ end
+
def input(assigns) do
~H"""
diff --git a/elixir/apps/web/lib/web/components/page_components.ex b/elixir/apps/web/lib/web/components/page_components.ex
index 1edd15898..14135abe0 100644
--- a/elixir/apps/web/lib/web/components/page_components.ex
+++ b/elixir/apps/web/lib/web/components/page_components.ex
@@ -38,7 +38,7 @@ defmodule Web.PageComponents do
slot :action, required: false, doc: "A slot for action to the right of the title"
- slot :content, required: true, doc: "A slot for content of the section" do
+ slot :content, required: false, doc: "A slot for content of the section" do
attr :flash, :any, doc: "The flash to be displayed above the content"
end
diff --git a/elixir/apps/web/lib/web/controllers/home_controller.ex b/elixir/apps/web/lib/web/controllers/home_controller.ex
index 7e4754e3a..f69dcdb96 100644
--- a/elixir/apps/web/lib/web/controllers/home_controller.ex
+++ b/elixir/apps/web/lib/web/controllers/home_controller.ex
@@ -2,7 +2,7 @@ defmodule Web.HomeController do
use Web, :controller
alias Domain.Accounts
- def home(conn, _params) do
+ 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
@@ -16,12 +16,22 @@ defmodule Web.HomeController do
_other -> {[], conn}
end
+ redirect_params =
+ take_non_empty_params(params, ["client_platform", "client_csrf_token"])
+
conn
|> put_layout(html: {Web.Layouts, :public})
- |> render("home.html", accounts: accounts)
+ |> render("home.html", accounts: accounts, redirect_params: redirect_params)
end
- def redirect_to_sign_in(conn, %{"account_id_or_slug" => account_id_or_slug}) do
- redirect(conn, to: ~p"/#{account_id_or_slug}")
+ def redirect_to_sign_in(conn, %{"account_id_or_slug" => account_id_or_slug} = params) do
+ redirect_params =
+ take_non_empty_params(params, ["client_platform", "client_csrf_token"])
+
+ redirect(conn, to: ~p"/#{account_id_or_slug}?#{redirect_params}")
+ end
+
+ defp take_non_empty_params(map, keys) do
+ map |> Map.take(keys) |> Map.reject(fn {_key, value} -> value in ["", nil] end)
end
end
diff --git a/elixir/apps/web/lib/web/controllers/home_html.ex b/elixir/apps/web/lib/web/controllers/home_html.ex
index 9a89186b2..5263bb426 100644
--- a/elixir/apps/web/lib/web/controllers/home_html.ex
+++ b/elixir/apps/web/lib/web/controllers/home_html.ex
@@ -21,25 +21,39 @@ defmodule Web.HomeHTML do
- <.account_button :for={account <- @accounts} account={account} />
+ <.account_button
+ :for={account <- @accounts}
+ account={account}
+ redirect_params={@redirect_params}
+ />
<.separator :if={@accounts != []} />
- <.form :let={f} for={%{}} action={~p"/"} class="space-y-4 lg:space-y-6">
+ <.form
+ :let={f}
+ for={%{}}
+ action={~p"/?#{@redirect_params}"}
+ 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|}
+ prefix={url(~p"/")}
required
+ autofocus
/>
+
As shown in your "Welcome to Firezone" email
<.button class="w-full">
Go to Sign In page
-
+
Don't have an account?
Sign up here.
@@ -54,7 +68,7 @@ defmodule Web.HomeHTML do
def account_button(assigns) do
~H"""
-
diff --git a/elixir/apps/web/lib/web/live/policies/index.ex b/elixir/apps/web/lib/web/live/policies/index.ex
index b023245d8..77fa7d49f 100644
--- a/elixir/apps/web/lib/web/live/policies/index.ex
+++ b/elixir/apps/web/lib/web/live/policies/index.ex
@@ -42,6 +42,17 @@ defmodule Web.Policies.Index do
<%= policy.resource.name %>
+ <:col :let={policy} label="STATUS">
+ <%= if is_nil(policy.deleted_at) do %>
+ <%= if is_nil(policy.disabled_at) do %>
+ Active
+ <% else %>
+ Disabled
+ <% end %>
+ <% else %>
+ Deleted
+ <% end %>
+
<:empty>
diff --git a/elixir/apps/web/lib/web/live/policies/show.ex b/elixir/apps/web/lib/web/live/policies/show.ex
index 3df3050bc..fecef894b 100644
--- a/elixir/apps/web/lib/web/live/policies/show.ex
+++ b/elixir/apps/web/lib/web/live/policies/show.ex
@@ -38,6 +38,7 @@ defmodule Web.Policies.Show do
<.section>
<:title>
<%= @page_title %>: <%= @policy.id %>
+ (disabled)
(deleted)
<:action :if={is_nil(@policy.deleted_at)}>
@@ -45,6 +46,22 @@ defmodule Web.Policies.Show do
Edit Policy
+ <:action :if={is_nil(@policy.deleted_at)}>
+ <.button
+ :if={not is_nil(@policy.disabled_at)}
+ phx-click="enable"
+ data-confirm="Are you sure want to enable this policy?"
+ >
+ Enable Policy
+
+ <.button
+ :if={is_nil(@policy.disabled_at)}
+ phx-click="disable"
+ data-confirm="Are you sure want to disable this policy? All authorizations will be revoked and users can loose access to the resource."
+ >
+ Disable Policy
+
+
<:content>
<.vertical_table id="policy">
<.vertical_table_row>
@@ -157,12 +174,37 @@ defmodule Web.Policies.Show do
Delete Policy
- <:content>
"""
end
- def handle_event("delete", %{"id" => _policy_id}, socket) do
+ def handle_event("disable", _params, socket) do
+ {:ok, policy} = Policies.disable_policy(socket.assigns.policy, socket.assigns.subject)
+
+ policy = %{
+ policy
+ | actor_group: socket.assigns.policy.actor_group,
+ resource: socket.assigns.policy.resource,
+ created_by_identity: socket.assigns.policy.created_by_identity
+ }
+
+ {:noreply, assign(socket, policy: policy)}
+ end
+
+ def handle_event("enable", _params, socket) do
+ {:ok, policy} = Policies.enable_policy(socket.assigns.policy, socket.assigns.subject)
+
+ policy = %{
+ policy
+ | actor_group: socket.assigns.policy.actor_group,
+ resource: socket.assigns.policy.resource,
+ created_by_identity: socket.assigns.policy.created_by_identity
+ }
+
+ {:noreply, assign(socket, policy: policy)}
+ end
+
+ def handle_event("delete", _params, socket) do
{:ok, _} = Policies.delete_policy(socket.assigns.policy, socket.assigns.subject)
{:noreply, push_navigate(socket, to: ~p"/#{socket.assigns.account}/policies")}
end
diff --git a/elixir/apps/web/lib/web/live/settings/account.ex b/elixir/apps/web/lib/web/live/settings/account.ex
index 1139cc284..46c6c5dfe 100644
--- a/elixir/apps/web/lib/web/live/settings/account.ex
+++ b/elixir/apps/web/lib/web/live/settings/account.ex
@@ -24,7 +24,9 @@ defmodule Web.Settings.Account do
<.vertical_table_row>
<:label>Account Slug
- <:value><%= @account.slug %>
+ <:value>
+ <.copy id="account-slug"><%= @account.slug %>
+
diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex
index 2b1f57a83..a8a00204a 100644
--- a/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex
+++ b/elixir/apps/web/lib/web/live/settings/identity_providers/google_workspace/show.ex
@@ -29,29 +29,35 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.Show do
<.section>
<:title>
Identity Provider
<%= @provider.name %>
+
(disabled)
+
(deleted)
- <:action>
+ <:action :if={is_nil(@provider.deleted_at)}>
<.edit_button navigate={
~p"/#{@account}/settings/identity_providers/google_workspace/#{@provider.id}/edit"
}>
Edit
- <:action>
+ <:action :if={is_nil(@provider.deleted_at)}>
<%= if @provider.adapter_state["status"] != "pending_access_token" do %>
- <.button :if={not is_nil(@provider.disabled_at)} phx-click="enable">
+ <.button
+ :if={not is_nil(@provider.disabled_at)}
+ phx-click="enable"
+ data-confirm="Are you sure want to enable this provider?"
+ >
Enable Identity Provider
<.button
:if={is_nil(@provider.disabled_at)}
phx-click="disable"
- data-confirm="Are you sure want to disable this provider?"
+ data-confirm="Are you sure want to disable this provider? All authorizations will be revoked and actors won't be able to use it to access Firezone."
>
Disable Identity Provider
<% end %>
- <:action>
+ <:action :if={is_nil(@provider.deleted_at)}>
<.button
style="primary"
navigate={
@@ -96,7 +102,7 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.Show do
- <.danger_zone>
+ <.danger_zone :if={is_nil(@provider.deleted_at)}>
<:action>
<.delete_button
data-confirm="Are you sure want to delete this provider along with all related data?"
diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex
index 9992b0547..c4b2826eb 100644
--- a/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex
+++ b/elixir/apps/web/lib/web/live/settings/identity_providers/openid_connect/show.ex
@@ -28,8 +28,10 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Show do
<.section>
<:title>
Identity Provider
<%= @provider.name %>
+
(disabled)
+
(deleted)
- <:action>
+ <:action :if={is_nil(@provider.deleted_at)}>
<.edit_button navigate={
~p"/#{@account}/settings/identity_providers/openid_connect/#{@provider}/edit"
}>
@@ -37,21 +39,25 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Show do
- <:action>
- <.button :if={not is_nil(@provider.disabled_at)} phx-click="enable">
+ <:action :if={is_nil(@provider.deleted_at)}>
+ <.button
+ :if={not is_nil(@provider.disabled_at)}
+ phx-click="enable"
+ data-confirm="Are you sure want to enable this provider?"
+ >
Enable
- <:action>
+ <:action :if={is_nil(@provider.deleted_at)}>
<.button
:if={is_nil(@provider.disabled_at)}
phx-click="disable"
- data-confirm="Are you sure want to disable this provider?"
+ data-confirm="Are you sure want to disable this provider? All authorizations will be revoked and actors won't be able to use it to access Firezone."
>
Disable
- <:action>
+ <:action :if={is_nil(@provider.deleted_at)}>
<.button
style="primary"
navigate={
@@ -115,10 +121,7 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Show do
- <.section>
- <:title>
- Danger zone
-
+ <.danger_zone :if={is_nil(@provider.deleted_at)}>
<:action>
<.delete_button
data-confirm="Are you sure want to delete this provider along with all related data?"
@@ -127,8 +130,7 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Show do
Delete Identity Provider
- <:content>
-
+
"""
end
diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex
index 3aa3899e7..9df83682a 100644
--- a/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex
+++ b/elixir/apps/web/lib/web/live/settings/identity_providers/system/show.ex
@@ -31,15 +31,21 @@ defmodule Web.Settings.IdentityProviders.System.Show do
<.section>
<:title>
Identity Provider <%= @provider.name %>
+ (disabled)
+ (deleted)
- <:action>
- <.button :if={not is_nil(@provider.disabled_at)} phx-click="enable">
+ <:action :if={is_nil(@provider.deleted_at)}>
+ <.button
+ :if={not is_nil(@provider.disabled_at)}
+ phx-click="enable"
+ data-confirm="Are you sure want to enable this provider?"
+ >
Enable Identity Provider
<.button
:if={is_nil(@provider.disabled_at)}
phx-click="disable"
- data-confirm="Are you sure want to disable this provider?"
+ data-confirm="Are you sure want to disable this provider? All authorizations will be revoked and actors won't be able to use it to access Firezone."
>
Disable Identity Provider
@@ -74,10 +80,7 @@ defmodule Web.Settings.IdentityProviders.System.Show do
- <.section>
- <:title>
- Danger zone
-
+ <.danger_zone :if={is_nil(@provider.deleted_at)}>
<:action>
<.delete_button
data-confirm="Are you sure want to delete this provider along with all related data?"
@@ -86,8 +89,7 @@ defmodule Web.Settings.IdentityProviders.System.Show do
Delete Identity Provider
- <:content>
-
+
"""
end
diff --git a/elixir/apps/web/lib/web/live/sign_up.ex b/elixir/apps/web/lib/web/live/sign_up.ex
index 171c94675..48d9f1456 100644
--- a/elixir/apps/web/lib/web/live/sign_up.ex
+++ b/elixir/apps/web/lib/web/live/sign_up.ex
@@ -16,8 +16,8 @@ defmodule Web.SignUp do
embeds_one(:actor, Actors.Actor)
end
- def changeset(%Registration{} = registration, attrs) do
- registration
+ def changeset(attrs) do
+ %Registration{}
|> Ecto.Changeset.cast(attrs, [:email])
|> Ecto.Changeset.validate_required([:email])
|> Ecto.Changeset.validate_format(:email, ~r/.+@.+/)
@@ -35,7 +35,7 @@ defmodule Web.SignUp do
real_ip = Web.Auth.real_ip(socket)
changeset =
- Registration.changeset(%Registration{}, %{
+ Registration.changeset(%{
account: %{slug: "placeholder"},
actor: %{type: :account_admin_user}
})
@@ -44,9 +44,12 @@ defmodule Web.SignUp do
assign(socket,
form: to_form(changeset),
account: nil,
+ provider: nil,
user_agent: user_agent,
real_ip: real_ip,
- sign_up_enabled?: Config.sign_up_enabled?()
+ sign_up_enabled?: Config.sign_up_enabled?(),
+ account_name_changed?: false,
+ actor_name_changed?: false
)
{:ok, socket}
@@ -76,7 +79,12 @@ defmodule Web.SignUp do
<:item>
<.sign_up_form :if={@account == nil && @sign_up_enabled?} flash={@flash} form={@form} />
- <.welcome :if={@account && @sign_up_enabled?} account={@account} />
+ <.welcome
+ :if={@account && @sign_up_enabled?}
+ account={@account}
+ provider={@provider}
+ identity={@identity}
+ />
<.sign_up_disabled :if={!@sign_up_enabled?} />
@@ -101,41 +109,61 @@ defmodule Web.SignUp do
~H"""
- Your account has been created! Please check your email for sign in instructions.
+ Your account has been created!
+
Please check your email for sign in instructions.
-
+
- |
+ |
Account Name:
|
-
+ |
<%= @account.name %>
|
- |
+ |
Account Slug:
|
-
+ |
<%= @account.slug %>
|
+
+ |
+ Sign In URL:
+ |
+
+ <.link class="font-medium text-blue-600 hover:underline" navigate={~p"/#{@account}"}>
+ <%= url(~p"/#{@account}") %>
+
+ |
+
-
- Sign In URL
-
-
- <.link class="font-medium text-blue-600 hover:underline" navigate={~p"/#{@account.slug}"}>
- <%= "#{Web.Endpoint.url()}/#{@account.slug}" %>
-
-
+ <.form
+ for={%{}}
+ id="resend-email"
+ as={:email}
+ class="inline"
+ action={~p"/#{@account}/sign_in/providers/#{@provider}/request_magic_link"}
+ method="post"
+ >
+ <.input
+ type="hidden"
+ name="email[provider_identifier]"
+ value={@identity.provider_identifier}
+ />
+ <.submit_button class="w-full">
+ Sign In
+
+
"""
@@ -147,12 +175,22 @@ defmodule Web.SignUp do
Sign Up Now
<.simple_form for={@form} class="space-y-4 lg:space-y-6" phx-submit="submit" phx-change="validate">
+ <.input
+ field={@form[:email]}
+ type="text"
+ label="Email"
+ placeholder="Enter your work email here"
+ required
+ autofocus
+ phx-debounce="300"
+ />
+
<.inputs_for :let={account} field={@form[:account]}>
<.input
field={account[:name]}
type="text"
label="Account Name"
- placeholder="Enter an Account Name here"
+ placeholder="Enter an account name"
required
phx-debounce="300"
/>
@@ -171,15 +209,6 @@ defmodule Web.SignUp do
<.input field={actor[:type]} type="hidden" />
- <.input
- field={@form[:email]}
- type="text"
- label="Email"
- placeholder="Enter your email here"
- required
- phx-debounce="300"
- />
-
<:actions>
<.button phx-disable-with="Creating Account..." class="w-full">
Create Account
@@ -218,23 +247,37 @@ defmodule Web.SignUp do
"""
end
- def handle_event("validate", %{"registration" => attrs}, socket) do
+ def handle_event("validate", %{"registration" => attrs} = payload, socket) do
+ account_name_changed? =
+ socket.assigns.account_name_changed? ||
+ payload["_target"] == ["registration", "account", "name"]
+
+ actor_name_changed? =
+ socket.assigns.actor_name_changed? ||
+ payload["_target"] == ["registration", "actor", "name"]
+
changeset =
- %Registration{}
- |> Registration.changeset(attrs)
+ attrs
+ |> maybe_put_default_account_name(account_name_changed?)
+ |> maybe_put_default_actor_name(actor_name_changed?)
+ |> Registration.changeset()
|> Map.put(:action, :validate)
- socket = assign(socket, form: to_form(changeset))
-
- {:noreply, socket}
+ {:noreply,
+ assign(socket,
+ form: to_form(changeset),
+ account_name_changed?: account_name_changed?,
+ actor_name_changed?: actor_name_changed?
+ )}
end
- def handle_event("submit", %{"registration" => orig_attrs}, socket) do
- attrs = put_in(orig_attrs, ["actor", "type"], :account_admin_user)
-
+ def handle_event("submit", %{"registration" => attrs}, socket) do
changeset =
- %Registration{}
- |> Registration.changeset(attrs)
+ attrs
+ |> maybe_put_default_account_name()
+ |> maybe_put_default_actor_name()
+ |> put_in(["actor", "type"], :account_admin_user)
+ |> Registration.changeset()
|> Map.put(:action, :insert)
if changeset.valid? && socket.assigns.sign_up_enabled? do
@@ -279,7 +322,7 @@ defmodule Web.SignUp do
)
case Domain.Repo.transaction(multi) do
- {:ok, %{account: account, identity: identity}} ->
+ {:ok, %{account: account, provider: provider, identity: identity}} ->
{:ok, _} =
Web.Mailer.AuthEmail.sign_up_link_email(
account,
@@ -289,7 +332,7 @@ defmodule Web.SignUp do
)
|> Web.Mailer.deliver()
- socket = assign(socket, account: account)
+ socket = assign(socket, account: account, provider: provider, identity: identity)
{:noreply, socket}
{:error, :account, err_changeset, _effects_so_far} ->
@@ -302,4 +345,31 @@ defmodule Web.SignUp do
{:noreply, assign(socket, form: to_form(changeset))}
end
end
+
+ defp maybe_put_default_account_name(attrs, account_name_changed? \\ true)
+
+ defp maybe_put_default_account_name(attrs, true) do
+ attrs
+ end
+
+ defp maybe_put_default_account_name(attrs, false) do
+ case String.split(attrs["email"], "@", parts: 2) do
+ [default_name | _] when byte_size(default_name) > 0 ->
+ put_in(attrs, ["account", "name"], "#{default_name}'s account")
+
+ _ ->
+ attrs
+ end
+ end
+
+ defp maybe_put_default_actor_name(attrs, actor_name_changed? \\ true)
+
+ defp maybe_put_default_actor_name(attrs, true) do
+ attrs
+ end
+
+ defp maybe_put_default_actor_name(attrs, false) do
+ [default_name | _] = String.split(attrs["email"], "@", parts: 2)
+ put_in(attrs, ["actor", "name"], default_name)
+ end
end
diff --git a/elixir/apps/web/test/web/auth_test.exs b/elixir/apps/web/test/web/auth_test.exs
index 26beb2192..482e5ef68 100644
--- a/elixir/apps/web/test/web/auth_test.exs
+++ b/elixir/apps/web/test/web/auth_test.exs
@@ -80,7 +80,14 @@ defmodule Web.AuthTest do
redirected_to = conn |> signed_in_redirect(subject, "android", "foo") |> redirected_to()
assert redirected_to =~ "/handle_client_auth_callback?"
+ assert redirected_to =~ "client_auth_token="
assert redirected_to =~ "client_csrf_token=foo"
+ assert redirected_to =~ "actor_name=#{URI.encode_www_form(subject.actor.name)}"
+ assert redirected_to =~ "account_name=#{subject.account.name}"
+ assert redirected_to =~ "account_slug=#{subject.account.slug}"
+
+ assert redirected_to =~
+ "identity_provider_identifier=#{subject.identity.provider_identifier}"
end
test "redirects admin user to the post-login path if platform url is missing", %{
diff --git a/elixir/apps/web/test/web/live/policies/show_test.exs b/elixir/apps/web/test/web/live/policies/show_test.exs
index ebf8bb6e3..ea85c5c99 100644
--- a/elixir/apps/web/test/web/live/policies/show_test.exs
+++ b/elixir/apps/web/test/web/live/policies/show_test.exs
@@ -183,5 +183,36 @@ defmodule Web.Live.Policies.ShowTest do
{:error, {:live_redirect, %{to: ~p"/#{account}/policies", kind: :push}}}
assert Repo.get(Domain.Policies.Policy, policy.id).deleted_at
+
+ {:ok, _lv, html} =
+ conn
+ |> authorize_conn(identity)
+ |> live(~p"/#{account}/policies/#{policy}")
+
+ assert html =~ "(deleted)"
+ end
+
+ test "allows disabling and enabling policy", %{
+ account: account,
+ policy: policy,
+ identity: identity,
+ conn: conn
+ } do
+ {:ok, lv, _html} =
+ conn
+ |> authorize_conn(identity)
+ |> live(~p"/#{account}/policies/#{policy}")
+
+ assert lv
+ |> element("button", "Disable Policy")
+ |> render_click() =~ "(disabled)"
+
+ assert Repo.get(Domain.Policies.Policy, policy.id).disabled_at
+
+ refute lv
+ |> element("button", "Enable Policy")
+ |> render_click() =~ "(disabled)"
+
+ refute Repo.get(Domain.Policies.Policy, policy.id).disabled_at
end
end
diff --git a/elixir/apps/web/test/web/live/settings/account/index_test.exs b/elixir/apps/web/test/web/live/settings/account/index_test.exs
index 2d007bf3f..d45eb4532 100644
--- a/elixir/apps/web/test/web/live/settings/account/index_test.exs
+++ b/elixir/apps/web/test/web/live/settings/account/index_test.exs
@@ -56,6 +56,6 @@ defmodule Web.Live.Settings.Account.IndexTest do
assert rows["account name"] == account.name
assert rows["account id"] == account.id
- assert rows["account slug"] == account.slug
+ assert rows["account slug"] =~ account.slug
end
end
diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs
index de62a709a..1aaac87d3 100644
--- a/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs
+++ b/elixir/apps/web/test/web/live/settings/identity_providers/google_workspace/show_test.exs
@@ -35,7 +35,7 @@ defmodule Web.Live.Settings.IdentityProviders.GoogleWorkspace.ShowTest do
}}}
end
- test "renders not found error when provider is deleted", %{
+ test "renders deleted provider without action buttons", %{
account: account,
provider: provider,
identity: identity,
@@ -43,11 +43,16 @@ defmodule Web.Live.Settings.IdentityProviders.GoogleWorkspace.ShowTest do
} do
provider = Fixtures.Auth.delete_provider(provider)
- assert_raise Web.LiveErrors.NotFoundError, fn ->
+ {:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/settings/identity_providers/google_workspace/#{provider}")
- end
+
+ assert html =~ "(deleted)"
+ refute html =~ "Danger Zone"
+ refute html =~ "Add"
+ refute html =~ "Edit"
+ refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{
diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs
index 79b0163e2..867cb2319 100644
--- a/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs
+++ b/elixir/apps/web/test/web/live/settings/identity_providers/openid_connect/show_test.exs
@@ -35,7 +35,7 @@ defmodule Web.Live.Settings.IdentityProviders.OpenIDConnect.ShowTest do
}}}
end
- test "renders not found error when provider is deleted", %{
+ test "renders deleted provider without action buttons", %{
account: account,
provider: provider,
identity: identity,
@@ -43,11 +43,15 @@ defmodule Web.Live.Settings.IdentityProviders.OpenIDConnect.ShowTest do
} do
provider = Fixtures.Auth.delete_provider(provider)
- assert_raise Web.LiveErrors.NotFoundError, fn ->
+ {:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/settings/identity_providers/openid_connect/#{provider}")
- end
+
+ assert html =~ "(deleted)"
+ refute html =~ "Danger Zone"
+ refute html =~ "Edit"
+ refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{
diff --git a/elixir/apps/web/test/web/live/settings/identity_providers/system/show_test.exs b/elixir/apps/web/test/web/live/settings/identity_providers/system/show_test.exs
index d05a1a674..57a4d4188 100644
--- a/elixir/apps/web/test/web/live/settings/identity_providers/system/show_test.exs
+++ b/elixir/apps/web/test/web/live/settings/identity_providers/system/show_test.exs
@@ -31,7 +31,7 @@ defmodule Web.Live.Settings.IdentityProviders.System.ShowTest do
}}}
end
- test "renders not found error when provider is deleted", %{
+ test "renders deleted provider without action buttons", %{
account: account,
provider: provider,
identity: identity,
@@ -39,11 +39,16 @@ defmodule Web.Live.Settings.IdentityProviders.System.ShowTest do
} do
provider = Fixtures.Auth.delete_provider(provider)
- assert_raise Web.LiveErrors.NotFoundError, fn ->
+ {:ok, _lv, html} =
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/settings/identity_providers/system/#{provider}")
- end
+
+ assert html =~ "(deleted)"
+ refute html =~ "Danger Zone"
+ refute html =~ "Add"
+ refute html =~ "Edit"
+ refute html =~ "Deploy"
end
test "renders breadcrumbs item", %{
diff --git a/elixir/mix.lock b/elixir/mix.lock
index 272c036fd..1b8d0c3c0 100644
--- a/elixir/mix.lock
+++ b/elixir/mix.lock
@@ -6,8 +6,8 @@
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
"castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
- "chatterbox": {:hex, :ts_chatterbox, "0.13.0", "6f059d97bcaa758b8ea6fffe2b3b81362bd06b639d3ea2bb088335511d691ebf", [:rebar3], [{:hpack, "~> 0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "b93d19104d86af0b3f2566c4cba2a57d2e06d103728246ba1ac6c3c0ff010aa7"},
- "cldr_utils": {:hex, :cldr_utils, "2.24.1", "5ff8c8c55f96666228827bcf85a23d632022def200566346545d01d15e4c30dc", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "1820300531b5b849d0bc468e5a87cd64f8f2c5191916f548cbe69b2efc203780"},
+ "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"},
+ "cldr_utils": {:hex, :cldr_utils, "2.24.2", "364fa30be55d328e704629568d431eb74cd2f085752b27f8025520b566352859", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3362b838836a9f0fa309de09a7127e36e67310e797d556db92f71b548832c7cf"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
@@ -23,12 +23,12 @@
"ecto_sql": {:hex, :ecto_sql, "3.11.0", "c787b24b224942b69c9ff7ab9107f258ecdc68326be04815c6cce2941b6fad1c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77aa3677169f55c2714dda7352d563002d180eb33c0dc29cd36d39c0a1a971f5"},
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
- "esbuild": {:hex, :esbuild, "0.7.1", "fa0947e8c3c3c2f86c9bf7e791a0a385007ccd42b86885e8e893bdb6631f5169", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "66661cdf70b1378ee4dc16573fcee67750b59761b2605a0207c267ab9d19f13c"},
- "ex_cldr": {:hex, :ex_cldr, "2.37.4", "3e2c04d9c691a75a8b7e808dfcbacedb9cdf3e73f819d1b4174f8f065e5f29c1", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "2bcb5de0095324ba645b4e156bf346add156d8b928f0ffd985c61664d981ad3d"},
+ "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
+ "ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"},
"ex_cldr_calendars": {:hex, :ex_cldr_calendars, "1.22.1", "3e5150f1fe7698e0fa118aeedcca1b5920d0a552bc40c81cf65ca9b0a4ea4cc3", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: true]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_cldr_units, "~> 3.16", [hex: :ex_cldr_units, repo: "hexpm", optional: true]}, {:ex_doc, "~> 0.21", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e7408cd9e8318b2ef93b76728e84484ddc3ea6d7c894fbc811c54122a7140169"},
- "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.0", "aadd34e91cfac7ef6b03fe8f47f8c6fa8c5daf3f89b5d9fee64ec545ded839cf", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0521316396c66877a2d636219767560bb2397c583341fcb154ecf9f3000e6ff8"},
- "ex_cldr_dates_times": {:hex, :ex_cldr_dates_times, "2.15.0", "63a3f611aec735bc2a29be8792db32d350277aadc07f0b1c09af539b87c93d66", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_calendars, "~> 1.22", [hex: :ex_cldr_calendars, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:tz, "~> 0.26", [hex: :tz, repo: "hexpm", optional: true]}], "hexpm", "4e61921901e8d58704f395f3e94b5ef6bcd02bc54aaf8475da3cf5297cb70369"},
- "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.2", "5e0e3031d3f54b51fe7078a7a94592987b70b06d631bdc88813b222dc5a8b1bd", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "91257684a9c4d6abdf738f0cc5671837de876e69552e8bd4bc5fa1bfd5817713"},
+ "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"},
+ "ex_cldr_dates_times": {:hex, :ex_cldr_dates_times, "2.16.0", "d9848a5de83b6f1bcba151cc43d63b5c6311813cd605b1df1afd896dfdd21001", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_calendars, "~> 1.22", [hex: :ex_cldr_calendars, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:tz, "~> 0.26", [hex: :tz, repo: "hexpm", optional: true]}], "hexpm", "0f2f250d479cadda4e0ef3a5e3d936ae7ba1a3f1199db6791e284e86203495b1"},
+ "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"file_size": {:hex, :file_size, "3.0.1", "ad447a69442a82fc701765a73992d7b1110136fa0d4a9d3190ea685d60034dcd", [:mix], [{:decimal, ">= 1.0.0 and < 3.0.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:number, "~> 1.0", [hex: :number, repo: "hexpm", optional: false]}], "hexpm", "64dd665bc37920480c249785788265f5d42e98830d757c6a477b3246703b8e20"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
@@ -36,12 +36,12 @@
"floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.23.1", "821e619a240e6000db2fc16a574ef68b3bd7fe0167ccc264a81563cc93e67a31", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "19d744a36b809d810d610b57c27b934425859d158ebd56561bc41f7eeb8795db"},
- "gproc": {:hex, :gproc, "0.8.0", "cea02c578589c61e5341fce149ea36ccef236cc2ecac8691fba408e7ea77ec2f", [:rebar3], [], "hexpm", "580adafa56463b75263ef5a5df4c86af321f68694e7786cb057fd805d1e2a7de"},
- "grpcbox": {:hex, :grpcbox, "0.16.0", "b83f37c62d6eeca347b77f9b1ec7e9f62231690cdfeb3a31be07cd4002ba9c82", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.13.0", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.8.0", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "294df743ae20a7e030889f00644001370a4f7ce0121f3bbdaf13cf3169c62913"},
+ "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"},
+ "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
- "hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm", "06f580167c4b8b8a6429040df36cc93bba6d571faeaec1b28816523379cbb23a"},
+ "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
- "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"},
+ "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
@@ -93,13 +93,13 @@
"sizeable": {:hex, :sizeable, "1.0.2", "625fe06a5dad188b52121a140286f1a6ae1adf350a942cf419499ecd8a11ee29", [:mix], [], "hexpm", "4bab548e6dfba777b400ca50830a9e3a4128e73df77ab1582540cf5860601762"},
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
- "swoosh": {:hex, :swoosh, "1.14.1", "d8813699ba410509008dd3dfdb2df057e3fce367d45d5e6d76b146a7c9d559cd", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87da72260b4351678f96aec61db5c2acc8a88cda2cf2c4f534eb4c9c461350c7"},
+ "swoosh": {:hex, :swoosh, "1.14.2", "cf686f92ad3b21e6651b20c50eeb1781f581dc7097ef6251b4d322a9f1d19339", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01d8fae72930a0b5c1bb9725df0408602ed8c5c3d59dc6e7a39c57b723cd1065"},
"tailwind": {:hex, :tailwind, "0.2.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
"telemetry_registry": {:hex, :telemetry_registry, "0.3.1", "14a3319a7d9027bdbff7ebcacf1a438f5f5c903057b93aee484cca26f05bdcba", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6d0ca77b691cf854ed074b459a93b87f4c7f5512f8f7743c635ca83da81f939e"},
- "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"},
+ "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
"tls_certificate_check": {:hex, :tls_certificate_check, "1.20.0", "1ac0c53f95e201feb8d398ef9d764ae74175231289d89f166ba88a7f50cd8e73", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "ab57b74b1a63dc5775650699a3ec032ec0065005eff1f020818742b7312a8426"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"},