Current section

Files

Jump to
indieweb lib indieweb auth.ex
Raw

lib/indieweb/auth.ex

defmodule IndieWeb.Auth do
@moduledoc """
Provides logic for handling [IndieAuth](https://indieauth.spec.indieweb.org) interactions.
"""
@doc "Provides endpoint information for well known endpoints in IndieAuth."
@spec endpoint_for(atom(), binary()) :: binary() | nil
def endpoint_for(type, uri)
def endpoint_for(component, url) when component in ~w(authorization token)a do
IndieWeb.LinkRel.find(url, "#{component}_endpoint") |> List.first()
end
def endpoint_for(:redirect_uri, url),
do: IndieWeb.LinkRel.find(url, "redirect_uri")
def endpoint_for(_, _), do: nil
@doc """
Generates an authentication URL.
Depending on the particular `response_type`, this will generate a URL
that can be used to sign in a user.
"""
@spec authenticate(map(), keyword()) :: {:ok, any()} | {:error, any()}
def authenticate(params, options \\ [])
def authenticate(%{"response_type" => "id"} = params, options) do
case do_validate_request(params, ~w(client_id redirect_uri state me), options) do
{:error, _} = error ->
error
{:ok,
%{
"client_id" => client_id,
"redirect_uri" => redirect_uri,
"state" => state
}} ->
code = IndieWeb.Auth.Code.generate(client_id, redirect_uri, %{}, options)
do_generate_redirect_uri(redirect_uri, code, state)
end
end
def authenticate(%{"response_type" => "code"} = params, options) do
case do_validate_request(params, ~w(client_id redirect_uri state me), options) do
{:error, _} = error ->
error
{:ok,
%{
"client_id" => client_id,
"redirect_uri" => redirect_uri,
"state" => state
} = args} ->
scope = Map.get(args, "scope", "read")
code =
IndieWeb.Auth.Code.generate(
client_id,
redirect_uri,
%{
"scope" => scope
},
options
)
do_generate_redirect_uri(redirect_uri, code, state)
end
end
def authenticate(_, _), do: {:error, :unrecognized_authorization_request}
def supported?(url) do
~w(authorization token)a
|> Enum.map(&endpoint_for(&1, url))
|> Enum.all?(&is_binary/1)
end
defp do_generate_redirect_uri(redirect_uri, code, state) do
query =
redirect_uri
|> URI.parse()
|> Map.get(:query)
|> (&(URI.decode_query(&1 || "", %{"code" => code, "state" => state})
|> URI.encode_query())).()
redirect_uri
|> URI.parse()
|> Map.put(:query, query)
|> URI.to_string()
end
defp do_validate_request(params, expected_keys, options) do
proper_args = Map.take(params, expected_keys)
missing_keys = expected_keys -- Map.keys(params)
cond do
!Enum.empty?(missing_keys) ->
{:error, :missing_required_keys, keys: missing_keys}
!user_adapter(options).valid_user?(params["me"]) ->
{:error, :invalid_user}
true ->
{:ok, proper_args}
end
end
defp user_adapter(options) do
user_adapter_module =
options
|> Keyword.get(:adapters, [])
|> Keyword.get(:user)
case user_adapter_module do
nil -> raise "No user adapter was provided for the IndieWeb library."
_ -> user_adapter_module
end
end
end