Refactor session and pw_reset into users table

This commit is contained in:
Jamil Bou Kheir
2020-05-31 21:11:21 -07:00
parent 72b3434b23
commit a7457a4368
24 changed files with 192 additions and 447 deletions

View File

@@ -8,28 +8,6 @@ defmodule FgHttp.PasswordResets do
alias FgHttp.Users.PasswordReset
@doc """
Returns the list of password_resets.
## Examples
iex> list_password_resets()
[%PasswordReset{}, ...]
"""
def list_password_resets do
Repo.all(PasswordReset)
end
def load_user_from_valid_token!(token) when is_binary(token) do
Repo.get_by!(
PasswordReset,
reset_token: token,
consumed_at: nil,
reset_sent_at: DateTime.utc_now() - PasswordReset.token_validity_secs()
)
end
@doc """
Gets a single password_reset.
@@ -44,70 +22,41 @@ defmodule FgHttp.PasswordResets do
** (Ecto.NoResultsError)
"""
def get_password_reset!(id), do: Repo.get!(PasswordReset, id)
def get_password_reset!(email: email) do
Repo.get_by(
PasswordReset,
email: email
)
end
@doc """
Creates a password_reset.
def get_password_reset!(reset_token: reset_token) do
validity_secs = -1 * PasswordReset.token_validity_secs()
now = DateTime.truncate(DateTime.utc_now(), :second)
## Examples
query =
from p in PasswordReset,
where:
p.reset_token == ^reset_token and is_nil(p.reset_consumed_at) and
p.reset_sent_at > datetime_add(^now, ^validity_secs, "second")
iex> create_password_reset(%{field: value})
{:ok, %PasswordReset{}}
iex> create_password_reset(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_password_reset(attrs \\ %{}) do
%PasswordReset{}
|> PasswordReset.create_changeset(attrs)
|> Repo.insert()
Repo.one(query)
end
@doc """
Updates a password_reset.
Updates a User with the password reset fields
## Examples
iex> update_password_reset(password_reset, %{field: new_value})
iex> update_password_reset(%{field: value})
{:ok, %PasswordReset{}}
iex> update_password_reset(password_reset, %{field: bad_value})
iex> update_password_reset(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_password_reset(%PasswordReset{} = password_reset, attrs) do
password_reset
|> PasswordReset.update_changeset(attrs)
def create_password_reset(%PasswordReset{} = record, attrs) do
record
|> PasswordReset.create_changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a password_reset.
## Examples
iex> delete_password_reset(password_reset)
{:ok, %PasswordReset{}}
iex> delete_password_reset(password_reset)
{:error, %Ecto.Changeset{}}
"""
def delete_password_reset(%PasswordReset{} = password_reset) do
Repo.delete(password_reset)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking password_reset changes.
## Examples
iex> change_password_reset(password_reset)
%Ecto.Changeset{data: %PasswordReset{}}
"""
def change_password_reset(%PasswordReset{} = password_reset, attrs \\ %{}) do
PasswordReset.changeset(password_reset, attrs)
end
end

View File

@@ -6,20 +6,7 @@ defmodule FgHttp.Sessions do
import Ecto.Query, warn: false
alias FgHttp.Repo
alias FgHttp.{Users.Session, Users.User}
@doc """
Returns the list of sessions.
## Examples
iex> list_sessions()
[%Session{}, ...]
"""
def list_sessions do
Repo.all(Session)
end
alias FgHttp.Users.Session
@doc """
Gets a single session.
@@ -35,6 +22,7 @@ defmodule FgHttp.Sessions do
** (Ecto.NoResultsError)
"""
def get_session!(email: email), do: Repo.get_by!(Session, email: email)
def get_session!(id), do: Repo.get!(Session, id)
@doc """
@@ -49,78 +37,9 @@ defmodule FgHttp.Sessions do
{:error, %Ecto.Changeset{}}
"""
def create_session(attrs \\ %{}) do
%Session{}
def create_session(%{email: email} = attrs) do
get_session!(email: email)
|> Session.create_changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a session.
## Examples
iex> update_session(session, %{field: new_value})
{:ok, %Session{}}
iex> update_session(session, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_session(%Session{} = session, attrs) do
session
|> Session.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a session.
## Examples
iex> delete_session(session)
{:ok, %Session{}}
iex> delete_session(session)
{:error, %Ecto.Changeset{}}
"""
def delete_session(%Session{} = session, really_delete: true) do
Repo.delete(session)
end
def delete_session(%Session{} = session) do
update_session(session, %{deleted_at: DateTime.utc_now()})
end
def load_session(_session_id) do
query =
from s in Session,
where: is_nil(s.deleted_at),
join: u in User,
on: s.user_id == u.id,
select: {s, u},
preload: :user
case session = Repo.one(query) do
nil ->
{:error, "Valid session not found"}
_ ->
{:ok, session}
end
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking session changes.
## Examples
iex> change_session(session)
%Ecto.Changeset{source: %Session{}}
"""
def change_session(%Session{} = session) do
Session.changeset(session, %{})
end
end

View File

@@ -6,59 +6,35 @@ defmodule FgHttp.Users.PasswordReset do
use Ecto.Schema
import Ecto.Changeset
alias FgHttp.{Users, Users.User}
@token_num_bytes 8
# 1 day
@token_validity_secs 86_400
schema "password_resets" do
schema "users" do
field :reset_sent_at, :utc_datetime
field :reset_consumed_at, :utc_datetime
field :reset_token, :string
field :consumed_at, :string
field :user_email, :string, virtual: true
belongs_to :user, User
timestamps()
field :email, :string
end
@doc false
def changeset(password_reset, attrs) do
password_reset
|> cast(attrs, [:user_id, :user_email, :reset_sent_at, :reset_token])
def changeset do
%__MODULE__{}
|> cast(%{}, [:email, :reset_sent_at, :reset_token])
end
@doc false
def create_changeset(password_reset, attrs) do
def create_changeset(%__MODULE__{} = password_reset, attrs) do
password_reset
|> cast(attrs, [:user_id, :user_email, :reset_sent_at, :reset_token])
|> load_user_from_email()
|> cast(attrs, [:email, :reset_sent_at, :reset_token])
|> validate_required([:email])
|> generate_reset_token()
|> validate_required([:reset_token, :user_id])
|> validate_required([:reset_token])
|> unique_constraint(:reset_token)
end
@doc false
def update_changeset(password_reset, attrs) do
password_reset
|> cast(attrs, [:user_id, :user_email, :reset_sent_at, :reset_token])
|> validate_required([:reset_token])
end
def token_validity_secs, do: @token_validity_secs
defp load_user_from_email(
%Ecto.Changeset{
valid?: true,
changes: %{user_email: user_email}
} = changeset
) do
user = Users.get_user!(email: user_email)
put_change(changeset, :user_id, user.id)
end
defp load_user_from_email(changeset), do: changeset
defp generate_reset_token(%Ecto.Changeset{valid?: true} = changeset) do
random_bytes = :crypto.strong_rand_bytes(@token_num_bytes)
random_string = Base.url_encode64(random_bytes)

View File

@@ -8,47 +8,46 @@ defmodule FgHttp.Users.Session do
alias FgHttp.{Users, Users.User}
schema "sessions" do
field :deleted_at, :utc_datetime
belongs_to :user, User
# VIRTUAL FIELDS
field :user_email, :string, virtual: true
field :user_password, :string, virtual: true
timestamps()
end
@doc false
def changeset(session, attrs \\ %{}) do
session
|> cast(attrs, [:deleted_at])
|> validate_required([])
schema "users" do
field :email, :string
field :password, :string, virtual: true
field :last_signed_in_at, :utc_datetime
end
def create_changeset(session, attrs \\ %{}) do
session
|> cast(attrs, [:user_email, :user_password])
|> validate_required([:user_email, :user_password])
|> cast(attrs, [:email, :password])
|> validate_required([:email, :password])
|> authenticate_user()
|> set_last_signed_in_at()
end
defp set_last_signed_in_at(%Ecto.Changeset{valid?: true} = changeset) do
last_signed_in_at = DateTime.truncate(DateTime.utc_now(), :second)
change(changeset, last_signed_in_at: last_signed_in_at)
end
defp set_last_signed_in_at(changeset), do: changeset
defp authenticate_user(
%Ecto.Changeset{
valid?: true,
changes: %{user_email: email, user_password: password}
changes: %{email: email, password: password}
} = changeset
) do
user = Users.get_user!(email: email)
case User.authenticate_user(user, password) do
{:ok, _} ->
change(changeset, user_id: user.id)
# Remove the user's password so it doesn't accidentally end up somewhere
changeset
|> delete_change(:password)
|> change(%{id: user.id})
{:error, error_msg} ->
raise("There was an issue with your password: #{error_msg}")
end
end
defp authenticate_user(changeset), do: changeset
defp authenticate_user(changeset), do: delete_change(changeset, :password)
end

View File

@@ -6,7 +6,7 @@ defmodule FgHttp.Users.User do
use Ecto.Schema
import Ecto.Changeset
alias FgHttp.{Devices.Device, Users.Session}
alias FgHttp.Devices.Device
schema "users" do
field :email, :string
@@ -20,7 +20,6 @@ defmodule FgHttp.Users.User do
field :current_password, :string, virtual: true
has_many :devices, Device, on_delete: :delete_all
has_many :sessions, Session, on_delete: :delete_all
timestamps()
end
@@ -67,6 +66,7 @@ defmodule FgHttp.Users.User do
user
|> cast(attrs, [:email, :password, :password_confirmation])
|> validate_required([:password, :password_confirmation])
|> validate_password_equality()
|> put_password_hash()
|> validate_required([:password_hash])
end
@@ -91,14 +91,34 @@ defmodule FgHttp.Users.User do
user
end
defp validate_password_equality(%Ecto.Changeset{valid?: true} = changeset) do
password = changeset.changes[:password]
password_confirmation = changeset.changes[:password_confirmation]
if password != password_confirmation do
add_error(changeset, :password, "does not match password confirmation.")
else
changeset
end
end
defp validate_password_equality(changeset), do: changeset
defp put_password_hash(
%Ecto.Changeset{
valid?: true,
changes: %{password: password}
} = changeset
) do
change(changeset, password_hash: Argon2.hash_pwd_salt(password))
changeset
|> change(password_hash: Argon2.hash_pwd_salt(password))
|> delete_change(:password)
|> delete_change(:password_confirmation)
end
defp put_password_hash(changeset), do: changeset
defp put_password_hash(changeset) do
changeset
|> delete_change(:password)
|> delete_change(:password_confirmation)
end
end

View File

@@ -9,7 +9,7 @@ defmodule FgHttpWeb.DeviceController do
plug FgHttpWeb.Plugs.SessionLoader
def index(conn, _params) do
devices = Devices.list_devices(conn.assigns.current_user.id)
devices = Devices.list_devices(conn.assigns.session.id)
render(conn, "index.html", devices: devices)
end
@@ -20,7 +20,7 @@ defmodule FgHttpWeb.DeviceController do
def create(conn, %{"device" => %{"public_key" => _public_key} = device_params}) do
our_params = %{
"user_id" => conn.assigns.current_user.id,
"user_id" => conn.assigns.session.id,
"name" => "Default",
"ifname" => "wg0"
}

View File

@@ -4,76 +4,35 @@ defmodule FgHttpWeb.PasswordResetController do
"""
use FgHttpWeb, :controller
alias FgHttp.{PasswordResets, Users.PasswordReset, Users.User}
alias FgHttp.{PasswordResets, Users.PasswordReset}
plug FgHttpWeb.Plugs.RedirectAuthenticated
def new(conn, _params) do
changeset = PasswordReset.changeset(%PasswordReset{}, %{})
conn
|> render("new.html", changeset: changeset)
|> render("new.html", changeset: PasswordReset.changeset())
end
def edit(conn, %{"token" => token}) when is_binary(token) do
_user = load_user(conn, token)
changeset = PasswordReset.changeset(%PasswordReset{}, %{})
def create(conn, %{"password_reset" => %{"email" => email}}) do
case PasswordResets.get_password_reset!(email: email) do
%PasswordReset{} = record ->
case PasswordResets.create_password_reset(record, %{email: email}) do
{:ok, _password_reset} ->
conn
|> clear_session()
|> put_flash(:info, "Check your email for the password reset link.")
|> redirect(to: Routes.session_path(conn, :new))
conn
|> render("edit.html", changeset: changeset)
end
{:error, changeset} ->
conn
|> put_flash(:error, "Error creating password reset.")
|> render("new.html", changeset: changeset)
end
def update(conn, %{
"password_reset" =>
%{
"reset_token" => token,
"user" => %{
"password" => _password,
"password_confirmation" => _password_confirmation
}
} = password_reset_params
})
when is_binary(token) do
user = load_user(conn, token)
case PasswordResets.update_password_reset(user, password_reset_params) do
{:ok, _user} ->
conn
|> clear_session()
|> put_flash(:info, "User password updated successfully. Please sign in.")
|> redirect(to: Routes.session_path(conn, :new))
{:error, changeset} ->
conn
|> put_flash(:error, "Error updating User password.")
|> render("edit.html", changeset: changeset)
end
end
def create(conn, %{"password_reset" => %{"user_email" => _} = password_reset_params}) do
case PasswordResets.create_password_reset(password_reset_params) do
{:ok, _password_reset} ->
conn
|> clear_session()
|> put_flash(:info, "Password reset successfully. Please sign in with your new password.")
|> redirect(to: Routes.session_path(conn, :new))
{:error, changeset} ->
conn
|> put_flash(:error, "Error creating password reset.")
|> render("new.html", changeset: changeset)
end
end
defp load_user(conn, token) do
case PasswordResets.load_user_from_valid_token!(token) do
nil ->
conn
|> put_status(:not_found)
|> halt()
%User{} = user ->
user
|> put_flash(:error, "User not found.")
|> render("new.html", changeset: PasswordReset.changeset())
end
end
end

View File

@@ -3,7 +3,7 @@ defmodule FgHttpWeb.SessionController do
Implements the CRUD for a Session
"""
alias FgHttp.{Sessions, Users.Session}
alias FgHttp.Sessions
use FgHttpWeb, :controller
plug FgHttpWeb.Plugs.RedirectAuthenticated when action in [:new]
@@ -11,42 +11,34 @@ defmodule FgHttpWeb.SessionController do
# GET /sessions/new
def new(conn, _params) do
changeset = Session.changeset(%Session{})
render(conn, "new.html", changeset: changeset)
render(conn, "new.html")
end
# Sign In
# POST /sessions
def create(conn, %{"session" => session_params}) do
case Sessions.create_session(session_params) do
{:ok, session} ->
conn
# Prevent session fixation
|> clear_session()
|> put_session(:session_id, session.id)
|> assign(:current_session, session)
|> put_session(:user_id, session.id)
|> assign(:session, session)
|> put_flash(:info, "Session created successfully")
|> redirect(to: Routes.device_path(conn, :index))
{:error, changeset} ->
conn
|> clear_session()
|> assign(:session, nil)
|> put_flash(:error, "Error creating session.")
|> render("new.html", changeset: changeset, user_signed_in?: false)
|> render("new.html", changeset: changeset)
end
end
# Sign Out
# DELETE /session
def delete(conn, _params) do
session = conn.assigns.current_session
case Sessions.delete_session(session) do
{:ok, _session} ->
conn
|> clear_session
|> put_flash(:info, "Signed out successfully.")
|> redirect(to: "/")
end
conn
|> clear_session()
|> put_flash(:info, "Signed out successfully.")
|> redirect(to: "/")
end
end

View File

@@ -4,7 +4,7 @@ defmodule FgHttpWeb.UserController do
"""
use FgHttpWeb, :controller
alias FgHttp.{Users, Users.User}
alias FgHttp.{Users, Users.Session, Users.User}
plug FgHttpWeb.Plugs.SessionLoader when action in [:show, :edit, :update, :delete]
@@ -19,7 +19,8 @@ defmodule FgHttpWeb.UserController do
case Users.create_user(user_params) do
{:ok, user} ->
conn
|> assign(:current_user, user)
|> put_session(:user_id, user.id)
|> assign(:session, %Session{id: user.id, email: user.email})
|> put_flash(:info, "User created successfully.")
|> redirect(to: Routes.device_path(conn, :index))

View File

@@ -9,13 +9,13 @@ defmodule FgHttpWeb.Plugs.RedirectAuthenticated do
def init(default), do: default
def call(conn, _default) do
if get_session(conn, :session_id) do
if get_session(conn, :email) do
conn
|> redirect(to: "/")
|> halt()
else
conn
|> assign(:user_signed_in?, false)
|> assign(:session, nil)
end
end
end

View File

@@ -4,24 +4,37 @@ defmodule FgHttpWeb.Plugs.SessionLoader do
"""
import Plug.Conn
import Phoenix.Controller, only: [redirect: 2]
alias FgHttp.Sessions
import Phoenix.Controller, only: [put_flash: 3, redirect: 2]
alias FgHttp.{Sessions, Users.Session}
alias FgHttpWeb.Router.Helpers, as: Routes
def init(default), do: default
def call(conn, _default) do
case Sessions.load_session(get_session(conn, :session_id)) do
{:ok, {session, user}} ->
conn
|> assign(:current_session, session)
|> assign(:current_user, user)
|> assign(:user_signed_in?, true)
# Don't load an already loaded session
def call(%Plug.Conn{assigns: %{session: %Session{}}} = conn, _default), do: conn
{:error, _} ->
conn
|> redirect(to: Routes.session_path(conn, :new))
|> halt
def call(conn, _default) do
case get_session(conn, :user_id) do
nil ->
unauthed(conn)
user_id ->
case Sessions.get_session!(user_id) do
%Session{} = session ->
conn
|> assign(:session, session)
_ ->
unauthed(conn)
end
end
end
defp unauthed(conn) do
conn
|> clear_session()
|> put_flash(:error, "There was an error loading your session. Please sign in again")
|> redirect(to: Routes.session_path(conn, :new))
|> halt()
end
end

View File

@@ -6,7 +6,7 @@
<%= aggregated_errors(@changeset) %>
<% end %>
<%= live_render(@conn, FgHttpWeb.NewDeviceLive, session: %{"current_user_id" => @conn.assigns.current_user.id}) %>
<%= live_render(@conn, FgHttpWeb.NewDeviceLive, session: %{"current_user_id" => @session.id}) %>
<p>
<%= link "Back", to: Routes.device_path(@conn, :index) %>

View File

@@ -15,9 +15,9 @@
<%= link("Fireguard", to: FgHttpWeb.Endpoint.url(), class: "link dim white dib mr3") %>
</nav>
<nav class="f6 fw6 ttu tracked fr">
<%= if @user_signed_in? do %>
<%= if assigns[:session] do %>
<%= link("Devices", to: Routes.device_path(@conn, :index), class: "link dim white dib mr3") %>
<%= link("Sign out", to: Routes.session_path(@conn, :delete, @current_session), method: :delete, class: "link dim white dib mr3") %>
<%= link("Sign out", to: Routes.session_path(@conn, :delete, @session), method: :delete, class: "link dim white dib mr3") %>
<% else %>
<%= link("Sign in", to: Routes.session_path(@conn, :new), class: "link dim white dib mr3") %>
<% end %>

View File

@@ -1,4 +1,4 @@
<h1>Change Password</h1>
<h1>Reset Password</h1>
<%= form_for @changeset, Routes.password_reset_path(@conn, :create), fn f -> %>
<%= if @changeset.action do %>
@@ -7,16 +7,12 @@
</div>
<% end %>
<%= label f, :reset_sent_at %>
<%= datetime_select f, :reset_sent_at %>
<%= error_tag f, :reset_sent_at %>
<%= label f, :reset_token %>
<%= text_input f, :reset_token %>
<%= error_tag f, :reset_token %>
<%= label f, :email %>
<%= text_input f, :email %>
<%= error_tag f, :email %>
<div>
<%= submit "Save" %>
<%= submit "Submit" %>
</div>
<% end %>

View File

@@ -1,5 +1,5 @@
<main class="pa4 black-80">
<%= form_for(@changeset, Routes.session_path(@conn, :create), [class: "measure center"], fn f -> %>
<%= form_for(@conn, Routes.session_path(@conn, :create), [as: :session, class: "measure center"], fn f -> %>
<%= if f.errors do %>
<!-- Errors -->
<% end %>
@@ -8,11 +8,11 @@
<legend class="f4 fw6 ph0 mh0">Sign In</legend>
<div class="mt3">
<%= label(:session_user, :email, class: "db fw6 lh-copy f6") %>
<%= text_input(f, :user_email, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
<%= text_input(f, :email, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
</div>
<div class="mv3">
<%= label(:session_user, :password, class: "db fw6 lh-copy f6") %>
<%= password_input(f, :user_password, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
<%= password_input(f, :password, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
</div>
<label class="pa0 ma0 lh-copy f6 pointer"><input type="checkbox"> Remember me</label>
</fieldset>

View File

@@ -7,10 +7,15 @@ defmodule FgHttp.Repo.Migrations.CreateUsers do
add :confirmed_at, :utc_datetime
add :password_hash, :string
add :last_signed_in_at, :utc_datetime
add :reset_token, :string
add :reset_sent_at, :utc_datetime
add :reset_consumed_at, :utc_datetime
timestamps()
end
create unique_index(:users, [:email])
create index(:users, [:reset_token, :reset_sent_at, :reset_consumed_at])
create index(:users, [:confirmed_at])
end
end

View File

@@ -1,15 +0,0 @@
defmodule FgHttp.Repo.Migrations.CreateSessions do
use Ecto.Migration
def change do
create table(:sessions) do
add :user_id, references(:users, on_delete: :delete_all), null: false
add :deleted_at, :utc_datetime
timestamps()
end
create index(:sessions, [:user_id])
create index(:sessions, [:deleted_at])
end
end

View File

@@ -1,18 +0,0 @@
defmodule FgHttp.Repo.Migrations.CreatePasswordResets do
use Ecto.Migration
def change do
create table(:password_resets) do
add :reset_sent_at, :utc_datetime
add :consumed_at, :utc_datetime
add :reset_token, :string, null: false
add :user_id, references(:users, on_delete: :delete_all), null: false
timestamps()
end
create unique_index(:password_resets, [:reset_token])
create index(:password_resets, [:user_id])
create index(:password_resets, [:consumed_at])
end
end

View File

@@ -6,82 +6,34 @@ defmodule FgHttp.PasswordResetsTest do
describe "password_resets" do
alias FgHttp.Users.PasswordReset
@valid_attrs %{reset_sent_at: "2010-04-17T14:00:00Z"}
@update_attrs %{reset_sent_at: "2011-05-18T15:01:01Z"}
@invalid_attrs %{reset_sent_at: nil}
@valid_attrs %{email: "test"}
@invalid_attrs %{email: ""}
def password_reset_fixture(attrs \\ %{}) do
{:ok, password_reset} =
attrs
|> Enum.into(%{user_id: Fixtures.user().id})
|> Enum.into(@valid_attrs)
|> PasswordResets.create_password_reset()
password_reset
end
test "list_password_resets/0 returns all password_resets" do
password_reset = password_reset_fixture()
assert PasswordResets.list_password_resets() == [password_reset]
end
test "get_password_reset!/1 returns the password_reset with given id" do
password_reset = password_reset_fixture()
assert PasswordResets.get_password_reset!(password_reset.id) == password_reset
test "get_password_reset!/1 returns the password_reset with given token" do
token = Fixtures.password_reset(%{reset_sent_at: DateTime.utc_now()}).reset_token
gotten = PasswordResets.get_password_reset!(reset_token: token)
assert gotten.reset_token == token
end
test "create_password_reset/1 with valid data creates a password_reset" do
user_id = Fixtures.user().id
valid_attrs = Map.merge(@valid_attrs, %{user_id: user_id})
email = Fixtures.user().email
assert {:ok, %PasswordReset{} = password_reset} =
PasswordResets.create_password_reset(valid_attrs)
PasswordResets.create_password_reset(Fixtures.password_reset(), @valid_attrs)
assert password_reset.reset_sent_at ==
DateTime.from_naive!(~N[2010-04-17T14:00:00Z], "Etc/UTC")
# reset_sent_at should be nil after creation
assert is_nil(password_reset.reset_sent_at)
assert password_reset.reset_token
assert password_reset.user_id == user_id
assert password_reset.email == email
end
test "create_password_reset/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = PasswordResets.create_password_reset(@invalid_attrs)
end
test "update_password_reset/2 with valid data updates the password_reset" do
password_reset = password_reset_fixture()
assert {:ok, %PasswordReset{} = password_reset} =
PasswordResets.update_password_reset(password_reset, @update_attrs)
assert password_reset.reset_sent_at ==
DateTime.from_naive!(~N[2011-05-18T15:01:01Z], "Etc/UTC")
assert password_reset.reset_token
end
test "update_password_reset/2 with invalid data returns error changeset" do
invalid_attrs = Map.merge(@invalid_attrs, %{reset_token: nil})
password_reset = password_reset_fixture()
assert {:error, %Ecto.Changeset{}} =
PasswordResets.update_password_reset(password_reset, invalid_attrs)
assert password_reset == PasswordResets.get_password_reset!(password_reset.id)
end
test "delete_password_reset/1 deletes the password_reset" do
password_reset = password_reset_fixture()
assert {:ok, %PasswordReset{}} = PasswordResets.delete_password_reset(password_reset)
assert_raise Ecto.NoResultsError, fn ->
PasswordResets.get_password_reset!(password_reset.id)
end
end
test "change_password_reset/1 returns a password_reset changeset" do
password_reset = password_reset_fixture()
assert %Ecto.Changeset{} = PasswordResets.change_password_reset(password_reset)
PasswordResets.create_password_reset(
Fixtures.password_reset(),
@invalid_attrs
)
end
end
end

View File

@@ -46,11 +46,11 @@ defmodule FgHttpWeb.DeviceControllerTest do
setup [:create_device]
test "redirects when data is valid", %{authed_conn: conn, device: device} do
conn = put(conn, Routes.device_path(conn, :update, device), device: @update_attrs)
assert redirected_to(conn) == Routes.device_path(conn, :show, device)
test_conn = put(conn, Routes.device_path(conn, :update, device), device: @update_attrs)
assert redirected_to(test_conn) == Routes.device_path(conn, :show, device)
conn = get(conn, Routes.device_path(conn, :show, device))
assert html_response(conn, 200) =~ "some updated name"
test_conn = get(conn, Routes.device_path(conn, :show, device))
assert html_response(test_conn, 200) =~ "some updated name"
end
test "renders errors when data is invalid", %{authed_conn: conn, device: device} do
@@ -63,8 +63,8 @@ defmodule FgHttpWeb.DeviceControllerTest do
setup [:create_device]
test "deletes chosen device", %{authed_conn: conn, device: device} do
conn = delete(conn, Routes.device_path(conn, :delete, device))
assert redirected_to(conn) == Routes.device_path(conn, :index)
test_conn = delete(conn, Routes.device_path(conn, :delete, device))
assert redirected_to(test_conn) == Routes.device_path(conn, :index)
assert_error_sent 404, fn ->
get(conn, Routes.device_path(conn, :show, device))

View File

@@ -1,12 +1,12 @@
defmodule FgHttpWeb.PasswordResetControllerTest do
use FgHttpWeb.ConnCase, async: true
@create_attrs %{user_email: "test"}
@create_attrs %{email: "test"}
describe "new password_reset" do
test "renders form", %{unauthed_conn: conn} do
conn = get(conn, Routes.password_reset_path(conn, :new))
assert html_response(conn, 200) =~ "New Password reset"
assert html_response(conn, 200) =~ "Reset Password"
end
end

View File

@@ -38,19 +38,10 @@ defmodule FgHttpWeb.ConnCase do
end
def authed_conn do
user = Fixtures.user()
session =
Fixtures.session(%{
user_id: user.id,
user_password: "test",
user_email: "test"
})
session = Fixtures.session()
new_conn()
|> Plug.Conn.assign(:current_user, user)
|> Plug.Conn.assign(:current_session, session)
|> Plug.Conn.assign(:user_signed_in?, true)
|> Plug.Conn.assign(:session, session)
end
setup tags do

View File

@@ -29,14 +29,20 @@ defmodule FgHttp.Fixtures do
device
end
def session(attrs \\ %{}) do
{:ok, session} = Sessions.create_session(attrs)
def session(_attrs \\ %{}) do
{:ok, session} = Sessions.create_session(%{email: user().email, password: "test"})
session
end
def password_reset(attrs \\ %{}) do
create_attrs = Map.merge(attrs, %{user_email: user().email})
{:ok, password_reset} = PasswordResets.create_password_reset(create_attrs)
email = user().email
create_attrs = Map.merge(attrs, %{email: email})
{:ok, password_reset} =
PasswordResets.get_password_reset!(email: email)
|> PasswordResets.create_password_reset(create_attrs)
password_reset
end
end