Implement internal token interface

This commit is contained in:
Nat 2023-06-12 16:28:47 -07:00
parent 64e9edec5a
commit 8524e90ac8
Signed by: nat
GPG Key ID: B53AB05285D710D6
2 changed files with 98 additions and 4 deletions

View File

@ -1,6 +1,7 @@
defmodule Hostas.Token do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query, only: [from: 2]
alias Hostas.Token
alias Hostas.Repo
@ -11,12 +12,11 @@ defmodule Hostas.Token do
@duration_days 30
schema "tokens" do
field :denizen_id, :integer
field :expires, :utc_datetime
field :token, :string
# Link tokens to denizens
belongs_to :denizens, Hostas.Denizen
belongs_to :denizen, Hostas.Denizen
timestamps()
end
@ -24,8 +24,8 @@ defmodule Hostas.Token do
@doc false
def changeset(token, attrs) do
token
|> cast(attrs, [:denizen_id, :expires])
|> validate_required([:denizen_id, :expires])
|> cast(attrs, [:denizen_id, :expires, :token])
|> validate_required([:denizen_id, :expires, :token])
end
@doc """
@ -46,4 +46,41 @@ defmodule Hostas.Token do
# Register the token
Repo.insert(changeset(%Token{}, %{denizen_id: denizen_id, token: token, expires: expiry}))
end
@doc """
Returns {:ok, struct} if the token hasn't expired. Otherwise, returns :expired
"""
def get(key) do
struct = Repo.one!(from t in Token, where: t.token == ^key)
unless expired?(struct) do
{:ok, struct}
else
:expired
end
end
@doc """
Returns true if the given token has expired
"""
def expired?(%Token{expires: expiry}) do
{:ok, now} = DateTime.now("Etc/UTC")
case DateTime.compare(expiry, now) do
:lt -> true
_ -> false
end
end
def expired?(token) when is_binary(token) do
expired?(Repo.one(from t in Token, where: t.token == ^token))
end
@doc """
Deletes a token. Does nothing if the token doesn't exist
"""
def revoke(%Token{token: token}), do: revoke(token)
def revoke(token) when is_binary(token) do
Repo.delete_all(from t in Token, where: t.token == ^token)
:ok
end
end

View File

@ -0,0 +1,57 @@
defmodule Hostas.TokenTest do
use Hostas.DataCase
# For testing with Ecto
import Ecto.Query, only: [from: 2]
alias Hostas.Repo
alias Hostas.Token
describe "token generation" do
test "succeeds" do
{:ok, struct} = Token.new(create_denizen().id)
assert Repo.one!(from t in Token, where: t.id == ^struct.id)
end
end
describe "check token expiry" do
test "by struct" do
{:ok, struct} = Token.new(create_denizen().id, -30)
assert Token.expired?(struct)
end
test "by key" do
{:ok, struct} = Token.new(create_denizen().id, -30)
assert Token.expired?(struct.token)
end
end
describe "fetch guarded against expiry" do
test "succeeds" do
{:ok, struct} = Token.new(create_denizen().id)
assert Token.get(struct.token) == {:ok, struct}
end
test "indicates the token is expired" do
{:ok, struct} = Token.new(create_denizen().id, -30)
assert Token.get(struct.token) == :expired
end
test "by key" do
{:ok, struct} = Token.new(create_denizen().id, -30)
assert Token.expired?(struct.token)
end
end
describe "revoke token" do
test "succeeds, given struct" do
{:ok, struct} = Token.new(create_denizen().id)
assert Token.revoke(struct) == :ok
assert Repo.one(from t in Token, where: t.token == ^struct.token) == nil
end
test "succeeds, given key" do
{:ok, struct} = Token.new(create_denizen().id)
assert Token.revoke(struct.token) == :ok
end
end
end