Implement tests for all token actions
This commit is contained in:
parent
89eea2d4c1
commit
64e9edec5a
|
@ -27,5 +27,6 @@ defmodule Hostas.Denizen do
|
||||||
%{password_hash: hash} = Bcrypt.add_hash(password)
|
%{password_hash: hash} = Bcrypt.add_hash(password)
|
||||||
change(changeset, password: hash)
|
change(changeset, password: hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp hash_password(changeset), do: changeset
|
defp hash_password(changeset), do: changeset
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,15 +29,18 @@ defmodule Hostas.Token do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a token
|
Creates a token for denizen with the id `denizen_id` that expires in
|
||||||
|
`duration` days.
|
||||||
"""
|
"""
|
||||||
def new(denizen_id) do
|
def new(denizen_id, duration \\ @duration_days) do
|
||||||
# Create a random token
|
# Create a random token
|
||||||
token = Base.encode64(:crypto.strong_rand_bytes(256))
|
token = Base.encode64(:crypto.strong_rand_bytes(256))
|
||||||
|
|
||||||
# Calculate the time of expiry
|
# Calculate the time of expiry
|
||||||
{:ok, time_now} = DateTime.now("Etc/UTC")
|
{:ok, time_now} = DateTime.now("Etc/UTC")
|
||||||
expiry = DateTime.add(time_now, @duration_days, :day)
|
|
||||||
|
expiry =
|
||||||
|
DateTime.add(time_now, duration, :day)
|
||||||
|> DateTime.truncate(:second)
|
|> DateTime.truncate(:second)
|
||||||
|
|
||||||
# Register the token
|
# Register the token
|
||||||
|
|
|
@ -11,9 +11,11 @@ defmodule HostasWeb.Auth.TokenController do
|
||||||
provides the correct password
|
provides the correct password
|
||||||
"""
|
"""
|
||||||
def create(conn, %{"handle" => handle, "password" => given_password}) do
|
def create(conn, %{"handle" => handle, "password" => given_password}) do
|
||||||
case Repo.one(from d in Denizen,
|
case Repo.one(
|
||||||
|
from d in Denizen,
|
||||||
where: d.handle == ^handle,
|
where: d.handle == ^handle,
|
||||||
select: %{id: d.id, password: d.password}) do
|
select: %{id: d.id, password: d.password}
|
||||||
|
) do
|
||||||
nil ->
|
nil ->
|
||||||
conn
|
conn
|
||||||
|> put_status(404)
|
|> put_status(404)
|
||||||
|
|
|
@ -26,8 +26,8 @@ defmodule HostasWeb.Router do
|
||||||
# create, verify, renew, revoke
|
# create, verify, renew, revoke
|
||||||
post "/auth/token", Auth.TokenController, :create
|
post "/auth/token", Auth.TokenController, :create
|
||||||
get "/auth/token", Auth.TokenController, :verify
|
get "/auth/token", Auth.TokenController, :verify
|
||||||
delete "/auth/token", Auth.TokenController, :revoke
|
delete "/auth/token/:id", Auth.TokenController, :revoke
|
||||||
get "/auth/token/renew", Auth.TokenController, :renew
|
get "/auth/token/:id/renew", Auth.TokenController, :renew
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||||
|
|
|
@ -2,19 +2,23 @@ defmodule HostasWeb.Auth.TokenControllerTest do
|
||||||
use HostasWeb.ConnCase
|
use HostasWeb.ConnCase
|
||||||
|
|
||||||
# For testing with Ecto
|
# For testing with Ecto
|
||||||
|
import Ecto.Query, only: [from: 2]
|
||||||
alias Hostas.Repo
|
alias Hostas.Repo
|
||||||
|
|
||||||
alias Hostas.Denizen
|
alias Hostas.Denizen
|
||||||
|
alias Hostas.Token
|
||||||
|
|
||||||
@denizen_data %{handle: "testuser", name: "Test User", password: "password"}
|
defp create_denizen(handle \\ "denizen") do
|
||||||
|
Repo.insert!(
|
||||||
defp create_denizen, do: Repo.insert!(Denizen.changeset(%Denizen{}, @denizen_data))
|
Denizen.changeset(%Denizen{}, %{handle: handle, name: "Test Denizen", password: "password"})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
describe "token create" do
|
describe "token create" do
|
||||||
test "creates a token", %{conn: conn} do
|
test "creates a token", %{conn: conn} do
|
||||||
create_denizen()
|
create_denizen()
|
||||||
|
|
||||||
conn = post(conn, ~p"/hostapi/auth/token", %{handle: "testuser", password: "password"})
|
conn = post(conn, ~p"/hostapi/auth/token", %{handle: "denizen", password: "password"})
|
||||||
assert Map.has_key?(json_response(conn, 201), "token")
|
assert Map.has_key?(json_response(conn, 201), "token")
|
||||||
assert Map.has_key?(json_response(conn, 201), "expires")
|
assert Map.has_key?(json_response(conn, 201), "expires")
|
||||||
end
|
end
|
||||||
|
@ -22,12 +26,12 @@ defmodule HostasWeb.Auth.TokenControllerTest do
|
||||||
test "fails due to password mismatch", %{conn: conn} do
|
test "fails due to password mismatch", %{conn: conn} do
|
||||||
create_denizen()
|
create_denizen()
|
||||||
|
|
||||||
conn = post(conn, ~p"/hostapi/auth/token", %{handle: "testuser", password: "incorrect"})
|
conn = post(conn, ~p"/hostapi/auth/token", %{handle: "denizen", password: "incorrect"})
|
||||||
assert json_response(conn, 401)["error"] == "Password mismatch"
|
assert json_response(conn, 401)["error"] == "Password mismatch"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "fails due to non-existant denizen", %{conn: conn} do
|
test "fails due to non-existant denizen", %{conn: conn} do
|
||||||
conn = post(conn, ~p"/hostapi/auth/token", %{handle: "testuser", password: "password"})
|
conn = post(conn, ~p"/hostapi/auth/token", %{handle: "denizen", password: "password"})
|
||||||
assert json_response(conn, 404)["error"] == "No user with handle testuser"
|
assert json_response(conn, 404)["error"] == "No user with handle testuser"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -36,4 +40,113 @@ defmodule HostasWeb.Auth.TokenControllerTest do
|
||||||
assert json_response(conn, 422)["error"] == "Missing required parameters"
|
assert json_response(conn, 422)["error"] == "Missing required parameters"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "token verification" do
|
||||||
|
test "succeeds", %{conn: conn} do
|
||||||
|
{:ok, struct} = Token.new(create_denizen().id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer #{struct.token}")
|
||||||
|
|> get(~p"/hostapi/auth/token")
|
||||||
|
|
||||||
|
assert Map.has_key?(json_response(conn, 200), "expires")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails because of expiry", %{conn: conn} do
|
||||||
|
{:ok, struct} = Token.new(create_denizen().id, -10)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer #{struct.token}")
|
||||||
|
|> get(~p"/hostapi/auth/token")
|
||||||
|
|
||||||
|
assert json_response(conn, 401)["error"] == "Token expired"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails because of unrecognized credentials", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer unknown_credential")
|
||||||
|
|> get(~p"/hostapi/auth/token")
|
||||||
|
|
||||||
|
assert json_response(conn, 200)["error"] == "Token expired"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "token revocation" do
|
||||||
|
test "succeeds", %{conn: conn} do
|
||||||
|
{:ok, struct} = Token.new(create_denizen().id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer #{struct.token}")
|
||||||
|
|> delete(~p"/hostapi/auth/token/#{struct.id}")
|
||||||
|
|
||||||
|
assert json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails because it's someone else's token", %{conn: conn} do
|
||||||
|
{:ok, struct1} = Token.new(create_denizen("denizen1").id)
|
||||||
|
{:ok, struct2} = Token.new(create_denizen("denizen2").id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer #{struct1.token}")
|
||||||
|
|> delete(~p"/hostapi/auth/token/#{struct2.id}")
|
||||||
|
|
||||||
|
assert json_response(conn, 404)["error"] == "Token not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails because the token doesn't exist", %{conn: conn} do
|
||||||
|
{:ok, struct} = Token.new(create_denizen().id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer #{struct.token}")
|
||||||
|
|> delete(~p"/hostapi/auth/token/#{struct.id + 1}")
|
||||||
|
|
||||||
|
assert json_response(conn, 404)["error"] == "Token not found"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "token renewal" do
|
||||||
|
test "succeeds", %{conn: conn} do
|
||||||
|
{:ok, struct} = Token.new(create_denizen().id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer #{struct.token}")
|
||||||
|
|> get(~p"/hostapi/auth/token/${struct.id}/renew")
|
||||||
|
|
||||||
|
assert Map.has_key?(json_response(conn, 200), "token")
|
||||||
|
assert Map.has_key?(json_response(conn, 200), "expires")
|
||||||
|
assert not Repo.exists?(from t in Token, where: t.id == ^struct.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails because it's not the user's token", %{conn: conn} do
|
||||||
|
{:ok, struct1} = Token.new(create_denizen("denizen1").id)
|
||||||
|
{:ok, struct2} = Token.new(create_denizen("denizen2").id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer #{struct1.token}")
|
||||||
|
|> get(~p"/hostapi/auth/token/#{struct2.id}/renew")
|
||||||
|
|
||||||
|
assert json_response(conn, 404)["error"] == "Token not found"
|
||||||
|
assert Repo.exists?(from t in Token, where: t.id == ^struct2.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails because it doesn't exist", %{conn: conn} do
|
||||||
|
{:ok, struct} = Token.new(create_denizen().id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("authorization", "Bearer #{struct.token}")
|
||||||
|
|> get(~p"/hostapi/auth/token/#{struct.id + 1}/renew")
|
||||||
|
|
||||||
|
assert json_response(conn, 404)["error"] == "Token not found"
|
||||||
|
assert Repo.exists?(from t in Token, where: t.id == ^struct.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue