Skip to content
Snippets Groups Projects
Commit 358ef169 authored by kerdo's avatar kerdo
Browse files

Merge branch '14-fr-04-change-password' into 'main'

Resolve "FR-04: Change Password"

Closes #14

See merge request !6
parents 4e3bbae5 d61ade59
No related branches found
No related tags found
1 merge request!6Resolve "FR-04: Change Password"
Pipeline #43206 passed
......@@ -3,9 +3,13 @@ defmodule WhiteBreadConfig do
suite name: "User Registration Features",
context: UserRegistrationContext,
feature_paths: ["features/"]
feature_paths: ["features/user_registration.feature"]
suite name: "User Login Features",
context: UserLoginContext,
feature_paths: ["features/"]
feature_paths: ["features/user_login.feature"]
suite name: "Password Change Features",
context: PasswordChangeContext,
feature_paths: ["features/password_change.feature"]
end
defmodule PasswordChangeContext do
use WhiteBread.Context
use Hound.Helpers
alias PropTrackr.Accounts
alias PropTrackr.Repo
alias PropTrackr.Accounts.User
import PropTrackr.Authentication
scenario_starting_state fn _state ->
Ecto.Adapters.SQL.Sandbox.checkout(PropTrackr.Repo)
Ecto.Adapters.SQL.Sandbox.mode(PropTrackr.Repo, {:shared, self()})
Hound.start_session()
%{}
end
scenario_finalize fn _status, _state ->
Ecto.Adapters.SQL.Sandbox.checkin(PropTrackr.Repo)
Hound.end_session()
end
given_ ~r/^there exists following accounts$/, fn state, %{table_data: table} ->
table
|> Enum.map(fn user_details -> User.changeset(%User{}, user_details) end)
|> Enum.each(fn changeset -> Repo.insert!(changeset) end)
existing_user = List.first(table)
new_password = "new password cool"
{
:ok,
state
|> Map.put(:email, existing_user[:email])
|> Map.put(:password, existing_user[:password])
|> Map.put(:new_password, new_password)
}
end
and_ ~r/^I am logged in$/, fn state ->
setup_session(state[:email], state[:password])
{:ok, state}
end
and_ ~r/^I want to change my password$/, fn state ->
navigate_to("/me/password")
{:ok, state}
end
and_ ~r/^I enter my current password$/, fn state ->
fill_field({:id, "current_password"}, state[:password])
{:ok, state}
end
and_ ~r/^I enter my current password invalid$/, fn state ->
fill_field({:id, "current_password"}, "obviously_invalid")
{:ok, state}
end
and_ ~r/^I enter my new password and confirm it$/, fn state ->
fill_field({:id, "new_password"}, state[:new_password])
fill_field({:id, "new_password_confirmation"}, state[:new_password])
{:ok, state}
end
and_ ~r/^I enter my new password and confirm it with a password that does not match$/, fn state ->
fill_field({:id, "new_password"}, state[:new_password])
fill_field({:id, "new_password_confirmation"}, "obviously_invalid")
{:ok, state}
end
and_ ~r/^I enter my new password and confirm it that match the current password$/, fn state ->
fill_field({:id, "new_password"}, state[:password])
fill_field({:id, "new_password_confirmation"}, state[:password])
{:ok, state}
end
when_ ~r/^I click change password$/, fn state ->
click({:id, "change_password"})
{:ok, state}
end
then_ ~r/^I should receive a success notification$/, fn state ->
assert visible_in_page? ~r"Password changed successfully! Please log in again"
{:ok, state}
end
then_ ~r/^I should receive an error message stating invalid current password$/, fn state ->
assert visible_in_page? ~r"Invalid current password!"
{:ok, state}
end
then_ ~r/^I should receive an error message stating that the new passwords don't match$/, fn state ->
assert visible_in_page? ~r"New passwords don't match!"
{:ok, state}
end
then_ ~r/^I should receive an error message stating that the new password is the same as the old password$/, fn state ->
assert visible_in_page? ~r"New password can't be the same as the old password!"
{:ok, state}
end
and_ ~r/^I should be shown the change password form again$/, fn state ->
assert visible_in_page? ~r"Change your password"
{:ok, state}
end
and_ ~r/^I should be redirected to the login page$/, fn state ->
assert current_path() == "/login"
{:ok, state}
end
defp setup_session(email, password) do
navigate_to("/login")
fill_field({:id, "email"}, email)
fill_field({:id, "password"}, password)
click({:id, "login_button"})
end
end
Feature: Password Change
Scenario: Authenticated user can change their password
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And I am logged in
And I want to change my password
And I enter my current password
And I enter my new password and confirm it
When I click change password
Then I should receive a success notification
And I should be redirected to the login page
Scenario: Authenticated user cannot change their password when invalid current password
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And I am logged in
And I want to change my password
And I enter my current password invalid
And I enter my new password and confirm it
When I click change password
Then I should receive an error message stating invalid current password
And I should be shown the change password form again
Scenario: Authenticated user cannot change their password when new passwords don't match
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And I am logged in
And I want to change my password
And I enter my current password
And I enter my new password and confirm it with a password that does not match
When I click change password
Then I should receive an error message stating that the new passwords don't match
And I should be shown the change password form again
Scenario: Authenticated user cannot change their password when new password is the same as the old password
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And I am logged in
And I want to change my password
And I enter my current password
And I enter my new password and confirm it that match the current password
When I click change password
Then I should receive an error message stating that the new password is the same as the old password
And I should be shown the change password form again
defmodule PropTrackrWeb.PasswordController do
use PropTrackrWeb, :controller
alias PropTrackr.Repo
alias PropTrackr.Accounts.User
import Ecto.Query, only: [from: 2]
def index(conn, _params) do
render conn, "index.html"
end
def create(conn, %{"current_password" => current_password, "new_password" => new_password, "new_password_confirmation" => new_password_confirmation}) do
cond do
new_password != new_password_confirmation ->
conn
|> put_flash(:error, "New passwords don't match!")
|> render("index.html")
# TODO: Insecure, needs refactoring when taking security into account
new_password == current_password ->
conn
|> put_flash(:error, "New password can't be the same as the old password!")
|> render("index.html")
true ->
user_id = get_session(conn, :user_id)
user = Repo.get(User, user_id)
if user.password == current_password do
{row_count, _} = from(u in User, where: u.id == ^user_id, select: u)
|> Repo.update_all(set: [password: new_password])
if row_count == 1 do
conn
|> delete_session(:user_id)
|> put_flash(:info, "Password changed successfully! Please log in again")
|> redirect(to: ~p"/login")
else
# Note: Adding this just in case the update fails for some odd reason
conn
|> put_flash(:error, "Unknown error has occurred!")
|> redirect(to: ~p"/")
end
else
conn
|> put_flash(:error, "Invalid current password!")
|> render("index.html")
end
end
end
end
defmodule PropTrackrWeb.PasswordHTML do
use PropTrackrWeb, :html
embed_templates "password_html/*"
end
<.header>
Change your password
</.header>
<.simple_form :let={f} for={} action={~p"/me/password"}>
<.input field={f[:current_password]} type="password" required label="Current password" />
<.input field={f[:new_password]} type="password" required label="New password" />
<.input field={f[:new_password_confirmation]} type="password" required label="Confirm password" />
<:actions>
<.button id="change_password">Change password</.button>
</:actions>
</.simple_form>
<.back navigate={~p"/"}>Changed your mind?</.back>
......@@ -22,6 +22,7 @@ defmodule PropTrackrWeb.Router do
resources "/users", UserController, only: [:index]
resources "/register", RegisterController, only: [:index, :create]
resources "/login", LoginController, only: [:index, :create]
resources "/me/password", PasswordController, only: [:index, :create]
end
# Other scopes may use custom stacks.
......
defmodule PropTrackrWeb.PasswordControllerTest do
use PropTrackrWeb.ConnCase
alias PropTrackr.Accounts.User
alias PropTrackr.Repo
@user_credentials %{email: "test.user@gmail.com", password: "testing", new_password: "new_password"}
setup do
user = %User{
name: "Test",
surname: "User",
birth_date: "2000-01-01",
phone_number: "000",
bio: "Yo",
email: "test.user@gmail.com",
password: "testing",
confirm_password: "testing",
}
user = Repo.insert!(user)
{:ok, %{user: user}}
end
test "Authenticated user should be able to change their password with correct current credentials and matching new passwords", %{conn: conn, user: user} do
conn = conn |> setup_session(user)
conn = conn
|> post("/me/password",
current_password: @user_credentials[:password],
new_password: @user_credentials[:new_password],
new_password_confirmation: @user_credentials[:new_password])
assert redirected_to(conn) == "/login"
conn = get conn, redirected_to(conn)
assert html_response(conn, 200)
assert get_flash(conn, :info) =~ ~r/Password changed successfully! Please log in again/
assert get_session(conn, :user_id) == nil
updated_user = Repo.get(User, user.id)
assert updated_user.password == @user_credentials[:new_password]
end
test "Authenticated user should receive an error if the current password is invalid", %{conn: conn, user: user} do
conn = conn |> setup_session(user)
conn = conn
|> post("/me/password",
current_password: "obviously_invalid",
new_password: @user_credentials[:new_password],
new_password_confirmation: @user_credentials[:new_password])
assert html_response(conn, 200)
assert get_flash(conn, :error) =~ ~r/Invalid current password!/
end
test "Authenticated user should receive an error if the new password does not match the new password confirmation", %{conn: conn, user: user} do
conn = conn |> setup_session(user)
conn = conn
|> post("/me/password",
current_password: @user_credentials[:password],
new_password: @user_credentials[:new_password],
new_password_confirmation: "obviously_does_not_match")
assert html_response(conn, 200)
assert get_flash(conn, :error) =~ ~r/New passwords don't match!/
end
test "Authenticated user should receive an error if the new password is the same as the old password", %{conn: conn, user: user} do
conn = conn |> setup_session(user)
conn = conn
|> post("/me/password",
current_password: @user_credentials[:password],
new_password: @user_credentials[:password],
new_password_confirmation: @user_credentials[:password])
assert html_response(conn, 200)
assert get_flash(conn, :error) =~ ~r/New password can't be the same as the old password!/
end
defp setup_session(conn, user) do
conn = conn |> post("/login", email: user.email, password: user.password)
conn = get conn, redirected_to(conn)
conn
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment