mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 21:47:59 +01:00
primitive sign in
This commit is contained in:
parent
77235013bf
commit
39470dc1ad
21 changed files with 234 additions and 101 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -35,3 +35,6 @@ npm-debug.log
|
||||||
# we ignore priv/static. You may want to comment
|
# we ignore priv/static. You may want to comment
|
||||||
# this depending on your deployment strategy.
|
# this depending on your deployment strategy.
|
||||||
/priv/static/
|
/priv/static/
|
||||||
|
|
||||||
|
# Mnesia
|
||||||
|
/Mnesia*
|
2
.iex.exs
Normal file
2
.iex.exs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
alias Philomena.{Repo, Users.User}
|
||||||
|
import Ecto.Query
|
|
@ -16,7 +16,8 @@ config :philomena,
|
||||||
config :philomena, :pow,
|
config :philomena, :pow,
|
||||||
user: Philomena.Users.User,
|
user: Philomena.Users.User,
|
||||||
repo: Philomena.Repo,
|
repo: Philomena.Repo,
|
||||||
extensions: [PhilomenaWeb.HaltTotp],
|
web_module: PhilomenaWeb,
|
||||||
|
extensions: [PowResetPassword, PowPersistentSession, PowMultiFactor],
|
||||||
controller_callbacks: Pow.Extension.Phoenix.ControllerCallbacks
|
controller_callbacks: Pow.Extension.Phoenix.ControllerCallbacks
|
||||||
|
|
||||||
config :bcrypt_elixir,
|
config :bcrypt_elixir,
|
||||||
|
|
|
@ -11,9 +11,10 @@ defmodule Philomena.Application do
|
||||||
# Start the Ecto repository
|
# Start the Ecto repository
|
||||||
Philomena.Repo,
|
Philomena.Repo,
|
||||||
# Start the endpoint when the application starts
|
# Start the endpoint when the application starts
|
||||||
PhilomenaWeb.Endpoint
|
PhilomenaWeb.Endpoint,
|
||||||
# Starts a worker by calling: Philomena.Worker.start_link(arg)
|
# Starts a worker by calling: Philomena.Worker.start_link(arg)
|
||||||
# {Philomena.Worker, arg},
|
# {Philomena.Worker, arg},
|
||||||
|
Pow.Store.Backend.MnesiaCache
|
||||||
]
|
]
|
||||||
|
|
||||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||||
|
|
|
@ -53,12 +53,12 @@ defmodule Philomena.Images.Query do
|
||||||
tag_include = %{terms: %{tag_ids: user.watched_tag_ids}}
|
tag_include = %{terms: %{tag_ids: user.watched_tag_ids}}
|
||||||
|
|
||||||
{:ok, include_query} =
|
{:ok, include_query} =
|
||||||
Philomena.Images.Query.user_parser(ctx, user.watched_images_query |> normalize())
|
Philomena.Images.Query.user_parser(ctx, user.watched_images_query_str |> normalize())
|
||||||
|
|
||||||
{:ok, exclude_query} =
|
{:ok, exclude_query} =
|
||||||
Philomena.Images.Query.user_parser(
|
Philomena.Images.Query.user_parser(
|
||||||
ctx,
|
ctx,
|
||||||
user.watched_images_exclude_query |> normalize()
|
user.watched_images_exclude_str |> normalize()
|
||||||
)
|
)
|
||||||
|
|
||||||
should = [tag_include, include_query]
|
should = [tag_include, include_query]
|
||||||
|
@ -125,12 +125,12 @@ defmodule Philomena.Images.Query do
|
||||||
tag_include = %{terms: %{tag_ids: user.watched_tag_ids}}
|
tag_include = %{terms: %{tag_ids: user.watched_tag_ids}}
|
||||||
|
|
||||||
{:ok, include_query} =
|
{:ok, include_query} =
|
||||||
Philomena.Images.Query.moderator_parser(ctx, user.watched_images_query |> normalize())
|
Philomena.Images.Query.moderator_parser(ctx, user.watched_images_query_str |> normalize())
|
||||||
|
|
||||||
{:ok, exclude_query} =
|
{:ok, exclude_query} =
|
||||||
Philomena.Images.Query.moderator_parser(
|
Philomena.Images.Query.moderator_parser(
|
||||||
ctx,
|
ctx,
|
||||||
user.watched_images_exclude_query |> normalize()
|
user.watched_images_exclude_str |> normalize()
|
||||||
)
|
)
|
||||||
|
|
||||||
should = [tag_include, include_query]
|
should = [tag_include, include_query]
|
||||||
|
|
|
@ -6,6 +6,9 @@ defmodule Philomena.Users.User do
|
||||||
use Pow.Ecto.Schema,
|
use Pow.Ecto.Schema,
|
||||||
password_hash_methods: {&Password.hash_pwd_salt/1, &Password.verify_pass/2}
|
password_hash_methods: {&Password.hash_pwd_salt/1, &Password.verify_pass/2}
|
||||||
|
|
||||||
|
use Pow.Extension.Ecto.Schema,
|
||||||
|
extensions: [PowResetPassword]
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
schema "users" do
|
schema "users" do
|
||||||
|
@ -100,6 +103,7 @@ defmodule Philomena.Users.User do
|
||||||
def changeset(user, attrs) do
|
def changeset(user, attrs) do
|
||||||
user
|
user
|
||||||
|> pow_changeset(attrs)
|
|> pow_changeset(attrs)
|
||||||
|
|> pow_extension_changeset(attrs)
|
||||||
|> cast(attrs, [])
|
|> cast(attrs, [])
|
||||||
|> validate_required([])
|
|> validate_required([])
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,7 +43,10 @@ defmodule PhilomenaWeb.Endpoint do
|
||||||
signing_salt: "signed cookie",
|
signing_salt: "signed cookie",
|
||||||
encryption_salt: "authenticated encrypted cookie"
|
encryption_salt: "authenticated encrypted cookie"
|
||||||
|
|
||||||
plug PhilomenaWeb.Plugs.Session, otp_app: :philomena
|
plug Pow.Plug.Session, otp_app: :philomena
|
||||||
|
plug PowPersistentSession.Plug.Cookie, otp_app: :philomena
|
||||||
|
|
||||||
|
plug PhilomenaWeb.Plugs.ReloadUser
|
||||||
plug PhilomenaWeb.Plugs.RenderTime
|
plug PhilomenaWeb.Plugs.RenderTime
|
||||||
plug PhilomenaWeb.Plugs.CurrentFilter
|
plug PhilomenaWeb.Plugs.CurrentFilter
|
||||||
plug PhilomenaWeb.Router
|
plug PhilomenaWeb.Router
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
defmodule PhilomenaWeb.HaltTotp.Phoenix.ControllerCallbacks do
|
|
||||||
use Pow.Extension.Phoenix.ControllerCallbacks.Base
|
|
||||||
alias Pow.Plug
|
|
||||||
import Phoenix.Controller
|
|
||||||
|
|
||||||
def before_respond(Pow.Phoenix.SessionController, :create, {:ok, conn}, _config) do
|
|
||||||
conn
|
|
||||||
|> Plug.current_user()
|
|
||||||
|> halt_totp(conn)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp halt_totp(%{otp_required_for_login: true}, conn) do
|
|
||||||
{:ok, conn} = Plug.clear_authenticated_user(conn)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> put_flash(:error, "Cannot yet authenticate accounts with TOTP enabled")
|
|
||||||
|> redirect(to: "/")
|
|
||||||
|
|
||||||
{:halt, conn}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp halt_totp(_, conn) do
|
|
||||||
{:ok, conn}
|
|
||||||
end
|
|
||||||
|
|
||||||
def before_process(Pow.Phoenix.RegistrationController, _method, conn, _config) do
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> put_flash(:error, "Registrations are disabled")
|
|
||||||
|> redirect(to: "/")
|
|
||||||
|
|
||||||
{:halt, conn}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -15,7 +15,7 @@ defmodule PhilomenaWeb.Plugs.CurrentFilter do
|
||||||
|
|
||||||
filter =
|
filter =
|
||||||
if user do
|
if user do
|
||||||
user = user |> preload(:current_filter)
|
user = user |> Repo.preload(:current_filter)
|
||||||
user.current_filter
|
user.current_filter
|
||||||
else
|
else
|
||||||
filter_id = conn |> get_session(:filter_id)
|
filter_id = conn |> get_session(:filter_id)
|
||||||
|
|
21
lib/philomena_web/plugs/reload_user.ex
Normal file
21
lib/philomena_web/plugs/reload_user.ex
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule PhilomenaWeb.Plugs.ReloadUser do
|
||||||
|
alias Pow.Plug
|
||||||
|
alias Philomena.Users.User
|
||||||
|
alias Philomena.Repo
|
||||||
|
|
||||||
|
def init(opts), do: opts
|
||||||
|
|
||||||
|
def call(conn, _opts) do
|
||||||
|
config = Plug.fetch_config(conn)
|
||||||
|
|
||||||
|
case Plug.current_user(conn, config) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|
||||||
|
user ->
|
||||||
|
reloaded_user = Repo.get!(User, user.id)
|
||||||
|
|
||||||
|
Plug.assign_current_user(conn, reloaded_user, config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,52 +0,0 @@
|
||||||
defmodule PhilomenaWeb.Plugs.Session do
|
|
||||||
use Pow.Plug.Base
|
|
||||||
|
|
||||||
alias Plug.Conn
|
|
||||||
alias Philomena.{Repo, Users.User}
|
|
||||||
|
|
||||||
@session_key :philomena_session
|
|
||||||
|
|
||||||
def fetch(conn, _config) do
|
|
||||||
conn = Conn.fetch_session(conn)
|
|
||||||
user = Conn.get_session(conn, @session_key)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> maybe_load_user(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, user, _config) do
|
|
||||||
value = session_value(user)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> Conn.fetch_session()
|
|
||||||
|> Conn.put_session(@session_key, value)
|
|
||||||
|
|
||||||
{conn, user}
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, _config) do
|
|
||||||
conn
|
|
||||||
|> Conn.fetch_session()
|
|
||||||
|> Conn.delete_session(@session_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_load_user(conn, {:ok, user}) do
|
|
||||||
with {:ok, [user_id, hash]} <- Jason.decode(user),
|
|
||||||
%User{} = user <- Repo.get(User, user_id),
|
|
||||||
true <- SecureCompare.compare(hash, binary_part(user.encrypted_password, 0, 25)) do
|
|
||||||
{conn, user}
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
{conn, nil}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_load_user(conn, _) do
|
|
||||||
{conn, nil}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp session_value(user) do
|
|
||||||
Jason.encode([user.id, binary_part(user.encrypted_password, 0, 25)])
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -16,11 +16,11 @@ defmodule PhilomenaWeb.Router do
|
||||||
plug :accepts, ["json"]
|
plug :accepts, ["json"]
|
||||||
end
|
end
|
||||||
|
|
||||||
#scope "/" do
|
scope "/" do
|
||||||
# pipe_through :browser
|
pipe_through :browser
|
||||||
#
|
|
||||||
# pow_routes()
|
pow_routes()
|
||||||
#end
|
end
|
||||||
|
|
||||||
scope "/", PhilomenaWeb do
|
scope "/", PhilomenaWeb do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
30
lib/philomena_web/templates/pow/registration/edit.html.eex
Normal file
30
lib/philomena_web/templates/pow/registration/edit.html.eex
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<h1>Edit profile</h1>
|
||||||
|
|
||||||
|
<%= form_for @changeset, @action, [as: :user], fn f -> %>
|
||||||
|
<%= if @changeset.action do %>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<p>Oops, something went wrong! Please check the errors below.</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= label f, :current_password %>
|
||||||
|
<%= password_input f, :current_password %>
|
||||||
|
<%= error_tag f, :current_password %>
|
||||||
|
|
||||||
|
<%= label f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
<%= text_input f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
<%= error_tag f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
|
||||||
|
<%= label f, :password %>
|
||||||
|
<%= password_input f, :password %>
|
||||||
|
<%= error_tag f, :password %>
|
||||||
|
|
||||||
|
<%= label f, :confirm_password %>
|
||||||
|
<%= password_input f, :confirm_password %>
|
||||||
|
<%= error_tag f, :confirm_password %>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%= submit "Update" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
28
lib/philomena_web/templates/pow/registration/new.html.eex
Normal file
28
lib/philomena_web/templates/pow/registration/new.html.eex
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<h1>Register</h1>
|
||||||
|
|
||||||
|
<%= form_for @changeset, @action, [as: :user], fn f -> %>
|
||||||
|
<%= if @changeset.action do %>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<p>Oops, something went wrong! Please check the errors below.</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= label f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
<%= text_input f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
<%= error_tag f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
|
||||||
|
<%= label f, :password %>
|
||||||
|
<%= password_input f, :password %>
|
||||||
|
<%= error_tag f, :password %>
|
||||||
|
|
||||||
|
<%= label f, :confirm_password %>
|
||||||
|
<%= password_input f, :confirm_password %>
|
||||||
|
<%= error_tag f, :confirm_password %>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%= submit "Register" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
|
||||||
|
<span><%= link "Sign in", to: Routes.pow_session_path(@conn, :new) %></span>
|
24
lib/philomena_web/templates/pow/session/new.html.eex
Normal file
24
lib/philomena_web/templates/pow/session/new.html.eex
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<h1>Sign in</h1>
|
||||||
|
|
||||||
|
<%= form_for @changeset, @action, [as: :user], fn f -> %>
|
||||||
|
<%= if @changeset.action do %>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<p>Oops, something went wrong! Please check the errors below.</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= label f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
<%= text_input f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
<%= error_tag f, Pow.Ecto.Schema.user_id_field(@changeset) %>
|
||||||
|
|
||||||
|
<%= label f, :password %>
|
||||||
|
<%= password_input f, :password %>
|
||||||
|
<%= error_tag f, :password %>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%= submit "Sign in" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
|
||||||
|
<span><%= link "Register", to: Routes.pow_registration_path(@conn, :new) %></span>
|
3
lib/philomena_web/views/pow/registration_view.ex
Normal file
3
lib/philomena_web/views/pow/registration_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule PhilomenaWeb.Pow.RegistrationView do
|
||||||
|
use PhilomenaWeb, :view
|
||||||
|
end
|
3
lib/philomena_web/views/pow/session_view.ex
Normal file
3
lib/philomena_web/views/pow/session_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule PhilomenaWeb.Pow.SessionView do
|
||||||
|
use PhilomenaWeb, :view
|
||||||
|
end
|
69
lib/pow_multi_factor/phoenix/controller_callbacks.ex
Normal file
69
lib/pow_multi_factor/phoenix/controller_callbacks.ex
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
defmodule PowMultiFactor.Phoenix.ControllerCallbacks do
|
||||||
|
@moduledoc """
|
||||||
|
Controller callback logic for multi-factor authentication.
|
||||||
|
|
||||||
|
### 2FA code not submitted
|
||||||
|
|
||||||
|
Triggers on `Pow.Phoenix.SessionController.create/2`.
|
||||||
|
|
||||||
|
When a user with 2FA enabled attempts to sign in without submitting their
|
||||||
|
TOTP token, the session will be cleared, and the user redirected back to
|
||||||
|
`Pow.Phoenix.Routes.session_path/1`.
|
||||||
|
|
||||||
|
### User updates account
|
||||||
|
|
||||||
|
Triggers on `Pow.Phoenix.RegistrationController.update/2`
|
||||||
|
|
||||||
|
When a user changes their account settings, they are required to confirm a
|
||||||
|
current 2FA token.
|
||||||
|
|
||||||
|
See `PowMultiFactor.Ecto.Schema` for more.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Pow.Extension.Phoenix.ControllerCallbacks.Base
|
||||||
|
|
||||||
|
alias Pow.Plug
|
||||||
|
alias PowMultiFactor.Plug, as: PowMultiFactorPlug
|
||||||
|
|
||||||
|
def before_respond(Pow.Phoenix.SessionController, :create, {:ok, conn}, _config) do
|
||||||
|
return_path = routes(conn).session_path(conn, :new)
|
||||||
|
|
||||||
|
clear_unauthorized(conn, {:ok, conn}, return_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def before_respond(Pow.Phoenix.RegistrationController, :update, {:ok, user, conn}, _config) do
|
||||||
|
return_path = routes(conn).registration_path(conn, :edit)
|
||||||
|
|
||||||
|
halt_unauthorized(conn, {:ok, user, conn}, return_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp clear_unauthorized(conn, success_response, return_path) do
|
||||||
|
case PowMultiFactorPlug.mfa_unauthorized?(conn) do
|
||||||
|
true -> clear_auth(conn) |> go_back(return_path)
|
||||||
|
false -> success_response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp halt_unauthorized(conn, success_response, return_path) do
|
||||||
|
case PowMultiFactorPlug.mfa_unauthorized?(conn) do
|
||||||
|
true -> go_back(conn, return_path)
|
||||||
|
false -> success_response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_auth(conn) do
|
||||||
|
{:ok, conn} = Plug.clear_authenticated_user(conn)
|
||||||
|
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
|
||||||
|
defp go_back(conn, return_path) do
|
||||||
|
error = extension_messages(conn).invalid_multi_factor(conn)
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> Phoenix.Controller.put_flash(:error, error)
|
||||||
|
|> Phoenix.Controller.redirect(to: return_path)
|
||||||
|
|
||||||
|
{:halt, conn}
|
||||||
|
end
|
||||||
|
end
|
26
lib/pow_multi_factor/plug.ex
Normal file
26
lib/pow_multi_factor/plug.ex
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
defmodule PowMultiFactor.Plug do
|
||||||
|
@moduledoc """
|
||||||
|
Plug helper methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pow.Plug
|
||||||
|
#alias PowMultiFactor.Ecto.Context
|
||||||
|
|
||||||
|
def mfa_unauthorized?(conn) do
|
||||||
|
user = Plug.current_user(conn)
|
||||||
|
|
||||||
|
if user.otp_required_for_login do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#defp otp_secret(user) do
|
||||||
|
|
||||||
|
#end
|
||||||
|
|
||||||
|
#defp otp_shared_key do
|
||||||
|
# Application.get_env
|
||||||
|
#end
|
||||||
|
end
|
3
mix.exs
3
mix.exs
|
@ -52,7 +52,8 @@ defmodule Philomena.MixProject do
|
||||||
{:elastix, "~> 0.7.1"},
|
{:elastix, "~> 0.7.1"},
|
||||||
{:nimble_parsec, "~> 0.5.1"},
|
{:nimble_parsec, "~> 0.5.1"},
|
||||||
{:canary, "~> 1.1.1"},
|
{:canary, "~> 1.1.1"},
|
||||||
{:scrivener_ecto, "~> 2.0"}
|
{:scrivener_ecto, "~> 2.0"},
|
||||||
|
{:elixir2fa, "~> 0.1.0"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
1
mix.lock
1
mix.lock
|
@ -13,6 +13,7 @@
|
||||||
"ecto_network": {:hex, :ecto_network, "1.1.0", "7062004b9324ff13e50c02dab84877f8a55e06db9eabbf2d04bda21da6fc6e8a", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm"},
|
"ecto_network": {:hex, :ecto_network, "1.1.0", "7062004b9324ff13e50c02dab84877f8a55e06db9eabbf2d04bda21da6fc6e8a", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.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"},
|
"ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.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"},
|
||||||
"elastix": {:hex, :elastix, "0.7.1", "8e199a764a0bc018e0a97afeea950a8069b988867d87f8d25ae121d8b3288612", [:mix], [{:httpoison, "~> 1.4", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:retry, "~> 0.8", [hex: :retry, repo: "hexpm", optional: false]}], "hexpm"},
|
"elastix": {:hex, :elastix, "0.7.1", "8e199a764a0bc018e0a97afeea950a8069b988867d87f8d25ae121d8b3288612", [:mix], [{:httpoison, "~> 1.4", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:retry, "~> 0.8", [hex: :retry, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"elixir2fa": {:hex, :elixir2fa, "0.1.0", "4585154695ad13a01c17c46e61b0884b0ce1569ebcc667d1d09cd1cbbb4f4ba8", [:mix], [], "hexpm"},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"},
|
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"},
|
||||||
"file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"},
|
"file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"},
|
||||||
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm"},
|
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm"},
|
||||||
|
|
Loading…
Reference in a new issue