diff --git a/lib/bones73k/accounts.ex b/lib/bones73k/accounts.ex index 6e2d9a1d..d0914c0f 100644 --- a/lib/bones73k/accounts.ex +++ b/lib/bones73k/accounts.ex @@ -172,10 +172,10 @@ defmodule Bones73k.Accounts do {:error, %Ecto.Changeset{}} """ - def apply_user_email(user, password, attrs) do + def apply_user_email(user, %{"current_password" => curr_pw} = attrs) do user |> User.email_changeset(attrs) - |> User.validate_current_password(password) + |> User.validate_current_password(curr_pw) |> Ecto.Changeset.apply_action(:update) end @@ -247,11 +247,11 @@ defmodule Bones73k.Accounts do {:error, %Ecto.Changeset{}} """ - def update_user_password(user, password, attrs) do + def update_user_password(user, %{"current_password" => curr_pw} = attrs) do changeset = user |> User.password_changeset(attrs) - |> User.validate_current_password(password) + |> User.validate_current_password(curr_pw) Ecto.Multi.new() |> Ecto.Multi.update(:user, changeset) diff --git a/lib/bones73k_web/controllers/user_auth.ex b/lib/bones73k_web/controllers/user_auth.ex index c7c280e1..30fbd50a 100644 --- a/lib/bones73k_web/controllers/user_auth.ex +++ b/lib/bones73k_web/controllers/user_auth.ex @@ -35,10 +35,6 @@ defmodule Bones73kWeb.UserAuth do |> put_session(:user_token, token) |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}") |> maybe_write_remember_me_cookie(token, params) - |> put_flash( - :info, - raw("Welcome back, #{user.email} — you were logged in successfuly.") - ) |> redirect(to: get_session(conn, :user_return_to) || signed_in_path(conn)) end diff --git a/lib/bones73k_web/controllers/user_session_controller.ex b/lib/bones73k_web/controllers/user_session_controller.ex index ecc84365..546943a4 100644 --- a/lib/bones73k_web/controllers/user_session_controller.ex +++ b/lib/bones73k_web/controllers/user_session_controller.ex @@ -1,6 +1,7 @@ defmodule Bones73kWeb.UserSessionController do use Bones73kWeb, :controller + alias Phoenix.HTML alias Bones73k.Accounts alias Bones73k.Accounts.User alias Bones73kWeb.UserAuth @@ -11,7 +12,12 @@ defmodule Bones73kWeb.UserSessionController do def create(conn, %{"user" => %{"email" => email, "password" => password} = user_params}) do if user = Accounts.get_user_by_email_and_password(email, password) do - UserAuth.log_in_user(conn, user, user_params) + conn + |> put_flash( + :info, + HTML.raw("Welcome back, #{user.email} — you were logged in successfuly.") + ) + |> UserAuth.log_in_user(user, user_params) else render(conn, "new.html", error_message: "Invalid email or password") end diff --git a/lib/bones73k_web/controllers/user_settings_controller.ex b/lib/bones73k_web/controllers/user_settings_controller.ex index a2e9b7eb..4aefacc0 100644 --- a/lib/bones73k_web/controllers/user_settings_controller.ex +++ b/lib/bones73k_web/controllers/user_settings_controller.ex @@ -2,36 +2,6 @@ defmodule Bones73kWeb.UserSettingsController do use Bones73kWeb, :controller alias Bones73k.Accounts - alias Bones73kWeb.UserAuth - - plug(:assign_email_and_password_changesets) - - def edit(conn, _params) do - render(conn, "edit.html") - end - - def update_email(conn, %{"current_password" => password, "user" => user_params}) do - user = conn.assigns.current_user - - case Accounts.apply_user_email(user, password, user_params) do - {:ok, applied_user} -> - Accounts.deliver_update_email_instructions( - applied_user, - user.email, - &Routes.user_settings_url(conn, :confirm_email, &1) - ) - - conn - |> put_flash( - :info, - "A link to confirm your email change has been sent to the new address." - ) - |> redirect(to: Routes.user_settings_path(conn, :edit)) - - {:error, changeset} -> - render(conn, "edit.html", email_changeset: changeset) - end - end def confirm_email(conn, %{"token" => token}) do case Accounts.update_user_email(conn.assigns.current_user, token) do @@ -46,27 +16,4 @@ defmodule Bones73kWeb.UserSettingsController do |> redirect(to: Routes.user_settings_path(conn, :edit)) end end - - def update_password(conn, %{"current_password" => password, "user" => user_params}) do - user = conn.assigns.current_user - - case Accounts.update_user_password(user, password, user_params) do - {:ok, user} -> - conn - |> put_flash(:info, "Password updated successfully.") - |> put_session(:user_return_to, Routes.user_settings_path(conn, :edit)) - |> UserAuth.log_in_user(user) - - {:error, changeset} -> - render(conn, "edit.html", password_changeset: changeset) - end - end - - defp assign_email_and_password_changesets(conn, _opts) do - user = conn.assigns.current_user - - conn - |> assign(:email_changeset, Accounts.change_user_email(user)) - |> assign(:password_changeset, Accounts.change_user_password(user)) - end end diff --git a/lib/bones73k_web/live/user/registration.ex b/lib/bones73k_web/live/user/registration.ex index 6e990fc0..0ad56c70 100644 --- a/lib/bones73k_web/live/user/registration.ex +++ b/lib/bones73k_web/live/user/registration.ex @@ -4,12 +4,6 @@ defmodule Bones73kWeb.UserLive.Registration do alias Bones73k.Accounts alias Bones73k.Accounts.User - @messages [ - success: "Welcome! New accout created.", - info: - "Some features may be unavailable until you confirm your email. Check your inbox for instructions." - ] - @impl true def mount(_params, session, socket) do socket @@ -25,7 +19,11 @@ defmodule Bones73kWeb.UserLive.Registration do %{ user_id: nil, user_return_to: Map.get(session, "user_return_to", "/"), - messages: @messages + messages: [ + success: "Welcome! Your new account has been created, and you've been logged in.", + info: + "Some features may be unavailable until you confirm your email address. Check your inbox for instructions." + ] } end diff --git a/lib/bones73k_web/live/user/reset_password.ex b/lib/bones73k_web/live/user/reset_password.ex index c3c9f041..b7d751b4 100644 --- a/lib/bones73k_web/live/user/reset_password.ex +++ b/lib/bones73k_web/live/user/reset_password.ex @@ -27,7 +27,7 @@ defmodule Bones73kWeb.UserLive.ResetPassword do {:ok, _} -> {:noreply, socket - |> put_flash(:success, "Password reset successfully.") + |> put_flash(:info, "Password reset successfully.") |> redirect(to: Routes.user_session_path(socket, :new))} {:error, changeset} -> diff --git a/lib/bones73k_web/live/user/settings.ex b/lib/bones73k_web/live/user/settings.ex new file mode 100644 index 00000000..2821f362 --- /dev/null +++ b/lib/bones73k_web/live/user/settings.ex @@ -0,0 +1,39 @@ +defmodule Bones73kWeb.UserLive.Settings do + use Bones73kWeb, :live_view + + alias Bones73k.Accounts.User + + @impl true + def mount(_params, session, socket) do + socket + |> assign_defaults(session) + |> alert_email_verified?() + |> live_okreply() + end + + defp alert_email_verified?(socket) do + case socket.assigns.current_user do + %{confirmed_at: nil} -> + put_flash(socket, :warning, [ + "Your email hasn't been confirmed, some areas may be restricted. Shall we ", + link("resend the verification email?", + to: Routes.user_confirmation_path(socket, :new), + class: "alert-link" + ) + ]) + + _ -> + socket + end + end + + @impl true + def handle_info({:put_flash_message, {flash_type, msg}}, socket) do + socket |> put_flash(flash_type, msg) |> live_noreply() + end + + @impl true + def handle_info({:clear_flash_message, flash_type}, socket) do + socket |> clear_flash(flash_type) |> live_noreply() + end +end diff --git a/lib/bones73k_web/live/user/settings.html.leex b/lib/bones73k_web/live/user/settings.html.leex new file mode 100644 index 00000000..994b1fcb --- /dev/null +++ b/lib/bones73k_web/live/user/settings.html.leex @@ -0,0 +1,9 @@ +<h3 class="mb-3"> + <%= icon_div @socket, "bi-sliders", [class: "icon baseline"] %> + User Settings +</h3> + +<div class="row"> + <%= live_component @socket, Bones73kWeb.UserLive.Settings.Email, id: "email-#{@current_user.id}", current_user: @current_user %> + <%= live_component @socket, Bones73kWeb.UserLive.Settings.Password, id: "password-#{@current_user.id}", current_user: @current_user %> +</div> diff --git a/lib/bones73k_web/live/user/settings/email.ex b/lib/bones73k_web/live/user/settings/email.ex new file mode 100644 index 00000000..341ed0ff --- /dev/null +++ b/lib/bones73k_web/live/user/settings/email.ex @@ -0,0 +1,62 @@ +defmodule Bones73kWeb.UserLive.Settings.Email do + use Bones73kWeb, :live_component + + alias Bones73k.Accounts + alias Bones73k.Accounts.User + + @impl true + def update(%{current_user: user} = assigns, socket) do + socket + |> assign(id: assigns.id) + |> assign(current_user: user) + |> assign(changeset: get_changeset(user)) + |> live_okreply() + end + + defp get_changeset(user, user_params \\ %{}) do + Accounts.change_user_email(user, user_params) + end + + @impl true + def handle_event("validate", %{"user" => user_params}, socket) do + cs = get_changeset(socket.assigns.current_user, user_params) + {:noreply, assign(socket, changeset: %{cs | action: :update})} + end + + # user_settings_path GET /users/settings/confirm_email/:token Bones73kWeb.UserSettingsController :confirm_email + + @impl true + def handle_event("save", %{"user" => user_params}, socket) do + case Accounts.apply_user_email(socket.assigns.current_user, user_params) do + {:ok, applied_user} -> + Accounts.deliver_update_email_instructions( + applied_user, + socket.assigns.current_user.email, + &Routes.user_settings_url(socket, :confirm_email, &1) + ) + + send(self(), {:clear_flash_message, :error}) + + send( + self(), + {:put_flash_message, + {:info, "A link to confirm your e-mail change has been sent to the new address."}} + ) + + socket + |> assign(changeset: get_changeset(socket.assigns.current_user)) + |> live_noreply() + + {:error, cs} -> + cu = socket.assigns.current_user + cpw = user_params["current_password"] + valid_password? = User.valid_password?(cu, cpw) + msg = (valid_password? && "Could not reset email.") || "Invalid current password." + send(self(), {:put_flash_message, {:error, msg}}) + + socket + |> assign(changeset: cs) + |> live_noreply() + end + end +end diff --git a/lib/bones73k_web/live/user/settings/email.html.leex b/lib/bones73k_web/live/user/settings/email.html.leex new file mode 100644 index 00000000..b589b26e --- /dev/null +++ b/lib/bones73k_web/live/user/settings/email.html.leex @@ -0,0 +1,51 @@ +<div id="<%= @id %>" class="col-sm-9 col-md-7 col-lg-5 col-xl-4 mt-1"> + + <h4>Change email</h4> + + <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, phx_target: @myself], fn f -> %> + + <div class="mb-3" phx-feedback-for="<%= input_id(f, :email) %>"> + <%= label f, :email, class: "form-label" %> + <div class="input-group has-validation"> + <span class="input-group-text"> + <%= icon_div @socket, "bi-at", [class: "icon fs-5"] %> + </span> + <%= email_input f, :email, + value: input_value(f, :email), + class: input_class(f, :email, "form-control"), + placeholder: "e.g., babka@73k.us", + maxlength: User.max_email, + phx_debounce: "600", + aria_describedby: error_id(f, :email) + %> + <%= error_tag f, :email %> + </div> + </div> + + + <div class="mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>"> + <%= label f, :current_password, class: "form-label" %> + <div class="input-group"> + <span class="input-group-text"> + <%= icon_div @socket, "bi-lock", [class: "icon fs-5"] %> + </span> + <%= password_input f, :current_password, + class: "form-control", + required: true, + aria_describedby: error_id(f, :current_password) + %> + <%= error_tag f, :current_password %> + </div> + </div> + + <div class="mb-3"> + <%= submit "Change email", + disabled: !@changeset.valid? || input_value(f, :current_password) == "", + class: "btn btn-primary", + phx_disable_with: "Saving..." + %> + </div> + + <% end %> + +</div> diff --git a/lib/bones73k_web/live/user/settings/password.ex b/lib/bones73k_web/live/user/settings/password.ex new file mode 100644 index 00000000..2225ff9f --- /dev/null +++ b/lib/bones73k_web/live/user/settings/password.ex @@ -0,0 +1,57 @@ +defmodule Bones73kWeb.UserLive.Settings.Password do + use Bones73kWeb, :live_component + + alias Bones73k.Accounts + alias Bones73k.Accounts.User + + @impl true + def update(%{current_user: user} = assigns, socket) do + socket + |> assign(id: assigns.id) + |> assign(current_user: user) + |> assign(changeset: get_changeset(user)) + |> assign(login_params: init_login_params(socket)) + |> assign(trigger_submit: false) + |> live_okreply() + end + + defp get_changeset(user, user_params \\ %{}) do + Accounts.change_user_password(user, user_params) + end + + defp init_login_params(socket) do + %{ + user_id: nil, + user_return_to: Routes.user_settings_path(socket, :edit), + messages: [info: "Password updated successfully."] + } + end + + @impl true + def handle_event("validate", %{"user" => user_params}, socket) do + cs = get_changeset(socket.assigns.current_user, user_params) + {:noreply, assign(socket, changeset: %{cs | action: :update})} + end + + @impl true + def handle_event("save", %{"user" => user_params}, socket) do + case Accounts.update_user_password(socket.assigns.current_user, user_params) do + {:ok, user} -> + socket + |> assign(login_params: %{socket.assigns.login_params | user_id: user.id}) + |> assign(trigger_submit: true) + |> live_noreply() + + {:error, cs} -> + cu = socket.assigns.current_user + cpw = user_params["current_password"] + valid_password? = User.valid_password?(cu, cpw) + msg = (valid_password? && "Could not change password.") || "Invalid current password." + send(self(), {:put_flash_message, {:error, msg}}) + + socket + |> assign(changeset: cs) + |> live_noreply() + end + end +end diff --git a/lib/bones73k_web/live/user/settings/password.html.leex b/lib/bones73k_web/live/user/settings/password.html.leex new file mode 100644 index 00000000..9d84ad4d --- /dev/null +++ b/lib/bones73k_web/live/user/settings/password.html.leex @@ -0,0 +1,76 @@ +<div id="<%= @id %>" class="col-sm-9 col-md-7 col-lg-5 col-xl-4 mt-1"> + + <h4>Change password</h4> + + <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, phx_target: @myself], fn f -> %> + + <div class="mb-3" phx-feedback-for="<%= input_id(f, :password) %>"> + <%= label f, :password, "New password", class: "form-label" %> + <div class="input-group has-validation"> + <span class="input-group-text"> + <%= icon_div @socket, "bi-key", [class: "icon fs-5"] %> + </span> + <%= password_input f, :password, + value: input_value(f, :password), + class: input_class(f, :password, "form-control"), + maxlength: User.max_password, + phx_debounce: "600", + aria_describedby: error_id(f, :password) + %> + <%= error_tag f, :password %> + </div> + </div> + + <div class="mb-3" phx-feedback-for="<%= input_id(f, :password_confirmation) %>"> + <%= label f, :password_confirmation, "Confirm new password", class: "form-label" %> + <div class="input-group has-validation"> + <span class="input-group-text"> + <%= icon_div @socket, "bi-key-fill", [class: "icon fs-5"] %> + </span> + <%= password_input f, :password_confirmation, + value: input_value(f, :password_confirmation), + class: input_class(f, :password_confirmation, "form-control"), + maxlength: User.max_password, + aria_describedby: error_id(f, :password_confirmation) + %> + <%= error_tag f, :password_confirmation %> + </div> + </div> + + <div class="mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>"> + <%= label f, :current_password, class: "form-label" %> + <div class="input-group"> + <span class="input-group-text"> + <%= icon_div @socket, "bi-lock", [class: "icon fs-5"] %> + </span> + <%= password_input f, :current_password, + class: "form-control", + required: true, + aria_describedby: error_id(f, :current_password) + %> + <%= error_tag f, :current_password %> + </div> + </div> + + <div class="mb-3"> + <%= submit "Change password", + disabled: !@changeset.valid? || input_value(f, :current_password) == "", + class: "btn btn-primary", + phx_disable_with: "Saving..." + %> + </div> + + <% end %> + + <%# hidden form for initial login after registration %> + <%= form_for :user, Routes.user_session_path(@socket, :create), [phx_trigger_action: @trigger_submit, id: "settings_pw_change_trigger"], fn f -> %> + <%= hidden_input f, :params_token, value: Phoenix.Token.encrypt(Bones73kWeb.Endpoint, "login_params", @login_params) %> + <% end %> + + <%# hidden form to submit user for relogin after password change %> + <%#= form_for :user_login, Routes.user_session_path(@socket, :create), [phx_trigger_action: @trigger_submit], fn f -> %> + <%#= hidden_input f, :login_params_token, value: Phoenix.Token.encrypt(Bones73kWeb.Endpoint, "login_params", @login_params) %> + <%#= hidden_input f, :remember_me, value: false %> + <%# end %> + +</div> diff --git a/lib/bones73k_web/router.ex b/lib/bones73k_web/router.ex index b7d3cfc8..3907c3ce 100644 --- a/lib/bones73k_web/router.ex +++ b/lib/bones73k_web/router.ex @@ -72,13 +72,9 @@ defmodule Bones73kWeb.Router do pipe_through([:browser, :require_authenticated_user]) # # liveview user settings - # live "/users/settings", UserLive.Settings, :edit + live "/users/settings", UserLive.Settings, :edit # original user routes from phx.gen.auth - # TODO: - get("/users/settings", UserSettingsController, :edit) - put("/users/settings/update_password", UserSettingsController, :update_password) - put("/users/settings/update_email", UserSettingsController, :update_email) get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email) end @@ -86,7 +82,6 @@ defmodule Bones73kWeb.Router do pipe_through([:browser]) delete("/users/log_out", UserSessionController, :delete) - # TODO: understanding/testing force_logout? get("/users/force_logout", UserSessionController, :force_logout) get("/users/confirm", UserConfirmationController, :new) post("/users/confirm", UserConfirmationController, :create) diff --git a/lib/bones73k_web/templates/layout/app.html.eex b/lib/bones73k_web/templates/layout/app.html.eex index dca8b5bd..c28896f7 100644 --- a/lib/bones73k_web/templates/layout/app.html.eex +++ b/lib/bones73k_web/templates/layout/app.html.eex @@ -2,7 +2,7 @@ <%# phoenix flash alerts: %> <div class="row justify-content-center"> - <div class="col-md-11 col-lg-9 col-xl-8 "> + <div class="col-md-12 col-lg-10 col-xl-9 "> <%= for {kind, color} <- alert_kinds() do %> <%= if flash_content = get_flash(@conn, kind) do %> <div class="alert alert-<%= color %> alert-dismissible fade show" role="alert"> diff --git a/lib/bones73k_web/templates/layout/live.html.leex b/lib/bones73k_web/templates/layout/live.html.leex index 889fc6d0..a67d71df 100644 --- a/lib/bones73k_web/templates/layout/live.html.leex +++ b/lib/bones73k_web/templates/layout/live.html.leex @@ -2,7 +2,7 @@ <%# liveview flash alerts: %> <div class="row justify-content-center"> - <div class="col-md-11 col-lg-9 col-xl-8 "> + <div class="col-md-12 col-lg-10 col-xl-9 "> <%= for {kind, color} <- alert_kinds() do %> <%= if flash_content = live_flash(@flash, kind) do %> <div class="alert alert-<%= color %> alert-dismissible fade show" role="alert" id="lv-alert-<%= kind %>" phx-hook="AlertRemover" data-key="<%= kind %>"> diff --git a/lib/bones73k_web/templates/user_settings/edit.html.eex b/lib/bones73k_web/templates/user_settings/edit.html.eex deleted file mode 100644 index f6c0801e..00000000 --- a/lib/bones73k_web/templates/user_settings/edit.html.eex +++ /dev/null @@ -1,49 +0,0 @@ -<h1>Settings</h1> - -<h3>Change email</h3> - -<%= form_for @email_changeset, Routes.user_settings_path(@conn, :update_email), fn f -> %> - <%= if @email_changeset.action do %> - <div class="alert alert-danger"> - <p>Oops, something went wrong! Please check the errors below.</p> - </div> - <% end %> - - <%= label f, :email %> - <%= email_input f, :email, required: true %> - <%= error_tag f, :email %> - - <%= label f, :current_password, for: "current_password_for_email" %> - <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_email" %> - <%= error_tag f, :current_password %> - - <div> - <%= submit "Change email" %> - </div> -<% end %> - -<h3>Change password</h3> - -<%= form_for @password_changeset, Routes.user_settings_path(@conn, :update_password), fn f -> %> - <%= if @password_changeset.action do %> - <div class="alert alert-danger"> - <p>Oops, something went wrong! Please check the errors below.</p> - </div> - <% end %> - - <%= label f, :password, "New password" %> - <%= password_input f, :password, required: true %> - <%= error_tag f, :password %> - - <%= label f, :password_confirmation, "Confirm new password" %> - <%= password_input f, :password_confirmation, required: true %> - <%= error_tag f, :password_confirmation %> - - <%= label f, :current_password, for: "current_password_for_password" %> - <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %> - <%= error_tag f, :current_password %> - - <div> - <%= submit "Change password" %> - </div> -<% end %> diff --git a/lib/bones73k_web/views/user_settings_view.ex b/lib/bones73k_web/views/user_settings_view.ex deleted file mode 100644 index 27735e86..00000000 --- a/lib/bones73k_web/views/user_settings_view.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Bones73kWeb.UserSettingsView do - use Bones73kWeb, :view -end diff --git a/test/bones73k/accounts_test.exs b/test/bones73k/accounts_test.exs index f12d70ff..30b777a8 100644 --- a/test/bones73k/accounts_test.exs +++ b/test/bones73k/accounts_test.exs @@ -58,19 +58,19 @@ defmodule Bones73k.AccountsTest do end test "validates email and password when given" do - {:error, changeset} = Accounts.register_user(%{email: "not valid", password: "not valid"}) - - assert %{ - email: ["must have the @ sign and no spaces"], - password: ["should be at least 12 character(s)"] - } = errors_on(changeset) + {:error, changeset} = Accounts.register_user(%{email: "not valid", password: "2shrt"}) + pw_err = "should be at least #{User.min_password()} character(s)" + assert "must be a valid email address" in errors_on(changeset).email + assert pw_err in errors_on(changeset).password end test "validates maximum values for email and password for security" do - too_long = String.duplicate("db", 100) + too_long = "#{String.duplicate("db", 300)}@example.com" {:error, changeset} = Accounts.register_user(%{email: too_long, password: too_long}) - assert "should be at most 160 character(s)" in errors_on(changeset).email - assert "should be at most 80 character(s)" in errors_on(changeset).password + em_err = "should be at most #{User.max_email()} character(s)" + pw_err = "should be at most #{User.max_password()} character(s)" + assert em_err in errors_on(changeset).email + assert pw_err in errors_on(changeset).password end test "validates email uniqueness" do @@ -92,16 +92,22 @@ defmodule Bones73k.AccountsTest do assert is_nil(user.password) assert user.role == :user end - end - describe "register_admin/1" do - test "registers users with a hashed password and sets role to :admin" do + test "registers different role :manager and sets role to :manager" do email = unique_user_email() - {:ok, user} = Accounts.register_admin(%{email: email, password: valid_user_password()}) + attrs = %{email: email, role: :manager, password: valid_user_password()} + {:ok, user} = Accounts.register_user(attrs) assert user.email == email assert is_binary(user.hashed_password) assert is_nil(user.confirmed_at) assert is_nil(user.password) + assert user.role == :manager + end + + test "registers different role :admin and sets role to :admin" do + email = unique_user_email() + attrs = %{email: email, role: :admin, password: valid_user_password()} + {:ok, user} = Accounts.register_user(attrs) assert user.role == :admin end end @@ -109,7 +115,7 @@ defmodule Bones73k.AccountsTest do describe "change_user_registration/2" do test "returns a changeset" do assert %Ecto.Changeset{} = changeset = Accounts.change_user_registration(%User{}) - assert changeset.required == [:password, :email] + assert changeset.required == [:password, :email, :role] end end @@ -126,45 +132,42 @@ defmodule Bones73k.AccountsTest do end test "requires email to change", %{user: user} do - {:error, changeset} = Accounts.apply_user_email(user, valid_user_password(), %{}) + attrs = %{"current_password" => valid_user_password()} + {:error, changeset} = Accounts.apply_user_email(user, attrs) assert %{email: ["did not change"]} = errors_on(changeset) end test "validates email", %{user: user} do - {:error, changeset} = - Accounts.apply_user_email(user, valid_user_password(), %{email: "not valid"}) - - assert %{email: ["must have the @ sign and no spaces"]} = errors_on(changeset) + attrs = %{"current_password" => valid_user_password(), "email" => "not valid"} + {:error, changeset} = Accounts.apply_user_email(user, attrs) + assert %{email: ["must be a valid email address"]} = errors_on(changeset) end test "validates maximum value for email for security", %{user: user} do - too_long = String.duplicate("db", 100) - - {:error, changeset} = - Accounts.apply_user_email(user, valid_user_password(), %{email: too_long}) - - assert "should be at most 160 character(s)" in errors_on(changeset).email + too_long = "#{String.duplicate("db", 300)}@example.com" + attrs = %{"current_password" => valid_user_password(), "email" => too_long} + {:error, changeset} = Accounts.apply_user_email(user, attrs) + em_err = "should be at most #{User.max_email()} character(s)" + assert em_err in errors_on(changeset).email end test "validates email uniqueness", %{user: user} do %{email: email} = user_fixture() - - {:error, changeset} = - Accounts.apply_user_email(user, valid_user_password(), %{email: email}) - + attrs = %{"current_password" => valid_user_password(), "email" => email} + {:error, changeset} = Accounts.apply_user_email(user, attrs) assert "has already been taken" in errors_on(changeset).email end test "validates current password", %{user: user} do - {:error, changeset} = - Accounts.apply_user_email(user, "invalid", %{email: unique_user_email()}) - + attrs = %{"current_password" => "invalid", "email" => unique_user_email()} + {:error, changeset} = Accounts.apply_user_email(user, attrs) assert %{current_password: ["is not valid"]} = errors_on(changeset) end test "applies the email without persisting it", %{user: user} do email = unique_user_email() - {:ok, user} = Accounts.apply_user_email(user, valid_user_password(), %{email: email}) + attrs = %{"current_password" => valid_user_password(), "email" => email} + {:ok, user} = Accounts.apply_user_email(user, attrs) assert user.email == email assert Accounts.get_user!(user.id).email != email end @@ -245,52 +248,47 @@ defmodule Bones73k.AccountsTest do end test "validates password", %{user: user} do - {:error, changeset} = - Accounts.update_user_password(user, valid_user_password(), %{ - password: "not valid", - password_confirmation: "another" - }) + attrs = %{ + "current_password" => valid_user_password(), + "password" => "2shrt", + "password_confirmation" => "another" + } - assert %{ - password: ["should be at least 12 character(s)"], - password_confirmation: ["does not match password"] - } = errors_on(changeset) + {:error, changeset} = Accounts.update_user_password(user, attrs) + pw_err = "should be at least #{User.min_password()} character(s)" + conf_err = "does not match password" + assert pw_err in errors_on(changeset).password + assert conf_err in errors_on(changeset).password_confirmation end test "validates maximum values for password for security", %{user: user} do - too_long = String.duplicate("db", 100) + attrs = %{ + "current_password" => valid_user_password(), + "password" => String.duplicate("db", 100) + } - {:error, changeset} = - Accounts.update_user_password(user, valid_user_password(), %{password: too_long}) - - assert "should be at most 80 character(s)" in errors_on(changeset).password + {:error, changeset} = Accounts.update_user_password(user, attrs) + pw_err = "should be at most #{User.max_password()} character(s)" + assert pw_err in errors_on(changeset).password end test "validates current password", %{user: user} do - {:error, changeset} = - Accounts.update_user_password(user, "invalid", %{password: valid_user_password()}) - + attrs = %{"current_password" => "invalid", "password" => valid_user_password()} + {:error, changeset} = Accounts.update_user_password(user, attrs) assert %{current_password: ["is not valid"]} = errors_on(changeset) end test "updates the password", %{user: user} do - {:ok, user} = - Accounts.update_user_password(user, valid_user_password(), %{ - password: "new valid password" - }) - + attrs = %{"current_password" => valid_user_password(), "password" => "new valid password"} + {:ok, user} = Accounts.update_user_password(user, attrs) assert is_nil(user.password) assert Accounts.get_user_by_email_and_password(user.email, "new valid password") end test "deletes all tokens for the given user", %{user: user} do _ = Accounts.generate_user_session_token(user) - - {:ok, _} = - Accounts.update_user_password(user, valid_user_password(), %{ - password: "new valid password" - }) - + attrs = %{"current_password" => valid_user_password(), "password" => "new valid password"} + {:ok, _} = Accounts.update_user_password(user, attrs) refute Repo.get_by(UserToken, user_id: user.id) end end @@ -456,14 +454,13 @@ defmodule Bones73k.AccountsTest do test "validates password", %{user: user} do {:error, changeset} = Accounts.reset_user_password(user, %{ - password: "not valid", + password: "2shrt", password_confirmation: "another" }) - assert %{ - password: ["should be at least 12 character(s)"], - password_confirmation: ["does not match password"] - } = errors_on(changeset) + pw_err = "should be at least #{User.min_password()} character(s)" + assert pw_err in errors_on(changeset).password + assert "does not match password" in errors_on(changeset).password_confirmation end test "validates maximum values for password for security", %{user: user} do diff --git a/test/bones73k_web/controllers/user_registration_controller_test.exs b/test/bones73k_web/controllers/user_registration_controller_test.exs index f1d708e4..5466e1d3 100644 --- a/test/bones73k_web/controllers/user_registration_controller_test.exs +++ b/test/bones73k_web/controllers/user_registration_controller_test.exs @@ -13,7 +13,8 @@ defmodule Bones73kWeb.UserRegistrationControllerTest do end test "redirects if already logged in", %{conn: conn} do - conn = conn |> log_in_user(user_fixture()) |> get(Routes.user_registration_path(conn, :new)) + to = Routes.user_registration_path(conn, :new) + conn = conn |> log_in_user(user_fixture()) |> get(to) assert redirected_to(conn) == "/" end end diff --git a/test/bones73k_web/controllers/user_reset_password_controller_test.exs b/test/bones73k_web/controllers/user_reset_password_controller_test.exs index 9a7de0ec..6e173ae6 100644 --- a/test/bones73k_web/controllers/user_reset_password_controller_test.exs +++ b/test/bones73k_web/controllers/user_reset_password_controller_test.exs @@ -15,6 +15,12 @@ defmodule Bones73kWeb.UserResetPasswordControllerTest do response = html_response(conn, 200) assert response =~ "Forgot your password?\n </h3>" end + + test "redirects if already logged in", %{conn: conn} do + to = Routes.user_reset_password_path(conn, :new) + conn = conn |> log_in_user(user_fixture()) |> get(to) + assert redirected_to(conn) == "/" + end end describe "POST /users/reset_password" do diff --git a/test/bones73k_web/controllers/user_settings_controller_test.exs b/test/bones73k_web/controllers/user_settings_controller_test.exs index 94dc734d..dde5fb66 100644 --- a/test/bones73k_web/controllers/user_settings_controller_test.exs +++ b/test/bones73k_web/controllers/user_settings_controller_test.exs @@ -10,7 +10,7 @@ defmodule Bones73kWeb.UserSettingsControllerTest do test "renders settings page", %{conn: conn} do conn = get(conn, Routes.user_settings_path(conn, :edit)) response = html_response(conn, 200) - assert response =~ "<h1>Settings</h1>" + assert response =~ "User Settings\n</h3>" end test "redirects if user is not logged in" do @@ -20,71 +20,6 @@ defmodule Bones73kWeb.UserSettingsControllerTest do end end - describe "PUT /users/settings/update_password" do - test "updates the user password and resets tokens", %{conn: conn, user: user} do - new_password_conn = - put(conn, Routes.user_settings_path(conn, :update_password), %{ - "current_password" => valid_user_password(), - "user" => %{ - "password" => "new valid password", - "password_confirmation" => "new valid password" - } - }) - - assert redirected_to(new_password_conn) == Routes.user_settings_path(conn, :edit) - assert get_session(new_password_conn, :user_token) != get_session(conn, :user_token) - assert get_flash(new_password_conn, :info) =~ "Password updated successfully" - assert Accounts.get_user_by_email_and_password(user.email, "new valid password") - end - - test "does not update password on invalid data", %{conn: conn} do - old_password_conn = - put(conn, Routes.user_settings_path(conn, :update_password), %{ - "current_password" => "invalid", - "user" => %{ - "password" => "too short", - "password_confirmation" => "does not match" - } - }) - - response = html_response(old_password_conn, 200) - assert response =~ "<h1>Settings</h1>" - assert response =~ "should be at least 12 character(s)" - assert response =~ "does not match password" - assert response =~ "is not valid" - - assert get_session(old_password_conn, :user_token) == get_session(conn, :user_token) - end - end - - describe "PUT /users/settings/update_email" do - @tag :capture_log - test "updates the user email", %{conn: conn, user: user} do - conn = - put(conn, Routes.user_settings_path(conn, :update_email), %{ - "current_password" => valid_user_password(), - "user" => %{"email" => unique_user_email()} - }) - - assert redirected_to(conn) == Routes.user_settings_path(conn, :edit) - assert get_flash(conn, :info) =~ "A link to confirm your email" - assert Accounts.get_user_by_email(user.email) - end - - test "does not update email on invalid data", %{conn: conn} do - conn = - put(conn, Routes.user_settings_path(conn, :update_email), %{ - "current_password" => "invalid", - "user" => %{"email" => "with spaces"} - }) - - response = html_response(conn, 200) - assert response =~ "<h1>Settings</h1>" - assert response =~ "must have the @ sign and no spaces" - assert response =~ "is not valid" - end - end - describe "GET /users/settings/confirm_email/:token" do setup %{user: user} do email = unique_user_email() diff --git a/test/bones73k_web/live/admin_dashboard_live_test.exs b/test/bones73k_web/live/admin_dashboard_live_test.exs index 356529d3..1f3c50a6 100644 --- a/test/bones73k_web/live/admin_dashboard_live_test.exs +++ b/test/bones73k_web/live/admin_dashboard_live_test.exs @@ -52,9 +52,6 @@ defmodule Bones73kWeb.AdminDashboardLiveTest do assert "/" = redir_path = redirected_to(conn, 302) conn = get(recycle(conn), redir_path) - assert "/users/log_in" = redir_path = redirected_to(conn, 302) - conn = get(recycle(conn), redir_path) - assert html_response(conn, 200) =~ "You were logged out. Please login again to continue using our application." end diff --git a/test/bones73k_web/live/page_live_test.exs b/test/bones73k_web/live/page_live_test.exs index b8719e7a..a271747e 100644 --- a/test/bones73k_web/live/page_live_test.exs +++ b/test/bones73k_web/live/page_live_test.exs @@ -4,12 +4,6 @@ defmodule Bones73kWeb.PageLiveTest do import Phoenix.LiveViewTest import Bones73k.AccountsFixtures - test "disconnected and connected render without authentication should redirect to login page", - %{conn: conn} do - # If we don't previously log in we will be redirected to the login page - assert {:error, {:redirect, %{to: "/users/log_in"}}} = live(conn, "/") - end - test "disconnected and connected render with authentication should redirect to index page", %{ conn: conn } do @@ -44,9 +38,6 @@ defmodule Bones73kWeb.PageLiveTest do assert "/" = redir_path = redirected_to(conn, 302) conn = get(recycle(conn), redir_path) - assert "/users/log_in" = redir_path = redirected_to(conn, 302) - conn = get(recycle(conn), redir_path) - assert html_response(conn, 200) =~ "You were logged out. Please login again to continue using our application." end diff --git a/test/bones73k_web/live/property_live_test.exs b/test/bones73k_web/live/property_live_test.exs index c3429664..64a4fc7d 100644 --- a/test/bones73k_web/live/property_live_test.exs +++ b/test/bones73k_web/live/property_live_test.exs @@ -188,16 +188,13 @@ defmodule Bones73kWeb.PropertyLiveTest do assert_receive {:DOWN, ^ref, _, _, _} refute Process.alive?(index_live.pid) - # Assert our liveview was redirected, following first to /users/force_logout, then to "/", and then to "/users/log_in" + # Assert our liveview was redirected, following first to /users/force_logout, then to "/" assert_redirect(index_live, "/users/force_logout") conn = get(conn, "/users/force_logout") assert "/" = redir_path = redirected_to(conn, 302) conn = get(recycle(conn), redir_path) - assert "/users/log_in" = redir_path = redirected_to(conn, 302) - conn = get(recycle(conn), redir_path) - assert html_response(conn, 200) =~ "You were logged out. Please login again to continue using our application." end @@ -338,9 +335,6 @@ defmodule Bones73kWeb.PropertyLiveTest do assert "/" = redir_path = redirected_to(conn, 302) conn = get(recycle(conn), redir_path) - assert "/users/log_in" = redir_path = redirected_to(conn, 302) - conn = get(recycle(conn), redir_path) - assert html_response(conn, 200) =~ "You were logged out. Please login again to continue using our application." end diff --git a/test/bones73k_web/live/user/reset_password_test.exs b/test/bones73k_web/live/user/reset_password_test.exs index afab58d1..3526766c 100644 --- a/test/bones73k_web/live/user/reset_password_test.exs +++ b/test/bones73k_web/live/user/reset_password_test.exs @@ -6,8 +6,7 @@ defmodule Bones73kWeb.UserLive.ResetPasswordTest do alias Bones73k.Repo alias Bones73k.Accounts - alias Bones73k.Accounts.User - alias Bones73k.Accounts.UserToken + alias Bones73k.Accounts.{User, UserToken} setup %{conn: conn} do user = user_fixture() @@ -46,7 +45,7 @@ defmodule Bones73kWeb.UserLive.ResetPasswordTest do # Confirm redirected flash = assert_redirected(view, Routes.user_session_path(conn, :new)) - assert flash["success"] == "Password reset successfully." + assert flash["info"] == "Password reset successfully." # Confirm password was updated assert Accounts.get_user_by_email_and_password(user.email, new_pw) diff --git a/test/bones73k_web/live/user_dashboard_live_test.exs b/test/bones73k_web/live/user_dashboard_live_test.exs index a0f4e8b2..d2fea218 100644 --- a/test/bones73k_web/live/user_dashboard_live_test.exs +++ b/test/bones73k_web/live/user_dashboard_live_test.exs @@ -54,9 +54,6 @@ defmodule Bones73kWeb.UserDashboardLiveTest do assert "/" = redir_path = redirected_to(conn, 302) conn = get(recycle(conn), redir_path) - assert "/users/log_in" = redir_path = redirected_to(conn, 302) - conn = get(recycle(conn), redir_path) - assert html_response(conn, 200) =~ "You were logged out. Please login again to continue using our application." end