Implement tests for all token actions

This commit is contained in:
Nat 2023-06-09 10:03:11 -07:00
parent 89eea2d4c1
commit 64e9edec5a
Signed by: nat
GPG Key ID: B53AB05285D710D6
5 changed files with 134 additions and 15 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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