Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a423b721d | ||
|
|
06dfee5b57 | ||
|
|
aff4973ea8 | ||
|
|
c793b46e53 |
@@ -57,3 +57,11 @@
|
|||||||
Add tests for UserController, Accounts, Generators.
|
Add tests for UserController, Accounts, Generators.
|
||||||
** 0.8.0 <2023-08-08 Tue>
|
** 0.8.0 <2023-08-08 Tue>
|
||||||
Add the Web Authentication.
|
Add the Web Authentication.
|
||||||
|
** 0.8.1 <2023-08-09 Wed>
|
||||||
|
Add the authentication for API sing in.
|
||||||
|
** 0.8.2 <2023-08-10 Thu>
|
||||||
|
Add the auth pipeline for the Link API path.
|
||||||
|
** 0.8.3 <2023-08-11 Fri>
|
||||||
|
Add tests for user login.
|
||||||
|
** 0.8.4 <2023-08-12 Sat>
|
||||||
|
Add ErrorHandler for API Auth.
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
defmodule LinkShortenerWeb.Auth.ErrorHandler do
|
||||||
|
import Plug.Conn
|
||||||
|
|
||||||
|
def auth_error(conn, {type, _reason}, _opts) do
|
||||||
|
body = Poison.encode!(%{error: to_string(type)})
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(401, body)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,6 +2,7 @@ defmodule LinkShortenerWeb.Auth.Guardian do
|
|||||||
use Guardian, otp_app: :link_shortener
|
use Guardian, otp_app: :link_shortener
|
||||||
|
|
||||||
alias LinkShortener.Accounts
|
alias LinkShortener.Accounts
|
||||||
|
alias LinkShortener.Accounts.User
|
||||||
|
|
||||||
def subject_for_token(user, _claims) do
|
def subject_for_token(user, _claims) do
|
||||||
sub = to_string(user.id)
|
sub = to_string(user.id)
|
||||||
@@ -13,4 +14,18 @@ defmodule LinkShortenerWeb.Auth.Guardian do
|
|||||||
resource = Accounts.get_user!(id)
|
resource = Accounts.get_user!(id)
|
||||||
{:ok, resource}
|
{:ok, resource}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authenticate(email, password) do
|
||||||
|
with user <- Accounts.get_user_by_email_and_password(email, password) do
|
||||||
|
case user do
|
||||||
|
%User{} -> create_token(user)
|
||||||
|
nil -> {:error, :unauthorized}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_token(user) do
|
||||||
|
{:ok, token, _claims} = encode_and_sign(user)
|
||||||
|
{:ok, user, token}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
defmodule LinkShortenerWeb.Auth.Pipeline do
|
||||||
|
use Guardian.Plug.Pipeline, otp_app: :link_shortener,
|
||||||
|
module: LinkShortenerWeb.Auth.Guardian,
|
||||||
|
error_handler: LinkShortenerWeb.Auth.ErrorHandler
|
||||||
|
|
||||||
|
plug Guardian.Plug.VerifyHeader
|
||||||
|
plug Guardian.Plug.EnsureAuthenticated
|
||||||
|
plug Guardian.Plug.LoadResource
|
||||||
|
end
|
||||||
@@ -21,4 +21,10 @@ defmodule LinkShortenerWeb.FallbackController do
|
|||||||
|> put_view(LinkShortenerWeb.ErrorView)
|
|> put_view(LinkShortenerWeb.ErrorView)
|
||||||
|> render(:"404")
|
|> render(:"404")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def call(conn, {:error, :unauthorized}) do
|
||||||
|
conn
|
||||||
|
|> put_status(:unauthorized)
|
||||||
|
|> render(LinkShortenerWeb.ErrorView, :"401")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ defmodule LinkShortenerWeb.Router do
|
|||||||
plug :accepts, ["json"]
|
plug :accepts, ["json"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pipeline :auth do
|
||||||
|
plug LinkShortenerWeb.Auth.Pipeline
|
||||||
|
end
|
||||||
|
|
||||||
scope "/", LinkShortenerWeb do
|
scope "/", LinkShortenerWeb do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
@@ -29,12 +33,19 @@ defmodule LinkShortenerWeb.Router do
|
|||||||
pipe_through :api
|
pipe_through :api
|
||||||
|
|
||||||
scope "/v1", Api.V1, as: :v1 do
|
scope "/v1", Api.V1, as: :v1 do
|
||||||
resources "/links", LinkController
|
|
||||||
post "/users/signup", UserController, :create
|
post "/users/signup", UserController, :create
|
||||||
post "/users/signin", UserController, :signin
|
post "/users/signin", UserController, :signin
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/api", LinkShortenerWeb do
|
||||||
|
pipe_through [:api, :auth]
|
||||||
|
|
||||||
|
scope "/v1", Api.V1, as: :v1 do
|
||||||
|
resources "/links", LinkController
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Enables LiveDashboard only for development
|
# Enables LiveDashboard only for development
|
||||||
#
|
#
|
||||||
# If you want to use the LiveDashboard in production, you should put
|
# If you want to use the LiveDashboard in production, you should put
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ defmodule LinkShortener.MixProject do
|
|||||||
{:plug_cowboy, "~> 2.5"},
|
{:plug_cowboy, "~> 2.5"},
|
||||||
{:guardian, "~> 1.0"},
|
{:guardian, "~> 1.0"},
|
||||||
{:comeonin, "~> 5.3"},
|
{:comeonin, "~> 5.3"},
|
||||||
|
{:poison, "~> 5.0"},
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
||||||
|
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
|
||||||
"postgrex": {:hex, :postgrex, "0.17.1", "01c29fd1205940ee55f7addb8f1dc25618ca63a8817e56fac4f6846fc2cddcbe", [: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", "14b057b488e73be2beee508fb1955d8db90d6485c6466428fe9ccf1d6692a555"},
|
"postgrex": {:hex, :postgrex, "0.17.1", "01c29fd1205940ee55f7addb8f1dc25618ca63a8817e56fac4f6846fc2cddcbe", [: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", "14b057b488e73be2beee508fb1955d8db90d6485c6466428fe9ccf1d6692a555"},
|
||||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||||
"swoosh": {:hex, :swoosh, "1.11.2", "39dd1e44f75bc03a34366d5f830599d248de2b9caaf05704dc76c0507a58c6a1", [:mix], [{: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]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c43f4591503e7d5bf028314af8ac7c06d1c4d340aa23faeefabfa2543fa726e"},
|
"swoosh": {:hex, :swoosh, "1.11.2", "39dd1e44f75bc03a34366d5f830599d248de2b9caaf05704dc76c0507a58c6a1", [:mix], [{: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]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c43f4591503e7d5bf028314af8ac7c06d1c4d340aa23faeefabfa2543fa726e"},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ defmodule LinkShortenerWeb.Api.V1.LinkControllerTest do
|
|||||||
use LinkShortenerWeb.ConnCase
|
use LinkShortenerWeb.ConnCase
|
||||||
|
|
||||||
import LinkShortener.LinksFixtures
|
import LinkShortener.LinksFixtures
|
||||||
|
import LinkShortener.AccountsFixtures
|
||||||
|
|
||||||
alias LinkShortener.Links.Link
|
alias LinkShortener.Links.Link
|
||||||
alias LinkShortener.Links
|
alias LinkShortener.Links
|
||||||
@@ -23,7 +24,13 @@ defmodule LinkShortenerWeb.Api.V1.LinkControllerTest do
|
|||||||
}
|
}
|
||||||
|
|
||||||
setup %{conn: conn} 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
|
end
|
||||||
|
|
||||||
describe "index" do
|
describe "index" do
|
||||||
@@ -90,6 +97,11 @@ defmodule LinkShortenerWeb.Api.V1.LinkControllerTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp create_user_token() do
|
||||||
|
token = user_token_fixture()
|
||||||
|
%{token: token}
|
||||||
|
end
|
||||||
|
|
||||||
defp create_link(_) do
|
defp create_link(_) do
|
||||||
link = link_fixture()
|
link = link_fixture()
|
||||||
%{link: link}
|
%{link: link}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ defmodule LinkShortenerWeb.Api.V1.UserControllerTest do
|
|||||||
alias LinkShortener.Accounts.User
|
alias LinkShortener.Accounts.User
|
||||||
|
|
||||||
@create_attrs %{
|
@create_attrs %{
|
||||||
email: "some_email@mail.com",
|
email: "user@mail.com",
|
||||||
password: "some password"
|
password: "some password"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,6 +15,11 @@ defmodule LinkShortenerWeb.Api.V1.UserControllerTest do
|
|||||||
password: "some updated password"
|
password: "some updated password"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@invalid_password_attrs %{
|
||||||
|
email: "user@mail.com",
|
||||||
|
password: ""
|
||||||
|
}
|
||||||
|
|
||||||
@invalid_attrs %{
|
@invalid_attrs %{
|
||||||
email: nil,
|
email: nil,
|
||||||
encrypted_password: nil
|
encrypted_password: nil
|
||||||
@@ -24,11 +29,11 @@ defmodule LinkShortenerWeb.Api.V1.UserControllerTest do
|
|||||||
{:ok, conn: put_req_header(conn, "accept", "application/json")}
|
{:ok, conn: put_req_header(conn, "accept", "application/json")}
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "create user" do
|
describe "create user with sign up" do
|
||||||
test "renders user when data is valid", %{conn: conn} do
|
test "renders user when data is valid", %{conn: conn} do
|
||||||
conn = post(conn, Routes.v1_user_path(conn, :create), user: @create_attrs)
|
conn = post(conn, Routes.v1_user_path(conn, :create), user: @create_attrs)
|
||||||
assert %{
|
assert %{
|
||||||
"email" => "some_email@mail.com",
|
"email" => "user@mail.com",
|
||||||
"token" => token
|
"token" => token
|
||||||
} = json_response(conn, 201)
|
} = json_response(conn, 201)
|
||||||
end
|
end
|
||||||
@@ -39,8 +44,26 @@ defmodule LinkShortenerWeb.Api.V1.UserControllerTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "user sign in" do
|
||||||
|
setup [:create_user]
|
||||||
|
|
||||||
|
test "renders user when data is valid", %{conn: conn} do
|
||||||
|
conn = post(conn, Routes.v1_user_path(conn, :signin), @create_attrs)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"email" => email,
|
||||||
|
"token" => token,
|
||||||
|
} = json_response(conn, 201)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn} do
|
||||||
|
conn = post(conn, Routes.v1_user_path(conn, :signin), @invalid_password_attrs)
|
||||||
|
assert "Unauthorized" == json_response(conn, 401)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp create_user(_) do
|
defp create_user(_) do
|
||||||
user = user_fixture()
|
user = user_fixture(@create_attrs)
|
||||||
%{user: user}
|
%{user: user}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ defmodule LinkShortener.AccountsFixtures do
|
|||||||
entities via the `LinkShortener.Accounts` context.
|
entities via the `LinkShortener.Accounts` context.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alias LinkShortener.Accounts
|
||||||
|
alias LinkShortener.Accounts.User
|
||||||
|
alias LinkShortenerWeb.Auth.Guardian
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Generate a unique user email.
|
Generate a unique user email.
|
||||||
"""
|
"""
|
||||||
@@ -26,6 +30,18 @@ defmodule LinkShortener.AccountsFixtures do
|
|||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_token_fixture(attrs \\ %{}) do
|
||||||
|
user_params = %{
|
||||||
|
email: "user@mail.com",
|
||||||
|
password: "some password"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, %User{} = user} = Accounts.register_user(user_params)
|
||||||
|
{:ok, token, _claims} = Guardian.encode_and_sign(user)
|
||||||
|
|
||||||
|
token
|
||||||
|
end
|
||||||
|
|
||||||
def extract_user_token(fun) do
|
def extract_user_token(fun) do
|
||||||
{:ok, captured_email} = fun.(&"[TOKEN]#{&1}[TOKEN]")
|
{:ok, captured_email} = fun.(&"[TOKEN]#{&1}[TOKEN]")
|
||||||
[_, token | _] = String.split(captured_email.text_body, "[TOKEN]")
|
[_, token | _] = String.split(captured_email.text_body, "[TOKEN]")
|
||||||
|
|||||||
Reference in New Issue
Block a user