plug_indie/lib/token.ex

127 lines
3.7 KiB
Elixir

defmodule PlugIndie.Token do
require Logger
def verify(
access_token,
token_endpoint,
required_scope,
supported_scopes,
own_hostname,
user_agent
) do
case do_verify_token(access_token, token_endpoint, user_agent) do
{:ok, %{status: 200, body: body}} ->
body
|> map_keys_to_string()
|> verify_token_response(required_scope, supported_scopes, own_hostname)
{:ok, %{status: status}} ->
{:error, :request_error, status}
{:error, %{code: code}} ->
Logger.error("Token endpoint responded with unexpected code: #{inspect(code)}")
{:error, :request_error, code}
{:error, %{reason: reason}} ->
Logger.error("Could not reach token endpoint: #{inspect(reason)}")
{:error, :request_error, reason}
error ->
Logger.error("Unexpected error: #{inspect(error)}")
{:error, :request_error, "Internal Server Error"}
end
end
defp do_verify_token(access_token, token_endpoint, user_agent) do
client =
Tesla.client([
Tesla.Middleware.JSON,
{Tesla.Middleware.Headers,
[
{"User-Agent", user_agent},
{"Authorization", "Bearer #{access_token}"},
{"Accept", "application/json"}
]}
])
Tesla.get(client, token_endpoint)
end
def verify_token_response(
%{
"me" => host_uri,
"scope" => scope,
"client_id" => client_id,
"issued_at" => _issued_at,
"issued_by" => _issued_by,
"nonce" => _nonce
},
required_scope,
supported_scopes,
own_hostname
) do
# {%{
# "client_id" => "https://indiepass.app/",
# "issued_at" => 1_733_382_601,
# "issued_by" => "https://tokens.indieauth.com/token",
# "me" => "https://blog.inhji.de/",
# "nonce" => 358_618_865,
# "scope" => "create update delete media read follow channels mute block"
# }, "create", ["create", "media"], "inhji.de"}
Logger.info("Host-URI: '#{host_uri}'")
Logger.info("ClientId: '#{client_id}'")
Logger.info("Scopes: '#{scope}'")
with :ok <- verify_hostname_match(host_uri, own_hostname),
:ok <- verify_scope_support(scope, required_scope, supported_scopes) do
:ok
else
{:error, name, reason} ->
Logger.error("Could not verify token response: #{reason}")
{:error, name, reason}
end
end
def verify_token_response(_, _, _, _), do: {:error, "verify_token_response", "bad request"}
defp verify_hostname_match(host_uri, own_hostname) do
hostnames_match? = get_hostname(host_uri) == own_hostname
case hostnames_match? do
true ->
:ok
_ ->
Logger.warning("Hostnames do not match: Given #{host_uri}, Actual: #{own_hostname}")
{:error, "verify_hostname_match", "hostname does not match"}
end
end
defp get_hostname(host_uri) do
host_uri |> URI.parse() |> Map.get(:host)
end
defp verify_scope_support(_scopes, nil, _supported_scopes), do: :ok
defp verify_scope_support(scopes, required_scope, supported_scopes)
when not is_nil(required_scope) do
required = Enum.member?(supported_scopes, required_scope)
requested = Enum.member?(String.split(scopes), required_scope)
cond do
required && requested ->
:ok
!required ->
{:error, "verify_scope_support", "scope '#{required_scope}' is not supported"}
!requested ->
{:error, "verify_scope_support", "scope '#{required_scope}' was not requested"}
end
end
defp map_keys_to_string(map) do
for {key, val} <- map, into: %{}, do: {to_string(key), val}
end
end