Current section

Files

Jump to
indieweb lib indieweb auth code.ex
Raw

lib/indieweb/auth/code.ex

defmodule IndieWeb.Auth.Code do
@moduledoc "Handles authentication codes for the IndieAuth flow."
@default_code_age 10 * 60
defmodule Adapter do
@moduledoc "Provides an abstraction regarding code actions for IndieAuth."
@callback generate() :: binary()
@callback generate_challenge(options :: keyword()) :: {binary(), binary(), binary()}
@callback persist(
code :: binary(),
client_id :: binary(),
redirect_uri :: binary(),
args :: map(),
options :: keyword()
) :: :ok | {:error, any()}
@callback persist_challenge(
code :: binary(),
challenge :: binary(),
method :: binary(),
options :: keyword()
) :: :ok | {:error, any()}
@callback destroy(
code :: binary(),
options :: keyword()
) :: :ok
@callback verify(
code :: binary(),
client_id :: binary(),
redirect_uri :: binary(),
options :: keyword()
) :: {:ok, map()} | {:error, any()}
@callback verify_challenge(
verifier :: binary(),
code :: binary(),
options :: keyword()
) :: :ok | {:error, any()}
end
@doc "Generates a code for IndieAuth based on the client_id, redirect_uri and any other extra parameters."
@spec generate(keyword()) :: binary()
def generate(options \\ []) do
adapter(options).generate()
end
@doc """
Stores a code for later verification.
Provided a `code`, a client's ID `client_id`, the URL that this should redirect
to `redirect_uri` as well as any arguments used to craft this authorization request in `params`, store said code and references to for later verification.
"""
@spec persist(binary(), binary(), binary(), map(), keyword()) :: :ok
def persist(code, client_id, redirect_uri, params \\ %{}, options \\ []) do
adapter(options).persist(code, client_id, redirect_uri, params, options)
end
@doc """
Verifies a code with the provided fields.
Confirms that a code `code` was created for the provided client's ID `client_id`,
the URL that this should redirect to `redirect_uri` as well as any arguments used
to craft this authorization request in `params`.
"""
@spec verify(binary(), binary(), binary(), keyword()) :: {:ok, map()} | {:error, any()}
def verify(code, client_id, redirect_uri, options \\ []) do
adapter(options).verify(code, client_id, redirect_uri, options)
end
@doc "Destroys all codes associated with the provided parameters."
@spec destroy(binary(), keyword()) :: :ok
def destroy(code, options \\ []) do
adapter(options).destroy(code, options)
end
@doc "Generates a PKCE-compatible challenge string for the provided parameters."
@spec generate_challenge(keyword()) :: {binary(), binary(), binary()}
def generate_challenge(options \\ []) do
adapter(options).generate_challenge(options)
end
@doc "Persists the provided challenge and method for the provided code."
@spec persist_challenge(binary(), binary(), binary(), keyword()) :: :ok | {:error, any()}
def persist_challenge(code, challenge, method, options \\ []) do
adapter(options).persist_challenge(code, challenge, method, options)
end
@doc "Provides the age, in seconds, of a code."
@spec age(keyword()) :: non_neg_integer()
def age(options \\ []) do
Keyword.get(options, :code_age, @default_code_age)
end
@spec verify_challenge(binary(), binary(), keyword()) ::
{:ok, code: binary()} | {:error, any()}
def verify_challenge(verifier, code, options \\ []) do
adapter(options).verify_challenge(verifier, code, options)
end
defp adapter(options) do
options
|> Keyword.get(:adapters, [])
|> Keyword.get(
:code,
IndieWeb.Auth.DefaultCodeAdapter
)
end
end