lib/ash_authentication/strategies/oauth2/verifier.ex

defmodule AshAuthentication.Strategy.OAuth2.Verifier do
  @moduledoc """
  DSL verifier for oauth2 strategies.
  """

  use Spark.Dsl.Transformer
  alias AshAuthentication.{Info, Strategy.OAuth2}
  alias Spark.Error.DslError
  import AshAuthentication.Validations

  @doc false
  @impl true
  @spec after?(module) :: boolean
  def after?(_), do: true

  @doc false
  @impl true
  @spec before?(module) :: boolean
  def before?(_), do: false

  @doc false
  @impl true
  @spec after_compile? :: boolean
  def after_compile?, do: true

  @doc false
  @impl true
  @spec transform(map) ::
          :ok
          | {:ok, map()}
          | {:error, term()}
          | {:warn, map(), String.t() | [String.t()]}
          | :halt
  def transform(dsl_state) do
    dsl_state
    |> Info.authentication_strategies()
    |> Stream.filter(&is_struct(&1, OAuth2))
    |> Enum.reduce_while(:ok, fn strategy, :ok ->
      case transform_strategy(strategy) do
        :ok -> {:cont, :ok}
        {:error, reason} -> {:halt, {:error, reason}}
      end
    end)
  end

  defp transform_strategy(strategy) do
    with :ok <- validate_secret(strategy, :authorize_url),
         :ok <- validate_secret(strategy, :client_id),
         :ok <- validate_secret(strategy, :client_secret),
         :ok <- validate_secret(strategy, :redirect_uri),
         :ok <- validate_secret(strategy, :site),
         :ok <- validate_secret(strategy, :token_url),
         :ok <- validate_secret(strategy, :user_url) do
      validate_secret(strategy, :private_key, strategy.auth_method != :private_key_jwt)
    end
  end

  defp validate_secret(strategy, option, allow_nil \\ false) do
    case Map.fetch(strategy, option) do
      {:ok, value} when is_binary(value) ->
        :ok

      {:ok, nil} when allow_nil ->
        :ok

      {:ok, {module, _}} when is_atom(module) ->
        validate_behaviour(module, AshAuthentication.Secret)

      _ ->
        {:error,
         DslError.exception(
           path: [:authentication, :strategies, :oauth2],
           message:
             "Expected `#{inspect(option)}` to be either a string or a module which implements the `AshAuthentication.Secret` behaviour."
         )}
    end
  end
end