diff --git a/.gitignore b/.gitignore index aeaabd7..355d481 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ npm-debug.log /.elixir_ls/ /log/ + +# local environment +.env* diff --git a/config/dev.exs b/config/dev.exs index b13af60..51de6f4 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -52,3 +52,12 @@ config :hammer, pool_size: 4, pool_max_overflow: 2 ]} + +config :recognizer, Recognizer.BigCommerce, + client_id: "bc_id", + client_secret: "bc_secret", + access_token: "bc_access_token", + store_hash: "bc_store_hash", + login_uri: "http://localhost/", + http_client: HTTPoison, + enabled?: true diff --git a/config/releases.exs b/config/releases.exs index ec4e2cf..af638cf 100644 --- a/config/releases.exs +++ b/config/releases.exs @@ -67,3 +67,12 @@ config :hammer, pool_size: 4, pool_max_overflow: 2 ]} + +config :recognizer, Recognizer.BigCommerce, + client_id: recognizer_config["BIGCOMMERCE_CLIENT_ID"], + client_secret: recognizer_config["BIGCOMMERCE_CLIENT_SECRET"], + access_token: recognizer_config["BIGCOMMERCE_ACCESS_TOKEN"], + store_hash: recognizer_config["BIGCOMMERCE_STORE_HASH"], + login_uri: recognizer_config["BIGCOMMERCE_LOGIN_URI"], + http_client: HTTPoison, + enabled?: false diff --git a/config/test.exs b/config/test.exs index 7fbf43d..441e975 100644 --- a/config/test.exs +++ b/config/test.exs @@ -30,3 +30,12 @@ config :hammer, pool_size: 4, pool_max_overflow: 2 ]} + +config :recognizer, Recognizer.BigCommerce, + client_id: "bc_id", + client_secret: "bc_secret", + access_token: "bc_access_token", + store_hash: "bc_store_hash", + login_uri: "http://localhost/", + http_client: HTTPoisonMock, + enabled?: true diff --git a/lib/recognizer/accounts.ex b/lib/recognizer/accounts.ex index 012e8cd..25a5727 100644 --- a/lib/recognizer/accounts.ex +++ b/lib/recognizer/accounts.ex @@ -11,6 +11,7 @@ defmodule Recognizer.Accounts do alias Recognizer.Accounts.RecoveryCode alias Recognizer.Accounts.User alias Recognizer.Accounts.VerificationCode + alias Recognizer.BigCommerce alias Recognizer.Notifications.Account, as: Notification alias Recognizer.Guardian alias Recognizer.Repo @@ -166,20 +167,29 @@ defmodule Recognizer.Accounts do {:error, %Ecto.Changeset{}} """ - def register_user(attrs, opts \\ []) + def register_user(attrs, opts \\ []) do + Repo.transaction(fn -> + case do_register_user(attrs, opts) do + {:ok, user} -> user + {:error, e} -> Repo.rollback(e) + end + end) + end - def register_user(attrs, verify_account_url_fun: verify_account_url_fun) do + defp do_register_user(attrs, verify_account_url_fun: verify_account_url_fun) do %User{} |> User.registration_changeset(attrs) |> insert_user_and_notification_preferences() + |> maybe_create_big_commerce_customer() |> maybe_generate_verification_code(verify_account_url_fun) |> maybe_send_newsletter(attrs) end - def register_user(attrs, opts) do + defp do_register_user(attrs, opts) do %User{} |> User.registration_changeset(attrs, opts) |> insert_user_and_notification_preferences() + |> maybe_create_big_commerce_customer() |> maybe_mark_user_verified() |> maybe_notify_new_user() |> maybe_send_newsletter(attrs) @@ -203,6 +213,7 @@ defmodule Recognizer.Accounts do %User{} |> User.oauth_registration_changeset(attrs) |> insert_user_and_notification_preferences() + |> maybe_create_big_commerce_customer() |> maybe_notify_new_user() end @@ -705,4 +716,16 @@ defmodule Recognizer.Accounts do Repo.delete_all(user_codes) end + + def maybe_create_big_commerce_customer({:ok, user}) do + if BigCommerce.enabled?() do + BigCommerce.create_customer(user) + else + {:ok, user} + end + end + + def maybe_create_big_commerce_customer(error) do + error + end end diff --git a/lib/recognizer/accounts/bc_customer_user.ex b/lib/recognizer/accounts/bc_customer_user.ex new file mode 100644 index 0000000..dd861e5 --- /dev/null +++ b/lib/recognizer/accounts/bc_customer_user.ex @@ -0,0 +1,15 @@ +defmodule Recognizer.Accounts.BCCustomerUser do + @moduledoc false + + use Ecto.Schema + + alias Recognizer.Accounts.User + + @primary_key false + schema "bigcommerce_customer_users" do + field :bc_id, :integer + belongs_to :user, User, primary_key: true + + timestamps() + end +end diff --git a/lib/recognizer/accounts/user.ex b/lib/recognizer/accounts/user.ex index 7bcd765..a7a81b2 100644 --- a/lib/recognizer/accounts/user.ex +++ b/lib/recognizer/accounts/user.ex @@ -8,7 +8,12 @@ defmodule Recognizer.Accounts.User do import Ecto.Changeset - alias Recognizer.Accounts.{NotificationPreference, OAuth, Organization, RecoveryCode, Role} + alias Recognizer.Accounts.BCCustomerUser, as: BcUser + alias Recognizer.Accounts.NotificationPreference + alias Recognizer.Accounts.OAuth + alias Recognizer.Accounts.Organization + alias Recognizer.Accounts.RecoveryCode + alias Recognizer.Accounts.Role alias Recognizer.Repo alias __MODULE__ @@ -41,6 +46,7 @@ defmodule Recognizer.Accounts.User do field :verified_at, :utc_datetime has_one :notification_preference, NotificationPreference, on_replace: :update + has_one :bigcommerce_user, BcUser belongs_to :organization, Organization diff --git a/lib/recognizer/bigcommerce.ex b/lib/recognizer/bigcommerce.ex new file mode 100644 index 0000000..1869d4b --- /dev/null +++ b/lib/recognizer/bigcommerce.ex @@ -0,0 +1,61 @@ +defmodule Recognizer.BigCommerce do + @moduledoc """ + BigCommerce context. + """ + + require Logger + + alias Recognizer.Accounts.BCCustomerUser, as: Customer + alias Recognizer.BigCommerce.Client + alias Recognizer.BigCommerce.Token + alias Recognizer.Repo + + def enabled?() do + config(:enabled?) + end + + def create_customer(user) do + case Client.create_customer(user) do + {:ok, bc_id} -> + Repo.insert(%Customer{user_id: user.id, bc_id: bc_id}) + {:ok, user} + + {:error, e} -> + Logger.error("error creating bigcommerce customer: #{inspect(e)}") + {:error, e} + end + end + + def generate_login_jwt(user) do + {:ok, token, _claims} = + user + |> Recognizer.Repo.preload(:bigcommerce_user) + |> jwt_claims() + |> Token.generate_and_sign(jwt_signer()) + + token + end + + def login_redirect_uri(jwt) do + config(:login_uri) <> jwt + end + + defp jwt_claims(user) do + %{ + "aud" => "BigCommerce", + "iss" => config(:client_id), + "jti" => Ecto.UUID.generate(), + "operation" => "customer_login", + "store_hash" => config(:store_hash), + "customer_id" => user.bigcommerce_user.bc_id + } + end + + defp jwt_signer() do + Joken.Signer.create("HS256", config(:client_secret)) + end + + defp config(key) do + Application.get_env(:recognizer, __MODULE__)[key] + end +end diff --git a/lib/recognizer/bigcommerce/client.ex b/lib/recognizer/bigcommerce/client.ex new file mode 100644 index 0000000..01eed97 --- /dev/null +++ b/lib/recognizer/bigcommerce/client.ex @@ -0,0 +1,98 @@ +defmodule Recognizer.BigCommerce.Client do + @moduledoc """ + BigCommerce v3 + """ + + require Logger + + alias Recognizer.Accounts.User + + alias HTTPoison.Response + + @default_retry_ms 5000 + + def create_customer(user) do + with {:ok, customer_params} <- user_as_customer_params(user), + {:ok, customer_json} <- Jason.encode(customer_params), + {:ok, response} <- post_customer(customer_json) do + get_id(response) + else + {:error, e} -> + Logger.error("cannot create customer with error: #{inspect(e)}") + {:error, e} + + e -> + Logger.error("cannot create customer with error: #{inspect(e)}") + {:error, e} + end + end + + defp user_as_customer_params(%User{email: email, first_name: first_name, last_name: last_name}) do + {:ok, [%{"email" => email, "first_name" => first_name, "last_name" => last_name}]} + end + + defp user_as_customer_params(_user) do + {:error, :invalid_user} + end + + defp post_customer(customer_json) do + case http_client().post(customers_uri(), customer_json, default_headers()) do + {:ok, %Response{body: response, status_code: 200}} -> + {:ok, response} + + {:ok, %Response{status_code: 429, headers: headers}} -> + sleep_for_rate_limit(headers) + post_customer(customer_json) + + {:error, e} -> + {:error, e} + + e -> + {:error, e} + end + end + + defp get_id(response) do + case Jason.decode(response) do + {:ok, %{"data" => [%{"id" => id}]}} -> {:ok, id} + {:error, e} -> {:error, e} + e -> {:error, e} + end + end + + defp default_headers() do + [ + {"Content-Type", "application/json"}, + {"Accept", "application/json"}, + {"Authorization", "Bearer #{config(:access_token)}"}, + {"x-Auth-Token", config(:access_token)} + ] + end + + defp customers_uri() do + uri("/v3/customers") + end + + defp uri(path) do + "https://api.bigcommerce.com/stores/#{config(:store_hash)}#{path}" + end + + defp http_client() do + config(:http_client) + end + + defp config(key) do + Application.get_env(:recognizer, Recognizer.BigCommerce)[key] + end + + defp sleep_for_rate_limit(headers) do + retry_ms = + case List.keyfind(headers, "x-rate-limit-time-reset-ms", 0) do + nil -> @default_retry_ms + {_, retry_value} -> String.to_integer(retry_value) + end + + Logger.warn("Rate limited, sleeping for ms: #{inspect(retry_ms)}") + Process.sleep(retry_ms) + end +end diff --git a/lib/recognizer/bigcommerce/token.ex b/lib/recognizer/bigcommerce/token.ex new file mode 100644 index 0000000..a1f11a5 --- /dev/null +++ b/lib/recognizer/bigcommerce/token.ex @@ -0,0 +1,5 @@ +defmodule Recognizer.BigCommerce.Token do + @moduledoc false + + use Joken.Config +end diff --git a/lib/recognizer_web/authentication.ex b/lib/recognizer_web/authentication.ex index ca6175a..6b70177 100644 --- a/lib/recognizer_web/authentication.ex +++ b/lib/recognizer_web/authentication.ex @@ -7,8 +7,9 @@ defmodule RecognizerWeb.Authentication do import Phoenix.Controller alias Guardian.DB, as: GuardianDB - alias RecognizerWeb.Router.Helpers, as: Routes + alias Recognizer.BigCommerce alias Recognizer.Guardian + alias RecognizerWeb.Router.Helpers, as: Routes @doc """ Logs the user in. @@ -31,15 +32,27 @@ defmodule RecognizerWeb.Authentication do |> redirect(to: Routes.prompt_two_factor_path(conn, :new)) {:ok, _user} -> - redirect_opts = login_redirect(conn) - - conn - |> clear_session() - |> Guardian.Plug.sign_in(user, params) - |> redirect(redirect_opts) + if BigCommerce.enabled?() && get_session(conn, :bc) do + log_in_bc_user(conn, user) + else + redirect_opts = login_redirect(conn) + + conn + |> clear_session() + |> Guardian.Plug.sign_in(user, params) + |> redirect(redirect_opts) + end end end + defp log_in_bc_user(conn, user) do + jwt = BigCommerce.generate_login_jwt(user) + + conn + |> clear_session() + |> redirect(external: BigCommerce.login_redirect_uri(jwt)) + end + @doc """ Logs the user in via the API. """ diff --git a/lib/recognizer_web/controllers/accounts/user_oauth_controller.ex b/lib/recognizer_web/controllers/accounts/user_oauth_controller.ex index 5ad622d..8ea28f1 100644 --- a/lib/recognizer_web/controllers/accounts/user_oauth_controller.ex +++ b/lib/recognizer_web/controllers/accounts/user_oauth_controller.ex @@ -33,6 +33,9 @@ defmodule RecognizerWeb.Accounts.UserOAuthController do conn |> put_flash(:error, friendly_error_message(changeset)) |> redirect(to: Routes.user_session_path(conn, :new)) + + {:error, e} -> + {:error, e} end end @@ -83,8 +86,8 @@ defmodule RecognizerWeb.Accounts.UserOAuthController do {:ok, _oauth} <- Accounts.create_oauth(user, provider, uid) do user else - {:error, changeset} -> - Repo.rollback(changeset) + {:error, e} -> + Repo.rollback(e) end end) end diff --git a/lib/recognizer_web/controllers/accounts/user_registration_controller.ex b/lib/recognizer_web/controllers/accounts/user_registration_controller.ex index e5731e8..e33383a 100644 --- a/lib/recognizer_web/controllers/accounts/user_registration_controller.ex +++ b/lib/recognizer_web/controllers/accounts/user_registration_controller.ex @@ -14,6 +14,12 @@ defmodule RecognizerWeb.Accounts.UserRegistrationController do ] when action in [:create] + def new(conn, %{"bc" => "true"} = params) do + conn + |> put_session(:bc, true) + |> new(Map.drop(params, ["bc"])) + end + def new(conn, params) do user_params = Map.get(params, "user", %{}) changeset = Accounts.change_user_registration(%User{}, user_params) @@ -29,6 +35,9 @@ defmodule RecognizerWeb.Accounts.UserRegistrationController do {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) + + {:error, e} -> + {:error, e} end end end diff --git a/lib/recognizer_web/controllers/accounts/user_session_controller.ex b/lib/recognizer_web/controllers/accounts/user_session_controller.ex index 3b320d1..c9b2b89 100644 --- a/lib/recognizer_web/controllers/accounts/user_session_controller.ex +++ b/lib/recognizer_web/controllers/accounts/user_session_controller.ex @@ -4,6 +4,12 @@ defmodule RecognizerWeb.Accounts.UserSessionController do alias Recognizer.Accounts alias RecognizerWeb.Authentication + def new(conn, %{"bc" => "true"}) do + conn + |> put_session(:bc, true) + |> render("new.html", error_message: nil) + end + def new(conn, _params) do render(conn, "new.html", error_message: nil) end diff --git a/lib/recognizer_web/controllers/fallback_controller.ex b/lib/recognizer_web/controllers/fallback_controller.ex index 41e9642..6309622 100644 --- a/lib/recognizer_web/controllers/fallback_controller.ex +++ b/lib/recognizer_web/controllers/fallback_controller.ex @@ -35,6 +35,7 @@ defmodule RecognizerWeb.FallbackController do respond(conn, :unauthorized, "401") end + @impl true def call(conn, {:error, %Ecto.Changeset{} = changeset}) do conn |> put_status(:bad_request) @@ -42,7 +43,6 @@ defmodule RecognizerWeb.FallbackController do |> render("error.json", changeset: changeset) end - @impl true def call(conn, {:error, :unauthenticated}) do conn |> Authentication.maybe_store_return_to() @@ -50,6 +50,13 @@ defmodule RecognizerWeb.FallbackController do |> halt() end + def call(conn, {:error, _}) do + conn + |> put_status(500) + |> put_view(ErrorView) + |> render("500.html") + end + defp respond(conn, :not_found, _template) do if Application.get_env(:recognizer, :redirect_url) do redirect(conn, external: Application.get_env(:recognizer, :redirect_url)) diff --git a/mix.exs b/mix.exs index ec870c9..9732288 100644 --- a/mix.exs +++ b/mix.exs @@ -52,8 +52,9 @@ defmodule Recognizer.MixProject do {:hammer, "~> 6.0"}, {:hammer_backend_redis, "~> 6.1"}, {:hammer_plug, "~> 3.0"}, - {:httpoison, "~> 0.13"}, + {:httpoison, "~> 1.8.2"}, {:jason, "~> 1.0"}, + {:joken, "~> 2.6.0"}, {:logger_json, github: "Nebo15/logger_json", ref: "8e4290a"}, {:myxql, ">= 0.0.0"}, {:redix, ">= 0.0.0"}, diff --git a/mix.lock b/mix.lock index 6502455..8581b5a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,85 +1,89 @@ %{ - "amqp": {:hex, :amqp, "3.2.0", "51e85e06e4d283d98f21dce95906e42cb181fc7ceeb967b33b3be73d6ea41aa5", [:mix], [{:amqp_client, "~> 3.9.1", [hex: :amqp_client, repo: "hexpm", optional: false]}], "hexpm", "1439570336df6e79000239938fb055a0944dc9a768b4dec0af1375404508a014"}, - "amqp_client": {:hex, :amqp_client, "3.9.24", "31e150d8e2efa3aa2166c69d90231170ec9249bf66ee97a84ec962ad0a7a0bf8", [:make, :rebar3], [{:rabbit_common, "3.9.24", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm", "e653db7acc1f96234c39287241678912fef59ce182ab53f1fe7df0940ddbc3cf"}, + "amqp": {:hex, :amqp, "3.3.0", "056d9f4bac96c3ab5a904b321e70e78b91ba594766a1fc2f32afd9c016d9f43b", [:mix], [{:amqp_client, "~> 3.9", [hex: :amqp_client, repo: "hexpm", optional: false]}], "hexpm", "8d3ae139d2646c630d674a1b8d68c7f85134f9e8b2a1c3dd5621616994b10a8b"}, + "amqp_client": {:hex, :amqp_client, "3.12.10", "dcc0d5d0037fa2b486c6eb8b52695503765b96f919e38ca864a7b300b829742d", [:make, :rebar3], [{:credentials_obfuscation, "3.4.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:rabbit_common, "3.12.10", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm", "16a23959899a82d9c2534ed1dcf1fa281d3b660fb7f78426b880647f0a53731f"}, "argon2_elixir": {:hex, :argon2_elixir, "2.4.1", "edb27bdd326bc738f3e4614eddc2f73507be6fedc9533c6bcc6f15bbac9c85cc", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0e21f52a373739d00bdfd5fe6da2f04eea623cb4f66899f7526dd9db03903d9f"}, "bottle": {:git, "https://github.com/system76/bottle.git", "bd102de771893e0135e566926e8f571578b5177b", [ref: "bd102de771893e0135e566926e8f571578b5177b"]}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, - "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"}, - "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"}, + "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"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, - "credentials_obfuscation": {:hex, :credentials_obfuscation, "3.1.0", "2c405ea0c5db7b3344aa5a99f86c33e7b6ecea97d2cb613371e1cf0d192ef2c6", [:rebar3], [], "hexpm", "04884e62b1c6cdfba999d4d6b3e99bc0a59d5e439517bc5c01767255afb7b778"}, - "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, - "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, - "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "credentials_obfuscation": {:hex, :credentials_obfuscation, "3.4.0", "34e18b126b3aefd6e8143776fbe1ceceea6792307c99ac5ee8687911f048cfd7", [:rebar3], [], "hexpm", "738ace0ed5545d2710d3f7383906fc6f6b582d019036e5269c4dbd85dbced566"}, + "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, - "ecto": {:hex, :ecto, "3.9.1", "67173b1687afeb68ce805ee7420b4261649d5e2deed8fe5550df23bab0bc4396", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c80bb3d736648df790f7f92f81b36c922d9dd3203ca65be4ff01d067f54eb304"}, + "ecto": {:hex, :ecto, "3.11.0", "ff8614b4e70a774f9d39af809c426def80852048440e8785d93a6e91f48fec00", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7769dad267ef967310d6e988e92d772659b11b09a0c015f101ce0fff81ce1f81"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, - "ecto_sql": {:hex, :ecto_sql, "3.9.0", "2bb21210a2a13317e098a420a8c1cc58b0c3421ab8e3acfa96417dab7817918c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.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", "a8f3f720073b8b1ac4c978be25fa7960ed7fd44997420c304a4a2e200b596453"}, - "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, + "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"}, "eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"}, - "ex_aws": {:hex, :ex_aws, "2.4.0", "f2c978e15145722258513907a9d73ef1d81ed92e76331138d42649ae2b1b6eba", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "66dd0bacaa4113d372d61d795c22d78af170672384c81ebab97a42edf4128543"}, - "ex_aws_sqs": {:hex, :ex_aws_sqs, "3.3.1", "04a76830ec161b03442d28d89bb12555a54a916a73c88b87c72c07e054210b5f", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:saxy, "~> 1.1", [hex: :saxy, repo: "hexpm", optional: true]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "47d8fc29fd3bd6f491d2478a0d431dbd88c3e1cd1992148c064c52f6adca2ff4"}, + "ex_aws": {:hex, :ex_aws, "2.5.0", "1785e69350b16514c1049330537c7da10039b1a53e1d253bbd703b135174aec3", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "971b86e5495fc0ae1c318e35e23f389e74cf322f2c02d34037c6fc6d405006f1"}, + "ex_aws_sqs": {:hex, :ex_aws_sqs, "3.4.0", "f7c4d0177c1c954776363d3dc05e5dfd37ddf0e2c65ec3f047e5c9c7dd1b71ac", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:saxy, "~> 1.1", [hex: :saxy, repo: "hexpm", optional: true]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "b504482206ccaf767b714888e9d41a1cfcdcb241577985517114191c812f155a"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, - "ex_oauth2_provider": {:hex, :ex_oauth2_provider, "0.5.6", "e1d5130c9062d3a24c5106e7a556b3335aaa7150801c89f6f5494c5f9ad8a2ba", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:plug, ">= 1.5.0 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0288475aa3a6224b70c990e61c89375dcb7c0b2669ba9a1114e94238cae615cf"}, + "ex_oauth2_provider": {:hex, :ex_oauth2_provider, "0.5.7", "48baa2e9929ec4ee04e9e235d592a6705ab78a3c0b12914a7b3d38e3a892bb16", [:mix], [{:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, ">= 1.5.0 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "5e370e5bceb0b87997548102f5151e8c8956f1ae95ab1f5fff47083cc41dae0e"}, + "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"}, + "gettext": {:hex, :gettext, "0.23.1", "821e619a240e6000db2fc16a574ef68b3bd7fe0167ccc264a81563cc93e67a31", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "19d744a36b809d810d610b57c27b934425859d158ebd56561bc41f7eeb8795db"}, "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm", "99cb4128cffcb3227581e5d4d803d5413fa643f4eb96523f77d9e6937d994ceb"}, "google_protos": {:hex, :google_protos, "0.3.0", "15faf44dce678ac028c289668ff56548806e313e4959a3aaf4f6e1ebe8db83f4", [:mix], [{:protobuf, "~> 0.10", [hex: :protobuf, repo: "hexpm", optional: false]}], "hexpm", "1f6b7fb20371f72f418b98e5e48dae3e022a9a6de1858d4b254ac5a5d0b4035f"}, "grpc": {:hex, :grpc, "0.5.0", "a44cb306625a52fa31a2189ce91b40d24e82569568f0cc214c1e1e0faf54f58a", [:mix], [{:cowboy, "~> 2.9", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}, {:gun, "~> 2.0.1", [hex: :grpc_gun, repo: "hexpm", optional: false]}], "hexpm", "17b98593fdb1a65be7b2722821266627b3f2fba29bbbd7d0945389427c0d0d5f"}, - "guardian": {:hex, :guardian, "2.3.0", "1e2a90e809fbd99439f5279db03fb30b7b2b2fc0d3870a0d76a84b099f1a2892", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ced3ace74fc2b22b2b25dbe99e6d205443dd0d57d1cc25155a223b5e9656205e"}, + "guardian": {:hex, :guardian, "2.3.2", "78003504b987f2b189d76ccf9496ceaa6a454bb2763627702233f31eb7212881", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b189ff38cd46a22a8a824866a6867ca8722942347f13c33f7d23126af8821b52"}, "guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"}, "gun": {:hex, :grpc_gun, "2.0.1", "221b792df3a93e8fead96f697cbaf920120deacced85c6cd3329d2e67f0871f8", [:rebar3], [{:cowlib, "~> 2.11", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "795a65eb9d0ba16697e6b0e1886009ce024799e43bb42753f0c59b029f592831"}, - "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.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", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "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"}, "hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"}, "hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"}, "hammer_plug": {:hex, :hammer_plug, "3.0.0", "7b1d000021e3ccc92cc6405c5537d3ec22f8f8f1274a1ae9351a8d98c32a2803", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "35275f98f887bef8d84a8e0a3021ba1cd14d0e15c11221f6f8c833a3d43f35d8"}, - "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "4846958172d6401c4f34ecc5c2c4607b5b0d90b8eec8f6df137ca4907942ed0f"}, + "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "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.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, - "jose": {:hex, :jose, "1.11.2", "f4c018ccf4fdce22c71e44d471f15f723cb3efab5d909ab2ba202b5bf35557b3", [:mix, :rebar3], [], "hexpm", "98143fbc48d55f3a18daba82d34fe48959d44538e9697c08f34200fa5f0947d2"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"}, + "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"}, "jsx": {:hex, :jsx, "3.1.0", "d12516baa0bb23a59bb35dccaf02a1bd08243fcbb9efe24f2d9d056ccff71268", [:rebar3], [], "hexpm", "0c5cc8fdc11b53cc25cf65ac6705ad39e54ecc56d1c22e4adb8f5a53fb9427f3"}, "lager": {:hex, :lager, "3.9.2", "4cab289120eb24964e3886bd22323cb5fefe4510c076992a23ad18cf85413d8c", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm", "7f904d9e87a8cb7e66156ed31768d1c8e26eba1d54f4bc85b1aa4ac1f6340c28"}, "logger_json": {:git, "https://github.com/Nebo15/logger_json.git", "8e4290a7377a3624b3eaab9db1889d7e9c537bb7", [ref: "8e4290a"]}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, + "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, "msgpax": {:hex, :msgpax, "2.2.4", "7b3790ef684089076b63c0f08c2f4b079c6311daeb006b69e4ed2bf67518291e", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b351b6d992d79624a8430a99d21a41b36b1b90edf84326a294e9f4a2de11f089"}, "myxql": {:hex, :myxql, "0.6.3", "3d77683a09f1227abb8b73d66b275262235c5cae68182f0cfa5897d72a03700e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "af9eb517ddaced5c5c28e8749015493757fd4413f2cfccea449c466d405d9f51"}, + "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "oauth2": {:hex, :oauth2, "2.0.1", "70729503e05378697b958919bb2d65b002ba6b28c8112328063648a9348aaa3f", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "c64e20d4d105bcdbcbe03170fb530d0eddc3a3e6b135a87528a22c8aecf74c52"}, "optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm", "1a06ea6a653120226b35b283a1cd10039550f2c566edcdec22b29316d73640fd"}, - "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.5.14", "2d5db884be496eefa5157505ec0134e66187cb416c072272420c5509d67bf808", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "207f1aa5520320cbb7940d7ff2dde2342162cf513875848f88249ea0ba02fef7"}, - "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_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"}, "phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.0", "4fe222c0be55fdc3f9c711e24955fc42a7cd9b7a2f5f406f2580a567c335a573", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "bebf0fc2d2113b61cb5968f585367234b7b4c21d963d691de7b4b2dc6cdaae6f"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phx_gen_auth": {:hex, :phx_gen_auth, "0.7.0", "2e10e9527b6b71abbfbb4601c7dc4aa4fb9f2db6f9a6be457c468b7f2b0f6319", [:mix], [{:phoenix, "~> 1.5.2", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b9dc3e3b866e67c5db8f00f4a2adb28fc8636e794f78600e35aba0e55bdac209"}, - "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [: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", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, + "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"}, "protobuf": {:hex, :protobuf, "0.11.0", "58d5531abadea3f71135e97bd214da53b21adcdb5b1420aee63f4be8173ec927", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "30ad9a867a5c5a0616cac9765c4d2c2b7b0030fa81ea6d0c14c2eb5affb6ac52"}, - "rabbit_common": {:hex, :rabbit_common, "3.9.24", "f78299b5e6d4f853ce7fd3223d17da464ef29c72636e2ff483fedeea1ff31022", [:make, :rebar3], [{:credentials_obfuscation, "3.1.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:jsx, "3.1.0", [hex: :jsx, repo: "hexpm", optional: false]}, {:recon, "2.5.2", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "a9625e893adb3143e50571f98e27a1d08b0ba1fac65cfd9106b8d515fc1de1aa"}, + "rabbit_common": {:hex, :rabbit_common, "3.12.10", "7fc633ee206ae48783d8a5302dfc8fe1e086a5d7de494785ed206f586ad64b34", [:make, :rebar3], [{:credentials_obfuscation, "3.4.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:recon, "2.5.3", [hex: :recon, repo: "hexpm", optional: false]}, {:thoas, "1.0.0", [hex: :thoas, repo: "hexpm", optional: false]}], "hexpm", "908a8b1bd059f5baefe225fe9d3e2545d35a28db8f6a14d60372556ca7afe641"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "recon": {:hex, :recon, "2.5.2", "cba53fa8db83ad968c9a652e09c3ed7ddcc4da434f27c3eaa9ca47ffb2b1ff03", [:mix, :rebar3], [], "hexpm", "2c7523c8dee91dff41f6b3d63cba2bd49eb6d2fe5bf1eec0df7f87eb5e230e1c"}, - "redix": {:hex, :redix, "1.2.0", "0d7eb3ccb7b82c461a6ea28b65c2c04486093d816dd6d901a09164800e004df1", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e1e0deb14599da07c77e66956a12863e85ee270ada826804a0ba8e61657e22a3"}, - "saxy": {:hex, :saxy, "1.4.0", "c7203ad20001f72eaaad07d08f82be063fa94a40924e6bb39d93d55f979abcba", [:mix], [], "hexpm", "3fe790354d3f2234ad0b5be2d99822a23fa2d4e8ccd6657c672901dac172e9a9"}, + "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"}, + "redix": {:hex, :redix, "1.3.0", "f4121163ff9d73bf72157539ff23b13e38422284520bb58c05e014b19d6f0577", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "60d483d320c77329c8cbd3df73007e51b23f3fae75b7693bc31120d83ab26131"}, + "saxy": {:hex, :saxy, "1.5.0", "0141127f2d042856f135fb2d94e0beecda7a2306f47546dbc6411fc5b07e28bf", [:mix], [], "hexpm", "ea7bb6328fbd1f2aceffa3ec6090bfb18c85aadf0f8e5030905e84235861cf89"}, "spandex": {:hex, :spandex, "3.0.3", "91aa318f3de696bb4d931adf65f7ebdbe5df25cccce1fe8fd376a44c46bcf69b", [:mix], [{:decorator, "~> 1.2", [hex: :decorator, repo: "hexpm", optional: true]}, {:optimal, "~> 0.3.3", [hex: :optimal, repo: "hexpm", optional: false]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e3e6c319d0ab478ddc9a39102a727a410c962b4d51c0932c72279b86d3b17044"}, "spandex_datadog": {:hex, :spandex_datadog, "1.1.0", "8c84e2f6c4067edc2e920dd79242f7bb0d6403652a7e9bc42109007f76b9be25", [:mix], [{:msgpax, "~> 2.2.1", [hex: :msgpax, repo: "hexpm", optional: false]}, {:spandex, "~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4c20d3e601cad869705d9789f17a9242f245ce0bf2579fc835e96a6834663e2"}, "spandex_ecto": {:hex, :spandex_ecto, "0.6.2", "845e0e0a115e84c218015e8a13cca7adb38e8b0a1b45010a51451a8c9961c551", [:mix], [{:spandex, "~> 2.2 or ~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm", "ddeb5c279ca850a38eee6decc8f91b3f4929c76f141b3329293793be54d0c1c7"}, "spandex_phoenix": {:hex, :spandex_phoenix, "1.0.6", "b2caf99cd37cf5c501c89de6099b07a8efab31747dbd63ed2fff802bb02c6937", [:mix], [{:optimal, "~> 0.3", [hex: :optimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}, {:spandex, "~> 2.2 or ~> 3.0", [hex: :spandex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "e286d4bfe6917ecddf56b47553322b55bdc328326b7d86a6c35b4835679e9784"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, "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, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"}, - "ueberauth": {:hex, :ueberauth, "0.7.0", "9c44f41798b5fa27f872561b6f7d2bb0f10f03fdd22b90f454232d7b087f4b75", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2efad9022e949834f16cc52cd935165049d81fa9e925690f91035c2e4b58d905"}, - "ueberauth_github": {:hex, :ueberauth_github, "0.8.1", "0be487b5afc29bc805fa5e31636f37c8f09d5159ef73fc08c4c7a98c9cfe2c18", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "143d6130b945ea9bdbd0ef94987f40788f1d7e8090decbfc0722773155e7a74a"}, - "ueberauth_google": {:hex, :ueberauth_google, "0.10.1", "db7bd2d99d2ff38e7449042a08d9560741b0dcaf1c31191729b97188b025465e", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "b799f547d279bb836e1f7039fc9fbb3a9d008a695e2a25bd06bffe591a168ba1"}, + "thoas": {:hex, :thoas, "1.0.0", "567c03902920827a18a89f05b79a37b5bf93553154b883e0131801600cf02ce0", [:rebar3], [], "hexpm", "fc763185b932ecb32a554fb735ee03c3b6b1b31366077a2427d2a97f3bd26735"}, + "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, + "ueberauth_github": {:hex, :ueberauth_github, "0.8.3", "1c478629b4c1dae446c68834b69194ad5cead3b6c67c913db6fdf64f37f0328f", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "ae0ab2879c32cfa51d7287a48219b262bfdab0b7ec6629f24160564247493cc6"}, + "ueberauth_google": {:hex, :ueberauth_google, "0.12.1", "90cf49743588193334f7a00da252f92d90bfd178d766c0e4291361681fafec7d", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.10.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "7f7deacd679b2b66e3bffb68ecc77aa1b5396a0cbac2941815f253128e458c38"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/priv/repo/migrations/20201205222922_create_users_auth_tables.exs b/priv/repo/migrations/20201205222922_create_users_auth_tables.exs index 2bcbec3..5130161 100644 --- a/priv/repo/migrations/20201205222922_create_users_auth_tables.exs +++ b/priv/repo/migrations/20201205222922_create_users_auth_tables.exs @@ -60,5 +60,13 @@ defmodule Recognizer.Repo.Migrations.CreateUsersAuthTables do create index(:users_tokens, [:user_id]) create unique_index(:users_tokens, [:context, :token]) + + # This table was created by a migration in a different application. + create table(:bigcommerce_customer_users, primary_key: false) do + add :bc_id, :integer, null: false + add :user_id, references(:users, type: :"int(11) unsigned"), null: false, primary_key: true + + timestamps() + end end end diff --git a/test/recognizer/accounts_test.exs b/test/recognizer/accounts_test.exs index 0eec073..cf12be8 100644 --- a/test/recognizer/accounts_test.exs +++ b/test/recognizer/accounts_test.exs @@ -5,10 +5,19 @@ defmodule Recognizer.AccountsTest do import Recognizer.AccountFactory alias Recognizer.Accounts + alias Recognizer.Accounts.BCCustomerUser alias Recognizer.Accounts.User @new_valid_password "NeWVal1DP!ssW0R$" + setup :verify_on_exit! + + defp ok_bigcommerce_response() do + body = Jason.encode!(%{data: [%{id: 1001}]}) + + {:ok, %HTTPoison.Response{body: body, status_code: 200}} + end + describe "get_user_by_email/1" do test "does not return the user if the email does not exist" do refute Accounts.get_user_by_email("unknown@example.com") @@ -94,6 +103,8 @@ defmodule Recognizer.AccountsTest do end test "registers users with a hashed password" do + expect(HTTPoisonMock, :post, 1, fn _, _, _ -> ok_bigcommerce_response() end) + {:ok, user} = :user |> params_for(%{email: "TEST@Example.com"}) @@ -102,9 +113,11 @@ defmodule Recognizer.AccountsTest do assert is_binary(user.hashed_password) assert is_nil(user.password) assert "test@example.com" == user.email + assert Repo.get_by(BCCustomerUser, bc_id: 1001) end test "adds login role" do + expect(HTTPoisonMock, :post, 1, fn _, _, _ -> ok_bigcommerce_response() end) {:ok, user} = Accounts.register_user(params_for(:user)) assert Enum.any?(user.roles, fn r -> r.role_id == 1 end) end diff --git a/test/recognizer_web/controllers/accounts/api/user_registration_controller_test.exs b/test/recognizer_web/controllers/accounts/api/user_registration_controller_test.exs index d2ffc6f..fc04c7b 100644 --- a/test/recognizer_web/controllers/accounts/api/user_registration_controller_test.exs +++ b/test/recognizer_web/controllers/accounts/api/user_registration_controller_test.exs @@ -1,27 +1,71 @@ defmodule RecognizerWeb.Api.UserRegistrationControllerTest do use RecognizerWeb.ConnCase + import Mox + + alias Recognizer.Accounts.BCCustomerUser alias Recognizer.Accounts.User alias Recognizer.Repo + @moduletag capture_log: true + + setup :verify_on_exit! setup :register_and_log_in_admin + defp ok_bigcommerce_response() do + body = Jason.encode!(%{data: [%{id: 1001}]}) + + {:ok, %HTTPoison.Response{body: body, status_code: 200}} + end + + defp limit_bigcommerce_response() do + headers = [{"x-rate-limit-time-reset-ms", "1"}] + + {:ok, %HTTPoison.Response{status_code: 429, headers: headers}} + end + describe "POST /api/create-account" do test "POST /api/create-account is limited to staff only", %{conn: conn} do + user = %{ + "email" => "test@example.com", + "first_name" => "Test", + "last_name" => "User" + } + + user_json = Jason.encode!([user]) + + expect(HTTPoisonMock, :post, 1, fn _, ^user_json, _ -> ok_bigcommerce_response() end) + + conn = post(conn, "/api/create-account", %{"user" => user}) + + assert %{"user" => _} = json_response(conn, 201) + assert Repo.get_by(BCCustomerUser, bc_id: 1001) + end + + test "POST /api/create-account verifies the user", %{conn: conn} do + expect(HTTPoisonMock, :post, 1, fn _, _, _ -> ok_bigcommerce_response() end) + + email = "test-verification@example.com" + conn = post(conn, "/api/create-account", %{ "user" => %{ - "email" => "test@example.com", + "email" => email, "first_name" => "Test", "last_name" => "User" } }) assert %{"user" => _} = json_response(conn, 201) + assert %User{verified_at: verified_at} = Repo.get_by!(User, email: email) + assert verified_at != nil end - test "POST /api/create-account verifies the user", %{conn: conn} do - email = "test-verification@example.com" + test "POST /api/create-account retries on rate limit", %{conn: conn} do + expect(HTTPoisonMock, :post, 1, fn _, _, _ -> limit_bigcommerce_response() end) + expect(HTTPoisonMock, :post, 1, fn _, _, _ -> ok_bigcommerce_response() end) + + email = "test-limit@example.com" conn = post(conn, "/api/create-account", %{ @@ -33,8 +77,6 @@ defmodule RecognizerWeb.Api.UserRegistrationControllerTest do }) assert %{"user" => _} = json_response(conn, 201) - assert %User{verified_at: verified_at} = Repo.get_by!(User, email: email) - assert verified_at != nil end test "POST /api/create-account fails for regular users", %{conn: conn} do diff --git a/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs b/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs index 231e0ef..3665d9c 100644 --- a/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs +++ b/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs @@ -1,8 +1,27 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do use RecognizerWeb.ConnCase + import Mox import Recognizer.AccountFactory + alias Recognizer.Accounts.BCCustomerUser + alias Recognizer.Accounts.User + alias Recognizer.Repo + + setup :verify_on_exit! + + defp ok_bigcommerce_response() do + body = Jason.encode!(%{data: [%{id: 1001}]}) + + {:ok, %HTTPoison.Response{body: body, status_code: 200}} + end + + defp bad_bigcommerce_response() do + body = Jason.encode!(%{errors: [%{failure: 1}]}) + + {:ok, %HTTPoison.Response{body: body, status_code: 400}} + end + describe "GET /users/register" do test "renders registration page", %{conn: conn} do conn = get(conn, Routes.user_registration_path(conn, :new)) @@ -20,6 +39,8 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do describe "POST /users/register" do @tag :capture_log test "creates account and prompts for verification", %{conn: conn} do + expect(HTTPoisonMock, :post, 1, fn _, _, _ -> ok_bigcommerce_response() end) + conn = post(conn, Routes.user_registration_path(conn, :create), %{ "user" => params_for(:user) @@ -27,6 +48,7 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do refute Recognizer.Guardian.Plug.current_resource(conn) assert redirected_to(conn) =~ "/prompt/verification" + assert Repo.get_by(BCCustomerUser, bc_id: 1001) end test "render errors for invalid data", %{conn: conn} do @@ -42,7 +64,24 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do assert response =~ "must not contain special characters" end + @tag :capture_log + test "renders an error page for a bigcommerce failure", %{conn: conn} do + expect(HTTPoisonMock, :post, 1, fn _, _, _ -> bad_bigcommerce_response() end) + + conn = + post(conn, Routes.user_registration_path(conn, :create), %{ + "user" => params_for(:user) + }) + + assert Repo.all(User) == [] + refute Recognizer.Guardian.Plug.current_resource(conn) + response = html_response(conn, 500) + assert response =~ "Something has gone horribly wrong" + end + test "rate limits account creation", %{conn: conn} do + stub(HTTPoisonMock, :post, fn _, _, _ -> ok_bigcommerce_response() end) + Enum.each(0..20, fn _ -> post(conn, Routes.user_registration_path(conn, :create), %{ "user" => params_for(:user) diff --git a/test/support/mocks.ex b/test/support/mocks.ex new file mode 100644 index 0000000..5f2341e --- /dev/null +++ b/test/support/mocks.ex @@ -0,0 +1 @@ +Mox.defmock(HTTPoisonMock, for: HTTPoison.Base)