]> Entropealabs - wampex_router.git/commitdiff
moves authentication out of wampex_router
authorChristopher <chris@entropealabs.com>
Tue, 24 Mar 2020 19:47:40 +0000 (14:47 -0500)
committerChristopher <chris@entropealabs.com>
Tue, 24 Mar 2020 19:47:40 +0000 (14:47 -0500)
12 files changed:
lib/router.ex
lib/router/admin.ex [deleted file]
lib/router/authentication.ex
lib/router/authentication/ensure_default_admin.ex [deleted file]
lib/router/authentication/peer.ex [deleted file]
lib/router/authentication/realm.ex [deleted file]
lib/router/authentication/repo.ex [deleted file]
lib/router/realms.ex
lib/router/session.ex
lib/router/transports/web_socket.ex
mix.exs
mix.lock

index c6988a55b34f3fa3bb21d409baaef7c7f53b02c1..f5a8872564783ae2ae317b019aa21d0db155b569 100644 (file)
@@ -1,8 +1,7 @@
 defmodule Wampex.Router do
   use Supervisor
 
-  alias Wampex.Router.{Admin, Authentication, Realms, REST}
-  alias Wampex.Router.Authentication.EnsureDefaultAdmin
+  alias Wampex.Router.{Realms, REST}
   alias Wampex.Router.Transports.WebSocket
 
   @spec start_link(
@@ -11,9 +10,8 @@ defmodule Wampex.Router do
           topologies: [],
           replicas: integer(),
           quorum: integer(),
-          admin_realm: String.t(),
-          admin_authid: String.t(),
-          admin_password: String.t()
+          authentication_module: module(),
+          authorization_module: module()
         ) ::
           {:ok, pid()}
           | {:error, {:already_started, pid()} | {:shutdown, term()} | term()}
@@ -23,28 +21,27 @@ defmodule Wampex.Router do
         topologies: topologies,
         replicas: repls,
         quorum: quorum,
-        admin_realm: admin_realm,
-        admin_authid: admin_authid,
-        admin_password: admin_password
+        authentication_module: authentication_module,
+        authorization_module: authorization_module
       )
       when is_atom(name) do
     Supervisor.start_link(
       __MODULE__,
-      {name, port, topologies, repls, quorum, admin_realm, admin_authid, admin_password},
+      {name, port, topologies, repls, quorum, authentication_module, authorization_module},
       name: name
     )
   end
 
-  @spec init({module(), pos_integer(), list(), integer(), integer(), String.t(), String.t(), String.t()}) ::
+  @spec init({module(), pos_integer(), list(), integer(), integer(), module(), module()}) ::
           {:ok, {:supervisor.sup_flags(), [:supervisor.child_spec()]}} | :ignore
-  def init({name, port, topologies, replicas, quorum, admin_realm, admin_authid, admin_password}) do
+  def init({name, port, topologies, replicas, quorum, authentication_module, authorization_module}) do
     children = [
       {ClusterKV, name: db_name(name), topologies: topologies, replicas: replicas, quorum: quorum},
-      Authentication.Repo,
-      {EnsureDefaultAdmin, [uri: admin_realm, authid: admin_authid, password: admin_password]},
       {Realms, name: realms_name(name)},
-      {Admin, name: admin_name(name), db: db_name(name), realm: admin_realm, realms: realms_name(name)},
-      {Plug.Cowboy, scheme: :http, plug: REST, options: [port: port, dispatch: dispatch(name)]}
+      {Plug.Cowboy,
+       scheme: :http,
+       plug: REST,
+       options: [port: port, dispatch: dispatch(name, authentication_module, authorization_module)]}
     ]
 
     Supervisor.init(children, strategy: :one_for_one, max_restarts: 10, max_seconds: 10)
@@ -54,11 +51,17 @@ defmodule Wampex.Router do
   def realms_name(name), do: Module.concat([name, Realms])
   def admin_name(name), do: Module.concat([name, Admin])
 
-  defp dispatch(name) do
+  defp dispatch(name, authentication_module, authorization_module) do
     [
       {:_,
        [
-         {"/ws", WebSocket, %{db: db_name(name), name: name}},
+         {"/ws", WebSocket,
+          %{
+            db: db_name(name),
+            name: name,
+            authentication_module: authentication_module,
+            authorization_module: authorization_module
+          }},
          {:_, Plug.Cowboy.Handler, {REST, []}}
        ]}
     ]
diff --git a/lib/router/admin.ex b/lib/router/admin.ex
deleted file mode 100644 (file)
index ce19a1d..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-defmodule Wampex.Router.Admin do
-  @moduledoc false
-  use GenServer
-  require Logger
-
-  alias Wampex.Roles.Dealer.{Invocation, Result}
-  alias Wampex.Roles.Peer.Error
-  alias Wampex.Router.Authentication.{Peer, Realm}
-  alias Wampex.Router.Realms
-  alias Wampex.Router.Realms.Session, as: RealmSession
-
-  @procedures [
-    "admin.create_peer",
-    "admin.create_realm"
-  ]
-
-  def start_link(name: name, db: db, realm: realm, realms: realms) do
-    GenServer.start_link(__MODULE__, {db, realm, realms}, name: name)
-  end
-
-  def init({db, realm, realms}) do
-    {:ok, %{db: db, realm: realm, proxy: Realms.start_realm(realms, realm), regs: []}, {:continue, :ok}}
-  end
-
-  def handle_continue(:ok, %{db: db, realm: realm, regs: regs} = state) do
-    regs = register_procedures(db, realm, regs)
-    {:noreply, %{state | regs: regs}}
-  end
-
-  def register_procedures(db, realm, regs) do
-    Logger.info("Registering admin procedures: #{inspect(@procedures)}")
-
-    Enum.reduce(@procedures, regs, fn proc, acc ->
-      val = {RealmSession.get_id(), {self(), Node.self()}}
-      RealmSession.register(db, realm, proc, val)
-      [{proc, val} | acc]
-    end)
-  end
-
-  def handle_info(
-        {req_id,
-         %Invocation{
-           arg_kw: %{"realm" => realm, "authid" => authid, "password" => password},
-           details: %{"procedure" => "admin.create_peer"}
-         } = event, {pid, node}},
-        %{proxy: proxy} = state
-      ) do
-    with realm <- Realm.get(uri: realm),
-         %Peer{id: id} <- Peer.create(authid: authid, password: password, realm: realm) do
-      Logger.info("Admin handled event: #{inspect(event)}")
-
-      send({proxy, node}, {%Result{request_id: req_id, arg_list: [id]}, pid})
-    else
-      _er ->
-        send({proxy, node}, {%Error{request_id: req_id, error: "Error registering user"}, pid})
-    end
-
-    {:noreply, state}
-  end
-end
index b0d90e5d081069cd171fc9f1b9b39f1ed8570efd..0fdd4d12b55d41a3cfefd412f10a1cce22199c72 100644 (file)
@@ -1,66 +1,13 @@
 defmodule Wampex.Router.Authentication do
   @moduledoc false
 
-  require Logger
-
-  alias Wampex.Crypto
-  alias Wampex.Router.Authentication.{Peer, Realm}
-  alias Wampex.Serializers.JSON
-
-  @wampcra "wampcra"
-  @auth_provider "userdb"
-  @auth_role "user"
-
-  def authenticate?(methods) do
-    can_auth(methods)
-  end
-
-  def method, do: @wampcra
-
-  def challenge(realm, authid, session_id) do
-    %Realm{} = realm = Realm.get(uri: realm)
-    %Peer{} = user = Peer.get(authid: authid, realm: realm)
-    now = DateTime.to_iso8601(DateTime.utc_now())
-
-    %{
-      challenge:
-        JSON.serialize!(%{
-          nonce: Crypto.random_string(user.keylen),
-          authprovider: @auth_provider,
-          authid: authid,
-          timestamp: now,
-          authrole: @auth_role,
-          authmethod: @wampcra,
-          session: session_id
-        }),
-      salt: user.salt,
-      keylen: user.keylen,
-      iterations: user.iterations
-    }
-  end
-
-  def parse_challenge(challenge) do
-    ch = JSON.deserialize!(challenge.challenge)
-
-    {get_in(ch, ["authid"]), get_in(ch, ["authrole"]), get_in(ch, ["authmethod"]), get_in(ch, ["authprovider"])}
-  end
-
-  def authenticate(signature, realm, authid, %{
-        challenge: challenge
-      }) do
-    authid
-    |> get_secret(realm)
-    |> Crypto.hash_challenge(challenge)
-    |> :pbkdf2.compare_secure(signature)
-  end
-
-  defp get_secret(authid, uri) do
-    realm = Realm.get(uri: uri)
-    %Peer{password: password} = Peer.get(authid: authid, realm: realm)
-    password
-  end
-
-  defp can_auth([]), do: false
-  defp can_auth([@wampcra | _]), do: true
-  defp can_auth([_ | t]), do: can_auth(t)
+  @type parse_challenge ::
+          {authid :: String.t(), authrole :: String.t(), authmethod :: String.t(), authprovider :: String.t()}
+
+  @callback authenticate?(methods :: list(String.t())) :: boolean()
+  @callback method() :: String.t()
+  @callback challenge(realm :: String.t(), authid :: String.t(), session_id :: integer()) :: %{challenge: String.t()}
+  @callback parse_challenge(Challenge.t()) :: parse_challenge()
+  @callback authenticate(signature :: String.t(), realm :: String.t(), authid :: String.t(), challenge :: Challenge.t()) ::
+              boolean()
 end
diff --git a/lib/router/authentication/ensure_default_admin.ex b/lib/router/authentication/ensure_default_admin.ex
deleted file mode 100644 (file)
index 423b02c..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-defmodule Wampex.Router.Authentication.EnsureDefaultAdmin do
-  @moduledoc false
-  require Logger
-  use GenServer
-
-  alias Wampex.Router.Authentication.{Peer, Realm, Repo}
-  @ecto_repos [Repo]
-
-  def start_link(uri: uri, authid: authid, password: password) do
-    GenServer.start_link(__MODULE__, {uri, authid, password})
-  end
-
-  def init({uri, authid, password}) do
-    ensure_migrations()
-    %Realm{} = realm = Realm.create(uri: uri)
-    %Peer{} = user = Peer.create(authid: authid, password: password, realm: realm)
-    Logger.debug("Realm: #{inspect(realm)}")
-    Logger.debug("Peer: #{inspect(user)}")
-    :ignore
-  end
-
-  defp ensure_migrations do
-    Logger.info("Ensuring tables have been migrated")
-
-    for repo <- @ecto_repos() do
-      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
-    end
-  end
-end
diff --git a/lib/router/authentication/peer.ex b/lib/router/authentication/peer.ex
deleted file mode 100644 (file)
index 6a35ac2..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-defmodule Wampex.Router.Authentication.Peer do
-  @moduledoc """
-  CREATE TABLE authentication.peers (
-        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-        authid STRING(255) NOT NULL,
-        password STRING NOT NULL,
-        salt STRING NOT NULL,
-        iterations INT NOT NULL,
-        keylen INT NOT NULL,
-        cidr STRING,
-        realm_id UUID NOT NULL REFERENCES authentication.realms (id) ON DELETE CASCADE,
-        inserted_at TIMESTAMP NOT NULL,
-        updated_at TIMESTAMP NOT NULL,
-        UNIQUE (authid, realm_id),
-        INDEX (authid)
-  );
-
-  """
-
-  use Ecto.Schema
-  alias __MODULE__
-  alias Wampex.Crypto
-  alias Wampex.Router.Authentication.{Realm, Repo}
-
-  @primary_key {:id, :binary_id, autogenerate: false, read_after_writes: true}
-  @foreign_key_type :binary_id
-  schema "peers" do
-    field(:authid, :string)
-    field(:password, :string)
-    field(:salt, :string)
-    field(:iterations, :integer)
-    field(:keylen, :integer)
-    field(:cidr, :string)
-    belongs_to(:realm, Realm)
-    timestamps()
-  end
-
-  def get(authid: _authid, realm: nil) do
-    :invalid_realm
-  end
-
-  def get(authid: authid, realm: realm) do
-    Repo.get_by(Peer, authid: authid, realm_id: realm.id)
-  end
-
-  def create(authid: authid, password: password, realm: realm) do
-    keylen = Application.get_env(:wampex_router, :keylen)
-    salt = Crypto.random_string(keylen)
-    iterations = Enum.random(10_000..50_000)
-
-    password = Crypto.pbkdf2(password, salt, iterations, keylen)
-
-    {:ok, u} =
-      %Peer{
-        authid: authid,
-        password: password,
-        realm: realm,
-        salt: salt,
-        iterations: iterations,
-        keylen: keylen
-      }
-      |> Repo.insert()
-
-    u
-  rescue
-    _er ->
-      get(authid: authid, realm: realm)
-  end
-end
diff --git a/lib/router/authentication/realm.ex b/lib/router/authentication/realm.ex
deleted file mode 100644 (file)
index 4d2632c..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-defmodule Wampex.Router.Authentication.Realm do
-  @moduledoc """
-  CREATE TABLE authentication.realms (
-        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-        uri STRING(255) NOT NULL UNIQUE,
-        parent STRING NULL,
-        inserted_at TIMESTAMP NOT NULL,
-        updated_at TIMESTAMP NOT NULL
-  );
-
-  """
-  use Ecto.Schema
-
-  alias __MODULE__
-  alias Wampex.Router.Authentication.Repo
-
-  @primary_key {:id, :binary_id, autogenerate: false, read_after_writes: true}
-  @foreign_key_type :binary_id
-  schema "realms" do
-    field(:uri, :string)
-    field(:parent, :string)
-    timestamps()
-  end
-
-  def get(uri: uri) do
-    Repo.get_by(Realm, uri: uri)
-  end
-
-  def create(uri: uri) do
-    {:ok, r} =
-      %Realm{uri: uri, parent: uri}
-      |> Repo.insert()
-
-    r
-  rescue
-    _er -> get(uri: uri)
-  end
-end
diff --git a/lib/router/authentication/repo.ex b/lib/router/authentication/repo.ex
deleted file mode 100644 (file)
index 44fac5f..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-defmodule Wampex.Router.Authentication.Repo do
-  use Ecto.Repo,
-    otp_app: :wampex_router,
-    adapter: Ecto.Adapters.Postgres
-
-  alias __MODULE__
-  require Logger
-
-  def init(:runtime, config) do
-    config = update_config(config, :database, "AUTH_DATABASE_NAME", nil)
-    config = update_config(config, :hostname, "AUTH_DATABASE_HOSTAME", nil)
-    config = update_config(config, :port, "AUTH_DATABASE_PORT", &String.to_integer/1)
-    config = update_config(config, :username, "AUTH_DATABASE_USERNAME", nil)
-    {:ok, config}
-  end
-
-  def init(:supervisor, config) do
-    ensure_db_created(config)
-    {:ok, config}
-  end
-
-  def ensure_db_created(config) do
-    Logger.info("Ensuring DB exists")
-    r = Repo.__adapter__().storage_up(config)
-    Logger.info("DB Creation: #{inspect(r)}")
-  end
-
-  def update_config(config, key, env, format) do
-    case System.get_env(env) do
-      nil ->
-        config
-
-      val ->
-        Keyword.put(config, key, do_format(val, format))
-    end
-  end
-
-  def do_format(val, nil), do: val
-  def do_format(val, fmt), do: fmt.(val)
-end
index ad99c78a7df0ef1eb8da228c626c8aa64c89ba7c..9baa3ce4be7c62e5b59fd3053fb71788f3b81565 100644 (file)
@@ -26,12 +26,22 @@ defmodule Wampex.Router.Realms do
     r_name
   end
 
-  def start_session(r_name, db, name, transport, socket) do
+  def start_session(r_name, db, name, transport, socket, atm, azm) do
     s_name = session_supervisor_name(r_name)
 
     DynamicSupervisor.start_child(
       s_name,
-      {Session, [%Session{db: db, name: name, transport: transport, transport_pid: socket}]}
+      {Session,
+       [
+         %Session{
+           db: db,
+           name: name,
+           authentication_module: atm,
+           authorization_module: azm,
+           transport: transport,
+           transport_pid: socket
+         }
+       ]}
     )
   end
 
index 07af4408f74d70e0ee9fea71680ac528ed9901a0..8ea81d1e61b07277b9885a4107f8426b5d461923 100644 (file)
@@ -14,7 +14,6 @@ defmodule Wampex.Router.Session do
   alias Wampex.Roles.Publisher.Publish
   alias Wampex.Roles.Subscriber.{Subscribe, Unsubscribe}
   alias Wampex.Router
-  alias Wampex.Router.{Authentication, Realms}
   alias Wampex.Router.Realms
   alias Wampex.Router.Realms.Session, as: RealmSession
   alias __MODULE__, as: Sess
@@ -41,8 +40,9 @@ defmodule Wampex.Router.Session do
     :goodbye,
     :peer_information,
     :challenge,
+    :authentication_module,
+    :authorization_module,
     error: "wamp.error.protocol_violation",
-    authentication: Authentication,
     roles: [Peer, Broker, Dealer],
     registrations: [],
     subscriptions: [],
@@ -72,6 +72,8 @@ defmodule Wampex.Router.Session do
           realm: String.t() | nil,
           name: module() | nil,
           challenge: Challenge.t() | nil,
+          authentication_module: module(),
+          authorization_module: module(),
           roles: [module()],
           registrations: [],
           subscriptions: [],
@@ -175,7 +177,7 @@ defmodule Wampex.Router.Session do
               transport_pid: t,
               hello: %Hello{realm: realm, options: options},
               hello_received: false,
-              authentication: auth
+              authentication_module: auth
             } = data
         } = sl
       ) do
@@ -204,7 +206,7 @@ defmodule Wampex.Router.Session do
             id: session_id,
             transport: tt,
             transport_pid: t,
-            authentication: auth,
+            authentication_module: auth,
             challenge: challenge,
             realm: realm,
             name: name,
index cf10e0809c3212f81c0df30c7d79d288c097cc12..861ddb25823a98b565d968ab3ced0307d9970c7c 100644 (file)
@@ -20,15 +20,15 @@ defmodule Wampex.Router.Transports.WebSocket do
   end
 
   @impl true
-  def init(req, %{db: db, name: name}) do
+  def init(req, %{db: db, name: name, authentication_module: atm, authorization_module: azm}) do
     {:ok, serializer, protocol} = get_serializer(req)
     req = :cowboy_req.set_resp_header(@protocol_header, protocol, req)
-    {:cowboy_websocket, req, {%WebSocket{serializer: serializer}, db, name}}
+    {:cowboy_websocket, req, {%WebSocket{serializer: serializer}, db, name, atm, azm}}
   end
 
   @impl true
-  def websocket_init({state, db, name}) do
-    {:ok, session} = Realms.start_session(Router.realms_name(name), db, name, WebSocket, self())
+  def websocket_init({state, db, name, atm, azm}) do
+    {:ok, session} = Realms.start_session(Router.realms_name(name), db, name, WebSocket, self(), atm, azm)
     Process.link(session)
     Logger.info("Websocket Initialized: #{inspect(state)}")
     {:ok, %WebSocket{state | session: session}}
diff --git a/mix.exs b/mix.exs
index 32665c64f6d4d1fb0cf99cbe8ef27ebdeda87357..6bb563be79b57403cf8c906a146ff2da409ec0ab 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -44,14 +44,11 @@ defmodule Wampex.Router.MixProject do
       {:cors_plug, "~> 2.0"},
       {:credo, "~> 1.2", only: [:dev, :test], runtime: false},
       {:dialyxir, "~> 0.5.1", only: [:dev, :test], runtime: false},
-      {:ecto_sql, "~> 3.0"},
       {:ex_doc, "~> 0.21", only: :dev, runtime: false},
       {:excoveralls, "~> 0.12.2", only: [:dev, :test], runtime: false},
       {:jason, "~> 1.1"},
       {:msgpack, "~> 0.7.0"},
-      {:pbkdf2, "~> 2.0"},
       {:plug_cowboy, "~> 2.1"},
-      {:postgrex, ">= 0.0.0"},
       {:states_language, "~> 0.2"},
       {:wampex, git: "https://gitlab.com/entropealabs/wampex.git", tag: "c2c8ee4fbf0b72dfb5b786b33a47a4698b4c5491"},
       {:wampex_client,
index 66955edf573733f3bde17e086e11dd2b0fbe474e..75273cc7f7383225acab414dc912ced9e83d8f89 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -2,18 +2,13 @@
   "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
   "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
   "cluster_kv": {:git, "https://gitlab.com/entropealabs/cluster_kv.git", "4c36b8d68f40711cd167edb9917d32a992f49561", [tag: "4c36b8d68f40711cd167edb9917d32a992f49561"]},
-  "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
   "conv_case": {:hex, :conv_case, "0.2.2", "5a98b74ab8f7ddbad670e5c7bb39ff280e60699aa3b25c7062ceccf48137433c", [:mix], [], "hexpm", "561c550ab6d55b2a4d4c14449e58c9957798613eb26ea182e14a962965377bca"},
   "cors_plug": {:hex, :cors_plug, "2.0.2", "2b46083af45e4bc79632bd951550509395935d3e7973275b2b743bd63cc942ce", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0d0e13f71c51fd4ef8b2c7e051388e4dfb267522a83a22392c856de7e46465f"},
   "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
   "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
   "credo": {:hex, :credo, "1.3.1", "082e8d9268a489becf8e7aa75671a7b9088b1277cd6c1b13f40a55554b3f5126", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0da816ed52fa520b9ea0e5d18a0d3ca269e0bd410b1174d88d8abd94be6cce3c"},
-  "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"},
-  "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
   "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm", "6c32a70ed5d452c6650916555b1f96c79af5fc4bf286997f8b15f213de786f73"},
   "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
-  "ecto": {:hex, :ecto, "3.3.4", "95b05c82ae91361475e5491c9f3ac47632f940b3f92ae3988ac1aad04989c5bb", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "9b96cbb83a94713731461ea48521b178b0e3863d310a39a3948c807266eebd69"},
-  "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
   "elixpath": {:hex, :elixpath, "0.1.0", "f860e931db7bda6856dc68145694ca429643cc068ef30d7ff6b4096d4357963e", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "30ce06079b41f1f5216ea2cd11605cfe4c82239628555cb3fde9f10055a6eb67"},
   "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
   "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"},
@@ -23,7 +18,7 @@
   "json_xema": {:hex, :json_xema, "0.4.0", "377446cd5c0e2cbba52b9d7ab67c05579e6d4a788335220215a8870eac821996", [:mix], [{:conv_case, "~> 0.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:xema, "~> 0.11", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "452724a5b2751cd69191edd3fd3da0c2194c164ebd49efd85f9abb64d6621b53"},
   "libcluster": {:hex, :libcluster, "3.2.1", "b2cd5b447cde25d5897749bee6f7aaeb6c96ac379481024e9b6ba495dabeb97d", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "89f225612d135edce9def56f43bf18d575d88ac4680e3f6161283f2e55cadca4"},
   "libring": {:hex, :libring, "1.5.0", "44313eb6862f5c9168594a061e9d5f556a9819da7c6444706a9e2da533396d70", [:mix], [], "hexpm", "04e843d4fdcff49a62d8e03778d17c6cb2a03fe2d14020d3825a1761b55bd6cc"},
-  "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
+  "makeup": {:hex, :makeup, "1.0.1", "82f332e461dc6c79dbd82fbe2a9c10d48ed07146f0a478286e590c83c52010b5", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "49736fe5b66a08d8575bf5321d716bac5da20c8e6b97714fec2bcd6febcfa1f8"},
   "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
   "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
   "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
   "pbkdf2": {:hex, :pbkdf2, "2.0.0", "11c23279fded5c0027ab3996cfae77805521d7ef4babde2bd7ec04a9086cf499", [:rebar3], [], "hexpm", "1e793ce6fdb0576613115714deae9dfc1d1537eaba74f07efb36de139774488d"},
-  "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"},
+  "plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.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", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"},
   "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"},
   "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
   "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
-  "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"},
   "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
   "states_language": {:hex, :states_language, "0.2.8", "f9dfd3c0bd9a9d7bda25ef315f2d90944cd6b2022a7f3c403deb1d4ec451825e", [:mix], [{:elixpath, "~> 0.1.0", [hex: :elixpath, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4.0", [hex: :json_xema, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xema, "~> 0.11.0", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "a5231691e7cb37fe32dc7de54c2dc86d1d60e84c4f0379f3246e55be2a85ec78"},