From d6e2e2f24aa69272c1ebe1e1b0aed207d4dada1c Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Mon, 29 Jan 2024 16:22:42 +0100 Subject: [PATCH] Users -> user --- lib/cklist/accounts.ex | 264 +++++------ lib/cklist/accounts/{users.ex => user.ex} | 28 +- .../{users_notifier.ex => user_notifier.ex} | 24 +- .../{users_token.ex => user_token.ex} | 62 +-- lib/cklist_web/components/core_components.ex | 2 +- ...ntroller.ex => user_session_controller.ex} | 18 +- ...=> user_confirmation_instructions_live.ex} | 18 +- ...tion_live.ex => user_confirmation_live.ex} | 26 +- ...d_live.ex => user_forgot_password_live.ex} | 18 +- ...users_login_live.ex => user_login_live.ex} | 10 +- ...tion_live.ex => user_registration_live.ex} | 30 +- ...rd_live.ex => user_reset_password_live.ex} | 34 +- ...settings_live.ex => user_settings_live.ex} | 60 +-- lib/cklist_web/router.ex | 42 +- .../{users_auth.ex => user_auth.ex} | 110 ++--- ...0240121112709_create_users_auth_tables.exs | 8 +- priv/repo/seeds.exs | 7 + test/cklist/accounts_test.exs | 437 +++++++++--------- .../user_session_controller_test.exs | 113 +++++ .../users_session_controller_test.exs | 113 ----- ...r_confirmation_instructions_live_test.exs} | 30 +- ...st.exs => user_confirmation_live_test.exs} | 40 +- ...exs => user_forgot_password_live_test.exs} | 28 +- ...live_test.exs => user_login_live_test.exs} | 32 +- ...st.exs => user_registration_live_test.exs} | 32 +- ....exs => user_reset_password_live_test.exs} | 42 +- ...e_test.exs => user_settings_live_test.exs} | 106 ++--- test/cklist_web/user_auth_test.exs | 272 +++++++++++ test/cklist_web/users_auth_test.exs | 272 ----------- test/support/conn_case.ex | 20 +- test/support/fixtures/accounts_fixtures.ex | 22 +- 31 files changed, 1166 insertions(+), 1154 deletions(-) rename lib/cklist/accounts/{users.ex => user.ex} (88%) rename lib/cklist/accounts/{users_notifier.ex => user_notifier.ex} (67%) rename lib/cklist/accounts/{users_token.ex => user_token.ex} (74%) rename lib/cklist_web/controllers/{users_session_controller.ex => user_session_controller.ex} (63%) rename lib/cklist_web/live/{users_confirmation_instructions_live.ex => user_confirmation_instructions_live.ex} (68%) rename lib/cklist_web/live/{users_confirmation_live.ex => user_confirmation_live.ex} (56%) rename lib/cklist_web/live/{users_forgot_password_live.ex => user_forgot_password_live.ex} (67%) rename lib/cklist_web/live/{users_login_live.ex => user_login_live.ex} (79%) rename lib/cklist_web/live/{users_registration_live.ex => user_registration_live.ex} (69%) rename lib/cklist_web/live/{users_reset_password_live.ex => user_reset_password_live.ex} (62%) rename lib/cklist_web/live/{users_settings_live.ex => user_settings_live.ex} (72%) rename lib/cklist_web/{users_auth.ex => user_auth.ex} (56%) create mode 100644 test/cklist_web/controllers/user_session_controller_test.exs delete mode 100644 test/cklist_web/controllers/users_session_controller_test.exs rename test/cklist_web/live/{users_confirmation_instructions_live_test.exs => user_confirmation_instructions_live_test.exs} (52%) rename test/cklist_web/live/{users_confirmation_live_test.exs => user_confirmation_live_test.exs} (55%) rename test/cklist_web/live/{users_forgot_password_live_test.exs => user_forgot_password_live_test.exs} (54%) rename test/cklist_web/live/{users_login_live_test.exs => user_login_live_test.exs} (63%) rename test/cklist_web/live/{users_registration_live_test.exs => user_registration_live_test.exs} (64%) rename test/cklist_web/live/{users_reset_password_live_test.exs => user_reset_password_live_test.exs} (66%) rename test/cklist_web/live/{users_settings_live_test.exs => user_settings_live_test.exs} (57%) create mode 100644 test/cklist_web/user_auth_test.exs delete mode 100644 test/cklist_web/users_auth_test.exs diff --git a/lib/cklist/accounts.ex b/lib/cklist/accounts.ex index 49af856..47acd07 100644 --- a/lib/cklist/accounts.ex +++ b/lib/cklist/accounts.ex @@ -6,106 +6,106 @@ defmodule Cklist.Accounts do import Ecto.Query, warn: false alias Cklist.Repo - alias Cklist.Accounts.{Users, UsersToken, UsersNotifier} + alias Cklist.Accounts.{User, UserToken, UserNotifier} ## Database getters @doc """ - Gets a users by email. + Gets a user by email. ## Examples - iex> get_users_by_email("foo@example.com") - %Users{} + iex> get_user_by_email("foo@example.com") + %User{} - iex> get_users_by_email("unknown@example.com") + iex> get_user_by_email("unknown@example.com") nil """ - def get_users_by_email(email) when is_binary(email) do - Repo.get_by(Users, email: email) + def get_user_by_email(email) when is_binary(email) do + Repo.get_by(User, email: email) end @doc """ - Gets a users by email and password. + Gets a user by email and password. ## Examples - iex> get_users_by_email_and_password("foo@example.com", "correct_password") - %Users{} + iex> get_user_by_email_and_password("foo@example.com", "correct_password") + %User{} - iex> get_users_by_email_and_password("foo@example.com", "invalid_password") + iex> get_user_by_email_and_password("foo@example.com", "invalid_password") nil """ - def get_users_by_email_and_password(email, password) + def get_user_by_email_and_password(email, password) when is_binary(email) and is_binary(password) do - users = Repo.get_by(Users, email: email) - if Users.valid_password?(users, password), do: users + user = Repo.get_by(User, email: email) + if User.valid_password?(user, password), do: user end @doc """ - Gets a single users. + Gets a single user. - Raises `Ecto.NoResultsError` if the Users does not exist. + Raises `Ecto.NoResultsError` if the User does not exist. ## Examples - iex> get_users!(123) - %Users{} + iex> get_user!(123) + %User{} - iex> get_users!(456) + iex> get_user!(456) ** (Ecto.NoResultsError) """ - def get_users!(id), do: Repo.get!(Users, id) + def get_user!(id), do: Repo.get!(User, id) - ## Users registration + ## User registration @doc """ - Registers a users. + Registers a user. ## Examples - iex> register_users(%{field: value}) - {:ok, %Users{}} + iex> register_user(%{field: value}) + {:ok, %User{}} - iex> register_users(%{field: bad_value}) + iex> register_user(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ - def register_users(attrs) do - %Users{} - |> Users.registration_changeset(attrs) + def register_user(attrs) do + %User{} + |> User.registration_changeset(attrs) |> Repo.insert() end @doc """ - Returns an `%Ecto.Changeset{}` for tracking users changes. + Returns an `%Ecto.Changeset{}` for tracking user changes. ## Examples - iex> change_users_registration(users) - %Ecto.Changeset{data: %Users{}} + iex> change_user_registration(user) + %Ecto.Changeset{data: %User{}} """ - def change_users_registration(%Users{} = users, attrs \\ %{}) do - Users.registration_changeset(users, attrs, hash_password: false, validate_email: false) + def change_user_registration(%User{} = user, attrs \\ %{}) do + User.registration_changeset(user, attrs, hash_password: false, validate_email: false) end ## Settings @doc """ - Returns an `%Ecto.Changeset{}` for changing the users email. + Returns an `%Ecto.Changeset{}` for changing the user email. ## Examples - iex> change_users_email(users) - %Ecto.Changeset{data: %Users{}} + iex> change_user_email(user) + %Ecto.Changeset{data: %User{}} """ - def change_users_email(users, attrs \\ %{}) do - Users.email_changeset(users, attrs, validate_email: false) + def change_user_email(user, attrs \\ %{}) do + User.email_changeset(user, attrs, validate_email: false) end @doc """ @@ -114,104 +114,104 @@ defmodule Cklist.Accounts do ## Examples - iex> apply_users_email(users, "valid password", %{email: ...}) - {:ok, %Users{}} + iex> apply_user_email(user, "valid password", %{email: ...}) + {:ok, %User{}} - iex> apply_users_email(users, "invalid password", %{email: ...}) + iex> apply_user_email(user, "invalid password", %{email: ...}) {:error, %Ecto.Changeset{}} """ - def apply_users_email(users, password, attrs) do - users - |> Users.email_changeset(attrs) - |> Users.validate_current_password(password) + def apply_user_email(user, password, attrs) do + user + |> User.email_changeset(attrs) + |> User.validate_current_password(password) |> Ecto.Changeset.apply_action(:update) end @doc """ - Updates the users email using the given token. + Updates the user email using the given token. - If the token matches, the users email is updated and the token is deleted. + If the token matches, the user email is updated and the token is deleted. The confirmed_at date is also updated to the current time. """ - def update_users_email(users, token) do - context = "change:#{users.email}" + def update_user_email(user, token) do + context = "change:#{user.email}" - with {:ok, query} <- UsersToken.verify_change_email_token_query(token, context), - %UsersToken{sent_to: email} <- Repo.one(query), - {:ok, _} <- Repo.transaction(users_email_multi(users, email, context)) do + with {:ok, query} <- UserToken.verify_change_email_token_query(token, context), + %UserToken{sent_to: email} <- Repo.one(query), + {:ok, _} <- Repo.transaction(user_email_multi(user, email, context)) do :ok else _ -> :error end end - defp users_email_multi(users, email, context) do + defp user_email_multi(user, email, context) do changeset = - users - |> Users.email_changeset(%{email: email}) - |> Users.confirm_changeset() + user + |> User.email_changeset(%{email: email}) + |> User.confirm_changeset() Ecto.Multi.new() - |> Ecto.Multi.update(:users, changeset) - |> Ecto.Multi.delete_all(:tokens, UsersToken.by_users_and_contexts_query(users, [context])) + |> Ecto.Multi.update(:user, changeset) + |> Ecto.Multi.delete_all(:tokens, UserToken.by_user_and_contexts_query(user, [context])) end @doc ~S""" - Delivers the update email instructions to the given users. + Delivers the update email instructions to the given user. ## Examples - iex> deliver_users_update_email_instructions(users, current_email, &url(~p"/users/settings/confirm_email/#{&1})") + iex> deliver_user_update_email_instructions(user, current_email, &url(~p"/user/settings/confirm_email/#{&1})") {:ok, %{to: ..., body: ...}} """ - def deliver_users_update_email_instructions(%Users{} = users, current_email, update_email_url_fun) + def deliver_user_update_email_instructions(%User{} = user, current_email, update_email_url_fun) when is_function(update_email_url_fun, 1) do - {encoded_token, users_token} = UsersToken.build_email_token(users, "change:#{current_email}") + {encoded_token, user_token} = UserToken.build_email_token(user, "change:#{current_email}") - Repo.insert!(users_token) - UsersNotifier.deliver_update_email_instructions(users, update_email_url_fun.(encoded_token)) + Repo.insert!(user_token) + UserNotifier.deliver_update_email_instructions(user, update_email_url_fun.(encoded_token)) end @doc """ - Returns an `%Ecto.Changeset{}` for changing the users password. + Returns an `%Ecto.Changeset{}` for changing the user password. ## Examples - iex> change_users_password(users) - %Ecto.Changeset{data: %Users{}} + iex> change_user_password(user) + %Ecto.Changeset{data: %User{}} """ - def change_users_password(users, attrs \\ %{}) do - Users.password_changeset(users, attrs, hash_password: false) + def change_user_password(user, attrs \\ %{}) do + User.password_changeset(user, attrs, hash_password: false) end @doc """ - Updates the users password. + Updates the user password. ## Examples - iex> update_users_password(users, "valid password", %{password: ...}) - {:ok, %Users{}} + iex> update_user_password(user, "valid password", %{password: ...}) + {:ok, %User{}} - iex> update_users_password(users, "invalid password", %{password: ...}) + iex> update_user_password(user, "invalid password", %{password: ...}) {:error, %Ecto.Changeset{}} """ - def update_users_password(users, password, attrs) do + def update_user_password(user, password, attrs) do changeset = - users - |> Users.password_changeset(attrs) - |> Users.validate_current_password(password) + user + |> User.password_changeset(attrs) + |> User.validate_current_password(password) Ecto.Multi.new() - |> Ecto.Multi.update(:users, changeset) - |> Ecto.Multi.delete_all(:tokens, UsersToken.by_users_and_contexts_query(users, :all)) + |> Ecto.Multi.update(:user, changeset) + |> Ecto.Multi.delete_all(:tokens, UserToken.by_user_and_contexts_query(user, :all)) |> Repo.transaction() |> case do - {:ok, %{users: users}} -> {:ok, users} - {:error, :users, changeset, _} -> {:error, changeset} + {:ok, %{user: user}} -> {:ok, user} + {:error, :user, changeset, _} -> {:error, changeset} end end @@ -220,134 +220,138 @@ defmodule Cklist.Accounts do @doc """ Generates a session token. """ - def generate_users_session_token(users) do - {token, users_token} = UsersToken.build_session_token(users) - Repo.insert!(users_token) + def generate_user_session_token(user) do + {token, user_token} = UserToken.build_session_token(user) + Repo.insert!(user_token) token end @doc """ - Gets the users with the given signed token. + Gets the user with the given signed token. """ - def get_users_by_session_token(token) do - {:ok, query} = UsersToken.verify_session_token_query(token) + def get_user_by_session_token(token) do + {:ok, query} = UserToken.verify_session_token_query(token) Repo.one(query) end @doc """ Deletes the signed token with the given context. """ - def delete_users_session_token(token) do - Repo.delete_all(UsersToken.by_token_and_context_query(token, "session")) + def delete_user_session_token(token) do + Repo.delete_all(UserToken.by_token_and_context_query(token, "session")) :ok end ## Confirmation @doc ~S""" - Delivers the confirmation email instructions to the given users. + Delivers the confirmation email instructions to the given user. ## Examples - iex> deliver_users_confirmation_instructions(users, &url(~p"/users/confirm/#{&1}")) + iex> deliver_user_confirmation_instructions(user, &url(~p"/user/confirm/#{&1}")) {:ok, %{to: ..., body: ...}} - iex> deliver_users_confirmation_instructions(confirmed_users, &url(~p"/users/confirm/#{&1}")) + iex> deliver_user_confirmation_instructions(confirmed_user, &url(~p"/user/confirm/#{&1}")) {:error, :already_confirmed} """ - def deliver_users_confirmation_instructions(%Users{} = users, confirmation_url_fun) + def deliver_user_confirmation_instructions(%User{} = user, confirmation_url_fun) when is_function(confirmation_url_fun, 1) do - if users.confirmed_at do + if user.confirmed_at do {:error, :already_confirmed} else - {encoded_token, users_token} = UsersToken.build_email_token(users, "confirm") - Repo.insert!(users_token) - UsersNotifier.deliver_confirmation_instructions(users, confirmation_url_fun.(encoded_token)) + {encoded_token, user_token} = UserToken.build_email_token(user, "confirm") + Repo.insert!(user_token) + UserNotifier.deliver_confirmation_instructions(user, confirmation_url_fun.(encoded_token)) end end @doc """ - Confirms a users by the given token. + Confirms a user by the given token. - If the token matches, the users account is marked as confirmed + If the token matches, the user account is marked as confirmed and the token is deleted. """ - def confirm_users(token) do - with {:ok, query} <- UsersToken.verify_email_token_query(token, "confirm"), - %Users{} = users <- Repo.one(query), - {:ok, %{users: users}} <- Repo.transaction(confirm_users_multi(users)) do - {:ok, users} + def confirm_user(token) do + with {:ok, query} <- UserToken.verify_email_token_query(token, "confirm"), + %User{} = user <- Repo.one(query), + {:ok, %{user: user}} <- Repo.transaction(confirm_user_multi(user)) do + {:ok, user} else _ -> :error end end - defp confirm_users_multi(users) do + defp confirm_user_multi(user) do Ecto.Multi.new() - |> Ecto.Multi.update(:users, Users.confirm_changeset(users)) - |> Ecto.Multi.delete_all(:tokens, UsersToken.by_users_and_contexts_query(users, ["confirm"])) + |> Ecto.Multi.update(:user, User.confirm_changeset(user)) + |> Ecto.Multi.delete_all(:tokens, UserToken.by_user_and_contexts_query(user, ["confirm"])) end ## Reset password @doc ~S""" - Delivers the reset password email to the given users. + Delivers the reset password email to the given user. ## Examples - iex> deliver_users_reset_password_instructions(users, &url(~p"/users/reset_password/#{&1}")) + iex> deliver_user_reset_password_instructions(user, &url(~p"/user/reset_password/#{&1}")) {:ok, %{to: ..., body: ...}} """ - def deliver_users_reset_password_instructions(%Users{} = users, reset_password_url_fun) + def deliver_user_reset_password_instructions(%User{} = user, reset_password_url_fun) when is_function(reset_password_url_fun, 1) do - {encoded_token, users_token} = UsersToken.build_email_token(users, "reset_password") - Repo.insert!(users_token) - UsersNotifier.deliver_reset_password_instructions(users, reset_password_url_fun.(encoded_token)) + {encoded_token, user_token} = UserToken.build_email_token(user, "reset_password") + Repo.insert!(user_token) + + UserNotifier.deliver_reset_password_instructions( + user, + reset_password_url_fun.(encoded_token) + ) end @doc """ - Gets the users by reset password token. + Gets the user by reset password token. ## Examples - iex> get_users_by_reset_password_token("validtoken") - %Users{} + iex> get_user_by_reset_password_token("validtoken") + %User{} - iex> get_users_by_reset_password_token("invalidtoken") + iex> get_user_by_reset_password_token("invalidtoken") nil """ - def get_users_by_reset_password_token(token) do - with {:ok, query} <- UsersToken.verify_email_token_query(token, "reset_password"), - %Users{} = users <- Repo.one(query) do - users + def get_user_by_reset_password_token(token) do + with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"), + %User{} = user <- Repo.one(query) do + user else _ -> nil end end @doc """ - Resets the users password. + Resets the user password. ## Examples - iex> reset_users_password(users, %{password: "new long password", password_confirmation: "new long password"}) - {:ok, %Users{}} + iex> reset_user_password(user, %{password: "new long password", password_confirmation: "new long password"}) + {:ok, %User{}} - iex> reset_users_password(users, %{password: "valid", password_confirmation: "not the same"}) + iex> reset_user_password(user, %{password: "valid", password_confirmation: "not the same"}) {:error, %Ecto.Changeset{}} """ - def reset_users_password(users, attrs) do + def reset_user_password(user, attrs) do Ecto.Multi.new() - |> Ecto.Multi.update(:users, Users.password_changeset(users, attrs)) - |> Ecto.Multi.delete_all(:tokens, UsersToken.by_users_and_contexts_query(users, :all)) + |> Ecto.Multi.update(:user, User.password_changeset(user, attrs)) + |> Ecto.Multi.delete_all(:tokens, UserToken.by_user_and_contexts_query(user, :all)) |> Repo.transaction() |> case do - {:ok, %{users: users}} -> {:ok, users} - {:error, :users, changeset, _} -> {:error, changeset} + {:ok, %{user: user}} -> {:ok, user} + {:error, :user, changeset, _} -> {:error, changeset} end end end diff --git a/lib/cklist/accounts/users.ex b/lib/cklist/accounts/user.ex similarity index 88% rename from lib/cklist/accounts/users.ex rename to lib/cklist/accounts/user.ex index a87345e..9447257 100644 --- a/lib/cklist/accounts/users.ex +++ b/lib/cklist/accounts/user.ex @@ -1,4 +1,4 @@ -defmodule Cklist.Accounts.Users do +defmodule Cklist.Accounts.User do use Ecto.Schema import Ecto.Changeset @@ -12,7 +12,7 @@ defmodule Cklist.Accounts.Users do end @doc """ - A users changeset for registration. + A user changeset for registration. It is important to validate the length of both email and password. Otherwise databases may truncate the email without warnings, which @@ -34,8 +34,8 @@ defmodule Cklist.Accounts.Users do submitting the form), this option can be set to `false`. Defaults to `true`. """ - def registration_changeset(users, attrs, opts \\ []) do - users + def registration_changeset(user, attrs, opts \\ []) do + user |> cast(attrs, [:email, :password]) |> validate_email(opts) |> validate_password(opts) @@ -88,12 +88,12 @@ defmodule Cklist.Accounts.Users do end @doc """ - A users changeset for changing the email. + A user changeset for changing the email. It requires the email to change otherwise an error is added. """ - def email_changeset(users, attrs, opts \\ []) do - users + def email_changeset(user, attrs, opts \\ []) do + user |> cast(attrs, [:email]) |> validate_email(opts) |> case do @@ -103,7 +103,7 @@ defmodule Cklist.Accounts.Users do end @doc """ - A users changeset for changing the password. + A user changeset for changing the password. ## Options @@ -114,8 +114,8 @@ defmodule Cklist.Accounts.Users do validations on a LiveView form), this option can be set to `false`. Defaults to `true`. """ - def password_changeset(users, attrs, opts \\ []) do - users + def password_changeset(user, attrs, opts \\ []) do + user |> cast(attrs, [:password]) |> validate_confirmation(:password, message: "does not match password") |> validate_password(opts) @@ -124,18 +124,18 @@ defmodule Cklist.Accounts.Users do @doc """ Confirms the account by setting `confirmed_at`. """ - def confirm_changeset(users) do + def confirm_changeset(user) do now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) - change(users, confirmed_at: now) + change(user, confirmed_at: now) end @doc """ Verifies the password. - If there is no users or the users doesn't have a password, we call + If there is no user or the user doesn't have a password, we call `Bcrypt.no_user_verify/0` to avoid timing attacks. """ - def valid_password?(%Cklist.Accounts.Users{hashed_password: hashed_password}, password) + def valid_password?(%Cklist.Accounts.User{hashed_password: hashed_password}, password) when is_binary(hashed_password) and byte_size(password) > 0 do Bcrypt.verify_pass(password, hashed_password) end diff --git a/lib/cklist/accounts/users_notifier.ex b/lib/cklist/accounts/user_notifier.ex similarity index 67% rename from lib/cklist/accounts/users_notifier.ex rename to lib/cklist/accounts/user_notifier.ex index 0ead643..3582b10 100644 --- a/lib/cklist/accounts/users_notifier.ex +++ b/lib/cklist/accounts/user_notifier.ex @@ -1,4 +1,4 @@ -defmodule Cklist.Accounts.UsersNotifier do +defmodule Cklist.Accounts.UserNotifier do import Swoosh.Email alias Cklist.Mailer @@ -20,12 +20,12 @@ defmodule Cklist.Accounts.UsersNotifier do @doc """ Deliver instructions to confirm account. """ - def deliver_confirmation_instructions(users, url) do - deliver(users.email, "Confirmation instructions", """ + def deliver_confirmation_instructions(user, url) do + deliver(user.email, "Confirmation instructions", """ ============================== - Hi #{users.email}, + Hi #{user.email}, You can confirm your account by visiting the URL below: @@ -38,14 +38,14 @@ defmodule Cklist.Accounts.UsersNotifier do end @doc """ - Deliver instructions to reset a users password. + Deliver instructions to reset a user password. """ - def deliver_reset_password_instructions(users, url) do - deliver(users.email, "Reset password instructions", """ + def deliver_reset_password_instructions(user, url) do + deliver(user.email, "Reset password instructions", """ ============================== - Hi #{users.email}, + Hi #{user.email}, You can reset your password by visiting the URL below: @@ -58,14 +58,14 @@ defmodule Cklist.Accounts.UsersNotifier do end @doc """ - Deliver instructions to update a users email. + Deliver instructions to update a user email. """ - def deliver_update_email_instructions(users, url) do - deliver(users.email, "Update email instructions", """ + def deliver_update_email_instructions(user, url) do + deliver(user.email, "Update email instructions", """ ============================== - Hi #{users.email}, + Hi #{user.email}, You can change your email by visiting the URL below: diff --git a/lib/cklist/accounts/users_token.ex b/lib/cklist/accounts/user_token.ex similarity index 74% rename from lib/cklist/accounts/users_token.ex rename to lib/cklist/accounts/user_token.ex index 62d30fe..17f0feb 100644 --- a/lib/cklist/accounts/users_token.ex +++ b/lib/cklist/accounts/user_token.ex @@ -1,7 +1,7 @@ -defmodule Cklist.Accounts.UsersToken do +defmodule Cklist.Accounts.UserToken do use Ecto.Schema import Ecto.Query - alias Cklist.Accounts.UsersToken + alias Cklist.Accounts.UserToken @hash_algorithm :sha256 @rand_size 32 @@ -13,11 +13,11 @@ defmodule Cklist.Accounts.UsersToken do @change_email_validity_in_days 7 @session_validity_in_days 60 - schema "users_tokens" do + schema "user_tokens" do field :token, :binary field :context, :string field :sent_to, :string - belongs_to :users, Cklist.Accounts.Users + belongs_to :user, Cklist.Accounts.User timestamps(updated_at: false) end @@ -34,22 +34,22 @@ defmodule Cklist.Accounts.UsersToken do valid indefinitely, unless you change the signing/encryption salt. - Therefore, storing them allows individual users + Therefore, storing them allows individual user sessions to be expired. The token system can also be extended to store additional data, such as the device used for logging in. You could then use this information to display all valid sessions - and devices in the UI and allow users to explicitly expire any + and devices in the UI and allow user to explicitly expire any session they deem invalid. """ - def build_session_token(users) do + def build_session_token(user) do token = :crypto.strong_rand_bytes(@rand_size) - {token, %UsersToken{token: token, context: "session", users_id: users.id}} + {token, %UserToken{token: token, context: "session", user_id: user.id}} end @doc """ Checks if the token is valid and returns its underlying lookup query. - The query returns the users found by the token, if any. + The query returns the user found by the token, if any. The token is valid if it matches the value in the database and it has not expired (after @session_validity_in_days). @@ -57,47 +57,47 @@ defmodule Cklist.Accounts.UsersToken do def verify_session_token_query(token) do query = from token in by_token_and_context_query(token, "session"), - join: users in assoc(token, :users), + join: user in assoc(token, :user), where: token.inserted_at > ago(@session_validity_in_days, "day"), - select: users + select: user {:ok, query} end @doc """ - Builds a token and its hash to be delivered to the users's email. + Builds a token and its hash to be delivered to the user's email. - The non-hashed token is sent to the users email while the + The non-hashed token is sent to the user email while the hashed part is stored in the database. The original token cannot be reconstructed, which means anyone with read-only access to the database cannot directly use the token in the application to gain access. Furthermore, if the user changes their email in the system, the tokens sent to the previous email are no longer valid. - Users can easily adapt the existing code to provide other types of delivery methods, + User can easily adapt the existing code to provide other types of delivery methods, for example, by phone numbers. """ - def build_email_token(users, context) do - build_hashed_token(users, context, users.email) + def build_email_token(user, context) do + build_hashed_token(user, context, user.email) end - defp build_hashed_token(users, context, sent_to) do + defp build_hashed_token(user, context, sent_to) do token = :crypto.strong_rand_bytes(@rand_size) hashed_token = :crypto.hash(@hash_algorithm, token) {Base.url_encode64(token, padding: false), - %UsersToken{ + %UserToken{ token: hashed_token, context: context, sent_to: sent_to, - users_id: users.id + user_id: user.id }} end @doc """ Checks if the token is valid and returns its underlying lookup query. - The query returns the users found by the token, if any. + The query returns the user found by the token, if any. The given token is valid if it matches its hashed counterpart in the database and the user email has not changed. This function also checks @@ -115,9 +115,9 @@ defmodule Cklist.Accounts.UsersToken do query = from token in by_token_and_context_query(hashed_token, context), - join: users in assoc(token, :users), - where: token.inserted_at > ago(^days, "day") and token.sent_to == users.email, - select: users + join: user in assoc(token, :user), + where: token.inserted_at > ago(^days, "day") and token.sent_to == user.email, + select: user {:ok, query} @@ -132,9 +132,9 @@ defmodule Cklist.Accounts.UsersToken do @doc """ Checks if the token is valid and returns its underlying lookup query. - The query returns the users found by the token, if any. + The query returns the user found by the token, if any. - This is used to validate requests to change the users + This is used to validate requests to change the user email. It is different from `verify_email_token_query/2` precisely because `verify_email_token_query/2` validates the email has not changed, which is the starting point by this function. @@ -163,17 +163,17 @@ defmodule Cklist.Accounts.UsersToken do Returns the token struct for the given token value and context. """ def by_token_and_context_query(token, context) do - from UsersToken, where: [token: ^token, context: ^context] + from UserToken, where: [token: ^token, context: ^context] end @doc """ - Gets all tokens for the given users for the given contexts. + Gets all tokens for the given user for the given contexts. """ - def by_users_and_contexts_query(users, :all) do - from t in UsersToken, where: t.users_id == ^users.id + def by_user_and_contexts_query(user, :all) do + from t in UserToken, where: t.user_id == ^user.id end - def by_users_and_contexts_query(users, [_ | _] = contexts) do - from t in UsersToken, where: t.users_id == ^users.id and t.context in ^contexts + def by_user_and_contexts_query(user, [_ | _] = contexts) do + from t in UserToken, where: t.user_id == ^user.id and t.context in ^contexts end end diff --git a/lib/cklist_web/components/core_components.ex b/lib/cklist_web/components/core_components.ex index 9478da4..eed9736 100644 --- a/lib/cklist_web/components/core_components.ex +++ b/lib/cklist_web/components/core_components.ex @@ -446,7 +446,7 @@ defmodule CklistWeb.CoreComponents do ## Examples - <.table id="users" rows={@users}> + <.table id="user" rows={@user}> <:col :let={user} label="id"><%= user.id %> <:col :let={user} label="username"><%= user.username %> diff --git a/lib/cklist_web/controllers/users_session_controller.ex b/lib/cklist_web/controllers/user_session_controller.ex similarity index 63% rename from lib/cklist_web/controllers/users_session_controller.ex rename to lib/cklist_web/controllers/user_session_controller.ex index 7ba07ce..4ebae27 100644 --- a/lib/cklist_web/controllers/users_session_controller.ex +++ b/lib/cklist_web/controllers/user_session_controller.ex @@ -1,8 +1,8 @@ -defmodule CklistWeb.UsersSessionController do +defmodule CklistWeb.UserSessionController do use CklistWeb, :controller alias Cklist.Accounts - alias CklistWeb.UsersAuth + alias CklistWeb.UserAuth def create(conn, %{"_action" => "registered"} = params) do create(conn, params, "Account created successfully!") @@ -10,7 +10,7 @@ defmodule CklistWeb.UsersSessionController do def create(conn, %{"_action" => "password_updated"} = params) do conn - |> put_session(:users_return_to, ~p"/users/settings") + |> put_session(:user_return_to, ~p"/user/settings") |> create(params, "Password updated successfully!") end @@ -18,25 +18,25 @@ defmodule CklistWeb.UsersSessionController do create(conn, params, "Welcome back!") end - defp create(conn, %{"users" => users_params}, info) do - %{"email" => email, "password" => password} = users_params + defp create(conn, %{"user" => user_params}, info) do + %{"email" => email, "password" => password} = user_params - if users = Accounts.get_users_by_email_and_password(email, password) do + if user = Accounts.get_user_by_email_and_password(email, password) do conn |> put_flash(:info, info) - |> UsersAuth.log_in_users(users, users_params) + |> UserAuth.log_in_user(user, user_params) else # In order to prevent user enumeration attacks, don't disclose whether the email is registered. conn |> put_flash(:error, "Invalid email or password") |> put_flash(:email, String.slice(email, 0, 160)) - |> redirect(to: ~p"/users/log_in") + |> redirect(to: ~p"/user/log_in") end end def delete(conn, _params) do conn |> put_flash(:info, "Logged out successfully.") - |> UsersAuth.log_out_users() + |> UserAuth.log_out_user() end end diff --git a/lib/cklist_web/live/users_confirmation_instructions_live.ex b/lib/cklist_web/live/user_confirmation_instructions_live.ex similarity index 68% rename from lib/cklist_web/live/users_confirmation_instructions_live.ex rename to lib/cklist_web/live/user_confirmation_instructions_live.ex index 98492ac..7beadf8 100644 --- a/lib/cklist_web/live/users_confirmation_instructions_live.ex +++ b/lib/cklist_web/live/user_confirmation_instructions_live.ex @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersConfirmationInstructionsLive do +defmodule CklistWeb.UserConfirmationInstructionsLive do use CklistWeb, :live_view alias Cklist.Accounts @@ -21,22 +21,22 @@ defmodule CklistWeb.UsersConfirmationInstructionsLive do

- <.link href={~p"/users/register"}>Register - | <.link href={~p"/users/log_in"}>Log in + <.link href={~p"/user/register"}>Register + | <.link href={~p"/user/log_in"}>Log in

""" end def mount(_params, _session, socket) do - {:ok, assign(socket, form: to_form(%{}, as: "users"))} + {:ok, assign(socket, form: to_form(%{}, as: "user"))} end - def handle_event("send_instructions", %{"users" => %{"email" => email}}, socket) do - if users = Accounts.get_users_by_email(email) do - Accounts.deliver_users_confirmation_instructions( - users, - &url(~p"/users/confirm/#{&1}") + def handle_event("send_instructions", %{"user" => %{"email" => email}}, socket) do + if user = Accounts.get_user_by_email(email) do + Accounts.deliver_user_confirmation_instructions( + user, + &url(~p"/user/confirm/#{&1}") ) end diff --git a/lib/cklist_web/live/users_confirmation_live.ex b/lib/cklist_web/live/user_confirmation_live.ex similarity index 56% rename from lib/cklist_web/live/users_confirmation_live.ex rename to lib/cklist_web/live/user_confirmation_live.ex index c6c727b..029194a 100644 --- a/lib/cklist_web/live/users_confirmation_live.ex +++ b/lib/cklist_web/live/user_confirmation_live.ex @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersConfirmationLive do +defmodule CklistWeb.UserConfirmationLive do use CklistWeb, :live_view alias Cklist.Accounts @@ -16,41 +16,41 @@ defmodule CklistWeb.UsersConfirmationLive do

- <.link href={~p"/users/register"}>Register - | <.link href={~p"/users/log_in"}>Log in + <.link href={~p"/user/register"}>Register + | <.link href={~p"/user/log_in"}>Log in

""" end def mount(%{"token" => token}, _session, socket) do - form = to_form(%{"token" => token}, as: "users") + form = to_form(%{"token" => token}, as: "user") {:ok, assign(socket, form: form), temporary_assigns: [form: nil]} end - # Do not log in the users after confirmation to avoid a - # leaked token giving the users access to the account. - def handle_event("confirm_account", %{"users" => %{"token" => token}}, socket) do - case Accounts.confirm_users(token) do + # Do not log in the user after confirmation to avoid a + # leaked token giving the user access to the account. + def handle_event("confirm_account", %{"user" => %{"token" => token}}, socket) do + case Accounts.confirm_user(token) do {:ok, _} -> {:noreply, socket - |> put_flash(:info, "Users confirmed successfully.") + |> put_flash(:info, "User confirmed successfully.") |> redirect(to: ~p"/")} :error -> - # If there is a current users and the account was already confirmed, + # If there is a current user and the account was already confirmed, # then odds are that the confirmation link was already visited, either - # by some automation or by the users themselves, so we redirect without + # by some automation or by the user themselves, so we redirect without # a warning message. case socket.assigns do - %{current_users: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) -> + %{current_user: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) -> {:noreply, redirect(socket, to: ~p"/")} %{} -> {:noreply, socket - |> put_flash(:error, "Users confirmation link is invalid or it has expired.") + |> put_flash(:error, "User confirmation link is invalid or it has expired.") |> redirect(to: ~p"/")} end end diff --git a/lib/cklist_web/live/users_forgot_password_live.ex b/lib/cklist_web/live/user_forgot_password_live.ex similarity index 67% rename from lib/cklist_web/live/users_forgot_password_live.ex rename to lib/cklist_web/live/user_forgot_password_live.ex index 48e4b00..43f621c 100644 --- a/lib/cklist_web/live/users_forgot_password_live.ex +++ b/lib/cklist_web/live/user_forgot_password_live.ex @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersForgotPasswordLive do +defmodule CklistWeb.UserForgotPasswordLive do use CklistWeb, :live_view alias Cklist.Accounts @@ -20,22 +20,22 @@ defmodule CklistWeb.UsersForgotPasswordLive do

- <.link href={~p"/users/register"}>Register - | <.link href={~p"/users/log_in"}>Log in + <.link href={~p"/user/register"}>Register + | <.link href={~p"/user/log_in"}>Log in

""" end def mount(_params, _session, socket) do - {:ok, assign(socket, form: to_form(%{}, as: "users"))} + {:ok, assign(socket, form: to_form(%{}, as: "user"))} end - def handle_event("send_email", %{"users" => %{"email" => email}}, socket) do - if users = Accounts.get_users_by_email(email) do - Accounts.deliver_users_reset_password_instructions( - users, - &url(~p"/users/reset_password/#{&1}") + def handle_event("send_email", %{"user" => %{"email" => email}}, socket) do + if user = Accounts.get_user_by_email(email) do + Accounts.deliver_user_reset_password_instructions( + user, + &url(~p"/user/reset_password/#{&1}") ) end diff --git a/lib/cklist_web/live/users_login_live.ex b/lib/cklist_web/live/user_login_live.ex similarity index 79% rename from lib/cklist_web/live/users_login_live.ex rename to lib/cklist_web/live/user_login_live.ex index d1142be..8fc277d 100644 --- a/lib/cklist_web/live/users_login_live.ex +++ b/lib/cklist_web/live/user_login_live.ex @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersLoginLive do +defmodule CklistWeb.UserLoginLive do use CklistWeb, :live_view def render(assigns) do @@ -8,20 +8,20 @@ defmodule CklistWeb.UsersLoginLive do Sign in to account <:subtitle> Don't have an account? - <.link navigate={~p"/users/register"} class="font-semibold text-brand hover:underline"> + <.link navigate={~p"/user/register"} class="font-semibold text-brand hover:underline"> Sign up for an account now. - <.simple_form for={@form} id="login_form" action={~p"/users/log_in"} phx-update="ignore"> + <.simple_form for={@form} id="login_form" action={~p"/user/log_in"} phx-update="ignore"> <.input field={@form[:email]} type="email" label="Email" required /> <.input field={@form[:password]} type="password" label="Password" required /> <:actions> <.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" /> - <.link href={~p"/users/reset_password"} class="text-sm font-semibold"> + <.link href={~p"/user/reset_password"} class="text-sm font-semibold"> Forgot your password? @@ -37,7 +37,7 @@ defmodule CklistWeb.UsersLoginLive do def mount(_params, _session, socket) do email = live_flash(socket.assigns.flash, :email) - form = to_form(%{"email" => email}, as: "users") + form = to_form(%{"email" => email}, as: "user") {:ok, assign(socket, form: form), temporary_assigns: [form: form]} end end diff --git a/lib/cklist_web/live/users_registration_live.ex b/lib/cklist_web/live/user_registration_live.ex similarity index 69% rename from lib/cklist_web/live/users_registration_live.ex rename to lib/cklist_web/live/user_registration_live.ex index 106fea7..994fe91 100644 --- a/lib/cklist_web/live/users_registration_live.ex +++ b/lib/cklist_web/live/user_registration_live.ex @@ -1,8 +1,8 @@ -defmodule CklistWeb.UsersRegistrationLive do +defmodule CklistWeb.UserRegistrationLive do use CklistWeb, :live_view alias Cklist.Accounts - alias Cklist.Accounts.Users + alias Cklist.Accounts.User def render(assigns) do ~H""" @@ -11,7 +11,7 @@ defmodule CklistWeb.UsersRegistrationLive do Register for an account <:subtitle> Already registered? - <.link navigate={~p"/users/log_in"} class="font-semibold text-brand hover:underline"> + <.link navigate={~p"/user/log_in"} class="font-semibold text-brand hover:underline"> Sign in to your account now. @@ -24,7 +24,7 @@ defmodule CklistWeb.UsersRegistrationLive do phx-submit="save" phx-change="validate" phx-trigger-action={@trigger_submit} - action={~p"/users/log_in?_action=registered"} + action={~p"/user/log_in?_action=registered"} method="post" > <.error :if={@check_errors}> @@ -43,7 +43,7 @@ defmodule CklistWeb.UsersRegistrationLive do end def mount(_params, _session, socket) do - changeset = Accounts.change_users_registration(%Users{}) + changeset = Accounts.change_user_registration(%User{}) socket = socket @@ -53,16 +53,16 @@ defmodule CklistWeb.UsersRegistrationLive do {:ok, socket, temporary_assigns: [form: nil]} end - def handle_event("save", %{"users" => users_params}, socket) do - case Accounts.register_users(users_params) do - {:ok, users} -> + def handle_event("save", %{"user" => user_params}, socket) do + case Accounts.register_user(user_params) do + {:ok, user} -> {:ok, _} = - Accounts.deliver_users_confirmation_instructions( - users, - &url(~p"/users/confirm/#{&1}") + Accounts.deliver_user_confirmation_instructions( + user, + &url(~p"/user/confirm/#{&1}") ) - changeset = Accounts.change_users_registration(users) + changeset = Accounts.change_user_registration(user) {:noreply, socket |> assign(trigger_submit: true) |> assign_form(changeset)} {:error, %Ecto.Changeset{} = changeset} -> @@ -70,13 +70,13 @@ defmodule CklistWeb.UsersRegistrationLive do end end - def handle_event("validate", %{"users" => users_params}, socket) do - changeset = Accounts.change_users_registration(%Users{}, users_params) + def handle_event("validate", %{"user" => user_params}, socket) do + changeset = Accounts.change_user_registration(%User{}, user_params) {:noreply, assign_form(socket, Map.put(changeset, :action, :validate))} end defp assign_form(socket, %Ecto.Changeset{} = changeset) do - form = to_form(changeset, as: "users") + form = to_form(changeset, as: "user") if changeset.valid? do assign(socket, form: form, check_errors: false) diff --git a/lib/cklist_web/live/users_reset_password_live.ex b/lib/cklist_web/live/user_reset_password_live.ex similarity index 62% rename from lib/cklist_web/live/users_reset_password_live.ex rename to lib/cklist_web/live/user_reset_password_live.ex index 758f92b..f0a9536 100644 --- a/lib/cklist_web/live/users_reset_password_live.ex +++ b/lib/cklist_web/live/user_reset_password_live.ex @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersResetPasswordLive do +defmodule CklistWeb.UserResetPasswordLive do use CklistWeb, :live_view alias Cklist.Accounts @@ -31,20 +31,20 @@ defmodule CklistWeb.UsersResetPasswordLive do

- <.link href={~p"/users/register"}>Register - | <.link href={~p"/users/log_in"}>Log in + <.link href={~p"/user/register"}>Register + | <.link href={~p"/user/log_in"}>Log in

""" end def mount(params, _session, socket) do - socket = assign_users_and_token(socket, params) + socket = assign_user_and_token(socket, params) form_source = case socket.assigns do - %{users: users} -> - Accounts.change_users_password(users) + %{user: user} -> + Accounts.change_user_password(user) _ -> %{} @@ -53,29 +53,29 @@ defmodule CklistWeb.UsersResetPasswordLive do {:ok, assign_form(socket, form_source), temporary_assigns: [form: nil]} end - # Do not log in the users after reset password to avoid a - # leaked token giving the users access to the account. - def handle_event("reset_password", %{"users" => users_params}, socket) do - case Accounts.reset_users_password(socket.assigns.users, users_params) do + # Do not log in the user after reset password to avoid a + # leaked token giving the user access to the account. + def handle_event("reset_password", %{"user" => user_params}, socket) do + case Accounts.reset_user_password(socket.assigns.user, user_params) do {:ok, _} -> {:noreply, socket |> put_flash(:info, "Password reset successfully.") - |> redirect(to: ~p"/users/log_in")} + |> redirect(to: ~p"/user/log_in")} {:error, changeset} -> {:noreply, assign_form(socket, Map.put(changeset, :action, :insert))} end end - def handle_event("validate", %{"users" => users_params}, socket) do - changeset = Accounts.change_users_password(socket.assigns.users, users_params) + def handle_event("validate", %{"user" => user_params}, socket) do + changeset = Accounts.change_user_password(socket.assigns.user, user_params) {:noreply, assign_form(socket, Map.put(changeset, :action, :validate))} end - defp assign_users_and_token(socket, %{"token" => token}) do - if users = Accounts.get_users_by_reset_password_token(token) do - assign(socket, users: users, token: token) + defp assign_user_and_token(socket, %{"token" => token}) do + if user = Accounts.get_user_by_reset_password_token(token) do + assign(socket, user: user, token: token) else socket |> put_flash(:error, "Reset password link is invalid or it has expired.") @@ -84,6 +84,6 @@ defmodule CklistWeb.UsersResetPasswordLive do end defp assign_form(socket, %{} = source) do - assign(socket, :form, to_form(source, as: "users")) + assign(socket, :form, to_form(source, as: "user")) end end diff --git a/lib/cklist_web/live/users_settings_live.ex b/lib/cklist_web/live/user_settings_live.ex similarity index 72% rename from lib/cklist_web/live/users_settings_live.ex rename to lib/cklist_web/live/user_settings_live.ex index 92290dd..bef5be5 100644 --- a/lib/cklist_web/live/users_settings_live.ex +++ b/lib/cklist_web/live/user_settings_live.ex @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersSettingsLive do +defmodule CklistWeb.UserSettingsLive do use CklistWeb, :live_view alias Cklist.Accounts @@ -37,7 +37,7 @@ defmodule CklistWeb.UsersSettingsLive do <.simple_form for={@password_form} id="password_form" - action={~p"/users/log_in?_action=password_updated"} + action={~p"/user/log_in?_action=password_updated"} method="post" phx-change="validate_password" phx-submit="update_password" @@ -46,7 +46,7 @@ defmodule CklistWeb.UsersSettingsLive do <.input field={@password_form[:email]} type="hidden" - id="hidden_users_email" + id="hidden_user_email" value={@current_email} /> <.input field={@password_form[:password]} type="password" label="New password" required /> @@ -75,7 +75,7 @@ defmodule CklistWeb.UsersSettingsLive do def mount(%{"token" => token}, _session, socket) do socket = - case Accounts.update_users_email(socket.assigns.current_users, token) do + case Accounts.update_user_email(socket.assigns.current_user, token) do :ok -> put_flash(socket, :info, "Email changed successfully.") @@ -83,19 +83,19 @@ defmodule CklistWeb.UsersSettingsLive do put_flash(socket, :error, "Email change link is invalid or it has expired.") end - {:ok, push_navigate(socket, to: ~p"/users/settings")} + {:ok, push_navigate(socket, to: ~p"/user/settings")} end def mount(_params, _session, socket) do - users = socket.assigns.current_users - email_changeset = Accounts.change_users_email(users) - password_changeset = Accounts.change_users_password(users) + user = socket.assigns.current_user + email_changeset = Accounts.change_user_email(user) + password_changeset = Accounts.change_user_password(user) socket = socket |> assign(:current_password, nil) |> assign(:email_form_current_password, nil) - |> assign(:current_email, users.email) + |> assign(:current_email, user.email) |> assign(:email_form, to_form(email_changeset)) |> assign(:password_form, to_form(password_changeset)) |> assign(:trigger_submit, false) @@ -104,11 +104,11 @@ defmodule CklistWeb.UsersSettingsLive do end def handle_event("validate_email", params, socket) do - %{"current_password" => password, "users" => users_params} = params + %{"current_password" => password, "user" => user_params} = params email_form = - socket.assigns.current_users - |> Accounts.change_users_email(users_params) + socket.assigns.current_user + |> Accounts.change_user_email(user_params) |> Map.put(:action, :validate) |> to_form() @@ -116,15 +116,15 @@ defmodule CklistWeb.UsersSettingsLive do end def handle_event("update_email", params, socket) do - %{"current_password" => password, "users" => users_params} = params - users = socket.assigns.current_users - - case Accounts.apply_users_email(users, password, users_params) do - {:ok, applied_users} -> - Accounts.deliver_users_update_email_instructions( - applied_users, - users.email, - &url(~p"/users/settings/confirm_email/#{&1}") + %{"current_password" => password, "user" => user_params} = params + user = socket.assigns.current_user + + case Accounts.apply_user_email(user, password, user_params) do + {:ok, applied_user} -> + Accounts.deliver_user_update_email_instructions( + applied_user, + user.email, + &url(~p"/user/settings/confirm_email/#{&1}") ) info = "A link to confirm your email change has been sent to the new address." @@ -136,11 +136,11 @@ defmodule CklistWeb.UsersSettingsLive do end def handle_event("validate_password", params, socket) do - %{"current_password" => password, "users" => users_params} = params + %{"current_password" => password, "user" => user_params} = params password_form = - socket.assigns.current_users - |> Accounts.change_users_password(users_params) + socket.assigns.current_user + |> Accounts.change_user_password(user_params) |> Map.put(:action, :validate) |> to_form() @@ -148,14 +148,14 @@ defmodule CklistWeb.UsersSettingsLive do end def handle_event("update_password", params, socket) do - %{"current_password" => password, "users" => users_params} = params - users = socket.assigns.current_users + %{"current_password" => password, "user" => user_params} = params + user = socket.assigns.current_user - case Accounts.update_users_password(users, password, users_params) do - {:ok, users} -> + case Accounts.update_user_password(user, password, user_params) do + {:ok, user} -> password_form = - users - |> Accounts.change_users_password(users_params) + user + |> Accounts.change_user_password(user_params) |> to_form() {:noreply, assign(socket, trigger_submit: true, password_form: password_form)} diff --git a/lib/cklist_web/router.ex b/lib/cklist_web/router.ex index 9ac52b9..b1255e8 100644 --- a/lib/cklist_web/router.ex +++ b/lib/cklist_web/router.ex @@ -1,7 +1,7 @@ defmodule CklistWeb.Router do use CklistWeb, :router - import CklistWeb.UsersAuth + import CklistWeb.UserAuth require CklistWeb.Cldr pipeline :browser do @@ -11,7 +11,7 @@ defmodule CklistWeb.Router do plug :put_root_layout, html: {CklistWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers - plug :fetch_current_users + plug :fetch_current_user plug(Cldr.Plug.PutLocale, apps: [:cldr, :gettext], cldr: CklistWeb.Cldr) plug(Cldr.Plug.AcceptLanguage, cldr_backend: CklistWeb.Cldr) end @@ -51,38 +51,38 @@ defmodule CklistWeb.Router do ## Authentication routes scope "/", CklistWeb do - pipe_through [:browser, :redirect_if_users_is_authenticated] - - live_session :redirect_if_users_is_authenticated, - on_mount: [{CklistWeb.UsersAuth, :redirect_if_users_is_authenticated}] do - live "/users/register", UsersRegistrationLive, :new - live "/users/log_in", UsersLoginLive, :new - live "/users/reset_password", UsersForgotPasswordLive, :new - live "/users/reset_password/:token", UsersResetPasswordLive, :edit + pipe_through [:browser, :redirect_if_user_is_authenticated] + + live_session :redirect_if_user_is_authenticated, + on_mount: [{CklistWeb.UserAuth, :redirect_if_user_is_authenticated}] do + live "/user/register", UserRegistrationLive, :new + live "/user/log_in", UserLoginLive, :new + live "/user/reset_password", UserForgotPasswordLive, :new + live "/user/reset_password/:token", UserResetPasswordLive, :edit end - post "/users/log_in", UsersSessionController, :create + post "/user/log_in", UserSessionController, :create end scope "/", CklistWeb do - pipe_through [:browser, :require_authenticated_users] + pipe_through [:browser, :require_authenticated_user] - live_session :require_authenticated_users, - on_mount: [{CklistWeb.UsersAuth, :ensure_authenticated}] do - live "/users/settings", UsersSettingsLive, :edit - live "/users/settings/confirm_email/:token", UsersSettingsLive, :confirm_email + live_session :require_authenticated_user, + on_mount: [{CklistWeb.UserAuth, :ensure_authenticated}] do + live "/user/settings", UserSettingsLive, :edit + live "/user/settings/confirm_email/:token", UserSettingsLive, :confirm_email end end scope "/", CklistWeb do pipe_through [:browser] - delete "/users/log_out", UsersSessionController, :delete + delete "/user/log_out", UserSessionController, :delete - live_session :current_users, - on_mount: [{CklistWeb.UsersAuth, :mount_current_users}] do - live "/users/confirm/:token", UsersConfirmationLive, :edit - live "/users/confirm", UsersConfirmationInstructionsLive, :new + live_session :current_user, + on_mount: [{CklistWeb.UserAuth, :mount_current_user}] do + live "/user/confirm/:token", UserConfirmationLive, :edit + live "/user/confirm", UserConfirmationInstructionsLive, :new end end end diff --git a/lib/cklist_web/users_auth.ex b/lib/cklist_web/user_auth.ex similarity index 56% rename from lib/cklist_web/users_auth.ex rename to lib/cklist_web/user_auth.ex index 600d924..6673df8 100644 --- a/lib/cklist_web/users_auth.ex +++ b/lib/cklist_web/user_auth.ex @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersAuth do +defmodule CklistWeb.UserAuth do use CklistWeb, :verified_routes import Plug.Conn @@ -8,13 +8,13 @@ defmodule CklistWeb.UsersAuth do # Make the remember me cookie valid for 60 days. # If you want bump or reduce this value, also change - # the token expiry itself in UsersToken. + # the token expiry itself in UserToken. @max_age 60 * 60 * 24 * 60 - @remember_me_cookie "_cklist_web_users_remember_me" + @remember_me_cookie "_cklist_web_user_remember_me" @remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"] @doc """ - Logs the users in. + Logs the user in. It renews the session ID and clears the whole session to avoid fixation attacks. See the renew_session @@ -25,15 +25,15 @@ defmodule CklistWeb.UsersAuth do disconnected on log out. The line can be safely removed if you are not using LiveView. """ - def log_in_users(conn, users, params \\ %{}) do - token = Accounts.generate_users_session_token(users) - users_return_to = get_session(conn, :users_return_to) + def log_in_user(conn, user, params \\ %{}) do + token = Accounts.generate_user_session_token(user) + user_return_to = get_session(conn, :user_return_to) conn |> renew_session() |> put_token_in_session(token) |> maybe_write_remember_me_cookie(token, params) - |> redirect(to: users_return_to || signed_in_path(conn)) + |> redirect(to: user_return_to || signed_in_path(conn)) end defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do @@ -66,13 +66,13 @@ defmodule CklistWeb.UsersAuth do end @doc """ - Logs the users out. + Logs the user out. It clears all session data for safety. See renew_session. """ - def log_out_users(conn) do - users_token = get_session(conn, :users_token) - users_token && Accounts.delete_users_session_token(users_token) + def log_out_user(conn) do + user_token = get_session(conn, :user_token) + user_token && Accounts.delete_user_session_token(user_token) if live_socket_id = get_session(conn, :live_socket_id) do CklistWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{}) @@ -85,17 +85,17 @@ defmodule CklistWeb.UsersAuth do end @doc """ - Authenticates the users by looking into the session + Authenticates the user by looking into the session and remember me token. """ - def fetch_current_users(conn, _opts) do - {users_token, conn} = ensure_users_token(conn) - users = users_token && Accounts.get_users_by_session_token(users_token) - assign(conn, :current_users, users) + def fetch_current_user(conn, _opts) do + {user_token, conn} = ensure_user_token(conn) + user = user_token && Accounts.get_user_by_session_token(user_token) + assign(conn, :current_user, user) end - defp ensure_users_token(conn) do - if token = get_session(conn, :users_token) do + defp ensure_user_token(conn) do + if token = get_session(conn, :user_token) do {token, conn} else conn = fetch_cookies(conn, signed: [@remember_me_cookie]) @@ -109,82 +109,82 @@ defmodule CklistWeb.UsersAuth do end @doc """ - Handles mounting and authenticating the current_users in LiveViews. + Handles mounting and authenticating the current_user in LiveViews. ## `on_mount` arguments - * `:mount_current_users` - Assigns current_users - to socket assigns based on users_token, or nil if - there's no users_token or no matching users. + * `:mount_current_user` - Assigns current_user + to socket assigns based on user_token, or nil if + there's no user_token or no matching user. - * `:ensure_authenticated` - Authenticates the users from the session, - and assigns the current_users to socket assigns based - on users_token. - Redirects to login page if there's no logged users. + * `:ensure_authenticated` - Authenticates the user from the session, + and assigns the current_user to socket assigns based + on user_token. + Redirects to login page if there's no logged user. - * `:redirect_if_users_is_authenticated` - Authenticates the users from the session. - Redirects to signed_in_path if there's a logged users. + * `:redirect_if_user_is_authenticated` - Authenticates the user from the session. + Redirects to signed_in_path if there's a logged user. ## Examples Use the `on_mount` lifecycle macro in LiveViews to mount or authenticate - the current_users: + the current_user: defmodule CklistWeb.PageLive do use CklistWeb, :live_view - on_mount {CklistWeb.UsersAuth, :mount_current_users} + on_mount {CklistWeb.UserAuth, :mount_current_user} ... end Or use the `live_session` of your router to invoke the on_mount callback: - live_session :authenticated, on_mount: [{CklistWeb.UsersAuth, :ensure_authenticated}] do + live_session :authenticated, on_mount: [{CklistWeb.UserAuth, :ensure_authenticated}] do live "/profile", ProfileLive, :index end """ - def on_mount(:mount_current_users, _params, session, socket) do - {:cont, mount_current_users(socket, session)} + def on_mount(:mount_current_user, _params, session, socket) do + {:cont, mount_current_user(socket, session)} end def on_mount(:ensure_authenticated, _params, session, socket) do - socket = mount_current_users(socket, session) + socket = mount_current_user(socket, session) - if socket.assigns.current_users do + if socket.assigns.current_user do {:cont, socket} else socket = socket |> Phoenix.LiveView.put_flash(:error, "You must log in to access this page.") - |> Phoenix.LiveView.redirect(to: ~p"/users/log_in") + |> Phoenix.LiveView.redirect(to: ~p"/user/log_in") {:halt, socket} end end - def on_mount(:redirect_if_users_is_authenticated, _params, session, socket) do - socket = mount_current_users(socket, session) + def on_mount(:redirect_if_user_is_authenticated, _params, session, socket) do + socket = mount_current_user(socket, session) - if socket.assigns.current_users do + if socket.assigns.current_user do {:halt, Phoenix.LiveView.redirect(socket, to: signed_in_path(socket))} else {:cont, socket} end end - defp mount_current_users(socket, session) do - Phoenix.Component.assign_new(socket, :current_users, fn -> - if users_token = session["users_token"] do - Accounts.get_users_by_session_token(users_token) + defp mount_current_user(socket, session) do + Phoenix.Component.assign_new(socket, :current_user, fn -> + if user_token = session["user_token"] do + Accounts.get_user_by_session_token(user_token) end end) end @doc """ - Used for routes that require the users to not be authenticated. + Used for routes that require the user to not be authenticated. """ - def redirect_if_users_is_authenticated(conn, _opts) do - if conn.assigns[:current_users] do + def redirect_if_user_is_authenticated(conn, _opts) do + if conn.assigns[:current_user] do conn |> redirect(to: signed_in_path(conn)) |> halt() @@ -194,31 +194,31 @@ defmodule CklistWeb.UsersAuth do end @doc """ - Used for routes that require the users to be authenticated. + Used for routes that require the user to be authenticated. - If you want to enforce the users email is confirmed before + If you want to enforce the user email is confirmed before they use the application at all, here would be a good place. """ - def require_authenticated_users(conn, _opts) do - if conn.assigns[:current_users] do + def require_authenticated_user(conn, _opts) do + if conn.assigns[:current_user] do conn else conn |> put_flash(:error, "You must log in to access this page.") |> maybe_store_return_to() - |> redirect(to: ~p"/users/log_in") + |> redirect(to: ~p"/user/log_in") |> halt() end end defp put_token_in_session(conn, token) do conn - |> put_session(:users_token, token) - |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}") + |> put_session(:user_token, token) + |> put_session(:live_socket_id, "user_sessions:#{Base.url_encode64(token)}") end defp maybe_store_return_to(%{method: "GET"} = conn) do - put_session(conn, :users_return_to, current_path(conn)) + put_session(conn, :user_return_to, current_path(conn)) end defp maybe_store_return_to(conn), do: conn diff --git a/priv/repo/migrations/20240121112709_create_users_auth_tables.exs b/priv/repo/migrations/20240121112709_create_users_auth_tables.exs index 139cca8..ac66c02 100644 --- a/priv/repo/migrations/20240121112709_create_users_auth_tables.exs +++ b/priv/repo/migrations/20240121112709_create_users_auth_tables.exs @@ -13,15 +13,15 @@ defmodule Cklist.Repo.Migrations.CreateUsersAuthTables do create unique_index(:users, [:email]) - create table(:users_tokens) do - add :users_id, references(:users, on_delete: :delete_all), null: false + create table(:user_tokens) do + add :user_id, references(:users, on_delete: :delete_all), null: false add :token, :binary, null: false add :context, :string, null: false add :sent_to, :string timestamps(updated_at: false) end - create index(:users_tokens, [:users_id]) - create unique_index(:users_tokens, [:context, :token]) + create index(:user_tokens, [:user_id]) + create unique_index(:user_tokens, [:context, :token]) end end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 7c60622..067eff9 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -9,3 +9,10 @@ # # We recommend using the bang functions (`insert!`, `update!` # and so on) as they will fail if something goes wrong. + +Cklist.Repo.insert!(%Cklist.Accounts.User{ + email: "audry@cklist.org", + password: "romanholiday", + hashed_password: Bcrypt.hash_pwd_salt("romanholiday"), + confirmed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second) +}) diff --git a/test/cklist/accounts_test.exs b/test/cklist/accounts_test.exs index 4e8bff5..9fb3f60 100644 --- a/test/cklist/accounts_test.exs +++ b/test/cklist/accounts_test.exs @@ -4,53 +4,53 @@ defmodule Cklist.AccountsTest do alias Cklist.Accounts import Cklist.AccountsFixtures - alias Cklist.Accounts.{Users, UsersToken} + alias Cklist.Accounts.{User, UserToken} - describe "get_users_by_email/1" do - test "does not return the users if the email does not exist" do - refute Accounts.get_users_by_email("unknown@example.com") + describe "get_user_by_email/1" do + test "does not return the user if the email does not exist" do + refute Accounts.get_user_by_email("unknown@example.com") end - test "returns the users if the email exists" do - %{id: id} = users = users_fixture() - assert %Users{id: ^id} = Accounts.get_users_by_email(users.email) + test "returns the user if the email exists" do + %{id: id} = user = user_fixture() + assert %User{id: ^id} = Accounts.get_user_by_email(user.email) end end - describe "get_users_by_email_and_password/2" do - test "does not return the users if the email does not exist" do - refute Accounts.get_users_by_email_and_password("unknown@example.com", "hello world!") + describe "get_user_by_email_and_password/2" do + test "does not return the user if the email does not exist" do + refute Accounts.get_user_by_email_and_password("unknown@example.com", "hello world!") end - test "does not return the users if the password is not valid" do - users = users_fixture() - refute Accounts.get_users_by_email_and_password(users.email, "invalid") + test "does not return the user if the password is not valid" do + user = user_fixture() + refute Accounts.get_user_by_email_and_password(user.email, "invalid") end - test "returns the users if the email and password are valid" do - %{id: id} = users = users_fixture() + test "returns the user if the email and password are valid" do + %{id: id} = user = user_fixture() - assert %Users{id: ^id} = - Accounts.get_users_by_email_and_password(users.email, valid_users_password()) + assert %User{id: ^id} = + Accounts.get_user_by_email_and_password(user.email, valid_user_password()) end end - describe "get_users!/1" do + describe "get_user!/1" do test "raises if id is invalid" do assert_raise Ecto.NoResultsError, fn -> - Accounts.get_users!(-1) + Accounts.get_user!(-1) end end - test "returns the users with the given id" do - %{id: id} = users = users_fixture() - assert %Users{id: ^id} = Accounts.get_users!(users.id) + test "returns the user with the given id" do + %{id: id} = user = user_fixture() + assert %User{id: ^id} = Accounts.get_user!(user.id) end end - describe "register_users/1" do + describe "register_user/1" do test "requires email and password to be set" do - {:error, changeset} = Accounts.register_users(%{}) + {:error, changeset} = Accounts.register_user(%{}) assert %{ password: ["can't be blank"], @@ -59,7 +59,7 @@ defmodule Cklist.AccountsTest do end test "validates email and password when given" do - {:error, changeset} = Accounts.register_users(%{email: "not valid", password: "not valid"}) + {:error, changeset} = Accounts.register_user(%{email: "not valid", password: "not valid"}) assert %{ email: ["must have the @ sign and no spaces"], @@ -69,45 +69,45 @@ defmodule Cklist.AccountsTest do test "validates maximum values for email and password for security" do too_long = String.duplicate("db", 100) - {:error, changeset} = Accounts.register_users(%{email: too_long, password: too_long}) + {: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 72 character(s)" in errors_on(changeset).password end test "validates email uniqueness" do - %{email: email} = users_fixture() - {:error, changeset} = Accounts.register_users(%{email: email}) + %{email: email} = user_fixture() + {:error, changeset} = Accounts.register_user(%{email: email}) assert "has already been taken" in errors_on(changeset).email # Now try with the upper cased email too, to check that email case is ignored. - {:error, changeset} = Accounts.register_users(%{email: String.upcase(email)}) + {:error, changeset} = Accounts.register_user(%{email: String.upcase(email)}) assert "has already been taken" in errors_on(changeset).email end - test "registers users with a hashed password" do - email = unique_users_email() - {:ok, users} = Accounts.register_users(valid_users_attributes(email: email)) - assert users.email == email - assert is_binary(users.hashed_password) - assert is_nil(users.confirmed_at) - assert is_nil(users.password) + test "registers user with a hashed password" do + email = unique_user_email() + {:ok, user} = Accounts.register_user(valid_user_attributes(email: email)) + assert user.email == email + assert is_binary(user.hashed_password) + assert is_nil(user.confirmed_at) + assert is_nil(user.password) end end - describe "change_users_registration/2" do + describe "change_user_registration/2" do test "returns a changeset" do - assert %Ecto.Changeset{} = changeset = Accounts.change_users_registration(%Users{}) + assert %Ecto.Changeset{} = changeset = Accounts.change_user_registration(%User{}) assert changeset.required == [:password, :email] end test "allows fields to be set" do - email = unique_users_email() - password = valid_users_password() + email = unique_user_email() + password = valid_user_password() changeset = - Accounts.change_users_registration( - %Users{}, - valid_users_attributes(email: email, password: password) + Accounts.change_user_registration( + %User{}, + valid_user_attributes(email: email, password: password) ) assert changeset.valid? @@ -117,134 +117,135 @@ defmodule Cklist.AccountsTest do end end - describe "change_users_email/2" do - test "returns a users changeset" do - assert %Ecto.Changeset{} = changeset = Accounts.change_users_email(%Users{}) + describe "change_user_email/2" do + test "returns a user changeset" do + assert %Ecto.Changeset{} = changeset = Accounts.change_user_email(%User{}) assert changeset.required == [:email] end end - describe "apply_users_email/3" do + describe "apply_user_email/3" do setup do - %{users: users_fixture()} + %{user: user_fixture()} end - test "requires email to change", %{users: users} do - {:error, changeset} = Accounts.apply_users_email(users, valid_users_password(), %{}) + test "requires email to change", %{user: user} do + {:error, changeset} = Accounts.apply_user_email(user, valid_user_password(), %{}) assert %{email: ["did not change"]} = errors_on(changeset) end - test "validates email", %{users: users} do + test "validates email", %{user: user} do {:error, changeset} = - Accounts.apply_users_email(users, valid_users_password(), %{email: "not valid"}) + Accounts.apply_user_email(user, valid_user_password(), %{email: "not valid"}) assert %{email: ["must have the @ sign and no spaces"]} = errors_on(changeset) end - test "validates maximum value for email for security", %{users: users} do + test "validates maximum value for email for security", %{user: user} do too_long = String.duplicate("db", 100) {:error, changeset} = - Accounts.apply_users_email(users, valid_users_password(), %{email: too_long}) + Accounts.apply_user_email(user, valid_user_password(), %{email: too_long}) assert "should be at most 160 character(s)" in errors_on(changeset).email end - test "validates email uniqueness", %{users: users} do - %{email: email} = users_fixture() - password = valid_users_password() + test "validates email uniqueness", %{user: user} do + %{email: email} = user_fixture() + password = valid_user_password() - {:error, changeset} = Accounts.apply_users_email(users, password, %{email: email}) + {:error, changeset} = Accounts.apply_user_email(user, password, %{email: email}) assert "has already been taken" in errors_on(changeset).email end - test "validates current password", %{users: users} do + test "validates current password", %{user: user} do {:error, changeset} = - Accounts.apply_users_email(users, "invalid", %{email: unique_users_email()}) + Accounts.apply_user_email(user, "invalid", %{email: unique_user_email()}) assert %{current_password: ["is not valid"]} = errors_on(changeset) end - test "applies the email without persisting it", %{users: users} do - email = unique_users_email() - {:ok, users} = Accounts.apply_users_email(users, valid_users_password(), %{email: email}) - assert users.email == email - assert Accounts.get_users!(users.id).email != email + 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}) + assert user.email == email + assert Accounts.get_user!(user.id).email != email end end - describe "deliver_users_update_email_instructions/3" do + describe "deliver_user_update_email_instructions/3" do setup do - %{users: users_fixture()} + %{user: user_fixture()} end - test "sends token through notification", %{users: users} do + test "sends token through notification", %{user: user} do token = - extract_users_token(fn url -> - Accounts.deliver_users_update_email_instructions(users, "current@example.com", url) + extract_user_token(fn url -> + Accounts.deliver_user_update_email_instructions(user, "current@example.com", url) end) {:ok, token} = Base.url_decode64(token, padding: false) - assert users_token = Repo.get_by(UsersToken, token: :crypto.hash(:sha256, token)) - assert users_token.users_id == users.id - assert users_token.sent_to == users.email - assert users_token.context == "change:current@example.com" + assert user_token = Repo.get_by(UserToken, token: :crypto.hash(:sha256, token)) + assert user_token.user_id == user.id + assert user_token.sent_to == user.email + assert user_token.context == "change:current@example.com" end end - describe "update_users_email/2" do + describe "update_user_email/2" do setup do - users = users_fixture() - email = unique_users_email() + user = user_fixture() + email = unique_user_email() token = - extract_users_token(fn url -> - Accounts.deliver_users_update_email_instructions(%{users | email: email}, users.email, url) + extract_user_token(fn url -> + Accounts.deliver_user_update_email_instructions(%{user | email: email}, user.email, url) end) - %{users: users, token: token, email: email} + %{user: user, token: token, email: email} end - test "updates the email with a valid token", %{users: users, token: token, email: email} do - assert Accounts.update_users_email(users, token) == :ok - changed_users = Repo.get!(Users, users.id) - assert changed_users.email != users.email - assert changed_users.email == email - assert changed_users.confirmed_at - assert changed_users.confirmed_at != users.confirmed_at - refute Repo.get_by(UsersToken, users_id: users.id) + test "updates the email with a valid token", %{user: user, token: token, email: email} do + assert Accounts.update_user_email(user, token) == :ok + changed_user = Repo.get!(User, user.id) + assert changed_user.email != user.email + assert changed_user.email == email + assert changed_user.confirmed_at + assert changed_user.confirmed_at != user.confirmed_at + refute Repo.get_by(UserToken, user_id: user.id) end - test "does not update email with invalid token", %{users: users} do - assert Accounts.update_users_email(users, "oops") == :error - assert Repo.get!(Users, users.id).email == users.email - assert Repo.get_by(UsersToken, users_id: users.id) + test "does not update email with invalid token", %{user: user} do + assert Accounts.update_user_email(user, "oops") == :error + assert Repo.get!(User, user.id).email == user.email + assert Repo.get_by(UserToken, user_id: user.id) end - test "does not update email if users email changed", %{users: users, token: token} do - assert Accounts.update_users_email(%{users | email: "current@example.com"}, token) == :error - assert Repo.get!(Users, users.id).email == users.email - assert Repo.get_by(UsersToken, users_id: users.id) + test "does not update email if user email changed", %{user: user, token: token} do + assert Accounts.update_user_email(%{user | email: "current@example.com"}, token) == :error + assert Repo.get!(User, user.id).email == user.email + assert Repo.get_by(UserToken, user_id: user.id) end - test "does not update email if token expired", %{users: users, token: token} do - {1, nil} = Repo.update_all(UsersToken, set: [inserted_at: ~N[2020-01-01 00:00:00]]) - assert Accounts.update_users_email(users, token) == :error - assert Repo.get!(Users, users.id).email == users.email - assert Repo.get_by(UsersToken, users_id: users.id) + test "does not update email if token expired", %{user: user, token: token} do + {1, nil} = Repo.update_all(UserToken, set: [inserted_at: ~N[2020-01-01 00:00:00]]) + + assert Accounts.update_user_email(user, token) == :error + assert Repo.get!(User, user.id).email == user.email + assert Repo.get_by(UserToken, user_id: user.id) end end - describe "change_users_password/2" do - test "returns a users changeset" do - assert %Ecto.Changeset{} = changeset = Accounts.change_users_password(%Users{}) + describe "change_user_password/2" do + test "returns a user changeset" do + assert %Ecto.Changeset{} = changeset = Accounts.change_user_password(%User{}) assert changeset.required == [:password] end test "allows fields to be set" do changeset = - Accounts.change_users_password(%Users{}, %{ + Accounts.change_user_password(%User{}, %{ "password" => "new valid password" }) @@ -254,14 +255,14 @@ defmodule Cklist.AccountsTest do end end - describe "update_users_password/3" do + describe "update_user_password/3" do setup do - %{users: users_fixture()} + %{user: user_fixture()} end - test "validates password", %{users: users} do + test "validates password", %{user: user} do {:error, changeset} = - Accounts.update_users_password(users, valid_users_password(), %{ + Accounts.update_user_password(user, valid_user_password(), %{ password: "not valid", password_confirmation: "another" }) @@ -272,205 +273,205 @@ defmodule Cklist.AccountsTest do } = errors_on(changeset) end - test "validates maximum values for password for security", %{users: users} do + test "validates maximum values for password for security", %{user: user} do too_long = String.duplicate("db", 100) {:error, changeset} = - Accounts.update_users_password(users, valid_users_password(), %{password: too_long}) + Accounts.update_user_password(user, valid_user_password(), %{password: too_long}) assert "should be at most 72 character(s)" in errors_on(changeset).password end - test "validates current password", %{users: users} do + test "validates current password", %{user: user} do {:error, changeset} = - Accounts.update_users_password(users, "invalid", %{password: valid_users_password()}) + Accounts.update_user_password(user, "invalid", %{password: valid_user_password()}) assert %{current_password: ["is not valid"]} = errors_on(changeset) end - test "updates the password", %{users: users} do - {:ok, users} = - Accounts.update_users_password(users, valid_users_password(), %{ + test "updates the password", %{user: user} do + {:ok, user} = + Accounts.update_user_password(user, valid_user_password(), %{ password: "new valid password" }) - assert is_nil(users.password) - assert Accounts.get_users_by_email_and_password(users.email, "new valid password") + 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 users", %{users: users} do - _ = Accounts.generate_users_session_token(users) + test "deletes all tokens for the given user", %{user: user} do + _ = Accounts.generate_user_session_token(user) {:ok, _} = - Accounts.update_users_password(users, valid_users_password(), %{ + Accounts.update_user_password(user, valid_user_password(), %{ password: "new valid password" }) - refute Repo.get_by(UsersToken, users_id: users.id) + refute Repo.get_by(UserToken, user_id: user.id) end end - describe "generate_users_session_token/1" do + describe "generate_user_session_token/1" do setup do - %{users: users_fixture()} + %{user: user_fixture()} end - test "generates a token", %{users: users} do - token = Accounts.generate_users_session_token(users) - assert users_token = Repo.get_by(UsersToken, token: token) - assert users_token.context == "session" + test "generates a token", %{user: user} do + token = Accounts.generate_user_session_token(user) + assert user_token = Repo.get_by(UserToken, token: token) + assert user_token.context == "session" - # Creating the same token for another users should fail + # Creating the same token for another user should fail assert_raise Ecto.ConstraintError, fn -> - Repo.insert!(%UsersToken{ - token: users_token.token, - users_id: users_fixture().id, + Repo.insert!(%UserToken{ + token: user_token.token, + user_id: user_fixture().id, context: "session" }) end end end - describe "get_users_by_session_token/1" do + describe "get_user_by_session_token/1" do setup do - users = users_fixture() - token = Accounts.generate_users_session_token(users) - %{users: users, token: token} + user = user_fixture() + token = Accounts.generate_user_session_token(user) + %{user: user, token: token} end - test "returns users by token", %{users: users, token: token} do - assert session_users = Accounts.get_users_by_session_token(token) - assert session_users.id == users.id + test "returns user by token", %{user: user, token: token} do + assert session_user = Accounts.get_user_by_session_token(token) + assert session_user.id == user.id end - test "does not return users for invalid token" do - refute Accounts.get_users_by_session_token("oops") + test "does not return user for invalid token" do + refute Accounts.get_user_by_session_token("oops") end - test "does not return users for expired token", %{token: token} do - {1, nil} = Repo.update_all(UsersToken, set: [inserted_at: ~N[2020-01-01 00:00:00]]) - refute Accounts.get_users_by_session_token(token) + test "does not return user for expired token", %{token: token} do + {1, nil} = Repo.update_all(UserToken, set: [inserted_at: ~N[2020-01-01 00:00:00]]) + refute Accounts.get_user_by_session_token(token) end end - describe "delete_users_session_token/1" do + describe "delete_user_session_token/1" do test "deletes the token" do - users = users_fixture() - token = Accounts.generate_users_session_token(users) - assert Accounts.delete_users_session_token(token) == :ok - refute Accounts.get_users_by_session_token(token) + user = user_fixture() + token = Accounts.generate_user_session_token(user) + assert Accounts.delete_user_session_token(token) == :ok + refute Accounts.get_user_by_session_token(token) end end - describe "deliver_users_confirmation_instructions/2" do + describe "deliver_user_confirmation_instructions/2" do setup do - %{users: users_fixture()} + %{user: user_fixture()} end - test "sends token through notification", %{users: users} do + test "sends token through notification", %{user: user} do token = - extract_users_token(fn url -> - Accounts.deliver_users_confirmation_instructions(users, url) + extract_user_token(fn url -> + Accounts.deliver_user_confirmation_instructions(user, url) end) {:ok, token} = Base.url_decode64(token, padding: false) - assert users_token = Repo.get_by(UsersToken, token: :crypto.hash(:sha256, token)) - assert users_token.users_id == users.id - assert users_token.sent_to == users.email - assert users_token.context == "confirm" + assert user_token = Repo.get_by(UserToken, token: :crypto.hash(:sha256, token)) + assert user_token.user_id == user.id + assert user_token.sent_to == user.email + assert user_token.context == "confirm" end end - describe "confirm_users/1" do + describe "confirm_user/1" do setup do - users = users_fixture() + user = user_fixture() token = - extract_users_token(fn url -> - Accounts.deliver_users_confirmation_instructions(users, url) + extract_user_token(fn url -> + Accounts.deliver_user_confirmation_instructions(user, url) end) - %{users: users, token: token} + %{user: user, token: token} end - test "confirms the email with a valid token", %{users: users, token: token} do - assert {:ok, confirmed_users} = Accounts.confirm_users(token) - assert confirmed_users.confirmed_at - assert confirmed_users.confirmed_at != users.confirmed_at - assert Repo.get!(Users, users.id).confirmed_at - refute Repo.get_by(UsersToken, users_id: users.id) + test "confirms the email with a valid token", %{user: user, token: token} do + assert {:ok, confirmed_user} = Accounts.confirm_user(token) + assert confirmed_user.confirmed_at + assert confirmed_user.confirmed_at != user.confirmed_at + assert Repo.get!(User, user.id).confirmed_at + refute Repo.get_by(UserToken, user_id: user.id) end - test "does not confirm with invalid token", %{users: users} do - assert Accounts.confirm_users("oops") == :error - refute Repo.get!(Users, users.id).confirmed_at - assert Repo.get_by(UsersToken, users_id: users.id) + test "does not confirm with invalid token", %{user: user} do + assert Accounts.confirm_user("oops") == :error + refute Repo.get!(User, user.id).confirmed_at + assert Repo.get_by(UserToken, user_id: user.id) end - test "does not confirm email if token expired", %{users: users, token: token} do - {1, nil} = Repo.update_all(UsersToken, set: [inserted_at: ~N[2020-01-01 00:00:00]]) - assert Accounts.confirm_users(token) == :error - refute Repo.get!(Users, users.id).confirmed_at - assert Repo.get_by(UsersToken, users_id: users.id) + test "does not confirm email if token expired", %{user: user, token: token} do + {1, nil} = Repo.update_all(UserToken, set: [inserted_at: ~N[2020-01-01 00:00:00]]) + assert Accounts.confirm_user(token) == :error + refute Repo.get!(User, user.id).confirmed_at + assert Repo.get_by(UserToken, user_id: user.id) end end - describe "deliver_users_reset_password_instructions/2" do + describe "deliver_user_reset_password_instructions/2" do setup do - %{users: users_fixture()} + %{user: user_fixture()} end - test "sends token through notification", %{users: users} do + test "sends token through notification", %{user: user} do token = - extract_users_token(fn url -> - Accounts.deliver_users_reset_password_instructions(users, url) + extract_user_token(fn url -> + Accounts.deliver_user_reset_password_instructions(user, url) end) {:ok, token} = Base.url_decode64(token, padding: false) - assert users_token = Repo.get_by(UsersToken, token: :crypto.hash(:sha256, token)) - assert users_token.users_id == users.id - assert users_token.sent_to == users.email - assert users_token.context == "reset_password" + assert user_token = Repo.get_by(UserToken, token: :crypto.hash(:sha256, token)) + assert user_token.user_id == user.id + assert user_token.sent_to == user.email + assert user_token.context == "reset_password" end end - describe "get_users_by_reset_password_token/1" do + describe "get_user_by_reset_password_token/1" do setup do - users = users_fixture() + user = user_fixture() token = - extract_users_token(fn url -> - Accounts.deliver_users_reset_password_instructions(users, url) + extract_user_token(fn url -> + Accounts.deliver_user_reset_password_instructions(user, url) end) - %{users: users, token: token} + %{user: user, token: token} end - test "returns the users with valid token", %{users: %{id: id}, token: token} do - assert %Users{id: ^id} = Accounts.get_users_by_reset_password_token(token) - assert Repo.get_by(UsersToken, users_id: id) + test "returns the user with valid token", %{user: %{id: id}, token: token} do + assert %User{id: ^id} = Accounts.get_user_by_reset_password_token(token) + assert Repo.get_by(UserToken, user_id: id) end - test "does not return the users with invalid token", %{users: users} do - refute Accounts.get_users_by_reset_password_token("oops") - assert Repo.get_by(UsersToken, users_id: users.id) + test "does not return the user with invalid token", %{user: user} do + refute Accounts.get_user_by_reset_password_token("oops") + assert Repo.get_by(UserToken, user_id: user.id) end - test "does not return the users if token expired", %{users: users, token: token} do - {1, nil} = Repo.update_all(UsersToken, set: [inserted_at: ~N[2020-01-01 00:00:00]]) - refute Accounts.get_users_by_reset_password_token(token) - assert Repo.get_by(UsersToken, users_id: users.id) + test "does not return the user if token expired", %{user: user, token: token} do + {1, nil} = Repo.update_all(UserToken, set: [inserted_at: ~N[2020-01-01 00:00:00]]) + refute Accounts.get_user_by_reset_password_token(token) + assert Repo.get_by(UserToken, user_id: user.id) end end - describe "reset_users_password/2" do + describe "reset_user_password/2" do setup do - %{users: users_fixture()} + %{user: user_fixture()} end - test "validates password", %{users: users} do + test "validates password", %{user: user} do {:error, changeset} = - Accounts.reset_users_password(users, %{ + Accounts.reset_user_password(user, %{ password: "not valid", password_confirmation: "another" }) @@ -481,28 +482,28 @@ defmodule Cklist.AccountsTest do } = errors_on(changeset) end - test "validates maximum values for password for security", %{users: users} do + test "validates maximum values for password for security", %{user: user} do too_long = String.duplicate("db", 100) - {:error, changeset} = Accounts.reset_users_password(users, %{password: too_long}) + {:error, changeset} = Accounts.reset_user_password(user, %{password: too_long}) assert "should be at most 72 character(s)" in errors_on(changeset).password end - test "updates the password", %{users: users} do - {:ok, updated_users} = Accounts.reset_users_password(users, %{password: "new valid password"}) - assert is_nil(updated_users.password) - assert Accounts.get_users_by_email_and_password(users.email, "new valid password") + test "updates the password", %{user: user} do + {:ok, updated_user} = Accounts.reset_user_password(user, %{password: "new valid password"}) + assert is_nil(updated_user.password) + assert Accounts.get_user_by_email_and_password(user.email, "new valid password") end - test "deletes all tokens for the given users", %{users: users} do - _ = Accounts.generate_users_session_token(users) - {:ok, _} = Accounts.reset_users_password(users, %{password: "new valid password"}) - refute Repo.get_by(UsersToken, users_id: users.id) + test "deletes all tokens for the given user", %{user: user} do + _ = Accounts.generate_user_session_token(user) + {:ok, _} = Accounts.reset_user_password(user, %{password: "new valid password"}) + refute Repo.get_by(UserToken, user_id: user.id) end end - describe "inspect/2 for the Users module" do + describe "inspect/2 for the User module" do test "does not include password" do - refute inspect(%Users{password: "123456"}) =~ "password: \"123456\"" + refute inspect(%User{password: "123456"}) =~ "password: \"123456\"" end end end diff --git a/test/cklist_web/controllers/user_session_controller_test.exs b/test/cklist_web/controllers/user_session_controller_test.exs new file mode 100644 index 0000000..a441494 --- /dev/null +++ b/test/cklist_web/controllers/user_session_controller_test.exs @@ -0,0 +1,113 @@ +defmodule CklistWeb.UserSessionControllerTest do + use CklistWeb.ConnCase, async: true + + import Cklist.AccountsFixtures + + setup do + %{user: user_fixture()} + end + + describe "POST /user/log_in" do + test "logs the user in", %{conn: conn, user: user} do + conn = + post(conn, ~p"/user/log_in", %{ + "user" => %{"email" => user.email, "password" => valid_user_password()} + }) + + assert get_session(conn, :user_token) + assert redirected_to(conn) == ~p"/" + + # Now do a logged in request and assert on the menu + conn = get(conn, ~p"/") + response = html_response(conn, 200) + # assert response =~ user.email + assert response =~ ~p"/user/settings" + assert response =~ ~p"/user/log_out" + end + + test "logs the user in with remember me", %{conn: conn, user: user} do + conn = + post(conn, ~p"/user/log_in", %{ + "user" => %{ + "email" => user.email, + "password" => valid_user_password(), + "remember_me" => "true" + } + }) + + assert conn.resp_cookies["_cklist_web_user_remember_me"] + assert redirected_to(conn) == ~p"/" + end + + test "logs the user in with return to", %{conn: conn, user: user} do + conn = + conn + |> init_test_session(user_return_to: "/foo/bar") + |> post(~p"/user/log_in", %{ + "user" => %{ + "email" => user.email, + "password" => valid_user_password() + } + }) + + assert redirected_to(conn) == "/foo/bar" + assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Welcome back!" + end + + test "login following registration", %{conn: conn, user: user} do + conn = + conn + |> post(~p"/user/log_in", %{ + "_action" => "registered", + "user" => %{ + "email" => user.email, + "password" => valid_user_password() + } + }) + + assert redirected_to(conn) == ~p"/" + assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Account created successfully" + end + + test "login following password update", %{conn: conn, user: user} do + conn = + conn + |> post(~p"/user/log_in", %{ + "_action" => "password_updated", + "user" => %{ + "email" => user.email, + "password" => valid_user_password() + } + }) + + assert redirected_to(conn) == ~p"/user/settings" + assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Password updated successfully" + end + + test "redirects to login page with invalid credentials", %{conn: conn} do + conn = + post(conn, ~p"/user/log_in", %{ + "user" => %{"email" => "invalid@email.com", "password" => "invalid_password"} + }) + + assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invalid email or password" + assert redirected_to(conn) == ~p"/user/log_in" + end + end + + describe "DELETE /user/log_out" do + test "logs the user out", %{conn: conn, user: user} do + conn = conn |> log_in_user(user) |> delete(~p"/user/log_out") + assert redirected_to(conn) == ~p"/" + refute get_session(conn, :user_token) + assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully" + end + + test "succeeds even if the user is not logged in", %{conn: conn} do + conn = delete(conn, ~p"/user/log_out") + assert redirected_to(conn) == ~p"/" + refute get_session(conn, :user_token) + assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully" + end + end +end diff --git a/test/cklist_web/controllers/users_session_controller_test.exs b/test/cklist_web/controllers/users_session_controller_test.exs deleted file mode 100644 index bf5b451..0000000 --- a/test/cklist_web/controllers/users_session_controller_test.exs +++ /dev/null @@ -1,113 +0,0 @@ -defmodule CklistWeb.UsersSessionControllerTest do - use CklistWeb.ConnCase, async: true - - import Cklist.AccountsFixtures - - setup do - %{users: users_fixture()} - end - - describe "POST /users/log_in" do - test "logs the users in", %{conn: conn, users: users} do - conn = - post(conn, ~p"/users/log_in", %{ - "users" => %{"email" => users.email, "password" => valid_users_password()} - }) - - assert get_session(conn, :users_token) - assert redirected_to(conn) == ~p"/" - - # Now do a logged in request and assert on the menu - conn = get(conn, ~p"/") - response = html_response(conn, 200) - assert response =~ users.email - assert response =~ ~p"/users/settings" - assert response =~ ~p"/users/log_out" - end - - test "logs the users in with remember me", %{conn: conn, users: users} do - conn = - post(conn, ~p"/users/log_in", %{ - "users" => %{ - "email" => users.email, - "password" => valid_users_password(), - "remember_me" => "true" - } - }) - - assert conn.resp_cookies["_cklist_web_users_remember_me"] - assert redirected_to(conn) == ~p"/" - end - - test "logs the users in with return to", %{conn: conn, users: users} do - conn = - conn - |> init_test_session(users_return_to: "/foo/bar") - |> post(~p"/users/log_in", %{ - "users" => %{ - "email" => users.email, - "password" => valid_users_password() - } - }) - - assert redirected_to(conn) == "/foo/bar" - assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Welcome back!" - end - - test "login following registration", %{conn: conn, users: users} do - conn = - conn - |> post(~p"/users/log_in", %{ - "_action" => "registered", - "users" => %{ - "email" => users.email, - "password" => valid_users_password() - } - }) - - assert redirected_to(conn) == ~p"/" - assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Account created successfully" - end - - test "login following password update", %{conn: conn, users: users} do - conn = - conn - |> post(~p"/users/log_in", %{ - "_action" => "password_updated", - "users" => %{ - "email" => users.email, - "password" => valid_users_password() - } - }) - - assert redirected_to(conn) == ~p"/users/settings" - assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Password updated successfully" - end - - test "redirects to login page with invalid credentials", %{conn: conn} do - conn = - post(conn, ~p"/users/log_in", %{ - "users" => %{"email" => "invalid@email.com", "password" => "invalid_password"} - }) - - assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invalid email or password" - assert redirected_to(conn) == ~p"/users/log_in" - end - end - - describe "DELETE /users/log_out" do - test "logs the users out", %{conn: conn, users: users} do - conn = conn |> log_in_users(users) |> delete(~p"/users/log_out") - assert redirected_to(conn) == ~p"/" - refute get_session(conn, :users_token) - assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully" - end - - test "succeeds even if the users is not logged in", %{conn: conn} do - conn = delete(conn, ~p"/users/log_out") - assert redirected_to(conn) == ~p"/" - refute get_session(conn, :users_token) - assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully" - end - end -end diff --git a/test/cklist_web/live/users_confirmation_instructions_live_test.exs b/test/cklist_web/live/user_confirmation_instructions_live_test.exs similarity index 52% rename from test/cklist_web/live/users_confirmation_instructions_live_test.exs rename to test/cklist_web/live/user_confirmation_instructions_live_test.exs index ea19f4a..a9faf92 100644 --- a/test/cklist_web/live/users_confirmation_instructions_live_test.exs +++ b/test/cklist_web/live/user_confirmation_instructions_live_test.exs @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersConfirmationInstructionsLiveTest do +defmodule CklistWeb.UserConfirmationInstructionsLiveTest do use CklistWeb.ConnCase import Phoenix.LiveViewTest @@ -8,60 +8,60 @@ defmodule CklistWeb.UsersConfirmationInstructionsLiveTest do alias Cklist.Repo setup do - %{users: users_fixture()} + %{user: user_fixture()} end describe "Resend confirmation" do test "renders the resend confirmation page", %{conn: conn} do - {:ok, _lv, html} = live(conn, ~p"/users/confirm") + {:ok, _lv, html} = live(conn, ~p"/user/confirm") assert html =~ "Resend confirmation instructions" end - test "sends a new confirmation token", %{conn: conn, users: users} do - {:ok, lv, _html} = live(conn, ~p"/users/confirm") + test "sends a new confirmation token", %{conn: conn, user: user} do + {:ok, lv, _html} = live(conn, ~p"/user/confirm") {:ok, conn} = lv - |> form("#resend_confirmation_form", users: %{email: users.email}) + |> form("#resend_confirmation_form", user: %{email: user.email}) |> render_submit() |> follow_redirect(conn, ~p"/") assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "If your email is in our system" - assert Repo.get_by!(Accounts.UsersToken, users_id: users.id).context == "confirm" + assert Repo.get_by!(Accounts.UserToken, user_id: user.id).context == "confirm" end - test "does not send confirmation token if users is confirmed", %{conn: conn, users: users} do - Repo.update!(Accounts.Users.confirm_changeset(users)) + test "does not send confirmation token if user is confirmed", %{conn: conn, user: user} do + Repo.update!(Accounts.User.confirm_changeset(user)) - {:ok, lv, _html} = live(conn, ~p"/users/confirm") + {:ok, lv, _html} = live(conn, ~p"/user/confirm") {:ok, conn} = lv - |> form("#resend_confirmation_form", users: %{email: users.email}) + |> form("#resend_confirmation_form", user: %{email: user.email}) |> render_submit() |> follow_redirect(conn, ~p"/") assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "If your email is in our system" - refute Repo.get_by(Accounts.UsersToken, users_id: users.id) + refute Repo.get_by(Accounts.UserToken, user_id: user.id) end test "does not send confirmation token if email is invalid", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/confirm") + {:ok, lv, _html} = live(conn, ~p"/user/confirm") {:ok, conn} = lv - |> form("#resend_confirmation_form", users: %{email: "unknown@example.com"}) + |> form("#resend_confirmation_form", user: %{email: "unknown@example.com"}) |> render_submit() |> follow_redirect(conn, ~p"/") assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "If your email is in our system" - assert Repo.all(Accounts.UsersToken) == [] + assert Repo.all(Accounts.UserToken) == [] end end end diff --git a/test/cklist_web/live/users_confirmation_live_test.exs b/test/cklist_web/live/user_confirmation_live_test.exs similarity index 55% rename from test/cklist_web/live/users_confirmation_live_test.exs rename to test/cklist_web/live/user_confirmation_live_test.exs index 6699916..8366b91 100644 --- a/test/cklist_web/live/users_confirmation_live_test.exs +++ b/test/cklist_web/live/user_confirmation_live_test.exs @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersConfirmationLiveTest do +defmodule CklistWeb.UserConfirmationLiveTest do use CklistWeb.ConnCase import Phoenix.LiveViewTest @@ -8,22 +8,22 @@ defmodule CklistWeb.UsersConfirmationLiveTest do alias Cklist.Repo setup do - %{users: users_fixture()} + %{user: user_fixture()} end - describe "Confirm users" do + describe "Confirm user" do test "renders confirmation page", %{conn: conn} do - {:ok, _lv, html} = live(conn, ~p"/users/confirm/some-token") + {:ok, _lv, html} = live(conn, ~p"/user/confirm/some-token") assert html =~ "Confirm Account" end - test "confirms the given token once", %{conn: conn, users: users} do + test "confirms the given token once", %{conn: conn, user: user} do token = - extract_users_token(fn url -> - Accounts.deliver_users_confirmation_instructions(users, url) + extract_user_token(fn url -> + Accounts.deliver_user_confirmation_instructions(user, url) end) - {:ok, lv, _html} = live(conn, ~p"/users/confirm/#{token}") + {:ok, lv, _html} = live(conn, ~p"/user/confirm/#{token}") result = lv @@ -34,14 +34,14 @@ defmodule CklistWeb.UsersConfirmationLiveTest do assert {:ok, conn} = result assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ - "Users confirmed successfully" + "User confirmed successfully" - assert Accounts.get_users!(users.id).confirmed_at - refute get_session(conn, :users_token) - assert Repo.all(Accounts.UsersToken) == [] + assert Accounts.get_user!(user.id).confirmed_at + refute get_session(conn, :user_token) + assert Repo.all(Accounts.UserToken) == [] # when not logged in - {:ok, lv, _html} = live(conn, ~p"/users/confirm/#{token}") + {:ok, lv, _html} = live(conn, ~p"/user/confirm/#{token}") result = lv @@ -52,14 +52,14 @@ defmodule CklistWeb.UsersConfirmationLiveTest do assert {:ok, conn} = result assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ - "Users confirmation link is invalid or it has expired" + "User confirmation link is invalid or it has expired" # when logged in conn = build_conn() - |> log_in_users(users) + |> log_in_user(user) - {:ok, lv, _html} = live(conn, ~p"/users/confirm/#{token}") + {:ok, lv, _html} = live(conn, ~p"/user/confirm/#{token}") result = lv @@ -71,8 +71,8 @@ defmodule CklistWeb.UsersConfirmationLiveTest do refute Phoenix.Flash.get(conn.assigns.flash, :error) end - test "does not confirm email with invalid token", %{conn: conn, users: users} do - {:ok, lv, _html} = live(conn, ~p"/users/confirm/invalid-token") + test "does not confirm email with invalid token", %{conn: conn, user: user} do + {:ok, lv, _html} = live(conn, ~p"/user/confirm/invalid-token") {:ok, conn} = lv @@ -81,9 +81,9 @@ defmodule CklistWeb.UsersConfirmationLiveTest do |> follow_redirect(conn, ~p"/") assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ - "Users confirmation link is invalid or it has expired" + "User confirmation link is invalid or it has expired" - refute Accounts.get_users!(users.id).confirmed_at + refute Accounts.get_user!(user.id).confirmed_at end end end diff --git a/test/cklist_web/live/users_forgot_password_live_test.exs b/test/cklist_web/live/user_forgot_password_live_test.exs similarity index 54% rename from test/cklist_web/live/users_forgot_password_live_test.exs rename to test/cklist_web/live/user_forgot_password_live_test.exs index 9763c83..97989fd 100644 --- a/test/cklist_web/live/users_forgot_password_live_test.exs +++ b/test/cklist_web/live/user_forgot_password_live_test.exs @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersForgotPasswordLiveTest do +defmodule CklistWeb.UserForgotPasswordLiveTest do use CklistWeb.ConnCase import Phoenix.LiveViewTest @@ -9,18 +9,18 @@ defmodule CklistWeb.UsersForgotPasswordLiveTest do describe "Forgot password page" do test "renders email page", %{conn: conn} do - {:ok, lv, html} = live(conn, ~p"/users/reset_password") + {:ok, lv, html} = live(conn, ~p"/user/reset_password") assert html =~ "Forgot your password?" - assert has_element?(lv, ~s|a[href="#{~p"/users/register"}"]|, "Register") - assert has_element?(lv, ~s|a[href="#{~p"/users/log_in"}"]|, "Log in") + assert has_element?(lv, ~s|a[href="#{~p"/user/register"}"]|, "Register") + assert has_element?(lv, ~s|a[href="#{~p"/user/log_in"}"]|, "Log in") end test "redirects if already logged in", %{conn: conn} do result = conn - |> log_in_users(users_fixture()) - |> live(~p"/users/reset_password") + |> log_in_user(user_fixture()) + |> live(~p"/user/reset_password") |> follow_redirect(conn, ~p"/") assert {:ok, _conn} = result @@ -29,35 +29,35 @@ defmodule CklistWeb.UsersForgotPasswordLiveTest do describe "Reset link" do setup do - %{users: users_fixture()} + %{user: user_fixture()} end - test "sends a new reset password token", %{conn: conn, users: users} do - {:ok, lv, _html} = live(conn, ~p"/users/reset_password") + test "sends a new reset password token", %{conn: conn, user: user} do + {:ok, lv, _html} = live(conn, ~p"/user/reset_password") {:ok, conn} = lv - |> form("#reset_password_form", users: %{"email" => users.email}) + |> form("#reset_password_form", user: %{"email" => user.email}) |> render_submit() |> follow_redirect(conn, "/") assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "If your email is in our system" - assert Repo.get_by!(Accounts.UsersToken, users_id: users.id).context == + assert Repo.get_by!(Accounts.UserToken, user_id: user.id).context == "reset_password" end test "does not send reset password token if email is invalid", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/reset_password") + {:ok, lv, _html} = live(conn, ~p"/user/reset_password") {:ok, conn} = lv - |> form("#reset_password_form", users: %{"email" => "unknown@example.com"}) + |> form("#reset_password_form", user: %{"email" => "unknown@example.com"}) |> render_submit() |> follow_redirect(conn, "/") assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "If your email is in our system" - assert Repo.all(Accounts.UsersToken) == [] + assert Repo.all(Accounts.UserToken) == [] end end end diff --git a/test/cklist_web/live/users_login_live_test.exs b/test/cklist_web/live/user_login_live_test.exs similarity index 63% rename from test/cklist_web/live/users_login_live_test.exs rename to test/cklist_web/live/user_login_live_test.exs index 9d445ca..2637528 100644 --- a/test/cklist_web/live/users_login_live_test.exs +++ b/test/cklist_web/live/user_login_live_test.exs @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersLoginLiveTest do +defmodule CklistWeb.UserLoginLiveTest do use CklistWeb.ConnCase import Phoenix.LiveViewTest @@ -6,7 +6,7 @@ defmodule CklistWeb.UsersLoginLiveTest do describe "Log in page" do test "renders log in page", %{conn: conn} do - {:ok, _lv, html} = live(conn, ~p"/users/log_in") + {:ok, _lv, html} = live(conn, ~p"/user/log_in") assert html =~ "Log in" assert html =~ "Register" @@ -16,23 +16,23 @@ defmodule CklistWeb.UsersLoginLiveTest do test "redirects if already logged in", %{conn: conn} do result = conn - |> log_in_users(users_fixture()) - |> live(~p"/users/log_in") + |> log_in_user(user_fixture()) + |> live(~p"/user/log_in") |> follow_redirect(conn, "/") assert {:ok, _conn} = result end end - describe "users login" do - test "redirects if users login with valid credentials", %{conn: conn} do + describe "user login" do + test "redirects if user login with valid credentials", %{conn: conn} do password = "123456789abcd" - users = users_fixture(%{password: password}) + user = user_fixture(%{password: password}) - {:ok, lv, _html} = live(conn, ~p"/users/log_in") + {:ok, lv, _html} = live(conn, ~p"/user/log_in") form = - form(lv, "#login_form", users: %{email: users.email, password: password, remember_me: true}) + form(lv, "#login_form", user: %{email: user.email, password: password, remember_me: true}) conn = submit_form(form, conn) @@ -42,30 +42,30 @@ defmodule CklistWeb.UsersLoginLiveTest do test "redirects to login page with a flash error if there are no valid credentials", %{ conn: conn } do - {:ok, lv, _html} = live(conn, ~p"/users/log_in") + {:ok, lv, _html} = live(conn, ~p"/user/log_in") form = form(lv, "#login_form", - users: %{email: "test@email.com", password: "123456", remember_me: true} + user: %{email: "test@email.com", password: "123456", remember_me: true} ) conn = submit_form(form, conn) assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invalid email or password" - assert redirected_to(conn) == "/users/log_in" + assert redirected_to(conn) == "/user/log_in" end end describe "login navigation" do test "redirects to registration page when the Register button is clicked", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/log_in") + {:ok, lv, _html} = live(conn, ~p"/user/log_in") {:ok, _login_live, login_html} = lv |> element(~s|main a:fl-contains("Sign up")|) |> render_click() - |> follow_redirect(conn, ~p"/users/register") + |> follow_redirect(conn, ~p"/user/register") assert login_html =~ "Register" end @@ -73,13 +73,13 @@ defmodule CklistWeb.UsersLoginLiveTest do test "redirects to forgot password page when the Forgot Password button is clicked", %{ conn: conn } do - {:ok, lv, _html} = live(conn, ~p"/users/log_in") + {:ok, lv, _html} = live(conn, ~p"/user/log_in") {:ok, conn} = lv |> element(~s|main a:fl-contains("Forgot your password?")|) |> render_click() - |> follow_redirect(conn, ~p"/users/reset_password") + |> follow_redirect(conn, ~p"/user/reset_password") assert conn.resp_body =~ "Forgot your password?" end diff --git a/test/cklist_web/live/users_registration_live_test.exs b/test/cklist_web/live/user_registration_live_test.exs similarity index 64% rename from test/cklist_web/live/users_registration_live_test.exs rename to test/cklist_web/live/user_registration_live_test.exs index ba48a3c..f6ff04c 100644 --- a/test/cklist_web/live/users_registration_live_test.exs +++ b/test/cklist_web/live/user_registration_live_test.exs @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersRegistrationLiveTest do +defmodule CklistWeb.UserRegistrationLiveTest do use CklistWeb.ConnCase import Phoenix.LiveViewTest @@ -6,7 +6,7 @@ defmodule CklistWeb.UsersRegistrationLiveTest do describe "Registration page" do test "renders registration page", %{conn: conn} do - {:ok, _lv, html} = live(conn, ~p"/users/register") + {:ok, _lv, html} = live(conn, ~p"/user/register") assert html =~ "Register" assert html =~ "Log in" @@ -15,20 +15,20 @@ defmodule CklistWeb.UsersRegistrationLiveTest do test "redirects if already logged in", %{conn: conn} do result = conn - |> log_in_users(users_fixture()) - |> live(~p"/users/register") + |> log_in_user(user_fixture()) + |> live(~p"/user/register") |> follow_redirect(conn, "/") assert {:ok, _conn} = result end test "renders errors for invalid data", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/register") + {:ok, lv, _html} = live(conn, ~p"/user/register") result = lv |> element("#registration_form") - |> render_change(users: %{"email" => "with spaces", "password" => "too short"}) + |> render_change(user: %{"email" => "with spaces", "password" => "too short"}) assert result =~ "Register" assert result =~ "must have the @ sign and no spaces" @@ -36,12 +36,12 @@ defmodule CklistWeb.UsersRegistrationLiveTest do end end - describe "register users" do - test "creates account and logs the users in", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/register") + describe "register user" do + test "creates account and logs the user in", %{conn: conn} do + {:ok, lv, _html} = live(conn, ~p"/user/register") - email = unique_users_email() - form = form(lv, "#registration_form", users: valid_users_attributes(email: email)) + email = unique_user_email() + form = form(lv, "#registration_form", user: valid_user_attributes(email: email)) render_submit(form) conn = follow_trigger_action(form, conn) @@ -56,14 +56,14 @@ defmodule CklistWeb.UsersRegistrationLiveTest do end test "renders errors for duplicated email", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/register") + {:ok, lv, _html} = live(conn, ~p"/user/register") - users = users_fixture(%{email: "test@email.com"}) + user = user_fixture(%{email: "test@email.com"}) result = lv |> form("#registration_form", - users: %{"email" => users.email, "password" => "valid_password"} + user: %{"email" => user.email, "password" => "valid_password"} ) |> render_submit() @@ -73,13 +73,13 @@ defmodule CklistWeb.UsersRegistrationLiveTest do describe "registration navigation" do test "redirects to login page when the Log in button is clicked", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/register") + {:ok, lv, _html} = live(conn, ~p"/user/register") {:ok, _login_live, login_html} = lv |> element(~s|main a:fl-contains("Sign in")|) |> render_click() - |> follow_redirect(conn, ~p"/users/log_in") + |> follow_redirect(conn, ~p"/user/log_in") assert login_html =~ "Log in" end diff --git a/test/cklist_web/live/users_reset_password_live_test.exs b/test/cklist_web/live/user_reset_password_live_test.exs similarity index 66% rename from test/cklist_web/live/users_reset_password_live_test.exs rename to test/cklist_web/live/user_reset_password_live_test.exs index 305c61f..e2d2b3b 100644 --- a/test/cklist_web/live/users_reset_password_live_test.exs +++ b/test/cklist_web/live/user_reset_password_live_test.exs @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersResetPasswordLiveTest do +defmodule CklistWeb.UserResetPasswordLiveTest do use CklistWeb.ConnCase import Phoenix.LiveViewTest @@ -7,25 +7,25 @@ defmodule CklistWeb.UsersResetPasswordLiveTest do alias Cklist.Accounts setup do - users = users_fixture() + user = user_fixture() token = - extract_users_token(fn url -> - Accounts.deliver_users_reset_password_instructions(users, url) + extract_user_token(fn url -> + Accounts.deliver_user_reset_password_instructions(user, url) end) - %{token: token, users: users} + %{token: token, user: user} end describe "Reset password page" do test "renders reset password with valid token", %{conn: conn, token: token} do - {:ok, _lv, html} = live(conn, ~p"/users/reset_password/#{token}") + {:ok, _lv, html} = live(conn, ~p"/user/reset_password/#{token}") assert html =~ "Reset Password" end test "does not render reset password with invalid token", %{conn: conn} do - {:error, {:redirect, to}} = live(conn, ~p"/users/reset_password/invalid") + {:error, {:redirect, to}} = live(conn, ~p"/user/reset_password/invalid") assert to == %{ flash: %{"error" => "Reset password link is invalid or it has expired."}, @@ -34,13 +34,13 @@ defmodule CklistWeb.UsersResetPasswordLiveTest do end test "renders errors for invalid data", %{conn: conn, token: token} do - {:ok, lv, _html} = live(conn, ~p"/users/reset_password/#{token}") + {:ok, lv, _html} = live(conn, ~p"/user/reset_password/#{token}") result = lv |> element("#reset_password_form") |> render_change( - users: %{"password" => "secret12", "password_confirmation" => "secret123456"} + user: %{"password" => "secret12", "password_confirmation" => "secret123456"} ) assert result =~ "should be at least 12 character" @@ -49,32 +49,32 @@ defmodule CklistWeb.UsersResetPasswordLiveTest do end describe "Reset Password" do - test "resets password once", %{conn: conn, token: token, users: users} do - {:ok, lv, _html} = live(conn, ~p"/users/reset_password/#{token}") + test "resets password once", %{conn: conn, token: token, user: user} do + {:ok, lv, _html} = live(conn, ~p"/user/reset_password/#{token}") {:ok, conn} = lv |> form("#reset_password_form", - users: %{ + user: %{ "password" => "new valid password", "password_confirmation" => "new valid password" } ) |> render_submit() - |> follow_redirect(conn, ~p"/users/log_in") + |> follow_redirect(conn, ~p"/user/log_in") - refute get_session(conn, :users_token) + refute get_session(conn, :user_token) assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Password reset successfully" - assert Accounts.get_users_by_email_and_password(users.email, "new valid password") + assert Accounts.get_user_by_email_and_password(user.email, "new valid password") end test "does not reset password on invalid data", %{conn: conn, token: token} do - {:ok, lv, _html} = live(conn, ~p"/users/reset_password/#{token}") + {:ok, lv, _html} = live(conn, ~p"/user/reset_password/#{token}") result = lv |> form("#reset_password_form", - users: %{ + user: %{ "password" => "too short", "password_confirmation" => "does not match" } @@ -89,13 +89,13 @@ defmodule CklistWeb.UsersResetPasswordLiveTest do describe "Reset password navigation" do test "redirects to login page when the Log in button is clicked", %{conn: conn, token: token} do - {:ok, lv, _html} = live(conn, ~p"/users/reset_password/#{token}") + {:ok, lv, _html} = live(conn, ~p"/user/reset_password/#{token}") {:ok, conn} = lv |> element(~s|main a:fl-contains("Log in")|) |> render_click() - |> follow_redirect(conn, ~p"/users/log_in") + |> follow_redirect(conn, ~p"/user/log_in") assert conn.resp_body =~ "Log in" end @@ -104,13 +104,13 @@ defmodule CklistWeb.UsersResetPasswordLiveTest do conn: conn, token: token } do - {:ok, lv, _html} = live(conn, ~p"/users/reset_password/#{token}") + {:ok, lv, _html} = live(conn, ~p"/user/reset_password/#{token}") {:ok, conn} = lv |> element(~s|main a:fl-contains("Register")|) |> render_click() - |> follow_redirect(conn, ~p"/users/register") + |> follow_redirect(conn, ~p"/user/register") assert conn.resp_body =~ "Register" end diff --git a/test/cklist_web/live/users_settings_live_test.exs b/test/cklist_web/live/user_settings_live_test.exs similarity index 57% rename from test/cklist_web/live/users_settings_live_test.exs rename to test/cklist_web/live/user_settings_live_test.exs index efa4d65..12a2f0a 100644 --- a/test/cklist_web/live/users_settings_live_test.exs +++ b/test/cklist_web/live/user_settings_live_test.exs @@ -1,4 +1,4 @@ -defmodule CklistWeb.UsersSettingsLiveTest do +defmodule CklistWeb.UserSettingsLiveTest do use CklistWeb.ConnCase alias Cklist.Accounts @@ -9,48 +9,48 @@ defmodule CklistWeb.UsersSettingsLiveTest do test "renders settings page", %{conn: conn} do {:ok, _lv, html} = conn - |> log_in_users(users_fixture()) - |> live(~p"/users/settings") + |> log_in_user(user_fixture()) + |> live(~p"/user/settings") assert html =~ "Change Email" assert html =~ "Change Password" end - test "redirects if users is not logged in", %{conn: conn} do - assert {:error, redirect} = live(conn, ~p"/users/settings") + test "redirects if user is not logged in", %{conn: conn} do + assert {:error, redirect} = live(conn, ~p"/user/settings") assert {:redirect, %{to: path, flash: flash}} = redirect - assert path == ~p"/users/log_in" + assert path == ~p"/user/log_in" assert %{"error" => "You must log in to access this page."} = flash end end describe "update email form" do setup %{conn: conn} do - password = valid_users_password() - users = users_fixture(%{password: password}) - %{conn: log_in_users(conn, users), users: users, password: password} + password = valid_user_password() + user = user_fixture(%{password: password}) + %{conn: log_in_user(conn, user), user: user, password: password} end - test "updates the users email", %{conn: conn, password: password, users: users} do - new_email = unique_users_email() + test "updates the user email", %{conn: conn, password: password, user: user} do + new_email = unique_user_email() - {:ok, lv, _html} = live(conn, ~p"/users/settings") + {:ok, lv, _html} = live(conn, ~p"/user/settings") result = lv |> form("#email_form", %{ "current_password" => password, - "users" => %{"email" => new_email} + "user" => %{"email" => new_email} }) |> render_submit() assert result =~ "A link to confirm your email" - assert Accounts.get_users_by_email(users.email) + assert Accounts.get_user_by_email(user.email) end test "renders errors with invalid data (phx-change)", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/settings") + {:ok, lv, _html} = live(conn, ~p"/user/settings") result = lv @@ -58,21 +58,21 @@ defmodule CklistWeb.UsersSettingsLiveTest do |> render_change(%{ "action" => "update_email", "current_password" => "invalid", - "users" => %{"email" => "with spaces"} + "user" => %{"email" => "with spaces"} }) assert result =~ "Change Email" assert result =~ "must have the @ sign and no spaces" end - test "renders errors with invalid data (phx-submit)", %{conn: conn, users: users} do - {:ok, lv, _html} = live(conn, ~p"/users/settings") + test "renders errors with invalid data (phx-submit)", %{conn: conn, user: user} do + {:ok, lv, _html} = live(conn, ~p"/user/settings") result = lv |> form("#email_form", %{ "current_password" => "invalid", - "users" => %{"email" => users.email} + "user" => %{"email" => user.email} }) |> render_submit() @@ -84,21 +84,21 @@ defmodule CklistWeb.UsersSettingsLiveTest do describe "update password form" do setup %{conn: conn} do - password = valid_users_password() - users = users_fixture(%{password: password}) - %{conn: log_in_users(conn, users), users: users, password: password} + password = valid_user_password() + user = user_fixture(%{password: password}) + %{conn: log_in_user(conn, user), user: user, password: password} end - test "updates the users password", %{conn: conn, users: users, password: password} do - new_password = valid_users_password() + test "updates the user password", %{conn: conn, user: user, password: password} do + new_password = valid_user_password() - {:ok, lv, _html} = live(conn, ~p"/users/settings") + {:ok, lv, _html} = live(conn, ~p"/user/settings") form = form(lv, "#password_form", %{ "current_password" => password, - "users" => %{ - "email" => users.email, + "user" => %{ + "email" => user.email, "password" => new_password, "password_confirmation" => new_password } @@ -108,25 +108,25 @@ defmodule CklistWeb.UsersSettingsLiveTest do new_password_conn = follow_trigger_action(form, conn) - assert redirected_to(new_password_conn) == ~p"/users/settings" + assert redirected_to(new_password_conn) == ~p"/user/settings" - assert get_session(new_password_conn, :users_token) != get_session(conn, :users_token) + assert get_session(new_password_conn, :user_token) != get_session(conn, :user_token) assert Phoenix.Flash.get(new_password_conn.assigns.flash, :info) =~ "Password updated successfully" - assert Accounts.get_users_by_email_and_password(users.email, new_password) + assert Accounts.get_user_by_email_and_password(user.email, new_password) end test "renders errors with invalid data (phx-change)", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/settings") + {:ok, lv, _html} = live(conn, ~p"/user/settings") result = lv |> element("#password_form") |> render_change(%{ "current_password" => "invalid", - "users" => %{ + "user" => %{ "password" => "too short", "password_confirmation" => "does not match" } @@ -138,13 +138,13 @@ defmodule CklistWeb.UsersSettingsLiveTest do end test "renders errors with invalid data (phx-submit)", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/settings") + {:ok, lv, _html} = live(conn, ~p"/user/settings") result = lv |> form("#password_form", %{ "current_password" => "invalid", - "users" => %{ + "user" => %{ "password" => "too short", "password_confirmation" => "does not match" } @@ -160,49 +160,49 @@ defmodule CklistWeb.UsersSettingsLiveTest do describe "confirm email" do setup %{conn: conn} do - users = users_fixture() - email = unique_users_email() + user = user_fixture() + email = unique_user_email() token = - extract_users_token(fn url -> - Accounts.deliver_users_update_email_instructions(%{users | email: email}, users.email, url) + extract_user_token(fn url -> + Accounts.deliver_user_update_email_instructions(%{user | email: email}, user.email, url) end) - %{conn: log_in_users(conn, users), token: token, email: email, users: users} + %{conn: log_in_user(conn, user), token: token, email: email, user: user} end - test "updates the users email once", %{conn: conn, users: users, token: token, email: email} do - {:error, redirect} = live(conn, ~p"/users/settings/confirm_email/#{token}") + test "updates the user email once", %{conn: conn, user: user, token: token, email: email} do + {:error, redirect} = live(conn, ~p"/user/settings/confirm_email/#{token}") assert {:live_redirect, %{to: path, flash: flash}} = redirect - assert path == ~p"/users/settings" + assert path == ~p"/user/settings" assert %{"info" => message} = flash assert message == "Email changed successfully." - refute Accounts.get_users_by_email(users.email) - assert Accounts.get_users_by_email(email) + refute Accounts.get_user_by_email(user.email) + assert Accounts.get_user_by_email(email) # use confirm token again - {:error, redirect} = live(conn, ~p"/users/settings/confirm_email/#{token}") + {:error, redirect} = live(conn, ~p"/user/settings/confirm_email/#{token}") assert {:live_redirect, %{to: path, flash: flash}} = redirect - assert path == ~p"/users/settings" + assert path == ~p"/user/settings" assert %{"error" => message} = flash assert message == "Email change link is invalid or it has expired." end - test "does not update email with invalid token", %{conn: conn, users: users} do - {:error, redirect} = live(conn, ~p"/users/settings/confirm_email/oops") + test "does not update email with invalid token", %{conn: conn, user: user} do + {:error, redirect} = live(conn, ~p"/user/settings/confirm_email/oops") assert {:live_redirect, %{to: path, flash: flash}} = redirect - assert path == ~p"/users/settings" + assert path == ~p"/user/settings" assert %{"error" => message} = flash assert message == "Email change link is invalid or it has expired." - assert Accounts.get_users_by_email(users.email) + assert Accounts.get_user_by_email(user.email) end - test "redirects if users is not logged in", %{token: token} do + test "redirects if user is not logged in", %{token: token} do conn = build_conn() - {:error, redirect} = live(conn, ~p"/users/settings/confirm_email/#{token}") + {:error, redirect} = live(conn, ~p"/user/settings/confirm_email/#{token}") assert {:redirect, %{to: path, flash: flash}} = redirect - assert path == ~p"/users/log_in" + assert path == ~p"/user/log_in" assert %{"error" => message} = flash assert message == "You must log in to access this page." end diff --git a/test/cklist_web/user_auth_test.exs b/test/cklist_web/user_auth_test.exs new file mode 100644 index 0000000..7feb925 --- /dev/null +++ b/test/cklist_web/user_auth_test.exs @@ -0,0 +1,272 @@ +defmodule CklistWeb.UserAuthTest do + use CklistWeb.ConnCase, async: true + + alias Phoenix.LiveView + alias Cklist.Accounts + alias CklistWeb.UserAuth + import Cklist.AccountsFixtures + + @remember_me_cookie "_cklist_web_user_remember_me" + + setup %{conn: conn} do + conn = + conn + |> Map.replace!(:secret_key_base, CklistWeb.Endpoint.config(:secret_key_base)) + |> init_test_session(%{}) + + %{user: user_fixture(), conn: conn} + end + + describe "log_in_user/3" do + test "stores the user token in the session", %{conn: conn, user: user} do + conn = UserAuth.log_in_user(conn, user) + assert token = get_session(conn, :user_token) + assert get_session(conn, :live_socket_id) == "user_sessions:#{Base.url_encode64(token)}" + assert redirected_to(conn) == ~p"/" + assert Accounts.get_user_by_session_token(token) + end + + test "clears everything previously stored in the session", %{conn: conn, user: user} do + conn = conn |> put_session(:to_be_removed, "value") |> UserAuth.log_in_user(user) + refute get_session(conn, :to_be_removed) + end + + test "redirects to the configured path", %{conn: conn, user: user} do + conn = conn |> put_session(:user_return_to, "/hello") |> UserAuth.log_in_user(user) + assert redirected_to(conn) == "/hello" + end + + test "writes a cookie if remember_me is configured", %{conn: conn, user: user} do + conn = conn |> fetch_cookies() |> UserAuth.log_in_user(user, %{"remember_me" => "true"}) + assert get_session(conn, :user_token) == conn.cookies[@remember_me_cookie] + + assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie] + assert signed_token != get_session(conn, :user_token) + assert max_age == 5_184_000 + end + end + + describe "logout_user/1" do + test "erases session and cookies", %{conn: conn, user: user} do + user_token = Accounts.generate_user_session_token(user) + + conn = + conn + |> put_session(:user_token, user_token) + |> put_req_cookie(@remember_me_cookie, user_token) + |> fetch_cookies() + |> UserAuth.log_out_user() + + refute get_session(conn, :user_token) + refute conn.cookies[@remember_me_cookie] + assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie] + assert redirected_to(conn) == ~p"/" + refute Accounts.get_user_by_session_token(user_token) + end + + test "broadcasts to the given live_socket_id", %{conn: conn} do + live_socket_id = "user_sessions:abcdef-token" + CklistWeb.Endpoint.subscribe(live_socket_id) + + conn + |> put_session(:live_socket_id, live_socket_id) + |> UserAuth.log_out_user() + + assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: ^live_socket_id} + end + + test "works even if user is already logged out", %{conn: conn} do + conn = conn |> fetch_cookies() |> UserAuth.log_out_user() + refute get_session(conn, :user_token) + assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie] + assert redirected_to(conn) == ~p"/" + end + end + + describe "fetch_current_user/2" do + test "authenticates user from session", %{conn: conn, user: user} do + user_token = Accounts.generate_user_session_token(user) + conn = conn |> put_session(:user_token, user_token) |> UserAuth.fetch_current_user([]) + assert conn.assigns.current_user.id == user.id + end + + test "authenticates user from cookies", %{conn: conn, user: user} do + logged_in_conn = + conn |> fetch_cookies() |> UserAuth.log_in_user(user, %{"remember_me" => "true"}) + + user_token = logged_in_conn.cookies[@remember_me_cookie] + %{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie] + + conn = + conn + |> put_req_cookie(@remember_me_cookie, signed_token) + |> UserAuth.fetch_current_user([]) + + assert conn.assigns.current_user.id == user.id + assert get_session(conn, :user_token) == user_token + + assert get_session(conn, :live_socket_id) == + "user_sessions:#{Base.url_encode64(user_token)}" + end + + test "does not authenticate if data is missing", %{conn: conn, user: user} do + _ = Accounts.generate_user_session_token(user) + conn = UserAuth.fetch_current_user(conn, []) + refute get_session(conn, :user_token) + refute conn.assigns.current_user + end + end + + describe "on_mount: mount_current_user" do + test "assigns current_user based on a valid user_token", %{conn: conn, user: user} do + user_token = Accounts.generate_user_session_token(user) + session = conn |> put_session(:user_token, user_token) |> get_session() + + {:cont, updated_socket} = + UserAuth.on_mount(:mount_current_user, %{}, session, %LiveView.Socket{}) + + assert updated_socket.assigns.current_user.id == user.id + end + + test "assigns nil to current_user assign if there isn't a valid user_token", %{conn: conn} do + user_token = "invalid_token" + session = conn |> put_session(:user_token, user_token) |> get_session() + + {:cont, updated_socket} = + UserAuth.on_mount(:mount_current_user, %{}, session, %LiveView.Socket{}) + + assert updated_socket.assigns.current_user == nil + end + + test "assigns nil to current_user assign if there isn't a user_token", %{conn: conn} do + session = conn |> get_session() + + {:cont, updated_socket} = + UserAuth.on_mount(:mount_current_user, %{}, session, %LiveView.Socket{}) + + assert updated_socket.assigns.current_user == nil + end + end + + describe "on_mount: ensure_authenticated" do + test "authenticates current_user based on a valid user_token", %{conn: conn, user: user} do + user_token = Accounts.generate_user_session_token(user) + session = conn |> put_session(:user_token, user_token) |> get_session() + + {:cont, updated_socket} = + UserAuth.on_mount(:ensure_authenticated, %{}, session, %LiveView.Socket{}) + + assert updated_socket.assigns.current_user.id == user.id + end + + test "redirects to login page if there isn't a valid user_token", %{conn: conn} do + user_token = "invalid_token" + session = conn |> put_session(:user_token, user_token) |> get_session() + + socket = %LiveView.Socket{ + endpoint: CklistWeb.Endpoint, + assigns: %{__changed__: %{}, flash: %{}} + } + + {:halt, updated_socket} = UserAuth.on_mount(:ensure_authenticated, %{}, session, socket) + assert updated_socket.assigns.current_user == nil + end + + test "redirects to login page if there isn't a user_token", %{conn: conn} do + session = conn |> get_session() + + socket = %LiveView.Socket{ + endpoint: CklistWeb.Endpoint, + assigns: %{__changed__: %{}, flash: %{}} + } + + {:halt, updated_socket} = UserAuth.on_mount(:ensure_authenticated, %{}, session, socket) + assert updated_socket.assigns.current_user == nil + end + end + + describe "on_mount: :redirect_if_user_is_authenticated" do + test "redirects if there is an authenticated user ", %{conn: conn, user: user} do + user_token = Accounts.generate_user_session_token(user) + session = conn |> put_session(:user_token, user_token) |> get_session() + + assert {:halt, _updated_socket} = + UserAuth.on_mount( + :redirect_if_user_is_authenticated, + %{}, + session, + %LiveView.Socket{} + ) + end + + test "doesn't redirect if there is no authenticated user", %{conn: conn} do + session = conn |> get_session() + + assert {:cont, _updated_socket} = + UserAuth.on_mount( + :redirect_if_user_is_authenticated, + %{}, + session, + %LiveView.Socket{} + ) + end + end + + describe "redirect_if_user_is_authenticated/2" do + test "redirects if user is authenticated", %{conn: conn, user: user} do + conn = conn |> assign(:current_user, user) |> UserAuth.redirect_if_user_is_authenticated([]) + assert conn.halted + assert redirected_to(conn) == ~p"/" + end + + test "does not redirect if user is not authenticated", %{conn: conn} do + conn = UserAuth.redirect_if_user_is_authenticated(conn, []) + refute conn.halted + refute conn.status + end + end + + describe "require_authenticated_user/2" do + test "redirects if user is not authenticated", %{conn: conn} do + conn = conn |> fetch_flash() |> UserAuth.require_authenticated_user([]) + assert conn.halted + + assert redirected_to(conn) == ~p"/user/log_in" + + assert Phoenix.Flash.get(conn.assigns.flash, :error) == + "You must log in to access this page." + end + + test "stores the path to redirect to on GET", %{conn: conn} do + halted_conn = + %{conn | path_info: ["foo"], query_string: ""} + |> fetch_flash() + |> UserAuth.require_authenticated_user([]) + + assert halted_conn.halted + assert get_session(halted_conn, :user_return_to) == "/foo" + + halted_conn = + %{conn | path_info: ["foo"], query_string: "bar=baz"} + |> fetch_flash() + |> UserAuth.require_authenticated_user([]) + + assert halted_conn.halted + assert get_session(halted_conn, :user_return_to) == "/foo?bar=baz" + + halted_conn = + %{conn | path_info: ["foo"], query_string: "bar", method: "POST"} + |> fetch_flash() + |> UserAuth.require_authenticated_user([]) + + assert halted_conn.halted + refute get_session(halted_conn, :user_return_to) + end + + test "does not redirect if user is authenticated", %{conn: conn, user: user} do + conn = conn |> assign(:current_user, user) |> UserAuth.require_authenticated_user([]) + refute conn.halted + refute conn.status + end + end +end diff --git a/test/cklist_web/users_auth_test.exs b/test/cklist_web/users_auth_test.exs deleted file mode 100644 index 463e89f..0000000 --- a/test/cklist_web/users_auth_test.exs +++ /dev/null @@ -1,272 +0,0 @@ -defmodule CklistWeb.UsersAuthTest do - use CklistWeb.ConnCase, async: true - - alias Phoenix.LiveView - alias Cklist.Accounts - alias CklistWeb.UsersAuth - import Cklist.AccountsFixtures - - @remember_me_cookie "_cklist_web_users_remember_me" - - setup %{conn: conn} do - conn = - conn - |> Map.replace!(:secret_key_base, CklistWeb.Endpoint.config(:secret_key_base)) - |> init_test_session(%{}) - - %{users: users_fixture(), conn: conn} - end - - describe "log_in_users/3" do - test "stores the users token in the session", %{conn: conn, users: users} do - conn = UsersAuth.log_in_users(conn, users) - assert token = get_session(conn, :users_token) - assert get_session(conn, :live_socket_id) == "users_sessions:#{Base.url_encode64(token)}" - assert redirected_to(conn) == ~p"/" - assert Accounts.get_users_by_session_token(token) - end - - test "clears everything previously stored in the session", %{conn: conn, users: users} do - conn = conn |> put_session(:to_be_removed, "value") |> UsersAuth.log_in_users(users) - refute get_session(conn, :to_be_removed) - end - - test "redirects to the configured path", %{conn: conn, users: users} do - conn = conn |> put_session(:users_return_to, "/hello") |> UsersAuth.log_in_users(users) - assert redirected_to(conn) == "/hello" - end - - test "writes a cookie if remember_me is configured", %{conn: conn, users: users} do - conn = conn |> fetch_cookies() |> UsersAuth.log_in_users(users, %{"remember_me" => "true"}) - assert get_session(conn, :users_token) == conn.cookies[@remember_me_cookie] - - assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie] - assert signed_token != get_session(conn, :users_token) - assert max_age == 5_184_000 - end - end - - describe "logout_users/1" do - test "erases session and cookies", %{conn: conn, users: users} do - users_token = Accounts.generate_users_session_token(users) - - conn = - conn - |> put_session(:users_token, users_token) - |> put_req_cookie(@remember_me_cookie, users_token) - |> fetch_cookies() - |> UsersAuth.log_out_users() - - refute get_session(conn, :users_token) - refute conn.cookies[@remember_me_cookie] - assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie] - assert redirected_to(conn) == ~p"/" - refute Accounts.get_users_by_session_token(users_token) - end - - test "broadcasts to the given live_socket_id", %{conn: conn} do - live_socket_id = "users_sessions:abcdef-token" - CklistWeb.Endpoint.subscribe(live_socket_id) - - conn - |> put_session(:live_socket_id, live_socket_id) - |> UsersAuth.log_out_users() - - assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: ^live_socket_id} - end - - test "works even if users is already logged out", %{conn: conn} do - conn = conn |> fetch_cookies() |> UsersAuth.log_out_users() - refute get_session(conn, :users_token) - assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie] - assert redirected_to(conn) == ~p"/" - end - end - - describe "fetch_current_users/2" do - test "authenticates users from session", %{conn: conn, users: users} do - users_token = Accounts.generate_users_session_token(users) - conn = conn |> put_session(:users_token, users_token) |> UsersAuth.fetch_current_users([]) - assert conn.assigns.current_users.id == users.id - end - - test "authenticates users from cookies", %{conn: conn, users: users} do - logged_in_conn = - conn |> fetch_cookies() |> UsersAuth.log_in_users(users, %{"remember_me" => "true"}) - - users_token = logged_in_conn.cookies[@remember_me_cookie] - %{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie] - - conn = - conn - |> put_req_cookie(@remember_me_cookie, signed_token) - |> UsersAuth.fetch_current_users([]) - - assert conn.assigns.current_users.id == users.id - assert get_session(conn, :users_token) == users_token - - assert get_session(conn, :live_socket_id) == - "users_sessions:#{Base.url_encode64(users_token)}" - end - - test "does not authenticate if data is missing", %{conn: conn, users: users} do - _ = Accounts.generate_users_session_token(users) - conn = UsersAuth.fetch_current_users(conn, []) - refute get_session(conn, :users_token) - refute conn.assigns.current_users - end - end - - describe "on_mount: mount_current_users" do - test "assigns current_users based on a valid users_token", %{conn: conn, users: users} do - users_token = Accounts.generate_users_session_token(users) - session = conn |> put_session(:users_token, users_token) |> get_session() - - {:cont, updated_socket} = - UsersAuth.on_mount(:mount_current_users, %{}, session, %LiveView.Socket{}) - - assert updated_socket.assigns.current_users.id == users.id - end - - test "assigns nil to current_users assign if there isn't a valid users_token", %{conn: conn} do - users_token = "invalid_token" - session = conn |> put_session(:users_token, users_token) |> get_session() - - {:cont, updated_socket} = - UsersAuth.on_mount(:mount_current_users, %{}, session, %LiveView.Socket{}) - - assert updated_socket.assigns.current_users == nil - end - - test "assigns nil to current_users assign if there isn't a users_token", %{conn: conn} do - session = conn |> get_session() - - {:cont, updated_socket} = - UsersAuth.on_mount(:mount_current_users, %{}, session, %LiveView.Socket{}) - - assert updated_socket.assigns.current_users == nil - end - end - - describe "on_mount: ensure_authenticated" do - test "authenticates current_users based on a valid users_token", %{conn: conn, users: users} do - users_token = Accounts.generate_users_session_token(users) - session = conn |> put_session(:users_token, users_token) |> get_session() - - {:cont, updated_socket} = - UsersAuth.on_mount(:ensure_authenticated, %{}, session, %LiveView.Socket{}) - - assert updated_socket.assigns.current_users.id == users.id - end - - test "redirects to login page if there isn't a valid users_token", %{conn: conn} do - users_token = "invalid_token" - session = conn |> put_session(:users_token, users_token) |> get_session() - - socket = %LiveView.Socket{ - endpoint: CklistWeb.Endpoint, - assigns: %{__changed__: %{}, flash: %{}} - } - - {:halt, updated_socket} = UsersAuth.on_mount(:ensure_authenticated, %{}, session, socket) - assert updated_socket.assigns.current_users == nil - end - - test "redirects to login page if there isn't a users_token", %{conn: conn} do - session = conn |> get_session() - - socket = %LiveView.Socket{ - endpoint: CklistWeb.Endpoint, - assigns: %{__changed__: %{}, flash: %{}} - } - - {:halt, updated_socket} = UsersAuth.on_mount(:ensure_authenticated, %{}, session, socket) - assert updated_socket.assigns.current_users == nil - end - end - - describe "on_mount: :redirect_if_users_is_authenticated" do - test "redirects if there is an authenticated users ", %{conn: conn, users: users} do - users_token = Accounts.generate_users_session_token(users) - session = conn |> put_session(:users_token, users_token) |> get_session() - - assert {:halt, _updated_socket} = - UsersAuth.on_mount( - :redirect_if_users_is_authenticated, - %{}, - session, - %LiveView.Socket{} - ) - end - - test "doesn't redirect if there is no authenticated users", %{conn: conn} do - session = conn |> get_session() - - assert {:cont, _updated_socket} = - UsersAuth.on_mount( - :redirect_if_users_is_authenticated, - %{}, - session, - %LiveView.Socket{} - ) - end - end - - describe "redirect_if_users_is_authenticated/2" do - test "redirects if users is authenticated", %{conn: conn, users: users} do - conn = conn |> assign(:current_users, users) |> UsersAuth.redirect_if_users_is_authenticated([]) - assert conn.halted - assert redirected_to(conn) == ~p"/" - end - - test "does not redirect if users is not authenticated", %{conn: conn} do - conn = UsersAuth.redirect_if_users_is_authenticated(conn, []) - refute conn.halted - refute conn.status - end - end - - describe "require_authenticated_users/2" do - test "redirects if users is not authenticated", %{conn: conn} do - conn = conn |> fetch_flash() |> UsersAuth.require_authenticated_users([]) - assert conn.halted - - assert redirected_to(conn) == ~p"/users/log_in" - - assert Phoenix.Flash.get(conn.assigns.flash, :error) == - "You must log in to access this page." - end - - test "stores the path to redirect to on GET", %{conn: conn} do - halted_conn = - %{conn | path_info: ["foo"], query_string: ""} - |> fetch_flash() - |> UsersAuth.require_authenticated_users([]) - - assert halted_conn.halted - assert get_session(halted_conn, :users_return_to) == "/foo" - - halted_conn = - %{conn | path_info: ["foo"], query_string: "bar=baz"} - |> fetch_flash() - |> UsersAuth.require_authenticated_users([]) - - assert halted_conn.halted - assert get_session(halted_conn, :users_return_to) == "/foo?bar=baz" - - halted_conn = - %{conn | path_info: ["foo"], query_string: "bar", method: "POST"} - |> fetch_flash() - |> UsersAuth.require_authenticated_users([]) - - assert halted_conn.halted - refute get_session(halted_conn, :users_return_to) - end - - test "does not redirect if users is authenticated", %{conn: conn, users: users} do - conn = conn |> assign(:current_users, users) |> UsersAuth.require_authenticated_users([]) - refute conn.halted - refute conn.status - end - end -end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 98bbff0..d74b25b 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -37,28 +37,28 @@ defmodule CklistWeb.ConnCase do end @doc """ - Setup helper that registers and logs in users. + Setup helper that registers and logs in user. - setup :register_and_log_in_users + setup :register_and_log_in_user - It stores an updated connection and a registered users in the + It stores an updated connection and a registered user in the test context. """ - def register_and_log_in_users(%{conn: conn}) do - users = Cklist.AccountsFixtures.users_fixture() - %{conn: log_in_users(conn, users), users: users} + def register_and_log_in_user(%{conn: conn}) do + user = Cklist.AccountsFixtures.user_fixture() + %{conn: log_in_user(conn, user), user: user} end @doc """ - Logs the given `users` into the `conn`. + Logs the given `user` into the `conn`. It returns an updated `conn`. """ - def log_in_users(conn, users) do - token = Cklist.Accounts.generate_users_session_token(users) + def log_in_user(conn, user) do + token = Cklist.Accounts.generate_user_session_token(user) conn |> Phoenix.ConnTest.init_test_session(%{}) - |> Plug.Conn.put_session(:users_token, token) + |> Plug.Conn.put_session(:user_token, token) end end diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex index 8bf77cd..e4a5d2d 100644 --- a/test/support/fixtures/accounts_fixtures.ex +++ b/test/support/fixtures/accounts_fixtures.ex @@ -4,26 +4,26 @@ defmodule Cklist.AccountsFixtures do entities via the `Cklist.Accounts` context. """ - def unique_users_email, do: "users#{System.unique_integer()}@example.com" - def valid_users_password, do: "hello world!" + def unique_user_email, do: "user#{System.unique_integer()}@example.com" + def valid_user_password, do: "hello world!" - def valid_users_attributes(attrs \\ %{}) do + def valid_user_attributes(attrs \\ %{}) do Enum.into(attrs, %{ - email: unique_users_email(), - password: valid_users_password() + email: unique_user_email(), + password: valid_user_password() }) end - def users_fixture(attrs \\ %{}) do - {:ok, users} = + def user_fixture(attrs \\ %{}) do + {:ok, user} = attrs - |> valid_users_attributes() - |> Cklist.Accounts.register_users() + |> valid_user_attributes() + |> Cklist.Accounts.register_user() - users + user end - def extract_users_token(fun) do + def extract_user_token(fun) do {:ok, captured_email} = fun.(&"[TOKEN]#{&1}[TOKEN]") [_, token | _] = String.split(captured_email.text_body, "[TOKEN]") token