diff --git a/lib/chess/auth/auth.ex b/lib/chess/auth.ex
similarity index 100%
rename from lib/chess/auth/auth.ex
rename to lib/chess/auth.ex
diff --git a/lib/chess/auth/error_handler.ex b/lib/chess/auth/error_handler.ex
new file mode 100644
index 0000000..988a932
--- /dev/null
+++ b/lib/chess/auth/error_handler.ex
@@ -0,0 +1,12 @@
+defmodule Chess.Auth.ErrorHandler do
+ @moduledoc false
+
+ 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/chess/auth/pipeline.ex b/lib/chess/auth/pipeline.ex
new file mode 100644
index 0000000..16c013d
--- /dev/null
+++ b/lib/chess/auth/pipeline.ex
@@ -0,0 +1,17 @@
+defmodule Chess.Auth.Pipeline do
+ @moduledoc false
+
+ use Guardian.Plug.Pipeline,
+ otp_app: :chess,
+ error_handler: Chess.Auth.ErrorHandler,
+ module: Chess.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/chess/auth/user.ex b/lib/chess/auth/user.ex
index 2624306..7927536 100644
--- a/lib/chess/auth/user.ex
+++ b/lib/chess/auth/user.ex
@@ -14,6 +14,11 @@ defmodule Chess.Auth.User do
timestamps()
end
+ def changeset(user) do
+ user
+ |> cast(%{}, [:username, :password])
+ end
+
@doc false
def changeset(%User{} = user, attrs) do
user
diff --git a/lib/chess_web.ex b/lib/chess_web.ex
index d1c2e38..a8a2ced 100644
--- a/lib/chess_web.ex
+++ b/lib/chess_web.ex
@@ -40,6 +40,8 @@ defmodule ChessWeb do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
+ import Formulator
+
import ChessWeb.Router.Helpers
import ChessWeb.ErrorHelpers
import ChessWeb.Gettext
diff --git a/lib/chess_web/controllers/session_controller.ex b/lib/chess_web/controllers/session_controller.ex
new file mode 100644
index 0000000..27f4b38
--- /dev/null
+++ b/lib/chess_web/controllers/session_controller.ex
@@ -0,0 +1,28 @@
+defmodule ChessWeb.SessionController do
+ use ChessWeb, :controller
+
+ alias Chess.Auth
+ alias Chess.Auth.User
+
+ def new(conn, _params) do
+ changeset = User.changeset(%User{})
+ render(conn, "new.html", changeset: changeset)
+ end
+
+ def create(
+ conn,
+ %{"user" => %{"username" => username, "password" => password}}
+ ) do
+ case Auth.authenticate_user(username, password) do
+ {:ok, _user} ->
+ conn
+ |> put_flash(:info, "You are signed in")
+ |> redirect(to: game_path(conn, :index))
+ {:error, _error} ->
+ changeset = User.changeset(%User{})
+ conn
+ |> put_flash(:error, "Bad username or password")
+ |> render("new.html", changeset: changeset)
+ end
+ end
+end
diff --git a/lib/chess_web/router.ex b/lib/chess_web/router.ex
index c7769f4..ce0ba67 100644
--- a/lib/chess_web/router.ex
+++ b/lib/chess_web/router.ex
@@ -17,7 +17,8 @@ defmodule ChessWeb.Router do
pipe_through :browser # Use the default browser stack
get "/", GameController, :index
- resources "/games", GameController, only: [:create, :show, :delete]
+ resources "/games", GameController, only: [:index, :create, :show, :delete]
+ resources "/session", SessionController, only: [:new, :create], singleton: true
end
# Other scopes may use custom stacks.
diff --git a/lib/chess_web/templates/page/index.html.eex b/lib/chess_web/templates/page/index.html.eex
deleted file mode 100644
index 81d83e2..0000000
--- a/lib/chess_web/templates/page/index.html.eex
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/lib/chess_web/templates/session/new.html.eex b/lib/chess_web/templates/session/new.html.eex
new file mode 100644
index 0000000..235fed1
--- /dev/null
+++ b/lib/chess_web/templates/session/new.html.eex
@@ -0,0 +1,21 @@
+Sign in
+
+<%= form_for @changeset, session_path(@conn, :create), [class: "create-session"], fn f -> %>
+ <%= if @changeset.action do %>
+
+
Oops, something went wrong! Please check the errors below.
+
+ <% end %>
+
+
+
+ <%= submit "Sign in", class: "btn btn-primary" %>
+
+<% end %>
diff --git a/lib/chess_web/views/page_view.ex b/lib/chess_web/views/page_view.ex
deleted file mode 100644
index d2b928b..0000000
--- a/lib/chess_web/views/page_view.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule ChessWeb.PageView do
- use ChessWeb, :view
-end
diff --git a/lib/chess_web/views/session_view.ex b/lib/chess_web/views/session_view.ex
new file mode 100644
index 0000000..83ed1ce
--- /dev/null
+++ b/lib/chess_web/views/session_view.ex
@@ -0,0 +1,3 @@
+defmodule ChessWeb.SessionView do
+ use ChessWeb, :view
+end
diff --git a/mix.exs b/mix.exs
index a4ce3b4..9d1a4fa 100644
--- a/mix.exs
+++ b/mix.exs
@@ -33,6 +33,7 @@ defmodule Chess.Mixfile do
[{:argon2_elixir, "~> 1.2"},
{:comeonin, "~> 4.0"},
{:credo, "~> 0.8", only: [:dev, :test]},
+ {:formulator, "~> 0.1.6"},
{:phoenix, "~> 1.3.0"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_ecto, "~> 3.0"},
diff --git a/mix.lock b/mix.lock
index 2fda73e..5b5ba32 100644
--- a/mix.lock
+++ b/mix.lock
@@ -14,6 +14,7 @@
"elixir_make": {:hex, :elixir_make, "0.4.0", "992f38fabe705bb45821a728f20914c554b276838433349d4f2341f7a687cddf", [:mix], [], "hexpm"},
"file_system": {:hex, :file_system, "0.2.4", "f0bdda195c0e46e987333e986452ec523aed21d784189144f647c43eaf307064", [:mix], [], "hexpm"},
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [], []},
+ "formulator": {:hex, :formulator, "0.1.6", "266caf4c4bce2b7dc5be2da5e55d8c36120b3eaa6a5f82b9a6c1342ddbc18597", [:mix], [{:gettext, ">= 0.11.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.4", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm"},
"gettext": {:hex, :gettext, "0.14.0", "1a019a2e51d5ad3d126efe166dcdf6563768e5d06c32a99ad2281a1fa94b4c72", [:mix], [], "hexpm"},
"guardian": {:hex, :guardian, "1.0.1", "db0fbaf571c3b874785818b7272eaf5f1ed97a2f9b1f8bc5dc8b0fb8f8f7bb06", [: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]}, {:uuid, ">= 1.1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.10.1", "c38d0ca52ea80254936a32c45bb7eb414e7a96a521b4ce76d00a69753b157f21", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.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.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
diff --git a/test/chess/auth/auth_test.exs b/test/chess/auth_test.exs
similarity index 100%
rename from test/chess/auth/auth_test.exs
rename to test/chess/auth_test.exs
diff --git a/test/controllers/game_controller_test.exs b/test/chess_web/controllers/game_controller_test.exs
similarity index 100%
rename from test/controllers/game_controller_test.exs
rename to test/chess_web/controllers/game_controller_test.exs
diff --git a/test/chess_web/controllers/session_controller_test.exs b/test/chess_web/controllers/session_controller_test.exs
new file mode 100644
index 0000000..0222894
--- /dev/null
+++ b/test/chess_web/controllers/session_controller_test.exs
@@ -0,0 +1,8 @@
+defmodule Chess.SessionControllerTest do
+ use ChessWeb.ConnCase
+
+ test "shows sign in form", %{conn: conn} do
+ conn = get conn, session_path(conn, :new)
+ assert html_response(conn, 200) =~ "Sign in"
+ end
+end
diff --git a/test/features/session_test.exs b/test/features/session_test.exs
new file mode 100644
index 0000000..a9cef22
--- /dev/null
+++ b/test/features/session_test.exs
@@ -0,0 +1,39 @@
+defmodule Chess.SessionTest do
+ use ChessWeb.FeatureCase
+
+ alias Chess.Auth.User
+
+ import Wallaby.Query, only: [text_field: 1, button: 1]
+
+ test "user cannot sign in with incorrect password", %{session: session} do
+ create_user()
+
+ session
+ |> visit("/session/new")
+ |> fill_in(text_field("Username"), with: "link@example.com")
+ |> fill_in(text_field("Password"), with: "calamityganon")
+ |> click(button("Sign in"))
+
+ assert session |> has_text?("Bad username or password")
+ end
+
+ test "user can sign in with correct details", %{session: session} do
+ create_user()
+
+ session
+ |> visit("/session/new")
+ |> fill_in(text_field("Username"), with: "link@example.com")
+ |> fill_in(text_field("Password"), with: "ilovezelda")
+ |> click(button("Sign in"))
+
+ assert session |> has_text?("You are signed in")
+ end
+
+ defp create_user() do
+ changeset = User.changeset(
+ %User{},
+ %{username: "link@example.com", password: "ilovezelda"}
+ )
+ Repo.insert!(changeset)
+ end
+end