From 07111af2a19f18893f0a8a3ef1f423afaf33e59b Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Mon, 18 May 2020 20:47:05 -0700 Subject: [PATCH 1/8] Add fixtures lib, user stuff --- .tool-versions | 4 +- apps/fg_http/lib/fg_http/factory.ex | 32 +++++++++ apps/fg_http/lib/fg_http/users.ex | 4 +- apps/fg_http/lib/fg_http/users/user.ex | 68 +++++++++++++++++-- .../controllers/user_controller.ex | 8 +-- .../fg_http_web/templates/user/edit.html.eex | 1 + apps/fg_http/mix.exs | 2 + .../20200225005454_create_users.exs | 3 +- apps/fg_http/priv/repo/seeds.exs | 21 +----- .../controllers/device_controller_test.exs | 3 +- apps/fg_http/test/test_helper.exs | 1 + mix.lock | 4 ++ 12 files changed, 116 insertions(+), 35 deletions(-) create mode 100644 apps/fg_http/lib/fg_http/factory.ex diff --git a/.tool-versions b/.tool-versions index fb3f44dc7..dbc4e5140 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -elixir 1.10.3-otp-22 -erlang 22.3.3 +elixir 1.10.3-otp-23 +erlang 23.0 nodejs 10.20.1 diff --git a/apps/fg_http/lib/fg_http/factory.ex b/apps/fg_http/lib/fg_http/factory.ex new file mode 100644 index 000000000..4407bdffd --- /dev/null +++ b/apps/fg_http/lib/fg_http/factory.ex @@ -0,0 +1,32 @@ +defmodule FgHttp.Factory do + @moduledoc """ + Fixtures generator + """ + use ExMachina.Ecto, repo: FgHttp.Repo + + alias FgHttp.{Devices.Device, Rules.Rule, Users.User} + + def user_factory do + %User{ + email: "factory@factory", + password: "factory", + password_confirmation: "factory" + } + end + + def device_factory do + %Device{ + user: build(:user), + name: "Factory Device", + public_key: "factory public key", + last_ip: %Postgrex.INET{address: {127, 0, 0, 1}} + } + end + + def rule_factory do + %Rule{ + device: build(:device), + destination: %Postgrex.INET{address: {0, 0, 0, 0}, netmask: 0} + } + end +end diff --git a/apps/fg_http/lib/fg_http/users.ex b/apps/fg_http/lib/fg_http/users.ex index 5fb224803..0bb024cb1 100644 --- a/apps/fg_http/lib/fg_http/users.ex +++ b/apps/fg_http/lib/fg_http/users.ex @@ -51,7 +51,7 @@ defmodule FgHttp.Users do """ def create_user(attrs \\ %{}) do %User{} - |> User.changeset(attrs) + |> User.create_changeset(attrs) |> Repo.insert() end @@ -69,7 +69,7 @@ defmodule FgHttp.Users do """ def update_user(%User{} = user, attrs) do user - |> User.changeset(attrs) + |> User.update_changeset(attrs) |> Repo.update() end diff --git a/apps/fg_http/lib/fg_http/users/user.ex b/apps/fg_http/lib/fg_http/users/user.ex index 2df6319fe..bc28dee0f 100644 --- a/apps/fg_http/lib/fg_http/users/user.ex +++ b/apps/fg_http/lib/fg_http/users/user.ex @@ -11,20 +11,76 @@ defmodule FgHttp.Users.User do schema "users" do field :email, :string field :confirmed_at, :utc_datetime + field :reset_sent_at, :utc_datetime field :last_signed_in_at, :utc_datetime - field :password_digest, :string + field :password_hash, :string - has_many :devices, Device - has_many :sessions, Session + # VIRTUAL FIELDS + field :password, :string, virtual: true + field :password_confirmation, :string, virtual: true + field :current_password, :string, virtual: true + + has_many :devices, Device, on_delete: :delete_all + has_many :sessions, Session, on_delete: :delete_all timestamps() end @doc false + def create_changeset(user, attrs \\ %{}) do + user + |> cast(attrs, [:email, :password, :password_confirmation]) + |> validate_required([:email, :password, :password_confirmation]) + |> unique_constraint(:email) + |> put_password_hash() + end + + # Only password being updated + def update_changeset( + user, + %{ + user: %{ + password: _password, + password_confirmation: _password_confirmation, + current_password: _current_password + } + } = attrs + ) do + user + |> cast(attrs, [:email, :password, :password_confirmation, :current_password]) + |> verify_current_password(attrs[:current_password]) + |> validate_required([:password, :password_confirmation, :current_password]) + end + + # Only email being updated + def update_changeset(user, %{user: %{email: _email}} = attrs) do + user + |> cast(attrs, [:email]) + end + + # Edit user def changeset(user, attrs \\ %{}) do user - |> cast(attrs, [:email, :confirmed_at, :password_digest, :last_signed_in_at]) - |> validate_required([:email]) - |> unique_constraint(:email) + |> cast(attrs, [:email]) end + + def authenticate_user(user, password_candidate) do + Argon2.check_pass(user.password, password_candidate, hash_key: :password) + end + + defp verify_current_password(user, current_password) do + {:ok, user} = authenticate_user(user, current_password) + user + end + + defp put_password_hash( + %Ecto.Changeset{ + valid?: true, + changes: %{password: password} + } = changeset + ) do + change(changeset, password: Argon2.hash_pwd_salt(password)) + end + + defp put_password_hash(changeset), do: changeset end diff --git a/apps/fg_http/lib/fg_http_web/controllers/user_controller.ex b/apps/fg_http/lib/fg_http_web/controllers/user_controller.ex index 602fec260..5ab086f44 100644 --- a/apps/fg_http/lib/fg_http_web/controllers/user_controller.ex +++ b/apps/fg_http/lib/fg_http_web/controllers/user_controller.ex @@ -4,7 +4,7 @@ defmodule FgHttpWeb.UserController do """ use FgHttpWeb, :controller - alias FgHttp.{Repo, Users, Users.User} + alias FgHttp.{Users, Users.User} plug FgHttpWeb.Plugs.Authenticator when action in [:show, :edit, :update, :delete] @@ -46,9 +46,7 @@ defmodule FgHttpWeb.UserController do # PATCH /user def update(conn, params) do - changeset = User.changeset(conn.current_user, params) - - case Repo.update(changeset) do + case Users.update_user(conn.current_user, params) do {:ok, user} -> conn |> assign(:current_user, user) @@ -64,7 +62,7 @@ defmodule FgHttpWeb.UserController do # DELETE /user def delete(conn, _params) do - case Repo.delete(conn.current_user) do + case Users.delete_user(conn.current_user) do {:ok, _user} -> conn |> assign(:current_user, nil) diff --git a/apps/fg_http/lib/fg_http_web/templates/user/edit.html.eex b/apps/fg_http/lib/fg_http_web/templates/user/edit.html.eex index e69de29bb..aa341db86 100644 --- a/apps/fg_http/lib/fg_http_web/templates/user/edit.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/user/edit.html.eex @@ -0,0 +1 @@ +

Account

diff --git a/apps/fg_http/mix.exs b/apps/fg_http/mix.exs index e0bbd12d3..8f56af072 100644 --- a/apps/fg_http/mix.exs +++ b/apps/fg_http/mix.exs @@ -38,10 +38,12 @@ defmodule FgHttp.MixProject do defp deps do [ {:phoenix, "~> 1.5.1"}, + {:argon2_elixir, "~> 2.0"}, {:phoenix_pubsub, "~> 2.0"}, {:phoenix_ecto, "~> 4.0"}, {:ecto_sql, "~> 3.1"}, {:ecto_enum, "~> 1.4.0"}, + {:ex_machina, "~> 2.4"}, {:ecto_network, "~> 1.3.0"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 2.11"}, diff --git a/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs b/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs index bfbed843f..93fb623e8 100644 --- a/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs +++ b/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs @@ -5,8 +5,9 @@ defmodule FgHttp.Repo.Migrations.CreateUsers do create table(:users) do add :email, :string add :confirmed_at, :utc_datetime - add :password_digest, :string + add :password_hash, :string add :last_signed_in_at, :utc_datetime + add :reset_sent_at, :utc_datetime timestamps() end diff --git a/apps/fg_http/priv/repo/seeds.exs b/apps/fg_http/priv/repo/seeds.exs index adccbb974..b0b35321c 100644 --- a/apps/fg_http/priv/repo/seeds.exs +++ b/apps/fg_http/priv/repo/seeds.exs @@ -10,22 +10,7 @@ # We recommend using the bang functions (`insert!`, `update!` # and so on) as they will fail if something goes wrong. -alias FgHttp.Repo +import FgHttp.Factory -Repo.transaction(fn -> - {:ok, user} = FgHttp.Users.create_user(%{email: "testuser@fireguard.network"}) - - {:ok, device} = - FgHttp.Devices.create_device(%{ - name: "Seed", - public_key: "Seed", - last_ip: %Postgrex.INET{address: {127, 0, 0, 1}}, - user_id: user.id - }) - - {:ok, _rule} = - FgHttp.Rules.create_rule(%{ - device_id: device.id, - destination: %Postgrex.INET{address: {0, 0, 0, 0}, netmask: 0} - }) -end) +# Inserts needed associations +insert(:rule) 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 351710262..100eed468 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 @@ -9,7 +9,8 @@ defmodule FgHttpWeb.DeviceControllerTest do @invalid_attrs %{user_id: nil} def fixture(:user) do - {:ok, user} = Users.create_user(%{email: "test"}) + attrs = %{email: "test", password: "foobar", password_confirmation: "foobar"} + {:ok, user} = Users.create_user(attrs) user end diff --git a/apps/fg_http/test/test_helper.exs b/apps/fg_http/test/test_helper.exs index ae2a237b9..ee5a1ae26 100644 --- a/apps/fg_http/test/test_helper.exs +++ b/apps/fg_http/test/test_helper.exs @@ -1,2 +1,3 @@ +{:ok, _} = Application.ensure_all_started(:ex_machina) ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(FgHttp.Repo, :manual) diff --git a/mix.lock b/mix.lock index 11058b065..c981047c2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{ + "argon2_elixir": {:hex, :argon2_elixir, "2.3.0", "e251bdafd69308e8c1263e111600e6d68bd44f23d2cccbe43fcb1a417a76bc8e", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "28ccb63bff213aecec1f7f3dde9648418b031f822499973281d8f494b9d5a3b3"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, @@ -10,6 +12,8 @@ "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_network": {:hex, :ecto_network, "1.3.0", "1e77fa37c20e0f6a426d3862732f3317b0fa4c18f123d325f81752a491d7304e", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "053a5e46ef2837e8ea5ea97c82fa0f5494699209eddd764e663c85f11b2865bd"}, "ecto_sql": {:hex, :ecto_sql, "3.4.3", "c552aa8a7ccff2b64024f835503b3155d8e73452c180298527fbdbcd6e79710b", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ec9e59d6fa3f8cfda9963ada371e9e6659167c2338a997bd7ea23b10b245842b"}, + "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, + "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, From 27cbbd807ccda49af9cc0785f792d8be8ec5e26a Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Tue, 19 May 2020 18:12:46 -0700 Subject: [PATCH 2/8] basic login logout --- apps/fg_http/lib/fg_http/devices/device.ex | 2 +- apps/fg_http/lib/fg_http/factory.ex | 32 ----------------- apps/fg_http/lib/fg_http/sessions.ex | 28 +++++++++++++-- apps/fg_http/lib/fg_http/sessions/session.ex | 35 +++++++++++++++++-- apps/fg_http/lib/fg_http/users.ex | 4 +++ apps/fg_http/lib/fg_http/users/user.ex | 9 +++-- .../controllers/device_controller.ex | 12 +++---- .../controllers/rule_controller.ex | 2 +- .../controllers/session_controller.ex | 35 ++++++++++--------- .../controllers/user_controller.ex | 2 +- .../lib/fg_http_web/live/new_device_live.ex | 12 +++---- .../live/new_device_live.html.leex | 3 +- .../lib/fg_http_web/plugs/authenticator.ex | 15 -------- .../lib/fg_http_web/plugs/session_loader.ex | 27 ++++++++++++++ .../fg_http_web/templates/layout/app.html.eex | 13 ++++--- .../templates/session/new.html.eex | 18 ++++++---- .../lib/fg_http_web/views/session_view.ex | 3 ++ apps/fg_http/mix.exs | 1 - .../20200510162435_create_sessions.exs | 4 ++- apps/fg_http/priv/repo/seeds.exs | 24 +++++++++++-- 20 files changed, 174 insertions(+), 107 deletions(-) delete mode 100644 apps/fg_http/lib/fg_http/factory.ex delete mode 100644 apps/fg_http/lib/fg_http_web/plugs/authenticator.ex create mode 100644 apps/fg_http/lib/fg_http_web/plugs/session_loader.ex create mode 100644 apps/fg_http/lib/fg_http_web/views/session_view.ex diff --git a/apps/fg_http/lib/fg_http/devices/device.ex b/apps/fg_http/lib/fg_http/devices/device.ex index 794575654..1625e53fe 100644 --- a/apps/fg_http/lib/fg_http/devices/device.ex +++ b/apps/fg_http/lib/fg_http/devices/device.ex @@ -23,6 +23,6 @@ defmodule FgHttp.Devices.Device do def changeset(device, attrs) do device |> cast(attrs, [:last_ip, :user_id, :name, :public_key]) - |> validate_required([:user_id]) + |> validate_required([:user_id, :name, :public_key]) end end diff --git a/apps/fg_http/lib/fg_http/factory.ex b/apps/fg_http/lib/fg_http/factory.ex deleted file mode 100644 index 4407bdffd..000000000 --- a/apps/fg_http/lib/fg_http/factory.ex +++ /dev/null @@ -1,32 +0,0 @@ -defmodule FgHttp.Factory do - @moduledoc """ - Fixtures generator - """ - use ExMachina.Ecto, repo: FgHttp.Repo - - alias FgHttp.{Devices.Device, Rules.Rule, Users.User} - - def user_factory do - %User{ - email: "factory@factory", - password: "factory", - password_confirmation: "factory" - } - end - - def device_factory do - %Device{ - user: build(:user), - name: "Factory Device", - public_key: "factory public key", - last_ip: %Postgrex.INET{address: {127, 0, 0, 1}} - } - end - - def rule_factory do - %Rule{ - device: build(:device), - destination: %Postgrex.INET{address: {0, 0, 0, 0}, netmask: 0} - } - end -end diff --git a/apps/fg_http/lib/fg_http/sessions.ex b/apps/fg_http/lib/fg_http/sessions.ex index 3d1f7d325..de3cb808d 100644 --- a/apps/fg_http/lib/fg_http/sessions.ex +++ b/apps/fg_http/lib/fg_http/sessions.ex @@ -6,7 +6,7 @@ defmodule FgHttp.Sessions do import Ecto.Query, warn: false alias FgHttp.Repo - alias FgHttp.Sessions.Session + alias FgHttp.{Sessions.Session, Users.User} @doc """ Returns the list of sessions. @@ -51,7 +51,7 @@ defmodule FgHttp.Sessions do """ def create_session(attrs \\ %{}) do %Session{} - |> Session.changeset(attrs) + |> Session.create_changeset(attrs) |> Repo.insert() end @@ -85,10 +85,32 @@ defmodule FgHttp.Sessions do {:error, %Ecto.Changeset{}} """ - def delete_session(%Session{} = session) do + 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. diff --git a/apps/fg_http/lib/fg_http/sessions/session.ex b/apps/fg_http/lib/fg_http/sessions/session.ex index 506e6075c..bc191d2a8 100644 --- a/apps/fg_http/lib/fg_http/sessions/session.ex +++ b/apps/fg_http/lib/fg_http/sessions/session.ex @@ -6,18 +6,49 @@ defmodule FgHttp.Sessions.Session do use Ecto.Schema import Ecto.Changeset - alias FgHttp.{Users.User} + 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, []) + |> cast(attrs, [:deleted_at]) |> validate_required([]) end + + def create_changeset(session, attrs \\ %{}) do + session + |> cast(attrs, [:user_email, :user_password]) + |> validate_required([:user_email, :user_password]) + |> authenticate_user() + end + + defp authenticate_user( + %Ecto.Changeset{ + valid?: true, + changes: %{user_email: email, user_password: password} + } = changeset + ) do + user = Users.get_user!(email: email) + + case User.authenticate_user(user, password) do + {:ok, _} -> + change(changeset, user_id: user.id) + + {:error, error_msg} -> + raise("There was an issue with your password: #{error_msg}") + end + end + + defp authenticate_user(changeset), do: changeset end diff --git a/apps/fg_http/lib/fg_http/users.ex b/apps/fg_http/lib/fg_http/users.ex index 0bb024cb1..08f0a2b85 100644 --- a/apps/fg_http/lib/fg_http/users.ex +++ b/apps/fg_http/lib/fg_http/users.ex @@ -35,6 +35,10 @@ defmodule FgHttp.Users do ** (Ecto.NoResultsError) """ + def get_user!(email: email) do + Repo.get_by!(User, email: email) + end + def get_user!(id), do: Repo.get!(User, id) @doc """ diff --git a/apps/fg_http/lib/fg_http/users/user.ex b/apps/fg_http/lib/fg_http/users/user.ex index bc28dee0f..897c2b6b0 100644 --- a/apps/fg_http/lib/fg_http/users/user.ex +++ b/apps/fg_http/lib/fg_http/users/user.ex @@ -29,10 +29,11 @@ defmodule FgHttp.Users.User do @doc false def create_changeset(user, attrs \\ %{}) do user - |> cast(attrs, [:email, :password, :password_confirmation]) + |> cast(attrs, [:email, :password_hash, :password, :password_confirmation]) |> validate_required([:email, :password, :password_confirmation]) |> unique_constraint(:email) |> put_password_hash() + |> validate_required([:password_hash]) end # Only password being updated @@ -50,6 +51,8 @@ defmodule FgHttp.Users.User do |> cast(attrs, [:email, :password, :password_confirmation, :current_password]) |> verify_current_password(attrs[:current_password]) |> validate_required([:password, :password_confirmation, :current_password]) + |> put_password_hash() + |> validate_required([:password_hash]) end # Only email being updated @@ -65,7 +68,7 @@ defmodule FgHttp.Users.User do end def authenticate_user(user, password_candidate) do - Argon2.check_pass(user.password, password_candidate, hash_key: :password) + Argon2.check_pass(user, password_candidate) end defp verify_current_password(user, current_password) do @@ -79,7 +82,7 @@ defmodule FgHttp.Users.User do changes: %{password: password} } = changeset ) do - change(changeset, password: Argon2.hash_pwd_salt(password)) + change(changeset, password_hash: Argon2.hash_pwd_salt(password)) end defp put_password_hash(changeset), do: changeset 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 97aab5114..b07295ebc 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 @@ -6,7 +6,7 @@ defmodule FgHttpWeb.DeviceController do use FgHttpWeb, :controller alias FgHttp.{Devices, Devices.Device} - plug FgHttpWeb.Plugs.Authenticator + plug FgHttpWeb.Plugs.SessionLoader def index(conn, _params) do devices = Devices.list_devices(conn.assigns.current_user.id) @@ -18,13 +18,9 @@ defmodule FgHttpWeb.DeviceController do render(conn, "new.html", changeset: changeset) end - def create(conn, %{"device" => device_params}) do - create_params = %{ - "user_id" => conn.assigns.current_user.id, - "name" => "Auto" - } - - all_params = Map.merge(device_params, create_params) + def create(conn, %{"device" => %{"public_key" => _public_key} = device_params}) do + our_params = %{user_id: conn.assigns.current_user.id, name: "Default"} + all_params = Map.merge(device_params, our_params) case Devices.create_device(all_params) do {:ok, device} -> 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 8ad21d26c..deb6df527 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 @@ -6,7 +6,7 @@ defmodule FgHttpWeb.RuleController do use FgHttpWeb, :controller alias FgHttp.{Devices, Rules, Rules.Rule} - plug FgHttpWeb.Plugs.Authenticator + plug FgHttpWeb.Plugs.SessionLoader def index(conn, %{"device_id" => device_id}) do device = Devices.get_device!(device_id, with_rules: true) 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 c2cd30e7a..197b4c391 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 @@ -3,11 +3,11 @@ defmodule FgHttpWeb.SessionController do Implements the CRUD for a Session """ + alias FgHttp.{Sessions, Sessions.Session} use FgHttpWeb, :controller - alias FgHttp.{Repo, Sessions.Session, Users.User} plug :redirect_authenticated when action in [:new] - plug FgHttpWeb.Plugs.Authenticator when action in [:delete] + plug FgHttpWeb.Plugs.SessionLoader when action in [:delete] # GET /sessions/new def new(conn, _params) do @@ -18,10 +18,8 @@ defmodule FgHttpWeb.SessionController do # Sign In # POST /sessions - def create(conn, params) do - changeset = Session.changeset(%Session{}, params) - - case Repo.insert(changeset) do + def create(conn, %{"session" => session_params}) do + case Sessions.create_session(session_params) do {:ok, session} -> conn |> assign(:current_session, session) @@ -31,29 +29,32 @@ defmodule FgHttpWeb.SessionController do {:error, changeset} -> conn |> put_flash(:error, "Error creating session.") - |> render("new.html", changeset: changeset) + |> render("new.html", changeset: changeset, user_signed_in?: false) end end # Sign Out # DELETE /session def delete(conn, _params) do - case Repo.delete(conn.current_session) do + session = conn.assigns.current_session + + case Sessions.delete_session(session) do {:ok, _session} -> conn - |> assign(:current_session, nil) - |> put_flash(:info, "Session deleted successfully.") + |> clear_session + |> put_flash(:info, "Signed out successfully.") |> redirect(to: "/") end end defp redirect_authenticated(conn, _) do - user = %User{id: 1, email: "dev_user@fireguard.network"} - session = %Session{user_id: user.id} - - conn - |> assign(:current_session, session) - |> redirect(to: "/") - |> halt() + if Map.get(conn.assigns, :user_signed_in?) do + conn + |> redirect(to: "/") + |> halt() + else + conn + |> assign(:user_signed_in?, false) + end end end diff --git a/apps/fg_http/lib/fg_http_web/controllers/user_controller.ex b/apps/fg_http/lib/fg_http_web/controllers/user_controller.ex index 5ab086f44..a3912bbf4 100644 --- a/apps/fg_http/lib/fg_http_web/controllers/user_controller.ex +++ b/apps/fg_http/lib/fg_http_web/controllers/user_controller.ex @@ -6,7 +6,7 @@ defmodule FgHttpWeb.UserController do use FgHttpWeb, :controller alias FgHttp.{Users, Users.User} - plug FgHttpWeb.Plugs.Authenticator when action in [:show, :edit, :update, :delete] + plug FgHttpWeb.Plugs.SessionLoader when action in [:show, :edit, :update, :delete] # GET /users/new def new(conn, _params) do diff --git a/apps/fg_http/lib/fg_http_web/live/new_device_live.ex b/apps/fg_http/lib/fg_http_web/live/new_device_live.ex index 8e68ccf47..8a8137b67 100644 --- a/apps/fg_http/lib/fg_http_web/live/new_device_live.ex +++ b/apps/fg_http/lib/fg_http_web/live/new_device_live.ex @@ -15,13 +15,13 @@ defmodule FgHttpWeb.NewDeviceLive do {:ok, assign(socket, :device, device)} end - defp wait_for_device_connect(_socket) do - # XXX: pass socket to fg_vpn somehow - :timer.send_after(3000, self(), :update) + # XXX: Receive other device details to create an intelligent name + def handle_info({:pubkey, pubkey}, socket) do + device = %Device{public_key: pubkey} + {:noreply, assign(socket, :device, device)} end - def handle_info(:update, socket) do - new_device = Map.merge(socket.assigns.device, %{public_key: "foobar"}) - {:noreply, assign(socket, :device, new_device)} + defp wait_for_device_connect(_socket) do + :timer.send_after(3000, self(), {:pubkey, "foobar"}) end end diff --git a/apps/fg_http/lib/fg_http_web/live/new_device_live.html.leex b/apps/fg_http/lib/fg_http_web/live/new_device_live.html.leex index 1bdcfd0a9..5ff1ed6cc 100644 --- a/apps/fg_http/lib/fg_http_web/live/new_device_live.html.leex +++ b/apps/fg_http/lib/fg_http_web/live/new_device_live.html.leex @@ -42,8 +42,7 @@ Endpoint = <%= Application.fetch_env!(:fg_http, :vpn_endpoint) %>

<%= link("<- Something's wrong. Don't add this device and go back.", - to: Routes.device_path(@socket, :index), - method: :delete) + to: Routes.device_path(@socket, :index)) %>

<% end %> diff --git a/apps/fg_http/lib/fg_http_web/plugs/authenticator.ex b/apps/fg_http/lib/fg_http_web/plugs/authenticator.ex deleted file mode 100644 index 53c347906..000000000 --- a/apps/fg_http/lib/fg_http_web/plugs/authenticator.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule FgHttpWeb.Plugs.Authenticator do - @moduledoc """ - Loads the user's session from cookie - """ - - import Plug.Conn - alias FgHttp.{Repo, Users.User} - - def init(default), do: default - - def call(conn, _default) do - user = Repo.one(User) - assign(conn, :current_user, user) - end -end 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 new file mode 100644 index 000000000..7abed5e24 --- /dev/null +++ b/apps/fg_http/lib/fg_http_web/plugs/session_loader.ex @@ -0,0 +1,27 @@ +defmodule FgHttpWeb.Plugs.SessionLoader do + @moduledoc """ + Loads the user's session from cookie + """ + + import Plug.Conn + import Phoenix.Controller, only: [redirect: 2] + alias FgHttp.Sessions + alias FgHttpWeb.Router.Helpers, as: Routes + + def init(default), do: default + + def call(conn, _default) do + case Sessions.load_session("blah session id") do + {:ok, {session, user}} -> + conn + |> assign(:current_session, session) + |> assign(:current_user, user) + |> assign(:user_signed_in?, true) + + {:error, _} -> + conn + |> redirect(to: Routes.session_path(conn, :new)) + |> halt + end + end +end diff --git a/apps/fg_http/lib/fg_http_web/templates/layout/app.html.eex b/apps/fg_http/lib/fg_http_web/templates/layout/app.html.eex index b47b82095..a4f9211f2 100644 --- a/apps/fg_http/lib/fg_http_web/templates/layout/app.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/layout/app.html.eex @@ -12,11 +12,16 @@
- + <%= if @user_signed_in? do %> + + <% else %> + + <% end %>
<%= @inner_content %> diff --git a/apps/fg_http/lib/fg_http_web/templates/session/new.html.eex b/apps/fg_http/lib/fg_http_web/templates/session/new.html.eex index 5428d0e86..4a84e3776 100644 --- a/apps/fg_http/lib/fg_http_web/templates/session/new.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/session/new.html.eex @@ -1,23 +1,27 @@
-
+ <%= form_for(@changeset, Routes.session_path(@conn, :create), [class: "measure center"], fn f -> %> + <%= if f.errors do %> + + <% end %> +
Sign In
- - + <%= 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") %>
- - + <%= 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") %>
- + <%= submit "Sign in", class: "b ph3 pv2 input-reset ba b--black bg-transparent grow pointer f6 dib" %>
-
+ <% end) %>
diff --git a/apps/fg_http/lib/fg_http_web/views/session_view.ex b/apps/fg_http/lib/fg_http_web/views/session_view.ex new file mode 100644 index 000000000..c2ea01f49 --- /dev/null +++ b/apps/fg_http/lib/fg_http_web/views/session_view.ex @@ -0,0 +1,3 @@ +defmodule FgHttpWeb.SessionView do + use FgHttpWeb, :view +end diff --git a/apps/fg_http/mix.exs b/apps/fg_http/mix.exs index 8f56af072..9cb6a80df 100644 --- a/apps/fg_http/mix.exs +++ b/apps/fg_http/mix.exs @@ -43,7 +43,6 @@ defmodule FgHttp.MixProject do {:phoenix_ecto, "~> 4.0"}, {:ecto_sql, "~> 3.1"}, {:ecto_enum, "~> 1.4.0"}, - {:ex_machina, "~> 2.4"}, {:ecto_network, "~> 1.3.0"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 2.11"}, diff --git a/apps/fg_http/priv/repo/migrations/20200510162435_create_sessions.exs b/apps/fg_http/priv/repo/migrations/20200510162435_create_sessions.exs index 59542d7f1..f1abef545 100644 --- a/apps/fg_http/priv/repo/migrations/20200510162435_create_sessions.exs +++ b/apps/fg_http/priv/repo/migrations/20200510162435_create_sessions.exs @@ -3,11 +3,13 @@ defmodule FgHttp.Repo.Migrations.CreateSessions do def change do create table(:sessions) do - add :user_id, references(:users, on_delete: :delete_all) + 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 diff --git a/apps/fg_http/priv/repo/seeds.exs b/apps/fg_http/priv/repo/seeds.exs index b0b35321c..5d47aeee2 100644 --- a/apps/fg_http/priv/repo/seeds.exs +++ b/apps/fg_http/priv/repo/seeds.exs @@ -10,7 +10,25 @@ # We recommend using the bang functions (`insert!`, `update!` # and so on) as they will fail if something goes wrong. -import FgHttp.Factory +alias FgHttp.{Devices, Rules, Users} -# Inserts needed associations -insert(:rule) +{:ok, user} = + Users.create_user(%{ + email: "factory@factory", + password: "factory", + password_confirmation: "factory" + }) + +{:ok, device} = + Devices.create_device(%{ + user_id: user.id, + name: "Factory Device", + public_key: "factory public key", + last_ip: %Postgrex.INET{address: {127, 0, 0, 1}} + }) + +{:ok, _rule} = + Rules.create_rule(%{ + device_id: device.id, + destination: %Postgrex.INET{address: {0, 0, 0, 0}, netmask: 0} + }) From e382ed8584cb9e44670bc348ed2e4e0a8e49d6b9 Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Tue, 19 May 2020 20:34:03 -0700 Subject: [PATCH 3/8] fix keys --- apps/fg_http/lib/fg_http_web/controllers/device_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b07295ebc..8d4f146d2 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 @@ -19,7 +19,7 @@ defmodule FgHttpWeb.DeviceController do end def create(conn, %{"device" => %{"public_key" => _public_key} = device_params}) do - our_params = %{user_id: conn.assigns.current_user.id, name: "Default"} + our_params = %{"user_id" => conn.assigns.current_user.id, "name" => "Default"} all_params = Map.merge(device_params, our_params) case Devices.create_device(all_params) do From 633a46637a34bcf3ea8e9da36b369c35a537e1c6 Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Tue, 19 May 2020 21:20:49 -0700 Subject: [PATCH 4/8] remove ex_machina traces --- .pre-commit-config.yaml | 5 +++++ apps/fg_http/test/test_helper.exs | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 428bf71ea..ac4ba8562 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,6 +2,11 @@ repos: # Elixir config - repo: local hooks: + - id: mix-test + name: 'elixir: mix test' + entry: mix test + language: system + files: \.ex*$ - id: mix-format name: 'elixir: mix format' entry: mix format --check-formatted diff --git a/apps/fg_http/test/test_helper.exs b/apps/fg_http/test/test_helper.exs index ee5a1ae26..ae2a237b9 100644 --- a/apps/fg_http/test/test_helper.exs +++ b/apps/fg_http/test/test_helper.exs @@ -1,3 +1,2 @@ -{:ok, _} = Application.ensure_all_started(:ex_machina) ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(FgHttp.Repo, :manual) From 677e224dad8c315548d3f8527a8fe7fd11d11b3b Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Tue, 19 May 2020 21:22:54 -0700 Subject: [PATCH 5/8] precommit regexp --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ac4ba8562..82cacd58b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: name: 'elixir: mix test' entry: mix test language: system - files: \.ex*$ + files: \.exs?$ - id: mix-format name: 'elixir: mix format' entry: mix format --check-formatted From 8ae298f57e690c907d3e5edd89e5c9c38fbb01b5 Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Tue, 19 May 2020 21:25:09 -0700 Subject: [PATCH 6/8] ahh fuck it --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82cacd58b..4d3e693ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: name: 'elixir: mix test' entry: mix test language: system - files: \.exs?$ + files: \.exs*$ - id: mix-format name: 'elixir: mix format' entry: mix format --check-formatted From 0b43156e21dab220c4f61ee6dd96b274600046c9 Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Tue, 19 May 2020 22:55:18 -0700 Subject: [PATCH 7/8] checkpoint --- .pre-commit-config.yaml | 5 --- apps/fg_http/lib/fg_http/devices/device.ex | 5 ++- apps/fg_http/lib/fg_http/users.ex | 4 ++ apps/fg_http/lib/fg_http/users/user.ex | 8 ++-- .../controllers/password_reset_controller.ex | 39 +++++++++++++++++++ .../controllers/session_controller.ex | 16 ++------ .../plugs/redirect_authenticated.ex | 21 ++++++++++ .../lib/fg_http_web/plugs/session_loader.ex | 2 +- apps/fg_http/mix.exs | 1 + .../20200225005454_create_users.exs | 2 + .../20200228145810_create_devices.exs | 6 ++- apps/fg_http/priv/repo/seeds.exs | 1 + .../controllers/device_controller_test.exs | 7 +++- mix.lock | 9 +++++ 14 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 apps/fg_http/lib/fg_http_web/controllers/password_reset_controller.ex create mode 100644 apps/fg_http/lib/fg_http_web/plugs/redirect_authenticated.ex diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d3e693ab..428bf71ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,11 +2,6 @@ repos: # Elixir config - repo: local hooks: - - id: mix-test - name: 'elixir: mix test' - entry: mix test - language: system - files: \.exs*$ - id: mix-format name: 'elixir: mix format' entry: mix format --check-formatted diff --git a/apps/fg_http/lib/fg_http/devices/device.ex b/apps/fg_http/lib/fg_http/devices/device.ex index 1625e53fe..178e64445 100644 --- a/apps/fg_http/lib/fg_http/devices/device.ex +++ b/apps/fg_http/lib/fg_http/devices/device.ex @@ -11,6 +11,7 @@ defmodule FgHttp.Devices.Device do schema "devices" do field :name, :string field :public_key, :string + field :ifname, :string field :last_ip, EctoNetwork.INET has_many :rules, Rule @@ -22,7 +23,7 @@ defmodule FgHttp.Devices.Device do @doc false def changeset(device, attrs) do device - |> cast(attrs, [:last_ip, :user_id, :name, :public_key]) - |> validate_required([:user_id, :name, :public_key]) + |> cast(attrs, [:last_ip, :ifname, :user_id, :name, :public_key]) + |> validate_required([:user_id, :ifname, :name, :public_key]) end end diff --git a/apps/fg_http/lib/fg_http/users.ex b/apps/fg_http/lib/fg_http/users.ex index 08f0a2b85..323dd66b3 100644 --- a/apps/fg_http/lib/fg_http/users.ex +++ b/apps/fg_http/lib/fg_http/users.ex @@ -39,6 +39,10 @@ defmodule FgHttp.Users do Repo.get_by!(User, email: email) end + def get_user!(reset_token: reset_token) do + Repo.get_by!(User, reset_token: reset_token) + end + def get_user!(id), do: Repo.get!(User, id) @doc """ diff --git a/apps/fg_http/lib/fg_http/users/user.ex b/apps/fg_http/lib/fg_http/users/user.ex index 897c2b6b0..b01f6f987 100644 --- a/apps/fg_http/lib/fg_http/users/user.ex +++ b/apps/fg_http/lib/fg_http/users/user.ex @@ -12,6 +12,7 @@ defmodule FgHttp.Users.User do field :email, :string field :confirmed_at, :utc_datetime field :reset_sent_at, :utc_datetime + field :reset_token, :string field :last_signed_in_at, :utc_datetime field :password_hash, :string @@ -59,12 +60,11 @@ defmodule FgHttp.Users.User do def update_changeset(user, %{user: %{email: _email}} = attrs) do user |> cast(attrs, [:email]) + |> validate_required([:email]) end - # Edit user - def changeset(user, attrs \\ %{}) do - user - |> cast(attrs, [:email]) + def changeset(%__MODULE__{} = _user, _attrs \\ %{}) do + change(%__MODULE__{}) end def authenticate_user(user, password_candidate) do diff --git a/apps/fg_http/lib/fg_http_web/controllers/password_reset_controller.ex b/apps/fg_http/lib/fg_http_web/controllers/password_reset_controller.ex new file mode 100644 index 000000000..f5531b7db --- /dev/null +++ b/apps/fg_http/lib/fg_http_web/controllers/password_reset_controller.ex @@ -0,0 +1,39 @@ +defmodule FgHttpWeb.PasswordResetController do + @moduledoc """ + Implements the CRUD for password resets + """ + + use FgHttpWeb, :controller + alias FgHttp.{Users, Users.User} + + plug FgHttpWeb.Plugs.RedirectAuthenticated + + def new(conn, _params) do + conn + |> render("new.html", changeset: User.changeset(%User{})) + end + + # Don't actually create anything. Instead, update the user with a reset token and send + # the password reset email. + def create(conn, %{ + "password_reset" => + %{ + reset_token: reset_token, + password: _password, + password_confirmation: _password_confirmation, + current_password: _current_password + } = user_params + }) do + user = Users.get_user!(reset_token: reset_token) + + case Users.update_user(user, user_params) do + {:ok, _user} -> + conn + |> render("success.html") + + {:error, changeset} -> + conn + |> render("new.html", changeset: changeset) + end + 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 197b4c391..9196d8046 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 @@ -6,7 +6,7 @@ defmodule FgHttpWeb.SessionController do alias FgHttp.{Sessions, Sessions.Session} use FgHttpWeb, :controller - plug :redirect_authenticated when action in [:new] + plug FgHttpWeb.Plugs.RedirectAuthenticated when action in [:new] plug FgHttpWeb.Plugs.SessionLoader when action in [:delete] # GET /sessions/new @@ -22,6 +22,9 @@ defmodule FgHttpWeb.SessionController 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_flash(:info, "Session created successfully") |> redirect(to: Routes.device_path(conn, :index)) @@ -46,15 +49,4 @@ defmodule FgHttpWeb.SessionController do |> redirect(to: "/") end end - - defp redirect_authenticated(conn, _) do - if Map.get(conn.assigns, :user_signed_in?) do - conn - |> redirect(to: "/") - |> halt() - else - conn - |> assign(:user_signed_in?, false) - end - end end diff --git a/apps/fg_http/lib/fg_http_web/plugs/redirect_authenticated.ex b/apps/fg_http/lib/fg_http_web/plugs/redirect_authenticated.ex new file mode 100644 index 000000000..ff99a1aba --- /dev/null +++ b/apps/fg_http/lib/fg_http_web/plugs/redirect_authenticated.ex @@ -0,0 +1,21 @@ +defmodule FgHttpWeb.Plugs.RedirectAuthenticated do + @moduledoc """ + Redirects users when he/she tries to access an open resource while authenticated. + """ + + import Plug.Conn + import Phoenix.Controller, only: [redirect: 2] + + def init(default), do: default + + def call(conn, _default) do + if get_session(conn, :session_id) do + conn + |> redirect(to: "/") + |> halt() + else + conn + |> assign(:user_signed_in?, false) + end + end +end 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 7abed5e24..b6966a2e5 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 @@ -11,7 +11,7 @@ defmodule FgHttpWeb.Plugs.SessionLoader do def init(default), do: default def call(conn, _default) do - case Sessions.load_session("blah session id") do + case Sessions.load_session(get_session(conn, :session_id)) do {:ok, {session, user}} -> conn |> assign(:current_session, session) diff --git a/apps/fg_http/mix.exs b/apps/fg_http/mix.exs index 9cb6a80df..59347a0f1 100644 --- a/apps/fg_http/mix.exs +++ b/apps/fg_http/mix.exs @@ -44,6 +44,7 @@ defmodule FgHttp.MixProject do {:ecto_sql, "~> 3.1"}, {:ecto_enum, "~> 1.4.0"}, {:ecto_network, "~> 1.3.0"}, + {:bamboo, "~> 1.5"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 2.11"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs b/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs index 93fb623e8..926607f58 100644 --- a/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs +++ b/apps/fg_http/priv/repo/migrations/20200225005454_create_users.exs @@ -8,10 +8,12 @@ defmodule FgHttp.Repo.Migrations.CreateUsers do add :password_hash, :string add :last_signed_in_at, :utc_datetime add :reset_sent_at, :utc_datetime + add :reset_token, :utc_datetime timestamps() end create unique_index(:users, [:email]) + create unique_index(:users, [:reset_token]) end end diff --git a/apps/fg_http/priv/repo/migrations/20200228145810_create_devices.exs b/apps/fg_http/priv/repo/migrations/20200228145810_create_devices.exs index 1b0c692d9..4dbff63c9 100644 --- a/apps/fg_http/priv/repo/migrations/20200228145810_create_devices.exs +++ b/apps/fg_http/priv/repo/migrations/20200228145810_create_devices.exs @@ -3,8 +3,9 @@ defmodule FgHttp.Repo.Migrations.CreateDevices do def change do create table(:devices) do - add :name, :string - add :public_key, :string + add :name, :string, null: false + add :public_key, :string, null: false + add :ifname, :string, null: false add :last_ip, :inet add :user_id, references(:users, on_delete: :delete_all), null: false @@ -12,5 +13,6 @@ defmodule FgHttp.Repo.Migrations.CreateDevices do end create index(:devices, [:user_id]) + create unique_index(:devices, [:public_key]) end end diff --git a/apps/fg_http/priv/repo/seeds.exs b/apps/fg_http/priv/repo/seeds.exs index 5d47aeee2..9b754179f 100644 --- a/apps/fg_http/priv/repo/seeds.exs +++ b/apps/fg_http/priv/repo/seeds.exs @@ -22,6 +22,7 @@ alias FgHttp.{Devices, Rules, Users} {:ok, device} = Devices.create_device(%{ user_id: user.id, + ifname: "wg0", name: "Factory Device", public_key: "factory public key", last_ip: %Postgrex.INET{address: {127, 0, 0, 1}} 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 100eed468..f29d160b0 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 @@ -4,7 +4,7 @@ defmodule FgHttpWeb.DeviceControllerTest do alias FgHttp.Devices alias FgHttp.Users - @create_attrs %{name: "some name"} + @create_attrs %{name: "some name", ifname: "wg0", public_key: "foobar"} @update_attrs %{name: "some updated name"} @invalid_attrs %{user_id: nil} @@ -43,6 +43,11 @@ defmodule FgHttpWeb.DeviceControllerTest do setup [:create_device] test "renders form for editing chosen device", %{conn: conn, device: device} do + conn = + conn + |> Plug.Conn.assign(:current_user, fixture(:user)) + |> Plug.Conn.assign(:current_session, fixture(:user)) + conn = get(conn, Routes.device_path(conn, :edit, device)) assert html_response(conn, 200) =~ "Edit Device" end diff --git a/mix.lock b/mix.lock index c981047c2..a58454716 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,8 @@ %{ "argon2_elixir": {:hex, :argon2_elixir, "2.3.0", "e251bdafd69308e8c1263e111600e6d68bd44f23d2cccbe43fcb1a417a76bc8e", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "28ccb63bff213aecec1f7f3dde9648418b031f822499973281d8f494b9d5a3b3"}, + "bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, @@ -16,8 +18,13 @@ "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, + "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "phoenix": {:hex, :phoenix, "1.5.1", "95156589879dc69201d5fc0ebdbfdfc7901a09a3616ea611ec297f81340275a2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc272b38e79d2881790fccae6f67a9fbe9b790103d6878175ea03d23003152eb"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, @@ -29,5 +36,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "postgrex": {:hex, :postgrex, "0.15.4", "5d691c25fc79070705a2ff0e35ce0822b86a0ee3c6fdb7a4fb354623955e1aed", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "306515b9d975fcb2478dc337a1d27dc3bf8af7cd71017c333fe9db3a3d211b0a"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, } From 88e1067816762f0df1eaa4dbe02e0219956bb672 Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Wed, 20 May 2020 22:41:32 -0700 Subject: [PATCH 8/8] wrap up users --- .../controllers/device_controller.ex | 7 ++- .../fg_http_web/templates/device/new.html.eex | 6 ++ .../lib/fg_http_web/views/device_view.ex | 13 +++++ .../controllers/device_controller_test.exs | 55 ++++++++----------- .../controllers/rule_controller_test.exs | 2 +- apps/fg_http/test/support/conn_case.ex | 24 +++++++- apps/fg_http/test/support/fixtures.ex | 28 ++++++++++ 7 files changed, 99 insertions(+), 36 deletions(-) create mode 100644 apps/fg_http/test/support/fixtures.ex 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 8d4f146d2..1bc4abb9b 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 @@ -19,7 +19,12 @@ defmodule FgHttpWeb.DeviceController do end def create(conn, %{"device" => %{"public_key" => _public_key} = device_params}) do - our_params = %{"user_id" => conn.assigns.current_user.id, "name" => "Default"} + our_params = %{ + "user_id" => conn.assigns.current_user.id, + "name" => "Default", + "ifname" => "wg0" + } + all_params = Map.merge(device_params, our_params) case Devices.create_device(all_params) do diff --git a/apps/fg_http/lib/fg_http_web/templates/device/new.html.eex b/apps/fg_http/lib/fg_http_web/templates/device/new.html.eex index b2ff5a818..a2366b5bb 100644 --- a/apps/fg_http/lib/fg_http_web/templates/device/new.html.eex +++ b/apps/fg_http/lib/fg_http_web/templates/device/new.html.eex @@ -1,5 +1,11 @@

New Device

+<%= if assigns[:changeset] do %> + The following errors occurred when creating this Device: + + <%= aggregated_errors(@changeset) %> +<% end %> + <%= live_render(@conn, FgHttpWeb.NewDeviceLive, session: %{"current_user_id" => @conn.assigns.current_user.id}) %>

diff --git a/apps/fg_http/lib/fg_http_web/views/device_view.ex b/apps/fg_http/lib/fg_http_web/views/device_view.ex index 2963163b4..c21ec491f 100644 --- a/apps/fg_http/lib/fg_http_web/views/device_view.ex +++ b/apps/fg_http/lib/fg_http_web/views/device_view.ex @@ -1,3 +1,16 @@ defmodule FgHttpWeb.DeviceView do use FgHttpWeb, :view + import Ecto.Changeset, only: [traverse_errors: 2] + + def aggregated_errors(changeset) do + traverse_errors(changeset, fn {msg, opts} -> + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", to_string(value)) + end) + end) + |> Enum.reduce("", fn {key, value}, acc -> + joined_errors = Enum.join(value, "; ") + "#{acc}#{key}: #{joined_errors}\n" + end) + end 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 f29d160b0..173cd25a2 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 @@ -1,53 +1,42 @@ defmodule FgHttpWeb.DeviceControllerTest do - use FgHttpWeb.ConnCase + use FgHttpWeb.ConnCase, async: true - alias FgHttp.Devices - alias FgHttp.Users + import FgHttp.Fixtures - @create_attrs %{name: "some name", ifname: "wg0", public_key: "foobar"} + @create_attrs %{public_key: "foobar"} @update_attrs %{name: "some updated name"} - @invalid_attrs %{user_id: nil} - - def fixture(:user) do - attrs = %{email: "test", password: "foobar", password_confirmation: "foobar"} - {:ok, user} = Users.create_user(attrs) - user - end - - def fixture(:device) do - {:ok, device} = Devices.create_device(Map.merge(%{user_id: fixture(:user).id}, @create_attrs)) - device - end + @invalid_attrs %{public_key: nil} describe "index" do - test "lists all devices", %{conn: conn} do - # Mock authentication - conn = Plug.Conn.assign(conn, :current_user, fixture(:user)) - + test "lists all devices", %{authed_conn: conn} do conn = get(conn, Routes.device_path(conn, :index)) assert html_response(conn, 200) =~ "Listing Devices" end end describe "new device" do - test "renders form", %{conn: conn} do - # Mock authentication - conn = Plug.Conn.assign(conn, :current_user, fixture(:user)) - + test "renders form", %{authed_conn: conn} do conn = get(conn, Routes.device_path(conn, :new)) assert html_response(conn, 200) =~ "New Device" end end + 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" + end + + test "renders errors when data is invalid", %{authed_conn: conn} do + conn = post(conn, Routes.device_path(conn, :create), device: @invalid_attrs) + assert html_response(conn, 200) =~ "public_key: can't be blank" + end + end + describe "edit device" do setup [:create_device] - test "renders form for editing chosen device", %{conn: conn, device: device} do - conn = - conn - |> Plug.Conn.assign(:current_user, fixture(:user)) - |> Plug.Conn.assign(:current_session, fixture(:user)) - + test "renders form for editing chosen device", %{authed_conn: conn, device: device} do conn = get(conn, Routes.device_path(conn, :edit, device)) assert html_response(conn, 200) =~ "Edit Device" end @@ -56,7 +45,7 @@ defmodule FgHttpWeb.DeviceControllerTest do describe "update device" do setup [:create_device] - test "redirects when data is valid", %{conn: conn, device: device} do + 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) @@ -64,7 +53,7 @@ defmodule FgHttpWeb.DeviceControllerTest do assert html_response(conn, 200) =~ "some updated name" end - test "renders errors when data is invalid", %{conn: conn, device: device} do + test "renders errors when data is invalid", %{authed_conn: conn, device: device} do conn = put(conn, Routes.device_path(conn, :update, device), device: @invalid_attrs) assert html_response(conn, 200) =~ "Edit Device" end @@ -73,7 +62,7 @@ defmodule FgHttpWeb.DeviceControllerTest do describe "delete device" do setup [:create_device] - test "deletes chosen device", %{conn: conn, device: device} do + 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) 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 ce6ae46d5..1c082df85 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,5 +1,5 @@ defmodule FgHttpWeb.RuleControllerTest do - use FgHttpWeb.ConnCase + use FgHttpWeb.ConnCase, async: true describe "index" do end diff --git a/apps/fg_http/test/support/conn_case.ex b/apps/fg_http/test/support/conn_case.ex index 68dffa4aa..d2a6af700 100644 --- a/apps/fg_http/test/support/conn_case.ex +++ b/apps/fg_http/test/support/conn_case.ex @@ -19,6 +19,8 @@ defmodule FgHttpWeb.ConnCase do alias Ecto.Adapters.SQL.Sandbox + import FgHttp.Fixtures + using do quote do # Import conveniences for testing with connections @@ -31,6 +33,26 @@ defmodule FgHttpWeb.ConnCase do end end + def new_conn do + Phoenix.ConnTest.build_conn() + end + + def authed_conn do + user = fixture(:user) + + session = + fixture(:session, %{ + user_id: user.id, + user_password: "test", + user_email: "test" + }) + + new_conn() + |> Plug.Conn.assign(:current_user, user) + |> Plug.Conn.assign(:current_session, session) + |> Plug.Conn.assign(:user_signed_in?, true) + end + setup tags do :ok = Sandbox.checkout(FgHttp.Repo) @@ -38,6 +60,6 @@ defmodule FgHttpWeb.ConnCase do Sandbox.mode(FgHttp.Repo, {:shared, self()}) end - {:ok, conn: Phoenix.ConnTest.build_conn()} + {:ok, unauthed_conn: new_conn(), authed_conn: authed_conn()} end end diff --git a/apps/fg_http/test/support/fixtures.ex b/apps/fg_http/test/support/fixtures.ex new file mode 100644 index 000000000..31f5122fc --- /dev/null +++ b/apps/fg_http/test/support/fixtures.ex @@ -0,0 +1,28 @@ +defmodule FgHttp.Fixtures do + @moduledoc """ + Convenience helpers for inserting records + """ + alias FgHttp.{Devices, Repo, Sessions, Users, Users.User} + + def fixture(:user) do + case Repo.get_by(User, email: "test") do + nil -> + attrs = %{email: "test", password: "test", password_confirmation: "test"} + {:ok, user} = Users.create_user(attrs) + user + + %User{} = user -> + user + end + end + + def fixture(:device) do + attrs = %{public_key: "foobar", ifname: "wg0", name: "factory"} + {:ok, device} = Devices.create_device(Map.merge(%{user_id: fixture(:user).id}, attrs)) + device + end + + def fixture(:session, attrs \\ %{}) do + {:ok, _session} = Sessions.create_session(attrs) + end +end