lib/auth/refresh/index.ex

defmodule Rivet.Auth.Refresh do
  @moduledoc """
  Tooling for the second phase of auth: Refresh

  TODO: Extract from Phoenix/WebSvc better, abstract so conn isn't needed
        This will require a module that is imported into WebSvc -BJG
  """
  alias Rivet.Auth

  @doc """
  1. extract the validaton token from the refresh token
  2. decode & verify the validation token is ours & good
  3. using the sub:uuid from the validation token, verify the signature on the ref token
  4. update connection data (or abort)

  iex> assure(%Auth.Domain{}, nil)
  {:error, %Auth.Domain{log: "Invalid refresh request"}}
  """
  def assure(%Auth.Domain{} = auth, %{"client_assertion" => refresh_token})
      when is_binary(refresh_token) do
    auth = %Auth.Domain{auth | token: %{ref: refresh_token}}

    # do this manually first
    Auth.Token.extract(refresh_token, :claims)
    |> extract_validation_token(auth)
    |> check_refresh_token
  end

  def assure(_arg, _params) do
    {:error, %Auth.Domain{log: "Invalid refresh request"}}
  end

  ##############################################################################
  @doc ~S"""
  iex> {:error, %Auth.Domain{log: "Invalid authorization"}} = extract_validation_token({:ok, %{sub: "cas2:asdf"}}, %Auth.Domain{})
  iex> {:error, %Auth.Domain{log: "Unable to match validation token subject: \"wut\""}} = extract_validation_token({:ok, "wut"}, %Auth.Domain{})
  iex> {:error, %Auth.Domain{log: "narf!"}} = extract_validation_token({:error, "narf!"}, %Auth.Domain{})
  """
  def extract_validation_token({:ok, %{sub: "cas2:" <> validation_token}}, auth) do
    # now check the signature
    case Auth.Token.Verify.jwt(validation_token, :val) do
      {:error, reason} ->
        {:error, %Auth.Domain{auth | log: reason}}

      {:ok, result} ->
        valauth = %Auth.Domain{
          auth
          | status: :authed,
            token: %{ref: validation_token, claims: result}
        }

        # this checks the validation token embedded within the refresh token
        case Auth.Token.Check.jwt(valauth) do
          {:ok, %Auth.Domain{} = valauth} ->
            {:ok, auth, valauth}

          {:error, %Auth.Domain{} = auth} ->
            {:error, auth}
        end
    end
  end

  def extract_validation_token({:error, reason}, auth) when is_binary(reason) do
    {:error, %Auth.Domain{auth | log: reason}}
  end

  # turn it into an error if we didn't match above
  def extract_validation_token({:ok, args}, auth),
    do:
      {:error,
       %Auth.Domain{auth | log: "Unable to match validation token subject: #{inspect(args)}"}}

  ##############################################################################
  @doc """
  iex> {:error, "narf!"} = check_refresh_token({:error, "narf!"})
  """
  def check_refresh_token(
        {:ok, %Auth.Domain{token: %{ref: token}} = refauth,
         %Auth.Domain{factor: %Rivet.Ident.Factor{value: secret}} = valauth}
      )
      when not is_nil(secret) do
    case Auth.Token.Verify.jwt(token, [secret]) do
      {:ok, _} ->
        {:ok, %Auth.Domain{refauth | status: :authed}, valauth}

      {:error, msg} ->
        {:error, %Auth.Domain{refauth | log: "Unable to verify refresh token signature: #{msg}"}}
    end
  end

  def check_refresh_token(pass = {:error, _reason}), do: pass
end