defmodule Rivet.Auth.Graphql do
@moduledoc """
Helpers for Graphql resolvers.
"""
import Rivet.Graphql
alias Rivet.Auth
alias Rivet.Ident
alias Absinthe.Resolution
##############################################################################
@doc """
Handles extracting the current user from the Absinthe context, and then will
either call `func` and pass in the current user or return an error if there is
no user in the context
"""
def current_user(%Resolution{context: %{user: %{} = u}}),
do: {:ok, u}
def current_user(_), do: {:error, :authn}
def current_user(%Resolution{context: %{user: %{} = user}}, method) do
if not is_nil(method) do
graphql_log(method)
end
{:ok, user}
end
def current_user(_, method), do: graphql_error(method, :authn)
########################################################################
@doc """
Accept authorization from a user.
Punt to authz_action(), but log graphql bits.
"""
def authz_user(
context,
assertion \\ %Auth.Assertion{action: :system_admin},
method \\ nil,
kwlog \\ []
)
def authz_user(info, assertion, method, kwlog) do
with {:ok, user} <- Rivet.Auth.check_authz(info, assertion) do
graphql_log(method, kwlog)
{:ok, user}
else
_ ->
graphql_error(method, :authz)
end
end
##############################################################################
@doc """
check_authz/2 attempts to verify %Auth.Assertion{} against the specified user,
and if the assertion includes a domain reference, it will attempt to walk
up the reference tree (if a parent_id exists) and try checking auth again.
"""
# @spec
def check_authz(%{context: %{user: %Ident.User{} = u}}, assertion),
do: check_authz(u, assertion)
def check_authz(%Ident.User{} = user, %Auth.Assertion{domain: :global} = assertion) do
Ident.User.Lib.check_authz(user, assertion)
end
def check_authz(%Ident.User{} = user, %Auth.Assertion{domain: domain} = assertion) do
with {:error, user} <- Ident.User.Lib.check_authz(user, assertion),
{:ok, %{parent_id: parent_id}} when is_binary(parent_id) <-
domain.one(id: assertion.ref_id) do
# walk up the tree, but disable fallback -- if it didn't work before,
# it won't work again
check_authz(user, %Auth.Assertion{assertion | fallback: false})
else
{:ok, %Ident.User{}} = pass -> pass
_ -> {:error, user}
end
end
def check_authz(_, _), do: {:error, nil}
##############################################################################
@doc """
This wraps check_authz with logging and creates normal error output for
Absinthe to handle.
with {:ok, authed} <- authz(context, %Rivet.Auth.Assertion{}, "doTheThing") do
handle success
end
"""
@type az_user :: Ident.User.t() | map()
@type az_assertion :: Auth.Assertion.t()
@type az_log :: String.t() | nil
@spec authz(az_user, az_assertion, az_log) ::
{:ok, Ident.User.t()} | {:ok, map()}
def authz(meta, assertion, log \\ nil, kwlog \\ [])
def authz(meta, assertion, nil, _), do: check_authz(meta, assertion)
def authz(meta, assertion, log, kwlog) do
with {:ok, user} <- check_authz(meta, assertion) do
graphql_log(log, kwlog)
{:ok, user}
else
_ ->
graphql_error(log, :authz)
end
end
@spec action(atom(), keyword()) :: Auth.Assertion.t()
def action(a, opts \\ []), do: struct(Auth.Assertion, opts ++ [action: a])
end