analytics/lib/plausible/plugins/api/tokens.ex

75 lines
2.2 KiB
Elixir

defmodule Plausible.Plugins.API.Tokens do
@moduledoc """
Context module for Plugins API Tokens.
Exposes high-level operation for token-based authentication flows.
"""
alias Plausible.Plugins.API.Token
alias Plausible.Site
alias Plausible.Repo
import Ecto.Query
@spec create(Site.t(), String.t()) ::
{:ok, Token.t(), String.t()} | {:error, Ecto.Changeset.t()}
def create(%Site{} = site, description, generated_token \\ Token.generate()) do
with changeset <- Token.insert_changeset(site, generated_token, %{description: description}),
{:ok, saved_token} <- Repo.insert(changeset) do
{:ok, saved_token, generated_token.raw}
end
end
@spec find(String.t()) :: {:ok, Token.t()} | {:error, :not_found}
def find(raw) do
found =
Repo.one(
from(t in Token,
inner_join: s in Site,
on: s.id == t.site_id,
where: t.token_hash == ^Token.hash(raw),
preload: [:site]
)
)
if found do
{:ok, found}
else
{:error, :not_found}
end
end
@spec delete(Site.t(), String.t()) :: :ok
def delete(site, token_id) do
Repo.delete_all(from(t in Token, where: t.site_id == ^site.id and t.id == ^token_id))
:ok
end
@spec list(Site.t()) :: {:ok, [Token.t()]}
def list(site) do
Repo.all(
from(t in Token, where: t.site_id == ^site.id, order_by: [desc: t.inserted_at, desc: t.id])
)
end
@spec any?(Site.t()) :: boolean()
def any?(site) do
Repo.exists?(from(t in Token, where: t.site_id == ^site.id))
end
@spec update_last_seen(Token.t(), NaiveDateTime.t()) :: {:ok, Token.t()}
def update_last_seen(token, now \\ NaiveDateTime.utc_now()) do
# we don't need very precise timestamp tracking, and to spare postgres we only
# update that timestamp in 5m windows - this is mostly to help users reason
# about what token they have, in case of rotations
now = NaiveDateTime.truncate(now, :second)
last_used = token.last_used_at
if is_nil(last_used) or NaiveDateTime.diff(now, last_used, :minute) > 5 do
token
|> Ecto.Changeset.change(%{last_used_at: now})
|> Repo.update()
else
{:ok, token}
end
end
end