From f3f512670acf03ffc6fb180b70064e5d6dfd818b Mon Sep 17 00:00:00 2001 From: zuhri Date: Wed, 21 Nov 2018 00:06:04 +0700 Subject: [PATCH 1/8] add bodyguard for authorization --- lib/phoaw/contents/policies/user.ex | 9 +++++++++ lib/phoaw/contents/user.ex | 2 ++ lib/phoaw_web/controllers/user_controller.ex | 8 +++++++- mix.exs | 3 ++- mix.lock | 1 + 5 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 lib/phoaw/contents/policies/user.ex diff --git a/lib/phoaw/contents/policies/user.ex b/lib/phoaw/contents/policies/user.ex new file mode 100644 index 0000000..20d2ad0 --- /dev/null +++ b/lib/phoaw/contents/policies/user.ex @@ -0,0 +1,9 @@ +defmodule Phoaw.Policies.User do + @behaviour Bodyguard.Policy + + alias Phoaw.Contents.User + + # Regular users can modify their own posts + def authorize(action, %User{id: id}, %User{id: id}) + when action in [:edit_user], do: true +end \ No newline at end of file diff --git a/lib/phoaw/contents/user.ex b/lib/phoaw/contents/user.ex index da87f6d..d24a305 100644 --- a/lib/phoaw/contents/user.ex +++ b/lib/phoaw/contents/user.ex @@ -6,6 +6,8 @@ defmodule Phoaw.Contents.User do import Ecto.Changeset import Comeonin.Bcrypt, only: [hashpwsalt: 1] + defdelegate authorize(action, user, params), to: Phoaw.Policies.User + schema "users" do field(:email, :string) field(:password_digest, :string) diff --git a/lib/phoaw_web/controllers/user_controller.ex b/lib/phoaw_web/controllers/user_controller.ex index 6ce3061..bae099a 100644 --- a/lib/phoaw_web/controllers/user_controller.ex +++ b/lib/phoaw_web/controllers/user_controller.ex @@ -34,7 +34,13 @@ defmodule PhoawWeb.UserController do def edit(conn, %{"id" => id}) do user = Contents.get_user!(id) changeset = Contents.change_user(user) - render(conn, "edit.html", user: user, changeset: changeset) + # render(conn, "edit.html", user: user, changeset: changeset) + with :ok <- Bodyguard.permit(User, :edit_user, user, user), + # {:ok, post} <- User.edit_user(post, post_params) + render(conn, "edit.html", user: user, changeset: changeset) + do + redirect(conn, to: Routes.user_path(conn, :show, user)) + end end def update(conn, %{"id" => id, "user" => user_params}) do diff --git a/mix.exs b/mix.exs index 4e0c477..b532d54 100644 --- a/mix.exs +++ b/mix.exs @@ -52,7 +52,8 @@ defmodule Phoaw.MixProject do {:comeonin, "~> 4.0"}, {:bcrypt_elixir, "~> 1.0"}, {:excoveralls, "~> 0.5.7", only: :test}, - {:credo, "~> 0.10.0", only: [:dev, :test], runtime: false}, + {:credo, "~> 0.10.0", only: [:dev, :test], runtime: false}, + {:bodyguard, "~> 2.2"}, ] end diff --git a/mix.lock b/mix.lock index 433af0e..6b3ef4a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "bcrypt_elixir": {:hex, :bcrypt_elixir, "1.1.1", "6b5560e47a02196ce5f0ab3f1d8265db79a23868c137e973b27afef928ed8006", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "bodyguard": {:hex, :bodyguard, "2.2.2", "d5d1ea325b395ed8edcdcedb8632290e92fe9061ee1fe2de86aa00ca918ab96b", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"}, From 72c67afd0daf65b61bc72f46a53234b28f0160e4 Mon Sep 17 00:00:00 2001 From: zuhri Date: Wed, 21 Nov 2018 03:03:41 +0700 Subject: [PATCH 2/8] user authentication use guardian --- config/config.exs | 5 +++ lib/phoaw/auth/auth.ex | 20 +++++++++++ lib/phoaw/auth/error_handler.ex | 9 +++++ lib/phoaw/auth/guardian.ex | 24 +++++++++++++ lib/phoaw/auth/pipeline.ex | 12 +++++++ .../controllers/session_controller.ex | 36 +++++++++++++++++++ lib/phoaw_web/router.ex | 16 +++++++++ lib/phoaw_web/templates/layout/app.html.eex | 1 + lib/phoaw_web/templates/session/form.html.eex | 19 ++++++++++ lib/phoaw_web/templates/session/new.html.eex | 3 ++ lib/phoaw_web/views/session_view.ex | 3 ++ mix.exs | 1 + mix.lock | 4 +++ 13 files changed, 153 insertions(+) create mode 100644 lib/phoaw/auth/auth.ex create mode 100644 lib/phoaw/auth/error_handler.ex create mode 100644 lib/phoaw/auth/guardian.ex create mode 100644 lib/phoaw/auth/pipeline.ex create mode 100644 lib/phoaw_web/controllers/session_controller.ex create mode 100644 lib/phoaw_web/templates/session/form.html.eex create mode 100644 lib/phoaw_web/templates/session/new.html.eex create mode 100644 lib/phoaw_web/views/session_view.ex diff --git a/config/config.exs b/config/config.exs index 3668f5d..c7fb92f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -25,6 +25,11 @@ config :logger, :console, # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason +# Configures Guardian +config :phoaw, Phoaw.Auth.Guardian, + issuer: "phoaw", + secret_key: "2n6mwHyW6EH71cRWyfvgZqzLLBpR18u02uErCOnSLqZmfg86OwZl036QkoU1Ezhl" + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/lib/phoaw/auth/auth.ex b/lib/phoaw/auth/auth.ex new file mode 100644 index 0000000..4fcfdea --- /dev/null +++ b/lib/phoaw/auth/auth.ex @@ -0,0 +1,20 @@ +defmodule Phoaw.Auth.Auth do + import Ecto.Query, warn: false + + alias Phoaw.Repo + alias Comeonin.Bcrypt + alias Phoaw.Contents.User + + def authenticate_user(username, plain_text_password) do + Repo.one(from u in User, where: u.username == ^username) + |> check_password(plain_text_password) + end + + defp check_password(nil, _), do: {:error, "Incorrect username or password"} + defp check_password(user, plain_text_password) do + case Bcrypt.checkpw(plain_text_password, user.password_digest) do + true -> {:ok, user} + false -> {:error, "Incorrect username or password"} + end + end +end diff --git a/lib/phoaw/auth/error_handler.ex b/lib/phoaw/auth/error_handler.ex new file mode 100644 index 0000000..18bcc0b --- /dev/null +++ b/lib/phoaw/auth/error_handler.ex @@ -0,0 +1,9 @@ +defmodule Phoaw.Auth.ErrorHandler do + import Plug.Conn + def auth_error(conn, {type, _reason}, _opts) do + body = to_string(type) + conn + |> put_resp_content_type("text/plain") + |> send_resp(401, body) + end +end diff --git a/lib/phoaw/auth/guardian.ex b/lib/phoaw/auth/guardian.ex new file mode 100644 index 0000000..7243afe --- /dev/null +++ b/lib/phoaw/auth/guardian.ex @@ -0,0 +1,24 @@ +defmodule Phoaw.Auth.Guardian do + @moduledoc false + + use Guardian, otp_app: :phoaw + + alias Phoaw.Contents + + def subject_for_token(user, _claims) do + sub = to_string(user.id) + {:ok, sub} + end + def subject_for_token(_, _) do + {:error, :reason_for_error} + end + + def resource_from_claims(claims) do + id = claims["sub"] + user = Contents.get_user(id) + {:ok, user} + end + def resource_from_claims(_claims) do + {:error, :reason_for_error} + end +end diff --git a/lib/phoaw/auth/pipeline.ex b/lib/phoaw/auth/pipeline.ex new file mode 100644 index 0000000..c0bbadc --- /dev/null +++ b/lib/phoaw/auth/pipeline.ex @@ -0,0 +1,12 @@ +defmodule Phoaw.Auth.Pipeline do + use Guardian.Plug.Pipeline, + otp_app: :auth_ex, + error_handler: Phoaw.Auth.ErrorHandler, + module: Phoaw.Auth.Guardian + # If there is a session token, validate it + plug Guardian.Plug.VerifySession, claims: %{"typ" => "access"} + # If there is an authorization header, validate it + plug Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"} + # Load the user if either of the verifications worked + plug Guardian.Plug.LoadResource, allow_blank: true +end diff --git a/lib/phoaw_web/controllers/session_controller.ex b/lib/phoaw_web/controllers/session_controller.ex new file mode 100644 index 0000000..08f4974 --- /dev/null +++ b/lib/phoaw_web/controllers/session_controller.ex @@ -0,0 +1,36 @@ +defmodule PhoawWeb.SessionController do + use PhoawWeb, :controller + + alias Phoaw.Contents + alias Phoaw.Contents.User + alias Phoaw.Auth.Auth + alias Phoaw.Auth.Guardian + + def new(conn, _params) do + changeset = Contents.change_user(%User{}) + render(conn, "new.html", changeset: changeset) + end + + def create(conn, %{"user" => session_params}) do + Auth.authenticate_user(session_params["username"], session_params["password"]) + |> login_reply(conn) + end + + defp login_reply({:error, error}, conn) do + conn + |> put_flash(:error, error) + |> redirect(to: Routes.session_path(conn, :new)) + end + defp login_reply({:ok, user}, conn) do + conn + |> put_flash(:success, "Welcome back!") + |> Guardian.Plug.sign_in(user) + |> redirect(to: Routes.user_path(conn, :index)) + end + + def logout(conn, _) do + conn + |> Guardian.Plug.sign_out() + |> redirect(to: "/") + end +end diff --git a/lib/phoaw_web/router.ex b/lib/phoaw_web/router.ex index 4caf129..7a3b955 100644 --- a/lib/phoaw_web/router.ex +++ b/lib/phoaw_web/router.ex @@ -9,10 +9,26 @@ defmodule PhoawWeb.Router do plug :put_secure_browser_headers end + pipeline :auth do + plug Phoaw.Auth.Pipeline + end + pipeline :ensure_auth do + plug Guardian.Plug.EnsureAuthenticated + end + scope "/", PhoawWeb do pipe_through :browser get "/", PageController, :index + # resources "/posts", PostController + resources "/sessions", SessionController + # resources "/users", UserController + post "/logout", SessionController, :logout + end + + scope "/", PhoawWeb do + pipe_through [:browser, :auth, :ensure_auth] + resources "/posts", PostController resources "/users", UserController end diff --git a/lib/phoaw_web/templates/layout/app.html.eex b/lib/phoaw_web/templates/layout/app.html.eex index 136473c..ab368d2 100644 --- a/lib/phoaw_web/templates/layout/app.html.eex +++ b/lib/phoaw_web/templates/layout/app.html.eex @@ -13,6 +13,7 @@