diff --git a/apps/fg_http/lib/fg_http/devices.ex b/apps/fg_http/lib/fg_http/devices.ex index bc8fa3944..94286b018 100644 --- a/apps/fg_http/lib/fg_http/devices.ex +++ b/apps/fg_http/lib/fg_http/devices.ex @@ -8,50 +8,10 @@ defmodule FgHttp.Devices do alias FgHttp.Devices.Device - @doc """ - Returns the list of devices. - - ## Examples - - iex> list_devices() - [%Device{}, ...] - - """ - def list_devices, do: Repo.all(Device) - def list_devices(user_id) do Repo.all(from d in Device, where: d.user_id == ^user_id) end - @doc """ - Returns a new device with an initialized configuration. - - ## Examples - - iex> new_device() - %Device{ - "conf" => "new_conf" - } - """ - def new_device(attrs \\ %{}) do - device = %Device{} - Map.merge(device, attrs) - end - - @doc """ - Gets a single device. - - Raises `Ecto.NoResultsError` if the Device does not exist. - - ## Examples - - iex> get_device!(123) - %Device{} - - iex> get_device!(456) - ** (Ecto.NoResultsError) - - """ def get_device!(id), do: Repo.get!(Device, id) def get_device!(id, with_rules: true) do @@ -62,67 +22,22 @@ defmodule FgHttp.Devices do ) end - @doc """ - Creates a device. - - ## Examples - - iex> create_device(%{field: value}) - {:ok, %Device{}} - - iex> create_device(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ def create_device(attrs \\ %{}) do %Device{} |> Device.changeset(attrs) |> Repo.insert() end - @doc """ - Updates a device. - - ## Examples - - iex> update_device(device, %{field: new_value}) - {:ok, %Device{}} - - iex> update_device(device, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ def update_device(%Device{} = device, attrs) do device |> Device.changeset(attrs) |> Repo.update() end - @doc """ - Deletes a device. - - ## Examples - - iex> delete_device(device) - {:ok, %Device{}} - - iex> delete_device(device) - {:error, %Ecto.Changeset{}} - - """ def delete_device(%Device{} = device) do Repo.delete(device) end - @doc """ - Returns an `%Ecto.Changeset{}` for tracking device changes. - - ## Examples - - iex> change_device(device) - %Ecto.Changeset{source: %Device{}} - - """ def change_device(%Device{} = device) do Device.changeset(device, %{}) end diff --git a/apps/fg_http/lib/fg_http/devices/device.ex b/apps/fg_http/lib/fg_http/devices/device.ex index 1e93550ed..c93b60c2b 100644 --- a/apps/fg_http/lib/fg_http/devices/device.ex +++ b/apps/fg_http/lib/fg_http/devices/device.ex @@ -20,7 +20,6 @@ defmodule FgHttp.Devices.Device do timestamps(type: :utc_datetime_usec) end - @doc false def changeset(device, attrs) do device |> cast(attrs, [:last_ip, :ifname, :user_id, :name, :public_key]) diff --git a/apps/fg_http/lib/fg_http/rules.ex b/apps/fg_http/lib/fg_http/rules.ex index fd426adae..0a1b57d23 100644 --- a/apps/fg_http/lib/fg_http/rules.ex +++ b/apps/fg_http/lib/fg_http/rules.ex @@ -8,15 +8,6 @@ defmodule FgHttp.Rules do alias FgHttp.Rules.Rule - @doc """ - Returns the list of rules. - - ## Examples - - iex> list_rules() - [%Rule{}, ...] - - """ def list_rules(device_id) do Repo.all(from r in Rule, where: r.device_id == ^device_id) end @@ -25,83 +16,24 @@ defmodule FgHttp.Rules do Repo.all(Rule) end - @doc """ - Gets a single rule. - - Raises `Ecto.NoResultsError` if the Rule does not exist. - - ## Examples - - iex> get_rule!(123) - %Rule{} - - iex> get_rule!(456) - ** (Ecto.NoResultsError) - - """ def get_rule!(id), do: Repo.get!(Rule, id) - @doc """ - Creates a rule. - - ## Examples - - iex> create_rule(%{field: value}) - {:ok, %Rule{}} - - iex> create_rule(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ def create_rule(attrs \\ %{}) do %Rule{} |> Rule.changeset(attrs) |> Repo.insert() end - @doc """ - Updates a rule. - - ## Examples - - iex> update_rule(rule, %{field: new_value}) - {:ok, %Rule{}} - - iex> update_rule(rule, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ def update_rule(%Rule{} = rule, attrs) do rule |> Rule.changeset(attrs) |> Repo.update() end - @doc """ - Deletes a rule. - - ## Examples - - iex> delete_rule(rule) - {:ok, %Rule{}} - - iex> delete_rule(rule) - {:error, %Ecto.Changeset{}} - - """ def delete_rule(%Rule{} = rule) do Repo.delete(rule) end - @doc """ - Returns an `%Ecto.Changeset{}` for tracking rule changes. - - ## Examples - - iex> change_rule(rule) - %Ecto.Changeset{source: %Rule{}} - - """ def change_rule(%Rule{} = rule) do Rule.changeset(rule, %{}) end diff --git a/apps/fg_http/lib/fg_http/rules/rule.ex b/apps/fg_http/lib/fg_http/rules/rule.ex index 6be9a535e..13bb981c7 100644 --- a/apps/fg_http/lib/fg_http/rules/rule.ex +++ b/apps/fg_http/lib/fg_http/rules/rule.ex @@ -21,7 +21,6 @@ defmodule FgHttp.Rules.Rule do timestamps(type: :utc_datetime_usec) end - @doc false def changeset(rule, attrs) do rule |> cast(attrs, [:device_id, :priority, :action, :destination, :port, :protocol, :enabled]) diff --git a/apps/fg_http/lib/fg_http/sessions.ex b/apps/fg_http/lib/fg_http/sessions.ex index 0f34da247..98f623bf8 100644 --- a/apps/fg_http/lib/fg_http/sessions.ex +++ b/apps/fg_http/lib/fg_http/sessions.ex @@ -24,6 +24,8 @@ defmodule FgHttp.Sessions do """ def get_session!(email: email), do: Repo.get_by!(Session, email: email) def get_session!(id), do: Repo.get!(Session, id) + def get_session(email: email), do: Repo.get_by(Session, email: email) + def get_session(id), do: Repo.get(Session, id) @doc """ Creates a session. diff --git a/apps/fg_http/lib/fg_http/users/password_reset.ex b/apps/fg_http/lib/fg_http/users/password_reset.ex index 4444648a3..11cc51c78 100644 --- a/apps/fg_http/lib/fg_http/users/password_reset.ex +++ b/apps/fg_http/lib/fg_http/users/password_reset.ex @@ -20,19 +20,16 @@ defmodule FgHttp.Users.PasswordReset do field :email, :string end - @doc false def changeset do %__MODULE__{} |> cast(%{}, [:password, :password_confirmation, :reset_token]) end - @doc false def changeset(%__MODULE__{} = password_reset, attrs \\ %{}) do password_reset |> cast(attrs, [:password, :password_confirmation, :reset_token]) end - @doc false def create_changeset(%__MODULE__{} = password_reset, attrs) do password_reset |> cast(attrs, [:email, :reset_sent_at, :reset_token]) @@ -44,7 +41,6 @@ defmodule FgHttp.Users.PasswordReset do |> validate_required([:reset_sent_at]) end - @doc false def update_changeset(%__MODULE__{} = password_reset, attrs) do password_reset |> cast(attrs, [ diff --git a/apps/fg_http/lib/fg_http/users/session.ex b/apps/fg_http/lib/fg_http/users/session.ex index c44ef2021..ff701f423 100644 --- a/apps/fg_http/lib/fg_http/users/session.ex +++ b/apps/fg_http/lib/fg_http/users/session.ex @@ -16,12 +16,16 @@ defmodule FgHttp.Users.Session do def create_changeset(session, attrs \\ %{}) do session - |> cast(attrs, [:email, :password]) - |> validate_required([:email, :password]) + |> cast(attrs, [:email, :password, :last_signed_in_at]) + |> log_it() |> authenticate_user() |> set_last_signed_in_at() end + defp log_it(changeset) do + changeset + end + defp set_last_signed_in_at(%Ecto.Changeset{valid?: true} = changeset) do last_signed_in_at = DateTime.utc_now() change(changeset, last_signed_in_at: last_signed_in_at) @@ -32,22 +36,24 @@ defmodule FgHttp.Users.Session do defp authenticate_user( %Ecto.Changeset{ valid?: true, - changes: %{email: email, password: password} + changes: %{ + password: password + } } = changeset ) do - user = Users.get_user!(email: email) + session = changeset.data + user = Users.get_user!(email: session.email) case User.authenticate_user(user, password) do {:ok, _} -> - # 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}") + add_error(changeset, :password, "invalid: #{error_msg}") end end - defp authenticate_user(changeset), do: delete_change(changeset, :password) + defp authenticate_user(changeset) do + changeset + end end diff --git a/apps/fg_http/lib/fg_http/users/user.ex b/apps/fg_http/lib/fg_http/users/user.ex index 1578f9c54..d6ed46744 100644 --- a/apps/fg_http/lib/fg_http/users/user.ex +++ b/apps/fg_http/lib/fg_http/users/user.ex @@ -25,7 +25,6 @@ defmodule FgHttp.Users.User do timestamps(type: :utc_datetime_usec) end - @doc false def create_changeset(user, attrs \\ %{}) do user |> cast(attrs, [:email, :password_hash, :password, :password_confirmation]) diff --git a/apps/fg_http/lib/fg_http_web/controllers/device_controller.ex b/apps/fg_http/lib/fg_http_web/controllers/device_controller.ex index 0d26a9198..f6643d3cd 100644 --- a/apps/fg_http/lib/fg_http_web/controllers/device_controller.ex +++ b/apps/fg_http/lib/fg_http_web/controllers/device_controller.ex @@ -28,8 +28,8 @@ defmodule FgHttpWeb.DeviceController do all_params = Map.merge(device_params, our_params) case Devices.create_device(all_params) do - {:ok, device} -> - redirect(conn, to: Routes.device_path(conn, :show, device)) + {:ok, _device} -> + redirect(conn, to: Routes.device_path(conn, :index)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) diff --git a/apps/fg_http/lib/fg_http_web/controllers/rule_controller.ex b/apps/fg_http/lib/fg_http_web/controllers/rule_controller.ex index deb6df527..7f2d440fc 100644 --- a/apps/fg_http/lib/fg_http_web/controllers/rule_controller.ex +++ b/apps/fg_http/lib/fg_http_web/controllers/rule_controller.ex @@ -28,9 +28,9 @@ defmodule FgHttpWeb.RuleController do {:ok, rule} -> conn |> put_flash(:info, "Rule created successfully.") - |> redirect(to: Routes.rule_path(conn, :show, rule)) + |> redirect(to: Routes.device_rule_path(conn, :index, rule.device_id)) - {:error, %Ecto.Changeset{} = changeset} -> + {:error, changeset} -> device = Devices.get_device!(device_id) render(conn, "new.html", device: device, changeset: changeset) end @@ -55,19 +55,20 @@ defmodule FgHttpWeb.RuleController do {:ok, rule} -> conn |> put_flash(:info, "Rule updated successfully.") - |> redirect(to: Routes.rule_path(conn, :show, rule)) + |> redirect(to: Routes.device_rule_path(conn, :index, rule.device_id)) - {:error, %Ecto.Changeset{} = changeset} -> + {:error, changeset} -> render(conn, "edit.html", rule: rule, changeset: changeset) end end def delete(conn, %{"id" => id}) do rule = Rules.get_rule!(id) + device_id = rule.device_id {:ok, _rule} = Rules.delete_rule(rule) conn |> put_flash(:info, "Rule deleted successfully.") - |> redirect(to: Routes.rule_path(conn, :index, rule.device)) + |> redirect(to: Routes.device_rule_path(conn, :index, device_id)) end end diff --git a/apps/fg_http/lib/fg_http_web/controllers/session_controller.ex b/apps/fg_http/lib/fg_http_web/controllers/session_controller.ex index 5b42e7e65..6ff135490 100644 --- a/apps/fg_http/lib/fg_http_web/controllers/session_controller.ex +++ b/apps/fg_http/lib/fg_http_web/controllers/session_controller.ex @@ -16,7 +16,7 @@ defmodule FgHttpWeb.SessionController do # POST /sessions def create(conn, %{"session" => %{"email" => email} = session_params}) do - case Sessions.get_session!(email: email) do + case Sessions.get_session(email: email) do %Session{} = session -> case Sessions.create_session(session, session_params) do {:ok, session} -> diff --git a/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex b/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex index 0c1a66857..37a5121ab 100644 --- a/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex +++ b/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex @@ -10,16 +10,13 @@ defmodule FgHttpWeb.Plugs.SessionLoader do def init(default), do: default - # Don't load an already loaded session - def call(%Plug.Conn{assigns: %{session: %Session{}}} = conn, _default), do: conn - def call(conn, _default) do case get_session(conn, :user_id) do nil -> unauthed(conn) user_id -> - case Sessions.get_session!(user_id) do + case Sessions.get_session(user_id) do %Session{} = session -> conn |> assign(:session, session) @@ -32,6 +29,7 @@ defmodule FgHttpWeb.Plugs.SessionLoader do defp unauthed(conn) do conn + |> clear_session() |> put_flash(:error, "Please sign in to access that page.") |> redirect(to: Routes.session_path(conn, :new)) |> halt() diff --git a/apps/fg_http/lib/fg_http_web/router.ex b/apps/fg_http/lib/fg_http_web/router.ex index ff9841bb3..252cef650 100644 --- a/apps/fg_http/lib/fg_http_web/router.ex +++ b/apps/fg_http/lib/fg_http_web/router.ex @@ -37,7 +37,8 @@ defmodule FgHttpWeb.Router do resources "/rules", RuleController, only: [:show, :update, :delete, :edit] - resources "/sessions", SessionController, only: [:new, :create, :delete] + resources "/sessions", SessionController, only: [:new, :create] + resources "/session", SessionController, singleton: true, only: [:delete] get "/", SessionController, :new end diff --git a/apps/fg_http/test/fg_http_web/controllers/device_controller_test.exs b/apps/fg_http/test/fg_http_web/controllers/device_controller_test.exs index 6fa1d658f..ec076d2b4 100644 --- a/apps/fg_http/test/fg_http_web/controllers/device_controller_test.exs +++ b/apps/fg_http/test/fg_http_web/controllers/device_controller_test.exs @@ -23,8 +23,8 @@ defmodule FgHttpWeb.DeviceControllerTest do describe "create device" do test "redirects when data is valid", %{authed_conn: conn} do - conn = post(conn, Routes.device_path(conn, :create), device: @create_attrs) - assert html_response(conn, 302) =~ "redirected" + test_conn = post(conn, Routes.device_path(conn, :create), device: @create_attrs) + assert redirected_to(test_conn) == Routes.device_path(test_conn, :index) end test "renders errors when data is invalid", %{authed_conn: conn} do diff --git a/apps/fg_http/test/fg_http_web/controllers/rule_controller_test.exs b/apps/fg_http/test/fg_http_web/controllers/rule_controller_test.exs index 1c082df85..e88be756c 100644 --- a/apps/fg_http/test/fg_http_web/controllers/rule_controller_test.exs +++ b/apps/fg_http/test/fg_http_web/controllers/rule_controller_test.exs @@ -1,21 +1,115 @@ defmodule FgHttpWeb.RuleControllerTest do use FgHttpWeb.ConnCase, async: true + alias FgHttp.Fixtures + + @valid_create_attrs %{ + destination: "1.1.1.1", + port: "53", + protocol: "udp" + } + @invalid_create_attrs %{ + destination: "problem" + } + @valid_update_attrs @valid_create_attrs + @invalid_update_attrs @invalid_create_attrs + describe "index" do + setup [:create_device] + + test "list all rules", %{authed_conn: conn, device: device} do + test_conn = get(conn, Routes.device_rule_path(conn, :index, device)) + + assert html_response(test_conn, 200) =~ "Listing Rules" + end end - describe "new rule" do + describe "new" do + setup [:create_device] + + test "renders form", %{authed_conn: conn, device: device} do + test_conn = get(conn, Routes.device_rule_path(conn, :new, device)) + + assert html_response(test_conn, 200) =~ "New Rule" + end end - describe "create rule" do + describe "create" do + setup [:create_device] + + test "redirects when data is valid", %{authed_conn: conn, device: device} do + test_conn = + post(conn, Routes.device_rule_path(conn, :create, device), rule: @valid_create_attrs) + + assert redirected_to(test_conn) == Routes.device_rule_path(test_conn, :index, device) + end + + test "renders edit when data is invalid", %{authed_conn: conn, device: device} do + test_conn = + post(conn, Routes.device_rule_path(conn, :create, device), rule: @invalid_create_attrs) + + assert html_response(test_conn, 200) =~ "New Rule" + end end - describe "edit rule" do + describe "edit" do + setup [:create_rule] + + test "renders form", %{authed_conn: conn, rule: rule} do + test_conn = get(conn, Routes.rule_path(conn, :edit, rule)) + + assert html_response(test_conn, 200) =~ "Edit Rule" + end end - describe "update rule" do + describe "show" do + setup [:create_rule] + + test "renders the rule", %{authed_conn: conn, rule: rule} do + test_conn = get(conn, Routes.rule_path(conn, :show, rule)) + + assert html_response(test_conn, 200) =~ "Show Rule" + end end - describe "delete rule" do + describe "update" do + setup [:create_rule] + + test "redirects to index with valid attrs", %{authed_conn: conn, rule: rule} do + test_conn = put(conn, Routes.rule_path(conn, :update, rule), rule: @valid_update_attrs) + + assert redirected_to(test_conn) == + Routes.device_rule_path(test_conn, :index, rule.device_id) + end + + test "renders edit form with invalid attrs", %{authed_conn: conn, rule: rule} do + test_conn = put(conn, Routes.rule_path(conn, :update, rule), rule: @invalid_update_attrs) + + assert html_response(test_conn, 200) =~ "Edit Rule" + end + end + + describe "delete" do + setup [:create_rule] + + test "deletes chosen rule", %{authed_conn: conn, rule: rule} do + test_conn = delete(conn, Routes.rule_path(conn, :delete, rule)) + + assert redirected_to(test_conn) == Routes.device_rule_path(conn, :index, rule.device_id) + + assert_error_sent 404, fn -> + get(conn, Routes.rule_path(conn, :show, rule)) + end + end + end + + defp create_device(_) do + device = Fixtures.device() + {:ok, device: device} + end + + defp create_rule(_) do + rule = Fixtures.rule() + {:ok, rule: rule} end end diff --git a/apps/fg_http/test/fg_http_web/controllers/session_controller_test.exs b/apps/fg_http/test/fg_http_web/controllers/session_controller_test.exs new file mode 100644 index 000000000..a5109b8ef --- /dev/null +++ b/apps/fg_http/test/fg_http_web/controllers/session_controller_test.exs @@ -0,0 +1,80 @@ +defmodule FgHttpWeb.SessionControllerTest do + use FgHttpWeb.ConnCase, async: true + + alias FgHttp.{Fixtures, Repo, Users.User} + + @valid_attrs %{email: "test", password: "test"} + @invalid_attrs %{email: "test", password: "wrong"} + + describe "new" do + test "renders sign in form", %{unauthed_conn: conn} do + test_conn = get(conn, Routes.session_path(conn, :new)) + + assert html_response(test_conn, 200) =~ "Sign In" + end + end + + describe "create when user exists" do + setup [:create_user] + + test "creates session when credentials are valid", %{unauthed_conn: conn, user: user} do + test_conn = post(conn, Routes.session_path(conn, :create), session: @valid_attrs) + + assert redirected_to(test_conn) == Routes.device_path(test_conn, :index) + assert get_flash(test_conn, :info) == "Session created successfully" + assert get_session(test_conn, :user_id) == user.id + end + + test "displays error if credentials are invalid", %{unauthed_conn: conn} do + test_conn = post(conn, Routes.session_path(conn, :create), session: @invalid_attrs) + + assert html_response(test_conn, 200) =~ "Sign In" + assert get_flash(test_conn, :error) == "Error creating session." + end + end + + describe "create when user doesn't exist" do + setup [:clear_users] + + test "renders sign in form", %{unauthed_conn: conn} do + test_conn = post(conn, Routes.session_path(conn, :create), session: @valid_attrs) + + assert html_response(test_conn, 200) =~ "Sign In" + assert get_flash(test_conn, :error) == "Email not found." + end + end + + describe "delete when user exists" do + setup [:create_user] + + test "removes session", %{authed_conn: conn} do + test_conn = delete(conn, Routes.session_path(conn, :delete)) + + assert redirected_to(test_conn) == "/" + assert get_flash(test_conn, :info) == "Signed out successfully." + assert is_nil(get_session(test_conn, :user_id)) + end + end + + describe "delete when user doesn't exist" do + setup [:clear_users] + + test "renders flash error", %{authed_conn: conn} do + test_conn = delete(conn, Routes.session_path(conn, :delete)) + + assert redirected_to(test_conn) == Routes.session_path(test_conn, :new) + assert get_flash(test_conn, :error) == "Please sign in to access that page." + assert is_nil(get_session(test_conn, :user_id)) + end + end + + defp create_user(_) do + user = Fixtures.user() + {:ok, user: user} + end + + defp clear_users(_) do + {count, _result} = Repo.delete_all(User) + {:ok, count: count} + end +end diff --git a/apps/fg_http/test/support/conn_case.ex b/apps/fg_http/test/support/conn_case.ex index fbe5f094d..f8bbc84c2 100644 --- a/apps/fg_http/test/support/conn_case.ex +++ b/apps/fg_http/test/support/conn_case.ex @@ -41,7 +41,7 @@ defmodule FgHttpWeb.ConnCase do session = Fixtures.session() new_conn() - |> Plug.Conn.assign(:session, session) + |> Plug.Test.init_test_session(%{user_id: session.id}) end setup tags do diff --git a/apps/fg_http/test/support/fixtures.ex b/apps/fg_http/test/support/fixtures.ex index 9bca64f70..9b564f35b 100644 --- a/apps/fg_http/test/support/fixtures.ex +++ b/apps/fg_http/test/support/fixtures.ex @@ -2,7 +2,7 @@ defmodule FgHttp.Fixtures do @moduledoc """ Convenience helpers for inserting records """ - alias FgHttp.{Devices, PasswordResets, Repo, Sessions, Users, Users.User} + alias FgHttp.{Devices, PasswordResets, Repo, Rules, Sessions, Users, Users.User} def user(attrs \\ %{}) do case Repo.get_by(User, email: "test") do @@ -29,6 +29,16 @@ defmodule FgHttp.Fixtures do device end + def rule(attrs \\ %{}) do + attrs = + attrs + |> Enum.into(%{device_id: device().id}) + |> Enum.into(%{destination: "0.0.0.0/0"}) + + {:ok, rule} = Rules.create_rule(attrs) + rule + end + def session(_attrs \\ %{}) do email = user().email record = Sessions.get_session!(email: email)