From 07111af2a19f18893f0a8a3ef1f423afaf33e59b Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Mon, 18 May 2020 20:47:05 -0700 Subject: [PATCH] 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"},