From f8482d8bbe082f07d5f9033c0a67b5789fe6b9e3 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 16 Mar 2021 05:58:33 -0400 Subject: [PATCH 1/6] add registration changeset --- mix.exs | 5 +++-- mix.lock | 1 + web/controllers/user_controller.ex | 2 +- web/models/user.ex | 16 ++++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index dc29baf..9269390 100644 --- a/mix.exs +++ b/mix.exs @@ -19,7 +19,7 @@ defmodule Rumbl.Mixfile do def application do [mod: {Rumbl, []}, applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext, - :phoenix_ecto, :postgrex]] + :phoenix_ecto, :postgrex, :comeonin]] end # Specifies which paths to compile per environment. @@ -37,7 +37,8 @@ defmodule Rumbl.Mixfile do {:phoenix_html, "~> 2.6"}, {:phoenix_live_reload, "~> 1.0", only: :dev}, {:gettext, "~> 0.11"}, - {:cowboy, "~> 1.0"}] + {:cowboy, "~> 1.0"}, + {:comeonin, "~> 2.0"}] end # Aliases are shortcuts or tasks specific to the current project. diff --git a/mix.lock b/mix.lock index d2313ce..2b3aaab 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,5 @@ %{ + "comeonin": {:hex, :comeonin, "2.6.0", "74c288338b33205f9ce97e2117bb9a2aaab103a1811d243443d76fdb62f904ac", [:make, :mix], [], "hexpm", "bc72f049a1c61048427f557821fc06e273abf09f6829377541475d7b36ac8ac6"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "f4763bbe08233eceed6f24bc4fcc8d71c17cfeafa6439157c57349aa1bb4f17c"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm", "db622da03aa039e6366ab953e31186cc8190d32905e33788a1acb22744e6abd2"}, diff --git a/web/controllers/user_controller.ex b/web/controllers/user_controller.ex index e87ed9a..b541035 100644 --- a/web/controllers/user_controller.ex +++ b/web/controllers/user_controller.ex @@ -8,7 +8,7 @@ defmodule Rumbl.UserController do end def create(conn, %{"user" => user_params}) do - changeset = User.changeset(%User{}, user_params) + changeset = User.registration_changeset(%User{}, user_params) case Repo.insert(changeset) do {:ok, user} -> diff --git a/web/models/user.ex b/web/models/user.ex index 6603c20..f32ee41 100644 --- a/web/models/user.ex +++ b/web/models/user.ex @@ -14,4 +14,20 @@ defmodule Rumbl.User do |> cast(params, ~w(name username), []) |> validate_length(:username, min: 1, max: 20) end + + def registration_changeset(model, params) do + model + |> changeset(params) + |> cast(params, ~w(password), []) + |> validate_length(:password, min: 6, max: 100) + |> put_pass_hash() + end + + defp put_pass_hash(changeset) do + case changeset do + %Ecto.Changeset{valid?: true, changes: %{password: pass}} -> + put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(pass)) + _ -> changeset + end + end end From 1e3045e28618bb38d3e490fb6822994247c57a4e Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 16 Mar 2021 11:49:41 -0400 Subject: [PATCH 2/6] restrict /users to authenticated sessions --- web/controllers/auth.ex | 13 +++++++++++++ web/controllers/user_controller.ex | 20 ++++++++++++++++++-- web/router.ex | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 web/controllers/auth.ex diff --git a/web/controllers/auth.ex b/web/controllers/auth.ex new file mode 100644 index 0000000..2ff2bfc --- /dev/null +++ b/web/controllers/auth.ex @@ -0,0 +1,13 @@ +defmodule Rumbl.Auth do + import Plug.Conn + + def init(opts) do + Keyword.fetch!(opts, :repo) + end + + def call(conn, repo) do + user_id = get_session(conn, :user_id) + user = user_id && repo.get(Rumbl.User, user_id) + assign(conn, :current_user, user) + end +end diff --git a/web/controllers/user_controller.ex b/web/controllers/user_controller.ex index b541035..bfece3e 100644 --- a/web/controllers/user_controller.ex +++ b/web/controllers/user_controller.ex @@ -21,12 +21,28 @@ defmodule Rumbl.UserController do end def index(conn, _params) do - users = Repo.all(Rumbl.User) - render conn, "index.html", users: users + case authenticate(conn) do + %Plug.Conn{halted: true} = conn -> + conn + conn -> + users = Repo.all(User) + render conn, "index.html", users: users + end end def show(conn, %{"id" => id}) do user = Repo.get(User, id) render conn, "show.html", user: user end + + defp authenticate(conn) do + if conn.assigns.current_user do + conn + else + conn + |> put_flash(:error, "You must be logged in to access that page") + |> redirect(to: page_path(conn, :index)) + |> halt() + end + end end diff --git a/web/router.ex b/web/router.ex index 81b6081..d82e595 100644 --- a/web/router.ex +++ b/web/router.ex @@ -7,6 +7,7 @@ defmodule Rumbl.Router do plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers + plug Rumbl.Auth, repo: Rumbl.Repo end pipeline :api do From 0cb4b3c5f532018f5ebc303b0e487a0f0aa74b1f Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 16 Mar 2021 11:55:54 -0400 Subject: [PATCH 3/6] call authenticate for index and show actions --- web/controllers/user_controller.ex | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/web/controllers/user_controller.ex b/web/controllers/user_controller.ex index bfece3e..4a7fc3b 100644 --- a/web/controllers/user_controller.ex +++ b/web/controllers/user_controller.ex @@ -1,6 +1,7 @@ defmodule Rumbl.UserController do use Rumbl.Web, :controller alias Rumbl.User + plug :authenticate when action in [:index, :show] def new(conn, _params) do changeset = User.changeset(%User{}) @@ -21,13 +22,8 @@ defmodule Rumbl.UserController do end def index(conn, _params) do - case authenticate(conn) do - %Plug.Conn{halted: true} = conn -> - conn - conn -> - users = Repo.all(User) - render conn, "index.html", users: users - end + users = Repo.all(User) + render conn, "index.html", users: users end def show(conn, %{"id" => id}) do @@ -35,7 +31,7 @@ defmodule Rumbl.UserController do render conn, "show.html", user: user end - defp authenticate(conn) do + defp authenticate(conn, _opts) do if conn.assigns.current_user do conn else From a82474f0f3036bda0eec6a5e8c1c67953de06363 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 16 Mar 2021 12:00:53 -0400 Subject: [PATCH 4/6] login user after completing form at /users/new --- web/controllers/auth.ex | 7 +++++++ web/controllers/user_controller.ex | 1 + 2 files changed, 8 insertions(+) diff --git a/web/controllers/auth.ex b/web/controllers/auth.ex index 2ff2bfc..c2188b9 100644 --- a/web/controllers/auth.ex +++ b/web/controllers/auth.ex @@ -10,4 +10,11 @@ defmodule Rumbl.Auth do user = user_id && repo.get(Rumbl.User, user_id) assign(conn, :current_user, user) end + + def login(conn, user) do + conn + |> assign(:current_user, user) + |> put_session(:user_id, user.id) + |> configure_session(renew: true) + end end diff --git a/web/controllers/user_controller.ex b/web/controllers/user_controller.ex index 4a7fc3b..00173bd 100644 --- a/web/controllers/user_controller.ex +++ b/web/controllers/user_controller.ex @@ -14,6 +14,7 @@ defmodule Rumbl.UserController do case Repo.insert(changeset) do {:ok, user} -> conn + |> Rumbl.Auth.login(user) |> put_flash(:info, "#{user.name} account created") |> redirect(to: user_path(conn, :index)) {:error, changeset} -> From 5280dc7452d506be2c3326b53cb06368f3e0b897 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 16 Mar 2021 12:28:43 -0400 Subject: [PATCH 5/6] login users at /sessions/new --- web/controllers/auth.ex | 16 ++++++++++++++++ web/controllers/session_controller.ex | 20 ++++++++++++++++++++ web/router.ex | 1 + web/templates/session/new.html.eex | 11 +++++++++++ web/views/session_view.ex | 3 +++ 5 files changed, 51 insertions(+) create mode 100644 web/controllers/session_controller.ex create mode 100644 web/templates/session/new.html.eex create mode 100644 web/views/session_view.ex diff --git a/web/controllers/auth.ex b/web/controllers/auth.ex index c2188b9..7b3f24f 100644 --- a/web/controllers/auth.ex +++ b/web/controllers/auth.ex @@ -1,5 +1,6 @@ defmodule Rumbl.Auth do import Plug.Conn + import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0] def init(opts) do Keyword.fetch!(opts, :repo) @@ -17,4 +18,19 @@ defmodule Rumbl.Auth do |> put_session(:user_id, user.id) |> configure_session(renew: true) end + + def login_by_username_and_password(conn, username, password, opts) do + repo = Keyword.fetch!(opts, :repo) + user = repo.get_by(Rumbl.User, username: username) + + cond do + user && checkpw(password, user.password_hash) -> + {:ok, login(conn, user)} + user -> + {:error, :unauthorized, conn} + true -> + dummy_checkpw() + {:error, :not_found, conn} + end + end end diff --git a/web/controllers/session_controller.ex b/web/controllers/session_controller.ex new file mode 100644 index 0000000..7eda366 --- /dev/null +++ b/web/controllers/session_controller.ex @@ -0,0 +1,20 @@ +defmodule Rumbl.SessionController do + use Rumbl.Web, :controller + + def new(conn, _) do + render conn, "new.html" + end + + def create(conn, %{"session" => %{"username" => user, "password" => password}}) do + case Rumbl.Auth.login_by_username_and_password(conn, user, password, repo: Repo) do + {:ok, conn} -> + conn + |> put_flash(:info, "Welcome back!") + |> redirect(to: page_path(conn, :index)) + {:error, _reason, conn} -> + conn + |> put_flash(:error, "Invalid username/password combination") + |> render("new.html") + end + end +end diff --git a/web/router.ex b/web/router.ex index d82e595..64d1810 100644 --- a/web/router.ex +++ b/web/router.ex @@ -20,6 +20,7 @@ defmodule Rumbl.Router do get "/", PageController, :index resources "/users", UserController, only: [:new, :create, :index, :show] + resources "/sessions", SessionController, only: [:new, :create, :delete] end # Other scopes may use custom stacks. diff --git a/web/templates/session/new.html.eex b/web/templates/session/new.html.eex new file mode 100644 index 0000000..3e9e504 --- /dev/null +++ b/web/templates/session/new.html.eex @@ -0,0 +1,11 @@ +

Login

+ +<%= form_for @conn, session_path(@conn, :create), [as: :session], fn f -> %> +
+ <%= text_input f, :username, placeholder: "Username", class: "form-control" %> +
+
+ <%= password_input f, :password, placeholder: "Password", class: "form-control" %> +
+ <%= submit "Log in", class: "btn btn-primary" %> +<% end %> diff --git a/web/views/session_view.ex b/web/views/session_view.ex new file mode 100644 index 0000000..ef32782 --- /dev/null +++ b/web/views/session_view.ex @@ -0,0 +1,3 @@ +defmodule Rumbl.SessionView do + use Rumbl.Web, :view +end From 24987830d3e5a84692a268d43378cee5e980f5fe Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 16 Mar 2021 12:37:01 -0400 Subject: [PATCH 6/6] add auth links, allow user to destroy session --- web/controllers/auth.ex | 5 +++++ web/controllers/session_controller.ex | 6 ++++++ web/templates/layout/app.html.eex | 20 +++++++++++++------- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/web/controllers/auth.ex b/web/controllers/auth.ex index 7b3f24f..9327e6d 100644 --- a/web/controllers/auth.ex +++ b/web/controllers/auth.ex @@ -33,4 +33,9 @@ defmodule Rumbl.Auth do {:error, :not_found, conn} end end + + def logout(conn) do + configure_session(conn, drop: true) # destroy the session, or + # delete_session(conn, :user_id) # to delete only the user_id + end end diff --git a/web/controllers/session_controller.ex b/web/controllers/session_controller.ex index 7eda366..6e5ee54 100644 --- a/web/controllers/session_controller.ex +++ b/web/controllers/session_controller.ex @@ -17,4 +17,10 @@ defmodule Rumbl.SessionController do |> render("new.html") end end + + def delete(conn, _) do + conn + |> Rumbl.Auth.logout() + |> redirect(to: page_path(conn, :index)) + end end diff --git a/web/templates/layout/app.html.eex b/web/templates/layout/app.html.eex index c8bef5f..9ca2225 100644 --- a/web/templates/layout/app.html.eex +++ b/web/templates/layout/app.html.eex @@ -13,14 +13,20 @@
-
- +
+ -
+