From 780652241f74525d7fc21c968b9ca584a3927e30 Mon Sep 17 00:00:00 2001 From: KKlochko Date: Sat, 31 Aug 2024 14:47:45 +0300 Subject: [PATCH] Add the logout for the API. --- .../controllers/api/v1/accounts_controller.ex | 8 ++++ .../controllers/api/v1/accounts_json.ex | 8 +++- lib/link_shortener_web/router.ex | 12 ++++++ mix.exs | 2 + mix.lock | 2 + .../api/v1/accounts_controller_test.exs | 39 +++++++++++++++++++ .../api/v1/link_controller_test.exs | 14 ++++++- test/support/factories/UserFactory.ex | 10 +++++ test/support/fixtures/accounts_fixtures.ex | 6 +-- 9 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 test/support/factories/UserFactory.ex diff --git a/lib/link_shortener_web/controllers/api/v1/accounts_controller.ex b/lib/link_shortener_web/controllers/api/v1/accounts_controller.ex index 0f7db44..589829b 100644 --- a/lib/link_shortener_web/controllers/api/v1/accounts_controller.ex +++ b/lib/link_shortener_web/controllers/api/v1/accounts_controller.ex @@ -23,4 +23,12 @@ defmodule LinkShortenerWeb.Api.V1.AccountsController do |> render(:user, %{user: user, token: token}) end end + + def sign_out(conn, %{}) do + token = Guardian.Plug.current_token(conn) + Guardian.revoke(token) + conn + |> put_status(:ok) + |> render(:sign_out, %{token: token}) + end end diff --git a/lib/link_shortener_web/controllers/api/v1/accounts_json.ex b/lib/link_shortener_web/controllers/api/v1/accounts_json.ex index db8320d..4c9d57c 100644 --- a/lib/link_shortener_web/controllers/api/v1/accounts_json.ex +++ b/lib/link_shortener_web/controllers/api/v1/accounts_json.ex @@ -3,9 +3,15 @@ defmodule LinkShortenerWeb.Api.V1.AccountsJSON do def user(%{user: user, token: token}) do %{ - id: user.id, email: user.email, token: token } end + + def sign_out(%{token: token}) do + %{ + message: "Successfully sign out", + token: token + } + end end diff --git a/lib/link_shortener_web/router.ex b/lib/link_shortener_web/router.ex index 935471f..727558f 100644 --- a/lib/link_shortener_web/router.ex +++ b/lib/link_shortener_web/router.ex @@ -17,6 +17,10 @@ defmodule LinkShortenerWeb.Router do plug :accepts, ["json"] end + pipeline :auth do + plug LinkShortenerWeb.Auth.Pipeline + end + scope "/", LinkShortenerWeb do pipe_through :browser @@ -30,6 +34,14 @@ defmodule LinkShortenerWeb.Router do scope "/v1", Api.V1, as: :v1 do post "/users/sign_up", AccountsController, :sign_up post "/users/sign_in", AccountsController, :sign_in + end + end + + scope "/api", LinkShortenerWeb do + pipe_through [:api, :auth] + + scope "/v1", Api.V1, as: :v1 do + post "/users/sign_out", AccountsController, :sign_out resources "/links", LinkController end diff --git a/mix.exs b/mix.exs index a07af9f..5ec1243 100644 --- a/mix.exs +++ b/mix.exs @@ -62,6 +62,8 @@ defmodule LinkShortener.MixProject do {:bandit, "~> 1.5"}, {:guardian, "~> 2.3"}, {:guardian_db, "~> 3.0"}, + {:poison, "~> 5.0"}, + {:ex_machina, "~> 2.8.0", only: :test}, ] end diff --git a/mix.lock b/mix.lock index 0f339e0..3428ae2 100644 --- a/mix.lock +++ b/mix.lock @@ -10,6 +10,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, + "ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [: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", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"}, "expo": {:hex, :expo, "1.0.0", "647639267e088717232f4d4451526e7a9de31a3402af7fcbda09b27e9a10395a", [:mix], [], "hexpm", "18d2093d344d97678e8a331ca0391e85d29816f9664a25653fd7e6166827827c"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, @@ -35,6 +36,7 @@ "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, "swoosh": {:hex, :swoosh, "1.16.12", "cbb24ad512f2f7f24c7a469661c188a00a8c2cd64e0ab54acd1520f132092dfd", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e262df1ae510d59eeaaa3db42189a2aa1b3746f73771eb2616fc3f7ee63cc20"}, "tailwind": {:hex, :tailwind, "0.2.3", "277f08145d407de49650d0a4685dc062174bdd1ae7731c5f1da86163a24dfcdb", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "8e45e7a34a676a7747d04f7913a96c770c85e6be810a1d7f91e713d3a3655b5d"}, diff --git a/test/link_shortener_web/controllers/api/v1/accounts_controller_test.exs b/test/link_shortener_web/controllers/api/v1/accounts_controller_test.exs index f355af6..846bf52 100644 --- a/test/link_shortener_web/controllers/api/v1/accounts_controller_test.exs +++ b/test/link_shortener_web/controllers/api/v1/accounts_controller_test.exs @@ -64,6 +64,45 @@ defmodule LinkShortenerWeb.Api.V1.AccountsControllerTest do end end + describe "user signs out" do + setup [:create_user] + + setup %{conn: conn} do + %{token: token} = create_user_token() + + conn = conn + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer #{token}") + + {:ok, conn: conn, token: token} + end + + test "renders the message and the token if successfully sign out", %{conn: conn, token: token} do + conn = post(conn, ~p"/api/v1/users/sign_out", %{}) + + assert %{ + "message" => "Successfully sign out", + "token" => token, + } = json_response(conn, 200) + end + + test "renders errors if the token is invalid after revoke", %{conn: conn, token: token} do + # revoking + conn = post(conn, ~p"/api/v1/users/sign_out", %{}) + # second revoking + conn = post(conn, ~p"/api/v1/users/sign_out", %{}) + + assert %{ + "error" => "invalid_token" + } = json_response(conn, 401) + end + end + + defp create_user_token() do + token = user_token_fixture() + %{token: token} + end + defp create_user(_) do user = user_fixture(@create_attrs) %{user: user} diff --git a/test/link_shortener_web/controllers/api/v1/link_controller_test.exs b/test/link_shortener_web/controllers/api/v1/link_controller_test.exs index ebdc6f9..2ae728a 100644 --- a/test/link_shortener_web/controllers/api/v1/link_controller_test.exs +++ b/test/link_shortener_web/controllers/api/v1/link_controller_test.exs @@ -2,6 +2,7 @@ defmodule LinkShortenerWeb.Api.V1.LinkControllerTest do use LinkShortenerWeb.ConnCase import LinkShortener.LinksFixtures + import LinkShortener.AccountsFixtures alias LinkShortener.Links.Link alias LinkShortener.Links @@ -25,7 +26,13 @@ defmodule LinkShortenerWeb.Api.V1.LinkControllerTest do } setup %{conn: conn} do - {:ok, conn: put_req_header(conn, "accept", "application/json")} + %{token: token} = create_user_token() + + conn = conn + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "Bearer #{token}") + + {:ok, conn: conn} end describe "index" do @@ -92,6 +99,11 @@ defmodule LinkShortenerWeb.Api.V1.LinkControllerTest do end end + defp create_user_token() do + token = user_token_fixture() + %{token: token} + end + defp create_link(_) do link = link_fixture() %{link: link} diff --git a/test/support/factories/UserFactory.ex b/test/support/factories/UserFactory.ex new file mode 100644 index 0000000..340458c --- /dev/null +++ b/test/support/factories/UserFactory.ex @@ -0,0 +1,10 @@ +defmodule LinkShortener.Factories.UserFactory do + use ExMachina + + def user_factory do + %{ + email: sequence(:email, &"user-#{&1}@mail.com"), + password: "some password" + } + end +end diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex index 96bc2e7..d5988a8 100644 --- a/test/support/fixtures/accounts_fixtures.ex +++ b/test/support/fixtures/accounts_fixtures.ex @@ -7,6 +7,7 @@ defmodule LinkShortener.AccountsFixtures do alias LinkShortener.Accounts alias LinkShortener.Accounts.User alias LinkShortenerWeb.Auth.Guardian + alias LinkShortener.Factories.UserFactory @doc """ Generate a unique user email. @@ -31,10 +32,7 @@ defmodule LinkShortener.AccountsFixtures do end def user_token_fixture(attrs \\ %{}) do - user_params = %{ - email: "user@mail.com", - password: "some password" - } + user_params = UserFactory.user_factory() {:ok, %User{} = user} = Accounts.register_user(user_params) {:ok, token, _claims} = Guardian.encode_and_sign(user)