Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
5 changes: 4 additions & 1 deletion coveralls.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
},

"skip_files": [
"test"
"test",
"lib/phoaw/application.ex",
"lib/phoaw_web.ex",
"lib/phoaw_web/views/error_helpers.ex"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why skip this files?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use this file yet, so we didn't have test for this file

]
}
22 changes: 22 additions & 0 deletions lib/phoaw/auth/auth.ex
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions lib/phoaw/auth/error_handler.ex
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions lib/phoaw/auth/guardian.ex
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions lib/phoaw/auth/pipeline.ex
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions lib/phoaw/contents/contents.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
37 changes: 37 additions & 0 deletions lib/phoaw_web/controllers/session_controller.ex
Original file line number Diff line number Diff line change
@@ -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
16 changes: 15 additions & 1 deletion lib/phoaw_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions lib/phoaw_web/templates/layout/app.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<nav role="navigation">
<ul>
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
<%= if Guardian.Plug.authenticated?(@conn, []) do %>
<%= link "Logout", to: Routes.session_path(@conn, :logout), method: :post %>
<% end %>
</ul>
</nav>
<a href="http://phoenixframework.org/" class="phx-logo">
Expand Down
19 changes: 19 additions & 0 deletions lib/phoaw_web/templates/session/form.html.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%= form_for @changeset, @action, 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, :username %>
<%= text_input f, :username %>
<%= error_tag f, :username %>

<%= label f, :password %>
<%= password_input f, :password %>
<%= error_tag f, :password %>

<div>
<%= submit "Save" %>
</div>
<% end %>
3 changes: 3 additions & 0 deletions lib/phoaw_web/templates/session/new.html.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<h1>Login</h1>

<%= render "form.html", Map.put(assigns, :action, Routes.session_path(@conn, :create)) %>
3 changes: 3 additions & 0 deletions lib/phoaw_web/views/session_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule PhoawWeb.SessionView do
use PhoawWeb, :view
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
{:guardian, "~> 1.0"},
]
end

Expand Down
5 changes: 5 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
%{
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"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"},
Expand All @@ -16,9 +18,11 @@
"exjsx": {:hex, :exjsx, "3.2.1", "1bc5bf1e4fd249104178f0885030bcd75a4526f4d2a1e976f4b428d347614f0f", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"},
"guardian": {:hex, :guardian, "1.1.1", "be14c4007eaf05268251ae114030cb7237ed9a9631c260022f020164ff4ed733", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
Expand All @@ -32,6 +36,7 @@
"plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.0.0", "ab0c92728f2ba43c544cce85f0f220d8d30fc0c90eaa1e6203683ab039655062", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.14.0", "f3d6ffea1ca8a156e0633900a5338a3d17b00435227726baed8982718232b694", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ranch": {:hex, :ranch, "1.6.2", "6db93c78f411ee033dbb18ba8234c5574883acb9a75af0fb90a9b82ea46afa00", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
Expand Down
17 changes: 17 additions & 0 deletions test/phoaw_web/controllers/post_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ defmodule PhoawWeb.PostControllerTest do

alias Phoaw.Contents

@user_attrs %{
email: "test@example.com",
password: "test1234",
password_confirmation: "test1234",
username: "example"
}
@login_attrs %{
username: "example",
password: "test1234"
}
@create_attrs %{body: "some body", title: "some title"}
@update_attrs %{body: "some updated body", title: "some updated title"}
@invalid_attrs %{body: nil, title: nil}
Expand All @@ -12,6 +22,13 @@ defmodule PhoawWeb.PostControllerTest do
post
end

setup do
{:ok, user} = Contents.create_user(@user_attrs)
conn = build_conn()
conn = post(conn, Routes.session_path(conn, :create), user: @login_attrs)
{:ok, conn: conn, user: user}
end

describe "index" do
test "lists all posts", %{conn: conn} do
conn = get(conn, Routes.post_path(conn, :index))
Expand Down
55 changes: 55 additions & 0 deletions test/phoaw_web/controllers/session_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
defmodule PhoawWeb.SessionControllerTest do
use PhoawWeb.ConnCase

alias Phoaw.Contents

@create_attrs %{
email: "test@example.com",
password: "test1234",
password_confirmation: "test1234",
username: "example"
}
@login_attrs %{
username: "example",
password: "test1234"
}
@invalid_attrs %{username: "", password: ""}

setup do
{:ok, user} = Contents.create_user(@create_attrs)
{:ok, conn: build_conn(), user: user}
end

describe "new session" do
test "render form login", %{conn: conn} do
conn = get(conn, Routes.session_path(conn, :new))
assert html_response(conn, 200) =~ "Login"
end
end

describe "create session login" do
test "redirects to user list when login successful", %{conn: conn} do
conn = post(conn, Routes.session_path(conn, :create), user: @login_attrs)
assert redirected_to(conn) == Routes.user_path(conn, :index)

conn = get(conn, Routes.user_path(conn, :index))
assert html_response(conn, 200) =~ "Listing Users"
end

test "renders errors when login failed", %{conn: conn} do
conn = post(conn, Routes.session_path(conn, :create), user: @invalid_attrs)
assert redirected_to(conn) == Routes.session_path(conn, :new)
end
end

describe "Log out user" do
test "redirects to home page when logout successful", %{conn: conn} do
conn = post(conn, Routes.session_path(conn, :create), user: @login_attrs)
assert redirected_to(conn) == Routes.user_path(conn, :index)

conn = post(conn, Routes.session_path(conn, :logout))
assert conn.status == 302
assert redirected_to(conn) == "/"
end
end
end
17 changes: 17 additions & 0 deletions test/phoaw_web/controllers/user_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ defmodule PhoawWeb.UserControllerTest do
password_confirmation: "test1234",
username: "example"
}
@login_attrs %{
username: "example",
password: "test1234"
}
@update_attrs %{
email: "some updated email",
password: "update1234",
Expand All @@ -22,11 +26,24 @@ defmodule PhoawWeb.UserControllerTest do
user
end

setup do
{:ok, user} = Contents.create_user(@create_attrs)
conn = build_conn()
conn = post(conn, Routes.session_path(conn, :create), user: @login_attrs)
{:ok, conn: conn, user: user}
end

describe "index" do
test "lists all users", %{conn: conn} do
conn = get(conn, Routes.user_path(conn, :index))
assert html_response(conn, 200) =~ "Listing Users"
end

test "unauthenticated user", %{conn: conn} do
conn = post(conn, Routes.session_path(conn, :logout))
conn = get(conn, Routes.user_path(conn, :index))
assert html_response(conn, 401) =~ "unauthenticated"
end
end

describe "new user" do
Expand Down