primitive sign in

This commit is contained in:
byte[] 2019-10-31 13:57:39 -04:00
parent 77235013bf
commit 39470dc1ad
21 changed files with 234 additions and 101 deletions

3
.gitignore vendored
View file

@ -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
View file

@ -0,0 +1,2 @@
alias Philomena.{Repo, Users.User}
import Ecto.Query

View file

@ -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,

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View 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

View file

@ -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

View file

@ -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

View 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 %>

View 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>

View 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>

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Pow.RegistrationView do
use PhilomenaWeb, :view
end

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Pow.SessionView do
use PhilomenaWeb, :view
end

View 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

View 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

View file

@ -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

View file

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