lib/boruta/config.ex

defmodule Boruta.Config do
  @moduledoc """
  Utilities to access Boruta configuration ad set defaults.

  Boruta configuration can be set as following in `config.exs` overriding following default configuration
  ```
  config :boruta, Boruta.Oauth,
    repo: MyApp.Repo, # mandatory
    cache_backend: Boruta.Cache,
    contexts: [
      access_tokens: Boruta.Ecto.AccessTokens,
      clients: Boruta.Ecto.Clients,
      codes: Boruta.Ecto.Codes,
      resource_owners: MyApp.ResourceOwners, # mandatory for user flows
      scopes: Boruta.Ecto.Scopes
    ],
    max_ttl: [
      authorization_code: 60,
      access_token: 60 * 60 * 24,
      id_token: 60 * 60 * 24,
      refresh_token: 60 * 60 * 24 * 30
    ],
    token_generator: Boruta.TokenGenerator,
    issuer: "boruta"
  ```
  """

  @defaults cache_backend: Boruta.Cache,
            contexts: [
              access_tokens: Boruta.Ecto.AccessTokens,
              clients: Boruta.Ecto.Clients,
              codes: Boruta.Ecto.Codes,
              resource_owners: nil,
              scopes: Boruta.Ecto.Scopes
            ],
            max_ttl: [
              authorization_code: 60,
              access_token: 60 * 60 * 24,
              id_token: 60 * 60 * 24,
              refresh_token: 60 * 60 * 24 * 30
            ],
            token_generator: Boruta.TokenGenerator,
            issuer: "boruta"

  @spec repo() :: module()
  @doc false
  def repo do
    Keyword.fetch!(oauth_config(), :repo)
  end

  @spec cache_backend() :: module()
  @doc false
  def cache_backend do
    Keyword.fetch!(oauth_config(), :cache_backend)
  end

  @spec access_token_max_ttl() :: integer()
  @doc false
  def access_token_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:access_token]
  end

  @spec authorization_code_max_ttl() :: integer()
  @doc false
  def authorization_code_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:authorization_code]
  end

  @spec id_token_max_ttl() :: integer()
  @doc false
  def id_token_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:id_token]
  end

  @spec refresh_token_max_ttl() :: integer()
  @doc false
  def refresh_token_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:refresh_token]
  end

  @spec token_generator() :: module()
  @doc false
  def token_generator do
    Keyword.fetch!(oauth_config(), :token_generator)
  end

  @spec access_tokens() :: module()
  @doc false
  def access_tokens do
    Keyword.fetch!(oauth_config(), :contexts)[:access_tokens]
  end

  @spec clients() :: module()
  @doc false
  def clients do
    Keyword.fetch!(oauth_config(), :contexts)[:clients]
  end

  @spec codes() :: module()
  @doc false
  def codes do
    Keyword.fetch!(oauth_config(), :contexts)[:codes]
  end

  @spec scopes() :: module()
  @doc false
  def scopes do
    Keyword.fetch!(oauth_config(), :contexts)[:scopes]
  end

  @spec resource_owners() :: module()
  @doc false
  def resource_owners do
    case Keyword.fetch!(oauth_config(), :contexts)[:resource_owners] do
      nil ->
        raise """
        Missing configuration for resource_owners context. You can set your own
        `Boruta.Oauth.ResourceOwners` behaviour implementation in config:

          config :boruta, Boruta.Oauth,
            repo: MyApp.Repo,
            contexts: [
              resource_owners: MyApp.ResourceOwners
            ]
        """

      module ->
        module
    end
  end

  @spec issuer() :: String.t()
  @doc false
  def issuer do
    Keyword.fetch!(oauth_config(), :issuer)
  end

  @spec oauth_config() :: keyword()
  @doc false
  defp oauth_config do
      Keyword.merge(
        @defaults,
        Application.get_env(:boruta, Boruta.Oauth) || [],
        fn _, a, b ->
          if Keyword.keyword?(a) && Keyword.keyword?(b) do
            Keyword.merge(a, b)
          else
            b
          end
        end
      )
  end
end