Commit 1f16488e9f844c71540c1c577b0e69d1f8790fe9

Thomas de Grivel 2021-11-29T07:27:06

recaptcha

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"},
 }