Current section

Files

Jump to
indieweb lib indieweb auth token.ex
Raw

lib/indieweb/auth/token.ex

defmodule IndieWeb.Auth.Token do
@moduledoc "Manages the token lifecycle for IndieAuth."
alias IndieWeb.{Auth.Code, Auth.Scope, Auth.Scope, Http, Auth.DefaultTokenAdapter}
defmodule Adapter do
@moduledoc "Provides an abstraction regarding code actions for IndieAuth."
@callback generate(client_id :: binary(), scope :: binary(), options :: keyword()) :: binary()
@callback info(token :: binary(), options :: keyword()) :: nil | {:error, any()} | map()
@callback delete(token :: binary(), options :: keyword()) :: :ok
end
@spec generate(binary(), binary(), binary(), keyword()) :: {:ok, binary()} | {:error, atom(), any()}
def generate(code, client_id, redirect_uri, options \\ []) do
with(
scope when is_list(scope) and scope != [] <- Scope.get(code, options),
scope_str <- Scope.to_string(scope),
:ok <-
(fn ->
case Code.verify(code, client_id, redirect_uri, options) do
{:ok, %{"scope" => ^scope_str}} -> :ok
{:ok, %{"scope" => scope}} -> {:error, {:scope_mismatch, scope: scope}}
end
end).()
) do
Code.destroy(code, options)
adapter(options).generate(client_id, scope_str)
else
nil -> {:error, :token_generation_failure, reason: :missing_scope}
{:error, reason} -> {:error, :token_generation_failure, reason: reason}
end
end
def delete(token, options \\ []) do
adapter(options).delete(token, options)
end
@spec info_for(binary(), keyword()) :: nil | map() | {:error, any()}
def info_for(token, options \\ []) do
adapter(options).info(token, options)
end
@doc "Verifies if the provided token is valid."
@spec verify(binary(), binary()) :: {:ok, map()} | {:error, any()}
def verify(endpoint, token) do
case Http.get(endpoint,
headers: [
{"Authorization", "Bearer #{token}"},
{"Accept", "application/json"}
]
) do
{:ok, %Http.Response{body: body, code: 200}} -> Jason.decode(body)
{:ok, %Http.Response{} = _resp} -> {:error, :unauthorized}
{:error, _} = error -> error
end
end
@doc "Revokes the provided token against the endpoint."
@spec revoke(binary(), binary()) :: :ok | :revocation_failed
def revoke(endpoint, token) do
case Http.post_encoded(endpoint,
headers: [{"Accept", "application/json"}],
body: %{action: :revoke, token: token}
) do
{:ok, %Http.Response{code: 200}} -> :ok
_ -> :revocation_failed
end
end
defp adapter(options) do
options
|> Keyword.get(:adapters, [])
|> Keyword.get(:token, DefaultTokenAdapter)
end
end