lib/ash_authentication/token_resource/get_token_preparation.ex

defmodule AshAuthentication.TokenResource.GetTokenPreparation do
  @moduledoc """
  Constrains a query to only records which match the `jti` or `token` argument
  and optionally by the `purpose` argument.
  """

  use Ash.Resource.Preparation
  alias Ash.{Error.Query.InvalidArgument, Query, Resource.Preparation}
  alias AshAuthentication.Jwt
  require Ash.Query

  @doc false
  @impl true
  @spec prepare(Query.t(), keyword, Preparation.context()) :: Query.t()
  def prepare(query, _, _) do
    jti = get_jti(query)
    purpose = Query.get_argument(query, :purpose)

    query
    |> Query.filter(jti: jti)
    |> then(fn query ->
      if purpose, do: Query.filter(query, purpose: purpose), else: query
    end)
    |> Query.filter(expires_at > now())
  end

  defp get_jti(query),
    do: get_jti(Query.get_argument(query, :jti), Query.get_argument(query, :token))

  defp get_jti(jti, _token) when byte_size(jti) > 0, do: jti

  defp get_jti(_jti, token) when byte_size(token) > 0 do
    token
    |> Jwt.peek()
    |> case do
      {:ok, %{"jti" => jti}} -> jti
      _ -> get_jti(nil, nil)
    end
  end

  defp get_jti(_jti, _token),
    do:
      raise(
        InvalidArgument.exception(
          field: :jti,
          message: "At least one of `jti` or `token` arguments must be present"
        )
      )
end