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/coveralls.json b/coveralls.json index dcfdb8f..0b15a6e 100644 --- a/coveralls.json +++ b/coveralls.json @@ -20,6 +20,9 @@ }, "skip_files": [ - "test" + "test", + "lib/phoaw/application.ex", + "lib/phoaw_web.ex", + "lib/phoaw_web/views/error_helpers.ex" ] } diff --git a/lib/phoaw/auth/auth.ex b/lib/phoaw/auth/auth.ex new file mode 100644 index 0000000..8f02cab --- /dev/null +++ b/lib/phoaw/auth/auth.ex @@ -0,0 +1,22 @@ +defmodule Phoaw.Auth.Auth do + @moduledoc """ + This module define Guardian authenticate user + """ + + alias Comeonin.Bcrypt + alias Phoaw.Contents + + def authenticate_user(username, plain_text_password) do + user = Contents.get_user_by_username!(username) + user |> 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..f28ad4e --- /dev/null +++ b/lib/phoaw/auth/error_handler.ex @@ -0,0 +1,13 @@ +defmodule Phoaw.Auth.ErrorHandler do + @moduledoc """ + This module handle response for error authentication using Guardian + """ + + import Plug.Conn + def auth_error(conn, {type, _reason}, _opts) do + body = to_string(type) + conn + |> put_resp_content_type("application/html") + |> 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..4378bca --- /dev/null +++ b/lib/phoaw/auth/guardian.ex @@ -0,0 +1,26 @@ +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 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..39f3468 --- /dev/null +++ b/lib/phoaw/auth/pipeline.ex @@ -0,0 +1,16 @@ +defmodule Phoaw.Auth.Pipeline do + @moduledoc """ + This module define pipeline used for Guardian authentication + """ + + use Guardian.Plug.Pipeline, + otp_app: :phoaw, + 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/contents/contents.ex b/lib/phoaw/contents/contents.ex index 997ca7c..464a6b2 100644 --- a/lib/phoaw/contents/contents.ex +++ b/lib/phoaw/contents/contents.ex @@ -133,6 +133,24 @@ defmodule Phoaw.Contents do """ def get_user!(id), do: Repo.get!(User, id) + @doc """ + Gets a single user by username. + + return nil if the User does not exist. + + ## Examples + + iex> get_user_by_username!('example@mail.com') + %User{} + + iex> get_user_by_username!('example@nothing.com') + nil + + """ + def get_user_by_username!(username) do + Repo.one(from u in User, where: u.username == ^username) + end + @doc """ Creates a user. diff --git a/lib/phoaw_web/controllers/session_controller.ex b/lib/phoaw_web/controllers/session_controller.ex new file mode 100644 index 0000000..7e0e244 --- /dev/null +++ b/lib/phoaw_web/controllers/session_controller.ex @@ -0,0 +1,37 @@ +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 + authenticated = Auth.authenticate_user(session_params["username"], session_params["password"]) + authenticated |> 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..ff1db7a 100644 --- a/lib/phoaw_web/router.ex +++ b/lib/phoaw_web/router.ex @@ -9,10 +9,24 @@ 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 + pipe_through [:browser, :auth] get "/", PageController, :index + post "/logout", SessionController, :logout + resources "/sessions", SessionController + 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..436e483 100644 --- a/lib/phoaw_web/templates/layout/app.html.eex +++ b/lib/phoaw_web/templates/layout/app.html.eex @@ -13,6 +13,9 @@