Current section

Files

Jump to
indieweb lib plug inject_credentials.ex
Raw

lib/plug/inject_credentials.ex

defmodule IndieWeb.Plug.InjectCredentials do
import Plug.Conn
require Logger
def init(opts) do
opts
end
def call(conn, _opts) do
with(
auth_token when is_binary(auth_token) and auth_token != "" <-
do_extract_from_request(conn),
me_url when is_binary(me_url) <- find_me_from_token(auth_token),
endpoint when is_binary(endpoint) <-
IndieWeb.Auth.endpoint_for(:token, me_url),
{:ok, %{"scope" => scope}} <-
IndieWeb.Clients.IndieAuth.verify_token(endpoint, auth_token)
) do
Logger.debug("Found a user and their accompanying token.",
indieauth_user: me_url
)
conn
|> put_private(:indieauth_me, me_url)
|> put_private(:indieauth_access_token, auth_token)
|> put_private(:indieauth_scope, scope)
else
nil ->
Logger.warn("The request was not authorized with IndieAuth information.")
# TODO: Make into a more OAuth2-y error.
conn
|> put_resp_content_type("application/json")
|> Explode.unauthorized()
{:error, error} ->
Logger.warn("Token provided but no user found.",
error: error
)
# TODO: Make into a more OAuth2-y error.
conn
|> put_resp_content_type("application/json")
|> Explode.bad_request()
{:error, name, data} ->
Logger.warn("Unexpected error.", error: name, data: inspect(data))
# TODO: Make into a more OAuth2-y error.
conn
|> put_resp_content_type("application/json")
|> Explode.bad_request()
end
end
defp do_extract_from_request(conn) do
[
conn
|> fetch_query_params()
|> Map.get(:params)
|> Map.get("access_token", nil),
conn
|> get_req_header("authorization")
|> List.first()
|> List.wrap()
|> List.first()
|> String.split(" ", parts: 2)
|> List.last()
]
|> Enum.reject(&is_nil/1)
|> Enum.reject(&(&1 == ""))
|> List.first()
end
@spec add(binary(), binary()) ::
{:ok, me: binary(), token: binary()}
| {:error, :failed_to_set_token}
def add(me, token) do
hashed_token = do_generate_hash(token)
Logger.info("Generated hashed token for user.",
me: me,
hashed_token: hashed_token
)
with(
:ok <- IndieWeb.Cache.set("indieauth:proxied_token:#{hashed_token}", me),
:ok <- IndieWeb.Cache.set("indieauth:proxied_me:#{me}", token)
) do
Logger.info("Added user with their provided token to the cache.", me: me)
{:ok, me: me, token: token}
else
error ->
Logger.error("Failed to insert user with their token.",
me: me,
error: inspect(error)
)
{:error, :failed_to_set_token}
end
end
@spec remove(binary()) :: :ok
def remove(token) do
hashed_token = do_generate_hash(token)
me = find_me_from_token(hashed_token)
IndieWeb.Cache.delete("indieauth:proxied_token:#{hashed_token}")
IndieWeb.Cache.delete("indieauth:proxied_me:#{me}")
Logger.info("Removed token and user from cache.", hashed_token: hashed_token)
:ok
end
@doc "Finds the user's URL from the provided IndieAuth token."
@spec find_me_from_token(binary()) :: binary() | nil
def find_me_from_token(token) do
hashed_token = do_generate_hash(token)
case IndieWeb.Cache.get("indieauth:proxied_token:#{hashed_token}") do
{:ok, me} when is_binary(me) ->
me
_ ->
nil
end
end
@doc "Finds the user's IndieAuth token from the provided me."
@spec find_token_from_me(binary()) :: binary() | nil
def find_token_from_me(me) do
case IndieWeb.Cache.get("indieauth:proxied_me:#{me}") do
{:ok, token} when is_binary(token) ->
token
_ ->
nil
end
end
defp do_generate_hash(token) do
:crypto.hash(:sha512, token)
|> Base.decode64(padding: false)
end
end