From 2b3ad0b217c57272eb27227ac17b67073a8cf998 Mon Sep 17 00:00:00 2001 From: Kevin ANATOLE Date: Sat, 18 Feb 2023 18:13:08 +0000 Subject: [PATCH] hopefully done --- config/config.exs | 5 ++ .../auth/error_response.ex | 4 ++ lib/phoenix_api_template_web/auth/guardian.ex | 46 +++++++++++++++++-- .../controllers/user_controller.ex | 45 ++++++++++++++++-- lib/phoenix_api_template_web/router.ex | 2 + lib/phoenix_api_template_web/telemetry.ex | 3 +- mix.exs | 3 +- mix.lock | 1 + .../migrations/20230218175046_guardiandb.exs | 17 +++++++ 9 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 priv/repo/migrations/20230218175046_guardiandb.exs diff --git a/config/config.exs b/config/config.exs index 5f63129..925498f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -28,6 +28,11 @@ config :phoenix_api_template, PhoenixApiTemplateWeb.Auth.Guardian, issuer: "phoenix_api_template", secret_key: "" +config :guardian, Guardian.DB, + repo: PhoenixApiTemplate.Repo, + schema_name: "guardian_tokens", + sweep_interval: 60 + # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason diff --git a/lib/phoenix_api_template_web/auth/error_response.ex b/lib/phoenix_api_template_web/auth/error_response.ex index 847c0e3..d687804 100644 --- a/lib/phoenix_api_template_web/auth/error_response.ex +++ b/lib/phoenix_api_template_web/auth/error_response.ex @@ -5,3 +5,7 @@ end defmodule PhoenixApiTemplateWeb.Auth.ErrorResponse.Forbidden do defexception message: "Forbidden", plug_status: 403 end + +defmodule PhoenixApiTemplateWeb.Auth.ErrorResponse.NotFound do + defexception message: "Not Found", plug_status: 404 +end diff --git a/lib/phoenix_api_template_web/auth/guardian.ex b/lib/phoenix_api_template_web/auth/guardian.ex index 5850a8d..017ac74 100644 --- a/lib/phoenix_api_template_web/auth/guardian.ex +++ b/lib/phoenix_api_template_web/auth/guardian.ex @@ -29,18 +29,58 @@ defmodule PhoenixApiTemplateWeb.Auth.Guardian do user -> case validate_password(password, user.hashed_password) do - true -> create_token(user) + true -> create_token(user, :access) false -> {:error, :unauthorized} end end end + def authenticate(token) do + with {:ok, claims} <- decode_and_verify(token), + {:ok, user} <- resource_from_claims(claims), + {:ok, _old, {new_token, _claims}} <- refresh(token) do + {:ok, user, new_token} + 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) + defp create_token(user, type) do + {:ok, token, _claims} = encode_and_sign(user, %{}, token_options(type)) {:ok, user, token} end + + defp token_options(type) do + case type do + :access -> [token_type: "access", ttl: {2, :hour}] + :reset -> [token_type: "reset", ttl: {15, :minute}] + :admin -> [token_type: "admin", ttl: {90, :day}] + end + end + + def after_encode_and_sign(resource, claims, token, _options) do + with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do + {:ok, token} + end + end + + def on_verify(claims, token, _options) do + with {:ok, _} <- Guardian.DB.on_verify(claims, token) do + {:ok, claims} + end + end + + def on_revoke(claims, token, _options) do + with {:ok, _} <- Guardian.DB.on_revoke(claims, token) do + {:ok, claims} + end + end + + def on_refresh({old_token, old_claims}, {new_token, new_claims}, _options) do + with {:ok, _, _} <- Guardian.DB.on_refresh({old_token, old_claims}, {new_token, new_claims}) do + {:ok, {old_token, old_claims}, {new_token, new_claims}} + end + end end diff --git a/lib/phoenix_api_template_web/controllers/user_controller.ex b/lib/phoenix_api_template_web/controllers/user_controller.ex index 9e6552e..dd6448f 100644 --- a/lib/phoenix_api_template_web/controllers/user_controller.ex +++ b/lib/phoenix_api_template_web/controllers/user_controller.ex @@ -1,8 +1,9 @@ defmodule PhoenixApiTemplateWeb.UserController do use PhoenixApiTemplateWeb, :controller - alias PhoenixApiTemplateWeb.Auth.ErrorResponse alias PhoenixApiTemplateWeb.Auth.ErrorResponse.Unauthorized + alias PhoenixApiTemplateWeb.Auth.ErrorResponse.Forbidden + alias PhoenixApiTemplateWeb.Auth.ErrorResponse.NotFound alias PhoenixApiTemplateWeb.Auth.Guardian alias PhoenixApiTemplate.Accounts alias PhoenixApiTemplate.Accounts.User @@ -20,7 +21,7 @@ defmodule PhoenixApiTemplateWeb.UserController do if conn.assigns.user.id == user.id do conn else - raise ErrorResponse.Forbidden + raise Forbidden end end @@ -40,7 +41,11 @@ defmodule PhoenixApiTemplateWeb.UserController do end def sign_in(conn, %{"email" => email, "password" => password}) do - case Guardian.authenticate(email, password) do + authorize_account(conn, email, password) + end + + defp authorize_account(conn, email, hash_password) do + case Guardian.authenticate(email, hash_password) do {:ok, user, token} -> conn |> Plug.Conn.put_session(:user_id, user.id) @@ -52,6 +57,17 @@ defmodule PhoenixApiTemplateWeb.UserController do end end + def sign_out(conn, %{}) do + user = conn.assigns[:user] + token = Guardian.Plug.current_token(conn) + Guardian.revoke(token) + + conn + |> Plug.Conn.clear_session() + |> put_status(:ok) + |> render("user_token.json", %{user: user, token: nil}) + end + def show(conn, %{"id" => id}) do user = Accounts.get_user!(id) render(conn, "show.json", user: user) @@ -72,4 +88,27 @@ defmodule PhoenixApiTemplateWeb.UserController do send_resp(conn, :no_content, "") end end + + def refresh_session(conn, %{}) do + old_token = Guardian.Plug.current_token(conn) + + case Guardian.decode_and_verify(old_token) do + {:ok, claims} -> + case Guardian.resource_from_claims(claims) do + {:ok, user} -> + {:ok, _old, {new_token, _new_claims}} = Guardian.refresh(old_token) + + conn + |> Plug.Conn.put_session(:user_id, user.id) + |> put_status(:ok) + |> render("user_token.json", %{user: user, token: new_token}) + + {:error, _reason} -> + raise NotFound + end + + {:error, _reason} -> + raise NotFound + end + end end diff --git a/lib/phoenix_api_template_web/router.ex b/lib/phoenix_api_template_web/router.ex index 4477da4..23daff9 100644 --- a/lib/phoenix_api_template_web/router.ex +++ b/lib/phoenix_api_template_web/router.ex @@ -45,5 +45,7 @@ defmodule PhoenixApiTemplateWeb.Router do get "/users/by_id/:id", UserController, :show put "/users/:id", UserController, :update + get "/user/refresh_session", UserController, :refresh_session + get "/sign_out", UserController, :sign_out end end diff --git a/lib/phoenix_api_template_web/telemetry.ex b/lib/phoenix_api_template_web/telemetry.ex index 5bf203b..7c5687d 100644 --- a/lib/phoenix_api_template_web/telemetry.ex +++ b/lib/phoenix_api_template_web/telemetry.ex @@ -11,7 +11,8 @@ defmodule PhoenixApiTemplateWeb.Telemetry do children = [ # Telemetry poller will execute the given period measurements # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics - {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}, + {Guardian.DB.Token.SweeperServer, []} # Add reporters as children of your supervision tree. # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} ] diff --git a/mix.exs b/mix.exs index 0eaaa7f..5d388f9 100644 --- a/mix.exs +++ b/mix.exs @@ -44,7 +44,8 @@ defmodule PhoenixApiTemplate.MixProject do {:plug_cowboy, "~> 2.5"}, {:guardian, "~> 2.0"}, {:bcrypt_elixir, "~> 3.0"}, - {:envar, "~> 1.1.0"} + {:envar, "~> 1.1.0"}, + {:guardian_db, "~> 2.0"} ] end diff --git a/mix.lock b/mix.lock index 239463c..617fcae 100644 --- a/mix.lock +++ b/mix.lock @@ -15,6 +15,7 @@ "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"}, + "guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"}, "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"}, diff --git a/priv/repo/migrations/20230218175046_guardiandb.exs b/priv/repo/migrations/20230218175046_guardiandb.exs new file mode 100644 index 0000000..3bb2e91 --- /dev/null +++ b/priv/repo/migrations/20230218175046_guardiandb.exs @@ -0,0 +1,17 @@ +defmodule PhoenixApiTemplate.Repo.Migrations.CreateGuardianDBTokensTable do + use Ecto.Migration + + def change do + create table(:guardian_tokens, primary_key: false) do + add(:jti, :string, primary_key: true) + add(:aud, :string, primary_key: true) + add(:typ, :string) + add(:iss, :string) + add(:sub, :string) + add(:exp, :bigint) + add(:jwt, :text) + add(:claims, :map) + timestamps() + end + end +end