diff --git a/.gitignore b/.gitignore
index 22c8fb9..fbe0c19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,4 +32,5 @@ kmxgit-*.tar
npm-debug.log
/assets/node_modules/
-/priv/git
+/priv/git/
+/.env
diff --git a/assets/js/app.js b/assets/js/app.js
index 17ae36d..5352fa5 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -1,3 +1,7 @@
+import $ from "jquery"
+window.$ = $
+window.jQuery = $
+
import "bootstrap"
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
diff --git a/lib/kmxgit/user_manager.ex b/lib/kmxgit/user_manager.ex
index c0f95b0..44b596d 100644
--- a/lib/kmxgit/user_manager.ex
+++ b/lib/kmxgit/user_manager.ex
@@ -70,7 +70,9 @@ defmodule Kmxgit.UserManager do
end
def delete_user(%User{} = user) do
- Repo.delete(user)
+ user
+ |> change_user()
+ |> Repo.delete()
end
def change_user(%User{} = user) do
diff --git a/lib/kmxgit/user_manager/user.ex b/lib/kmxgit/user_manager/user.ex
index ed6e00d..c7d0ebf 100644
--- a/lib/kmxgit/user_manager/user.ex
+++ b/lib/kmxgit/user_manager/user.ex
@@ -33,6 +33,7 @@ defmodule Kmxgit.UserManager.User do
|> validate_format(:email, ~r/^[-_+.0-9A-Za-z]+@([-_0-9A-Za-z]+[.])+[A-Za-z]+$/)
|> Markdown.validate_markdown(:description)
|> unique_constraint(:email, name: "users__lower_email_index")
+ |> foreign_key_constraint(:owned_repositories, name: :repositories_user_id_fkey)
end
def changeset(user, attrs \\ %{}) do
diff --git a/lib/kmxgit_web/controllers/user_controller.ex b/lib/kmxgit_web/controllers/user_controller.ex
index 49f4ad2..1a34881 100644
--- a/lib/kmxgit_web/controllers/user_controller.ex
+++ b/lib/kmxgit_web/controllers/user_controller.ex
@@ -19,8 +19,9 @@ defmodule KmxgitWeb.UserController do
if params["login"] == current_user.slug.slug do
changeset = User.changeset(current_user)
conn
+ |> assign(:changeset, changeset)
|> assign(:page_title, gettext("Edit user %{login}", login: current_user.slug.slug))
- |> render("edit.html", changeset: changeset)
+ |> render("edit.html")
else
not_found(conn)
end
@@ -73,9 +74,11 @@ defmodule KmxgitWeb.UserController do
{:ok, _} ->
conn
|> redirect(to: "/")
- {:error, _} ->
+ {:error, changeset} ->
conn
- |> redirect(to: Routes.user_path(conn, :edit, current_user.slug.slug))
+ |> assign(:changeset, changeset)
+ |> assign(:page_title, gettext("Edit user %{login}", login: current_user.slug.slug))
+ |> render("edit.html")
end
else
not_found(conn)
diff --git a/lib/kmxgit_web/router.ex b/lib/kmxgit_web/router.ex
index d91cff6..f080c36 100644
--- a/lib/kmxgit_web/router.ex
+++ b/lib/kmxgit_web/router.ex
@@ -14,6 +14,10 @@ defmodule KmxgitWeb.Router do
plug :accepts, ["json"]
end
+ pipeline :recaptcha do
+ plug PlugRecaptcha2, recaptcha_secret: Application.get_env(:kmxgit, :recaptcha_secret)
+ end
+
# Our pipeline implements "maybe" authenticated. We'll use the `:ensure_auth` below for when we need to make sure someone is logged in.
pipeline :auth do
plug Kmxgit.UserManager.Pipeline
@@ -41,11 +45,15 @@ defmodule KmxgitWeb.Router do
scope "/_sessions" do
get "/new", SessionController, :new
- post "/new", SessionController, :login
get "/logout", SessionController, :logout
+
+ pipe_through :recaptcha
+ post "/new", SessionController, :login
end
get "/_register", RegistrationController, :new
+
+ pipe_through :recaptcha
post "/_register", RegistrationController, :register
end
diff --git a/lib/kmxgit_web/templates/session/new.html.eex b/lib/kmxgit_web/templates/session/new.html.eex
index ca6d85e..a9eac1f 100644
--- a/lib/kmxgit_web/templates/session/new.html.eex
+++ b/lib/kmxgit_web/templates/session/new.html.eex
@@ -17,6 +17,8 @@
<%= error_tag f, :password %>
</div>
+ <%= render "recaptcha.html", assigns %>
+
<div class="mb-3">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
diff --git a/lib/kmxgit_web/templates/session/recaptcha.html.heex b/lib/kmxgit_web/templates/session/recaptcha.html.heex
new file mode 100644
index 0000000..fdd3565
--- /dev/null
+++ b/lib/kmxgit_web/templates/session/recaptcha.html.heex
@@ -0,0 +1,9 @@
+<%= tag :input, type: "hidden", name: "recaptcha" %>
+<script src={"https://www.google.com/recaptcha/api.js?render=#{recaptcha_site_key()}"}></script>
+<script>
+ grecaptcha.ready(function() {
+ grecaptcha.execute('<%= recaptcha_site_key() %>', {action: 'login'}).then(function(token) {
+ $('input[name="recaptcha"]').val(token);
+ });
+ });
+</script>
diff --git a/lib/kmxgit_web/templates/user/edit.html.heex b/lib/kmxgit_web/templates/user/edit.html.heex
index 8cc2342..e4d31ab 100644
--- a/lib/kmxgit_web/templates/user/edit.html.heex
+++ b/lib/kmxgit_web/templates/user/edit.html.heex
@@ -52,6 +52,10 @@
<%= error_tag f, :deploy_only %>
</div>
+ <div>
+ <%= error_tag f, :owned_repositories %>
+ </div>
+
<div class="mb-3">
<%= link gettext("Cancel"),
to: Routes.slug_path(@conn, :show, @current_user.slug.slug),
diff --git a/lib/kmxgit_web/views/session_view.ex b/lib/kmxgit_web/views/session_view.ex
index a7917f5..7b11688 100644
--- a/lib/kmxgit_web/views/session_view.ex
+++ b/lib/kmxgit_web/views/session_view.ex
@@ -1,3 +1,7 @@
defmodule KmxgitWeb.SessionView do
use KmxgitWeb, :view
+
+ def recaptcha_site_key do
+ Application.get_env :kmxgit, :recaptcha_site_key
+ end
end
diff --git a/lib/plug_recaptcha2.ex b/lib/plug_recaptcha2.ex
new file mode 100644
index 0000000..ca91234
--- /dev/null
+++ b/lib/plug_recaptcha2.ex
@@ -0,0 +1,45 @@
+defmodule PlugRecaptcha2 do
+ import Plug.Conn
+
+ def init(default), do: default
+
+ def call(conn, [recaptcha_secret: secret,
+ redirect: redirect]) do
+ case conn do
+ %Plug.Conn{params: %{"recaptcha" => signature}} ->
+ case verify_signature(signature, secret) do
+ {:ok} -> conn
+ _ -> halt_connection(conn, redirect)
+ end
+ _ ->
+ # IO.puts "no recaptcha param"
+ halt_connection(conn, redirect)
+ end
+ end
+ def call(conn, [recaptcha_secret: secret]) do
+ call(conn, [recaptcha_secret: secret,
+ redirect: nil])
+ end
+ def call(_conn, _opts), do: raise "Recaptcha Secret is missing"
+
+ defp verify_signature(signature, secret) do
+ post_url = "https://www.google.com/recaptcha/api/siteverify?secret=#{secret}&response=#{signature}"
+ resp = HTTPoison.post(post_url, "", [{"Content-Type", "application/json"}])
+ IO.inspect(recaptcha: resp)
+ case resp do
+ {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
+ Poison.decode(body)
+ |> verify_response
+ _ -> {:fail}
+ end
+ end
+
+ defp verify_response({:ok, %{"success" => true}}), do: {:ok}
+ defp verify_response(_), do: {:fail}
+
+ defp halt_connection(conn, redirect) do
+ url = hd(get_req_header(conn, "referer")) || redirect || "/"
+ conn
+ |> Phoenix.Controller.redirect(external: url)
+ end
+end
diff --git a/mix.exs b/mix.exs
index c4fdbe0..edee0bd 100644
--- a/mix.exs
+++ b/mix.exs
@@ -49,6 +49,7 @@ defmodule Kmxgit.MixProject do
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.16.0"},
{:plug_cowboy, "~> 2.5"},
+ {:plug_recaptcha, git: "https://github.com/thodg/plug_recaptcha.git"},
{:postgrex, ">= 0.0.0"},
{:swoosh, "~> 1.3"},
{:telemetry_metrics, "~> 0.6"},
diff --git a/mix.lock b/mix.lock
index af636fa..881937e 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,6 +1,7 @@
%{
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.0", "6cb662d5c1b0a8858801cf20997bd006e7016aa8c52959c9ef80e0f34fb60b7a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2c81d61d4f6ed0e5cf7bf27a9109b791ff216a1034b3d541327484f46dd43769"},
"castore": {:hex, :castore, "0.1.13", "ccf3ab251ffaebc4319f41d788ce59a6ab3f42b6c27e598ad838ffecee0b04f9", [:mix], [], "hexpm", "a14a7eecfec7e20385493dbb92b0d12c5d77ecfd6307de10102d58c94e8c49c0"},
+ "certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"},
"comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
@@ -19,10 +20,16 @@
"floki": {:hex, :floki, "0.32.0", "f915dc15258bc997d49be1f5ef7d3992f8834d6f5695270acad17b41f5bcc8e2", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "1c5a91cae1fd8931c26a4826b5e2372c284813904c8bacb468b5de39c7ececbd"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"guardian": {:hex, :guardian, "2.2.1", "5a4a949fd46eac79ef37f074ada7d1ef82e274bc99e335c286e042f5383f4f80", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "09b5c4d08f18524bd33ffe49617003cbca9f617237e23b5f42223cda61c5f052"},
+ "hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.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.3.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", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
+ "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"},
+ "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"jose": {:hex, :jose, "1.11.2", "f4c018ccf4fdce22c71e44d471f15f723cb3efab5d909ab2ba202b5bf35557b3", [:mix, :rebar3], [], "hexpm", "98143fbc48d55f3a18daba82d34fe48959d44538e9697c08f34200fa5f0947d2"},
+ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
+ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
+ "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.6.2", "6cbd5c8ed7a797f25a919a37fafbc2fb1634c9cdb12a4448d7a5d0b26926f005", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bbee475acae0c3abc229b7f189e210ea788e63bd168e585f60c299a4b2f9133"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
@@ -34,10 +41,14 @@
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
"plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
+ "plug_recaptcha": {:git, "https://github.com/thodg/plug_recaptcha.git", "191eda9f0bc6424e9a1a06da4be24b2a5a84d8e2", []},
+ "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"postgrex": {:hex, :postgrex, "0.15.13", "7794e697481799aee8982688c261901de493eb64451feee6ea58207d7266d54a", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "3ffb76e1a97cfefe5c6a95632a27ffb67f28871c9741fb585f9d1c3cd2af70f1"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
+ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"swoosh": {:hex, :swoosh, "1.5.1", "ec1b3fa6a092597ac02444c36c6e3c2bc90c89c02e4e0cb262725d07d610c989", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, 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_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "867395cbf0d764b24b6bf3c375137b98432cedb8c7f91ef9bd1c379cf626ac42"},
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
"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"},
+ "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
}