diff --git a/config/config.exs b/config/config.exs index 30cb587..b20f26a 100644 --- a/config/config.exs +++ b/config/config.exs @@ -23,9 +23,10 @@ config :logger, :console, metadata: [:request_id] # Configure authentication provider +# Replace secret_key in prod.secret.exs config :chess, Chess.Auth.Guardian, issuer: "chess", - secret_key: System.get_env("GUARDIAN_SECRET_KEY") + secret_key: "vd2vXkrYTTFKSKmNMoS2/Hk4Fxn8BkyzsVArRkxJazdQ3mr6bI4YgAC6f8ODiWlM" # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/lib/chess/auth/error_handler.ex b/lib/chess/auth/error_handler.ex index 988a932..eb4ccde 100644 --- a/lib/chess/auth/error_handler.ex +++ b/lib/chess/auth/error_handler.ex @@ -3,10 +3,10 @@ defmodule Chess.Auth.ErrorHandler do import Plug.Conn - def auth_error(conn, {type, _reason}, _opts) do - body = to_string(type) + def auth_error(conn, {_type, _reason}, _opts) do conn - |> put_resp_content_type("text/plain") - |> send_resp(401, body) + |> Phoenix.Controller.put_flash(:info, "You must be logged in") + |> Phoenix.Controller.redirect(to: "/") + |> halt() end end diff --git a/lib/chess/auth/guardian.ex b/lib/chess/auth/guardian.ex index db981ad..7af60b8 100644 --- a/lib/chess/auth/guardian.ex +++ b/lib/chess/auth/guardian.ex @@ -1,5 +1,5 @@ defmodule Chess.Auth.Guardian do - use Guardian, otp_app: :auth_ex + use Guardian, otp_app: :chess alias Chess.Auth @@ -10,8 +10,7 @@ defmodule Chess.Auth.Guardian do def resource_from_claims(claims) do user = claims["sub"] |> Auth.get_user! - {:ok, user} - # If something goes wrong here return {:error, reason} + {:ok, user} end end diff --git a/lib/chess_web.ex b/lib/chess_web.ex index a8a2ced..01acc79 100644 --- a/lib/chess_web.ex +++ b/lib/chess_web.ex @@ -35,7 +35,8 @@ defmodule ChessWeb do namespace: ChessWeb # Import convenience functions from controllers - import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] + import Phoenix.Controller, + only: [get_csrf_token: 0, get_flash: 2, view_module: 1] # Use all HTML functionality (forms, tags, etc) use Phoenix.HTML diff --git a/lib/chess_web/controllers/session_controller.ex b/lib/chess_web/controllers/session_controller.ex index 27f4b38..9959837 100644 --- a/lib/chess_web/controllers/session_controller.ex +++ b/lib/chess_web/controllers/session_controller.ex @@ -3,6 +3,7 @@ defmodule ChessWeb.SessionController do alias Chess.Auth alias Chess.Auth.User + alias Chess.Auth.Guardian def new(conn, _params) do changeset = User.changeset(%User{}) @@ -14,8 +15,9 @@ defmodule ChessWeb.SessionController do %{"user" => %{"username" => username, "password" => password}} ) do case Auth.authenticate_user(username, password) do - {:ok, _user} -> + {:ok, user} -> conn + |> Guardian.Plug.sign_in(user) |> put_flash(:info, "You are signed in") |> redirect(to: game_path(conn, :index)) {:error, _error} -> @@ -25,4 +27,11 @@ defmodule ChessWeb.SessionController do |> render("new.html", changeset: changeset) end end + + def delete(conn, _params) do + conn + |> Guardian.Plug.sign_out() + |> put_flash(:info, "You are logged out") + |> redirect(to: page_path(conn, :index)) + end end diff --git a/lib/chess_web/router.ex b/lib/chess_web/router.ex index 614844f..1bb7ca4 100644 --- a/lib/chess_web/router.ex +++ b/lib/chess_web/router.ex @@ -5,21 +5,37 @@ defmodule ChessWeb.Router do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash - # plug :protect_from_forgery + plug :protect_from_forgery plug :put_secure_browser_headers end + pipeline :auth do + plug Chess.Auth.Pipeline + end + + pipeline :ensure_auth do + plug Guardian.Plug.EnsureAuthenticated + end + pipeline :api do plug :accepts, ["json"] end scope "/", ChessWeb do - pipe_through :browser # Use the default browser stack + pipe_through [:browser, :auth] # Use the default browser stack get "/", PageController, :index - resources "/games", GameController, only: [:index, :create, :show, :delete] - resources "/session", SessionController, only: [:new, :create], singleton: true - resources "/registration", RegistrationController, only: [:new, :create], singleton: true + resources "/session", SessionController, + only: [:new, :create, :delete], singleton: true + resources "/registration", RegistrationController, + only: [:new, :create], singleton: true + end + + scope "/", ChessWeb do + pipe_through [:browser, :auth, :ensure_auth] + + resources "/games", GameController, + only: [:index, :create, :show, :delete] end # Other scopes may use custom stacks. diff --git a/lib/chess_web/templates/layout/app.html.eex b/lib/chess_web/templates/layout/app.html.eex index d9c756a..0b58593 100644 --- a/lib/chess_web/templates/layout/app.html.eex +++ b/lib/chess_web/templates/layout/app.html.eex @@ -15,9 +15,15 @@

Chess

diff --git a/test/chess_web/controllers/game_controller_test.exs b/test/chess_web/controllers/game_controller_test.exs index 2af7991..f23b6fc 100644 --- a/test/chess_web/controllers/game_controller_test.exs +++ b/test/chess_web/controllers/game_controller_test.exs @@ -2,26 +2,32 @@ defmodule Chess.GameControllerTest do use ChessWeb.ConnCase alias Chess.Store.Game + alias Chess.Auth.User + alias Chess.Auth.Guardian @valid_attrs %{} test "lists all entries on index", %{conn: conn} do + conn = login(conn) conn = get conn, game_path(conn, :index) assert html_response(conn, 200) =~ "Listing games" end test "creates resource and redirects when data is valid", %{conn: conn} do + conn = login(conn) conn = post conn, game_path(conn, :create), game: @valid_attrs game = Repo.one(Game) assert redirected_to(conn) == game_path(conn, :show, game) end test "shows chosen resource", %{conn: conn} do + conn = login(conn) game = Repo.insert! %Game{} conn = get conn, game_path(conn, :show, game) assert html_response(conn, 200) =~ "
" end test "renders page not found when id is nonexistent", %{conn: conn} do + conn = login(conn) assert_error_sent 404, fn -> get conn, game_path(conn, :show, -1) end @@ -29,8 +35,22 @@ defmodule Chess.GameControllerTest do test "deletes chosen resource", %{conn: conn} do game = Repo.insert! %Game{} + conn = login(conn) conn = delete conn, game_path(conn, :delete, game) assert redirected_to(conn) == game_path(conn, :index) refute Repo.get(Game, game.id) end + + defp login(conn) do + user = create_user() + conn |> Guardian.Plug.sign_in(user) + end + + defp create_user() do + changeset = User.changeset( + %User{}, + %{username: "link@hyrule.kingdom", password: "ilovezelda"} + ) + Repo.insert!(changeset) + end end diff --git a/test/features/games_test.exs b/test/features/games_test.exs index 84068b4..c13f466 100644 --- a/test/features/games_test.exs +++ b/test/features/games_test.exs @@ -1,7 +1,9 @@ defmodule Chess.GamesTest do use ChessWeb.FeatureCase - import Wallaby.Query, only: [css: 1, css: 2, button: 1] + import Wallaby.Query, only: [css: 1, css: 2, button: 1, text_field: 1] + + alias Chess.Auth.User test "visit homepage", %{session: session} do session @@ -11,12 +13,14 @@ defmodule Chess.GamesTest do test "can create a new game", %{session: session} do session + |> login() |> create_game() |> assert_has(css(".board")) end test "can move a piece", %{session: session} do session + |> login() |> create_game() session @@ -30,6 +34,24 @@ defmodule Chess.GamesTest do |> assert_has(square_containing("f4-r3", "white.pawn")) end + defp login(session) do + create_user() + + session + |> visit("/session/new") + |> fill_in(text_field("Username"), with: "link@hyrule.kingdom") + |> fill_in(text_field("Password"), with: "ilovezelda") + |> click(button("Sign in")) + end + + defp create_user() do + changeset = User.changeset( + %User{}, + %{username: "link@hyrule.kingdom", password: "ilovezelda"} + ) + Repo.insert!(changeset) + end + defp create_game(session) do session |> visit("/games") diff --git a/test/features/session_test.exs b/test/features/session_test.exs index 38821e5..f6eaa1a 100644 --- a/test/features/session_test.exs +++ b/test/features/session_test.exs @@ -42,6 +42,25 @@ defmodule Chess.SessionTest do |> click(button("Sign in")) assert session |> has_text?("You are signed in") + assert session |> has_text?("Listing games") + assert session |> has_text?("link@hyrule.kingdom") + end + + test "user can sign out", %{session: session} do + create_user() + + session + |> visit("/") + |> click(link("Log in")) + |> fill_in(text_field("Username"), with: "link@hyrule.kingdom") + |> fill_in(text_field("Password"), with: "ilovezelda") + |> click(button("Sign in")) + + session + |> visit("/") + |> click(link("Log out")) + + assert session |> has_text?("You are logged out") end defp create_user() do