From 0d2d460189473bdd2d7135e43801d43a338303ff Mon Sep 17 00:00:00 2001 From: Kevin ANATOLE Date: Fri, 17 Feb 2023 10:31:47 +0000 Subject: [PATCH] Installed guardian create authentication/password verification method --- .gitignore | 1 + config/config.exs | 5 ++ config/runtime.exs | 8 ++++ lib/phoenix_api_template/accounts.ex | 19 ++++++++ lib/phoenix_api_template/accounts/user.ex | 11 ++++- lib/phoenix_api_template_web/auth/guardian.ex | 46 +++++++++++++++++++ mix.exs | 5 +- mix.lock | 6 +++ 8 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 lib/phoenix_api_template_web/auth/guardian.ex diff --git a/.gitignore b/.gitignore index 8899cd5..b46eda2 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,4 @@ fabric.properties /config/*.secret.exs .elixir_ls/ +.env \ No newline at end of file diff --git a/config/config.exs b/config/config.exs index 493485d..5f63129 100644 --- a/config/config.exs +++ b/config/config.exs @@ -23,6 +23,11 @@ config :logger, :console, format: "$time $metadata[$level] $message\n", metadata: [:request_id] +# Guardian config +config :phoenix_api_template, PhoenixApiTemplateWeb.Auth.Guardian, + issuer: "phoenix_api_template", + secret_key: "" + # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason diff --git a/config/runtime.exs b/config/runtime.exs index a844a56..f326a7b 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -1,5 +1,7 @@ import Config +Envar.load(".env") + # config/runtime.exs is executed for all environments, including # during releases. It is executed after compilation and before the # system starts, so it is typically used to load production configuration @@ -63,3 +65,9 @@ if config_env() == :prod do ], secret_key_base: secret_key_base end + +guardian_secret_key = System.get_env("GUARDIAN_SECRET_KEY") + +config :phoenix_api_template, PhoenixApiTemplateWeb.Auth.Guardian, + issuer: "phoenix_api_template", + secret_key: guardian_secret_key diff --git a/lib/phoenix_api_template/accounts.ex b/lib/phoenix_api_template/accounts.ex index 624c5de..403ee9d 100644 --- a/lib/phoenix_api_template/accounts.ex +++ b/lib/phoenix_api_template/accounts.ex @@ -37,6 +37,25 @@ defmodule PhoenixApiTemplate.Accounts do """ def get_user!(id), do: Repo.get!(User, id) + @doc """ + Gets a single user by email + + Returns `nil` if the account does not exist + + ## Examples + + iex> get_user_by_email(test@email.com) + %User{} + + iex> get_user_by_email(nope@email.com) + nil + """ + def get_user_by_email(email) do + User + |> where(email: ^email) + |> Repo.one() + end + @doc """ Creates a user. diff --git a/lib/phoenix_api_template/accounts/user.ex b/lib/phoenix_api_template/accounts/user.ex index c14489a..324cdee 100644 --- a/lib/phoenix_api_template/accounts/user.ex +++ b/lib/phoenix_api_template/accounts/user.ex @@ -17,8 +17,17 @@ defmodule PhoenixApiTemplate.Accounts.User do user |> cast(attrs, [:email, :hashed_password]) |> validate_required([:email, :hashed_password]) - |> unique_constraint(:email) |> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces") |> validate_length(:email, max: 250) + |> unique_constraint(:email) + |> put_password_hash() end + + defp put_password_hash( + %Ecto.Changeset{valid?: true, changes: %{hashed_password: hashed_password}} = changeset + ) do + change(changeset, hashed_password: Bcrypt.hash_pwd_salt(hashed_password)) + end + + defp put_password_hash(changeset), do: changeset end diff --git a/lib/phoenix_api_template_web/auth/guardian.ex b/lib/phoenix_api_template_web/auth/guardian.ex new file mode 100644 index 0000000..5850a8d --- /dev/null +++ b/lib/phoenix_api_template_web/auth/guardian.ex @@ -0,0 +1,46 @@ +defmodule PhoenixApiTemplateWeb.Auth.Guardian do + use Guardian, otp_app: :phoenix_api_template + alias PhoenixApiTemplate.Accounts + + def subject_for_token(%{id: id}, _claims) do + sub = to_string(id) + {:ok, sub} + end + + def subject_for_token(_, _) do + {:error, :no_id_provided} + end + + def resource_from_claims(%{"sub" => id}) do + case Accounts.get_user!(id) do + nil -> {:error, :not_found} + resource -> {:ok, resource} + end + end + + def resource_from_claims(_claims) do + {:error, :no_id_provided} + end + + def authenticate(email, password) do + case Accounts.get_user_by_email(email) do + nil -> + {:error, :unauthorized} + + user -> + case validate_password(password, user.hashed_password) do + true -> create_token(user) + false -> {:error, :unauthorized} + end + end + end + + defp validate_password(password, hashed_password) do + Bcrypt.verify_pass(password, hashed_password) + end + + defp create_token(user) do + {:ok, token, _claims} = encode_and_sign(user) + {:ok, user, token} + end +end diff --git a/mix.exs b/mix.exs index f6e9976..0eaaa7f 100644 --- a/mix.exs +++ b/mix.exs @@ -41,7 +41,10 @@ defmodule PhoenixApiTemplate.MixProject do {:telemetry_poller, "~> 1.0"}, {:gettext, "~> 0.18"}, {:jason, "~> 1.2"}, - {:plug_cowboy, "~> 2.5"} + {:plug_cowboy, "~> 2.5"}, + {:guardian, "~> 2.0"}, + {:bcrypt_elixir, "~> 3.0"}, + {:envar, "~> 1.1.0"} ] end diff --git a/mix.lock b/mix.lock index e544c40..239463c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{ + "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"}, "castore": {:hex, :castore, "1.0.0", "c25cd0794c054ebe6908a86820c8b92b5695814479ec95eeff35192720b71eec", [:mix], [], "hexpm", "577d0e855983a97ca1dfa33cbb8a3b6ece6767397ffb4861514343b078fc284b"}, + "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, @@ -8,9 +10,13 @@ "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, + "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, + "envar": {:hex, :envar, "1.1.0", "105bcac5a03800a1eb21e2c7e229edc687359b0cc184150ec1380db5928c115c", [:mix], [], "hexpm", "97028ab4a040a5c19e613fdf46a41cf51c6e848d99077e525b338e21d2993320"}, "expo": {:hex, :expo, "0.3.0", "13127c1d5f653b2927f2616a4c9ace5ae372efd67c7c2693b87fd0fdc30c6feb", [:mix], [], "hexpm", "fb3cd4bf012a77bc1608915497dae2ff684a06f0fa633c7afa90c4d72b881823"}, "gettext": {:hex, :gettext, "0.22.0", "a25d71ec21b1848957d9207b81fd61cb25161688d282d58bdafef74c2270bdc4", [:mix], [{:expo, "~> 0.3.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "cb0675141576f73720c8e49b4f0fd3f2c69f0cd8c218202724d4aebab8c70ace"}, + "guardian": {:hex, :guardian, "2.3.1", "2b2d78dc399a7df182d739ddc0e566d88723299bfac20be36255e2d052fd215d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbe241f9ca1b09fad916ad42d6049d2600bbc688aba5b3c4a6c82592a54274c3"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"}, "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, "phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},