lib/guardian.ex

defmodule Guardian do
  @moduledoc ~S"""
  Guardian provides a singular interface for authentication in Elixir applications
  that is `token` based.

  Tokens should be:

  * tamper proof
  * include a payload (claims)

  JWT tokens (the default) fit this description.

  When using Guardian, you'll need an implementation module.

  ```elixir
  defmodule MyApp.Guardian do
    use Guardian, otp_app: :my_app

    def subject_for_token(resource, _claims), do: {:ok, to_string(resource.id)}
    def resource_from_claims(claims) do
      find_me_a_resource(claims["sub"]) # {:ok, resource} or {:error, reason}
    end
  end
  ```

  This module is what you will use to interact with tokens in your application.

  When you `use` Guardian, the `:otp_app` option is required.
  Any other option provided will be merged with the configuration in the config
  files.

  The Guardian module contains some generated functions and some callbacks.

  ## Generated functions

  ### `default_token_type()`

  Overridable.

  Provides the default token type for the token - `"access"`

  Token types allow a developer to mark a token as having a particular purpose.
  Different types of tokens can then be used specifically in your app.

  Types may include (but are not limited to):

  * `"access"`
  * `"refresh"`

  Access tokens should be short lived and are used to access resources on your API.
  Refresh tokens should be longer lived and whose only purpose is to exchange
  for a shorter lived access token.

  To specify the type of token, use the `:token_type` option in
  the `encode_and_sign` function.

  Token type is encoded into the token in the `"typ"` field.

  Return - a string.

  ### `peek(token)`

  Inspect a tokens payload. Note that this function does no verification.

  Return - a map including the `:claims` key.

  ### `config()`, `config(key, default \\ nil)`

  Without argument `config` will return the full configuration Keyword list.

  When given a `key` and optionally a default, `config` will fetch a resolved value
  contained in the key.

  See `Guardian.Config.resolve_value/1`

  ### `encode_and_sign(resource, claims \\ %{}, opts \\ [])`

  Creates a signed token.

  Arguments:

  * `resource` - The resource to represent in the token (i.e. the user)
  * `claims` - Any custom claims that you want to use in your token
  * `opts` - Options for the token module and callbacks

  For more information on options see the documentation for your token module.

  ```elixir
  # Provide a token using the defaults including the default_token_type
  {:ok, token, full_claims} = MyApp.Guardian.encode_and_sign(user)

  # Provide a token including custom claims
  {:ok, token, full_claims} = MyApp.Guardian.encode_and_sign(user, %{some: "claim"})

  # Provide a token including custom claims and a different token type/ttl
  {:ok, token, full_claims} =
    MyApp.Guardian.encode_and_sign(user, %{some: "claim"}, token_type: "refresh", ttl: {4, :weeks})
  ```

  The `encode_and_sign` function calls a number of callbacks on
  your implementation module. See `Guardian.encode_and_sign/4`

  ### `decode_and_verify(token, claims_to_check \\ %{}, opts \\ [])`

  Decodes a token and verifies the claims are valid.

  Arguments:

  * `token` - The token to decode
  * `claims_to_check` - A map of the literal claims that should be matched. If
    any of the claims do not literally match verification fails.
  * `opts` - The options to pass to the token module and callbacks

  Callbacks:

  `decode_and_verify` calls a number of callbacks on your implementation module,
  See `Guardian.decode_and_verify/4`

  ```elixir
  # Decode and verify using the defaults
  {:ok, claims} = MyApp.Guardian.decode_and_verify(token)

  # Decode and verify with literal claims check.
  # If the claims in the token do not match those given verification will fail
  {:ok, claims} = MyApp.Guardian.decode_and_verify(token, %{match: "claim"})

  # Decode and verify with literal claims check and options.
  # Options are passed to your token module and callbacks
  {:ok, claims} = MyApp.Guardian.decode_and_verify(token, %{match: "claim"}, some: "secret")
  ```

  ### `revoke(token, opts \\ [])`

  Revoke a token.

  *Note:* this is entirely dependent on your token module and implementation
  callbacks.

  ```elixir
  {:ok, claims} = MyApp.Guardian.revoke(token, some: "option")
  ```

  ### refresh(token, opts \\ [])

  Refreshes the time on a token. This is used to re-issue a token with
  essentially the same claims but with a different expiry.

  Tokens are verified before performing the refresh to ensure
  only valid tokens may be refreshed.

  Arguments:

  * `token` - The old token to refresh
  * `opts` - Options to pass to the Implementation Module and callbacks

  Options:

  * `:ttl` - The new ttl. If not specified the default will be used.

  ```elixir
  {:ok, {old_token, old_claims}, {new_token, new_claims}} =
    MyApp.Guardian.refresh(old_token, ttl: {1, :hour})
  ```

  See `Guardian.refresh`

  ### `exchange(old_token, from_type, to_type, options)`

  Exchanges one token for another of a different type.
  Especially useful to trade in a `refresh` token for an `access` one.

  Tokens are verified before performing the exchange to ensure that
  only valid tokens may be exchanged.

  Arguments:

  * `old_token` - The existing token you wish to exchange.
  * `from_type` - The type the old token must be. Can be given a list of types.
  * `to_type` - The new type of token that you want back.
  * `options` - The options to pass to the token module and callbacks.

  Options:

  Options may be used by your token module or callbacks.

  * `ttl` - The ttl for the new token

  See `Guardian.exchange`
  """

  @type options :: Keyword.t()
  @type conditional_tuple :: {:ok, any} | {:error, any}

  @default_token_module Guardian.Token.Jwt

  @doc """
  Fetches the subject for a token for the provided resource and claims
  The subject should be a short identifier that can be used to identify
  the resource.
  """
  @callback subject_for_token(
              resource :: Guardian.Token.resource(),
              claims :: Guardian.Token.claims()
            ) :: {:ok, String.t()} | {:error, atom}

  @doc """
  Fetches the resource that is represented by claims.

  For JWT this would normally be found in the `sub` field.
  """
  @callback resource_from_claims(claims :: Guardian.Token.claims()) :: {:ok, Guardian.Token.resource()} | {:error, atom}

  @doc """
  An optional callback that allows the claims to be modified
  while they're being built.

  This is useful to hook into the encoding lifecycle.
  """
  @callback build_claims(
              claims :: Guardian.Token.claims(),
              resource :: Guardian.Token.resource(),
              opts :: options
            ) :: {:ok, Guardian.Token.claims()} | {:error, atom}

  @doc """
  An optional callback invoked after the token has been generated
  and signed.
  """
  @callback after_encode_and_sign(
              resource :: any,
              claims :: Guardian.Token.claims(),
              token :: Guardian.Token.token(),
              options :: options
            ) :: {:ok, Guardian.Token.token()} | {:error, atom}

  @doc """
  An optional callback invoked after sign in has been called.

  By returning an error the sign in will be halted.

  * Note that if you return an error, a token still may have been generated.
  """
  @callback after_sign_in(
              conn :: Plug.Conn.t(),
              resource :: any,
              token :: Guardian.Token.token(),
              claims :: Guardian.Token.claims(),
              options :: options
            ) :: {:ok, Plug.Conn.t()} | {:error, atom}

  @doc """
  An optional callback invoked before sign out has happened.
  """
  @callback before_sign_out(conn :: Plug.Conn.t(), location :: atom | nil, options :: options) ::
              {:ok, Plug.Conn.t()} | {:error, atom}

  @doc """
  An optional callback to add custom verification to claims when
  decoding a token.

  Returning `{:ok, claims}` will allow the decoding to continue.
  Returning `{:error, reason}` will stop the decoding and return the error.
  """
  @callback verify_claims(claims :: Guardian.Token.claims(), options :: options) ::
              {:ok, Guardian.Token.claims()}
              | {:error, atom}

  @doc """
  An optional callback invoked after the claims have been validated.
  """
  @callback on_verify(
              claims :: Guardian.Token.claims(),
              token :: Guardian.Token.token(),
              options :: options
            ) :: {:ok, Guardian.Token.claims()} | {:error, any}

  @doc """
  An optional callback invoked when a token is revoked.
  """
  @callback on_revoke(
              claims :: Guardian.Token.claims(),
              token :: Guardian.Token.token(),
              options :: options
            ) :: {:ok, Guardian.Token.claims()} | {:error, any}

  @doc """
  An optional callback invoked when a token is refreshed.
  """
  @callback on_refresh(
              old_token_and_claims :: {Guardian.Token.token(), Guardian.Token.claims()},
              new_token_and_claims :: {Guardian.Token.token(), Guardian.Token.claims()},
              options :: options
            ) ::
              {
                :ok,
                {Guardian.Token.token(), Guardian.Token.claims()},
                {Guardian.Token.token(), Guardian.Token.claims()}
              }
              | {:error, any}

  @doc """
  An optional callback invoked when a token is exchanged.
  """
  @callback on_exchange(
              old_token_and_claims :: {Guardian.Token.token(), Guardian.Token.claims()},
              new_token_and_claims :: {Guardian.Token.token(), Guardian.Token.claims()},
              options :: options
            ) ::
              {
                :ok,
                {Guardian.Token.token(), Guardian.Token.claims()},
                {Guardian.Token.token(), Guardian.Token.claims()}
              }
              | {:error, any}

  alias Guardian.Token.Verify

  defmodule MalformedReturnValueError do
    defexception [:message]
  end

  defmacro __using__(opts \\ []) do
    otp_app = Keyword.get(opts, :otp_app)

    # credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
    quote do
      @behaviour Guardian

      if Code.ensure_loaded?(Plug) do
        __MODULE__
        |> Module.concat(:Plug)
        |> Module.create(
          quote do
            use Guardian.Plug, unquote(__MODULE__)
          end,
          Macro.Env.location(__ENV__)
        )
      end

      the_otp_app = unquote(otp_app)
      the_opts = unquote(opts)

      # Provide a way to get at the permissions during compile time. Uses
      # permissions from config if they are available and falls back to the
      # permissins defined on the `use Guardian` implementation
      #
      # NOTE: Generally you can't use compile_env for most keys because that
      # would prevent people from changing them at runtime for differen
      # environements.And hardcoding secret keys wouldn't be considered a good
      # practice.
      @config_permissions fn ->
        perms =
          Application.compile_env(the_otp_app, [__MODULE__, :permissions]) ||
            Keyword.get(the_opts, :permissions, [])

        Guardian.Config.resolve_value(perms)
      end

      @doc """
      The default type of token for this module.
      """

      @spec default_token_type() :: String.t()
      def default_token_type, do: "access"

      @doc """
      Fetches the configuration for this module.
      """

      @spec config() :: Keyword.t()
      def config,
        do:
          unquote(otp_app)
          |> Application.get_env(__MODULE__, [])
          |> Keyword.merge(unquote(opts))

      @doc """
      Returns a resolved value of the configuration found at a key.

      See `Guardian.Config.resolve_value/1`.
      """

      @spec config(atom | String.t(), any) :: any
      def config(key, default \\ nil),
        do: config() |> Keyword.get(key, default) |> Guardian.Config.resolve_value()

      @doc """
      Provides the content of the token but without verification
      of either the claims or the signature.

      Claims will be present at the `:claims` key.

      See `Guardian.peek/2` for more information.
      """
      @spec peek(String.t()) :: map
      def peek(token) do
        Guardian.token_module(__MODULE__).peek(__MODULE__, token)
      end

      @doc """
      Encodes the claims.

      See `Guardian.encode_and_sign/4` for more information.
      """

      @spec encode_and_sign(any, Guardian.Token.claims(), Guardian.options()) ::
              {:ok, Guardian.Token.token(), Guardian.Token.claims()} | {:error, any}
      def encode_and_sign(resource, claims \\ %{}, opts \\ []),
        do: Guardian.encode_and_sign(__MODULE__, resource, claims, opts)

      @doc """
      Decodes and verifies a token using the configuration on the implementation
      module.

      See `Guardian.decode_and_verify/4`.
      """

      @spec decode_and_verify(Guardian.Token.token(), Guardian.Token.claims(), Guardian.options()) ::
              {:ok, Guardian.Token.claims()} | {:error, any}
      def decode_and_verify(token, claims_to_check \\ %{}, opts \\ []),
        do: Guardian.decode_and_verify(__MODULE__, token, claims_to_check, opts)

      @doc """
      Fetch the resource and claims directly from a token.

      See `Guardian.resource_from_token` for more information.
      """

      @spec resource_from_token(
              token :: Guardian.Token.token(),
              claims_to_check :: Guardian.Token.claims() | nil,
              opts :: Guardian.options()
            ) :: {:ok, Guardian.Token.resource(), Guardian.Token.claims()} | {:error, any}
      def resource_from_token(token, claims_to_check \\ %{}, opts \\ []),
        do: Guardian.resource_from_token(__MODULE__, token, claims_to_check, opts)

      @doc """
      Revoke a token.

      See `Guardian.revoke` for more information.
      """

      @spec revoke(Guardian.Token.token(), Guardian.options()) :: {:ok, Guardian.Token.claims()} | {:error, any}
      def revoke(token, opts \\ []), do: Guardian.revoke(__MODULE__, token, opts)

      @doc """
      Refresh a token.

      See `Guardian.refresh` for more information.
      """

      @spec refresh(Guardian.Token.token(), Guardian.options()) ::
              {
                :ok,
                {Guardian.Token.token(), Guardian.Token.claims()},
                {Guardian.Token.token(), Guardian.Token.claims()}
              }
              | {:error, any}
      def refresh(old_token, opts \\ []), do: Guardian.refresh(__MODULE__, old_token, opts)

      @doc """
      Exchanges a token of one type for another.

      See `Guardian.exchange` for more information.
      """
      @spec exchange(
              token :: Guardian.Token.token(),
              from_type :: String.t() | [String.t(), ...],
              to_type :: String.t(),
              options :: Guardian.options()
            ) ::
              {
                :ok,
                {Guardian.Token.token(), Guardian.Token.claims()},
                {Guardian.Token.token(), Guardian.Token.claims()}
              }
              | {:error, any}
      def exchange(token, from_type, to_type, opts \\ []),
        do: Guardian.exchange(__MODULE__, token, from_type, to_type, opts)

      @doc """
      If Guardian.Plug.SlidingCookie is used, this callback will be invoked to
      return the new claims, or an error (which will mean the cookie will not
      be refreshed).
      """

      @spec sliding_cookie(
              current_claims :: Guardian.Token.claims(),
              current_resource :: Guardian.Token.resource(),
              options :: Guardian.options()
            ) :: {:ok, new_claims :: Guardian.Token.claims()} | {:error, any}
      def sliding_cookie(_current_claims, _current_resource, opts \\ []),
        do: {:error, :not_implemented}

      def after_encode_and_sign(_r, _claims, token, _), do: {:ok, token}
      def after_sign_in(conn, _r, _t, _c, _o), do: {:ok, conn}
      def before_sign_out(conn, _location, _opts), do: {:ok, conn}
      def on_verify(claims, _token, _options), do: {:ok, claims}
      def on_revoke(claims, _token, _options), do: {:ok, claims}
      def on_refresh(old_stuff, new_stuff, _options), do: {:ok, old_stuff, new_stuff}
      def on_exchange(old_stuff, new_stuff, _options), do: {:ok, old_stuff, new_stuff}

      def build_claims(c, _, _), do: {:ok, c}
      def verify_claims(claims, _options), do: {:ok, claims}

      defoverridable after_encode_and_sign: 4,
                     after_sign_in: 5,
                     before_sign_out: 3,
                     build_claims: 3,
                     default_token_type: 0,
                     on_exchange: 3,
                     on_revoke: 3,
                     on_refresh: 3,
                     on_verify: 3,
                     peek: 1,
                     verify_claims: 2,
                     sliding_cookie: 3
    end
  end

  @doc """
  Provides the current system time in seconds.
  """
  @spec timestamp() :: pos_integer
  def timestamp, do: DateTime.to_unix(DateTime.utc_now())

  @doc """
  Converts keys in a map or list of maps to strings.
  """

  @spec stringify_keys(map | list | any) :: map | list | any
  def stringify_keys(map) when is_map(map) do
    for {k, v} <- map, into: %{}, do: {to_string(k), stringify_keys(v)}
  end

  def stringify_keys(list) when is_list(list) do
    for item <- list, into: [], do: stringify_keys(item)
  end

  def stringify_keys(value), do: value

  @doc """
  Returns an inspection of the token (at least claims)
  without any verification.

  This should not be relied on since there is no verification.

  The implementation is provided by the implementation module specified.
  See the documentation for your implementation / token module for full details.
  """

  @spec peek(module, Guardian.Token.token()) :: %{claims: map}
  def peek(mod, token) do
    mod.peek(token)
  end

  @doc """
  Creates a signed token for a resource.

  The actual encoding depends on the implementation module
  which should be referenced for specifics.

  ### Lifecycle
  Once called, a number of callbacks will be invoked on the implementation module:

  * `subject_for_token` - gets the subject from the resource
  * `build_claims` - allows the implementation module to add or modify claims
    before the token is created
  * `after_encode_and_sign`

  ### Options
  The options will be passed through to the implementation / token modules
  and the appropriate callbacks.

  * `ttl` - How long to keep the token alive for. If not included the default will be used.
  * `token_type` - The type of token to generate if different from the default.

  The `ttl` option should take `{integer, unit}` where unit is one of:

  * `:second` | `:seconds`
  * `:minute` | `:minutes`
  * `:hour` | `:hours`
  * `:week` | `:weeks`

  See the documentation for your implementation / token module for more information on
  which options are available for your implementation / token module.
  """

  @spec encode_and_sign(module, any, Guardian.Token.claims(), options) ::
          {:ok, Guardian.Token.token(), Guardian.Token.claims()} | {:error, any}
  def encode_and_sign(mod, resource, claims \\ %{}, opts \\ []) do
    claims =
      claims
      |> Enum.into(%{})
      |> Guardian.stringify_keys()

    token_mod = Guardian.token_module(mod)

    with {:ok, subject} <- returning_tuple({mod, :subject_for_token, [resource, claims]}),
         {:ok, claims} <- returning_tuple({token_mod, :build_claims, [mod, resource, subject, claims, opts]}),
         {:ok, claims} <- returning_tuple({mod, :build_claims, [claims, resource, opts]}),
         {:ok, token} <- returning_tuple({token_mod, :create_token, [mod, claims, opts]}),
         {:ok, _} <- returning_tuple({mod, :after_encode_and_sign, [resource, claims, token, opts]}) do
      {:ok, token, claims}
    end
  end

  @doc """
  Decodes a token using the configuration of the implementation module.

  This will, using that configuration, delegate to the token module.

  Once the token module has decoded the token, your implementation module
  has an opportunity to further verify the claims contained in the token
  using the `verify_claims` callback.

  ### Lifecycle
  Once called, a number of callbacks will be invoked on the implementation module:

  * `verify_claims` - add custom claim verification, returns an error if the claims are not valid
  * `on_verify` - called after a successful verification

  ### Options
  The options will be passed through to the implementation / token modules
  and the appropriate callbacks.

  See the documentation for your implementation / token modules for more information on
  which options are available.
  """

  @spec decode_and_verify(module, Guardian.Token.token(), Guardian.Token.claims(), options) ::
          {:ok, Guardian.Token.claims()} | {:error, any}
  def decode_and_verify(mod, token, claims_to_check \\ %{}, opts \\ []) do
    claims_to_check = claims_to_check |> Enum.into(%{}) |> Guardian.stringify_keys()
    token_mod = Guardian.token_module(mod)

    with {:ok, claims} <- returning_tuple({token_mod, :decode_token, [mod, token, opts]}),
         {:ok, claims} <- Verify.verify_literal_claims(claims, claims_to_check, opts),
         {:ok, claims} <- returning_tuple({token_mod, :verify_claims, [mod, claims, opts]}),
         {:ok, claims} <- returning_tuple({mod, :verify_claims, [claims, opts]}),
         do: returning_tuple({mod, :on_verify, [claims, token, opts]})
  rescue
    e -> {:error, e}
  end

  @doc """
  Fetch the resource and claims directly from a token.

  This is a convenience function that first decodes the token using
  `Guardian.decode_and_verify/4` and then loads the resource.
  """

  @spec resource_from_token(
          mod :: module,
          token :: Guardian.Token.token(),
          claims_to_check :: Guardian.Token.claims() | nil,
          opts :: options
        ) :: {:ok, Guardian.Token.resource(), Guardian.Token.claims()} | {:error, any}
  def resource_from_token(mod, token, claims_to_check \\ %{}, opts \\ []) do
    with {:ok, claims} <- Guardian.decode_and_verify(mod, token, claims_to_check, opts),
         {:ok, resource} <- returning_tuple({mod, :resource_from_claims, [claims]}) do
      {:ok, resource, claims}
    end
  end

  @doc """
  Revoke a token.

  Note: This is entirely dependent on the token module and callbacks.

  ### Lifecycle

  * `<TokenModule>.revoke`
  * `<ImplModule>.on_revoke`

  ### Options

  The options are passed through to the token module and callback
  so check the documentation for your token module.
  """
  @spec revoke(module, Guardian.Token.token(), options) :: {:ok, Guardian.Token.claims()} | {:error, any}
  def revoke(mod, token, opts \\ []) do
    token_mod = Guardian.token_module(mod)

    with %{claims: claims} <- mod.peek(token),
         {:ok, claims} <- returning_tuple({token_mod, :revoke, [mod, claims, token, opts]}),
         {:ok, claims} <- returning_tuple({mod, :on_revoke, [claims, token, opts]}) do
      {:ok, claims}
    else
      nil -> {:error, :not_found}
      {:error, _} = err -> err
    end
  end

  @doc """
  Refreshes a token keeping all main claims intact.

  ### Options

  * `ttl` - How long to keep the token alive for. If not included the default will be used.

  The `ttl` option should take `{integer, unit}` where unit is one of:

  * `:second` | `:seconds`
  * `:minute` | `:minutes`
  * `:hour` | `:hours`
  * `:day` | `:days`
  * `:week` | `:weeks`

  See documentation for your token module for other options.
  """

  @spec refresh(module, Guardian.Token.token(), options) ::
          {
            :ok,
            {Guardian.Token.token(), Guardian.Token.claims()},
            {Guardian.Token.token(), Guardian.Token.claims()}
          }
          | {:error, any}
  def refresh(mod, old_token, opts) do
    with token_mod <- Guardian.token_module(mod),
         {:ok, _claims} <- apply(mod, :decode_and_verify, [old_token, %{}, opts]),
         {:ok, old_stuff, new_stuff} <- apply(token_mod, :refresh, [mod, old_token, opts]) do
      apply(mod, :on_refresh, [old_stuff, new_stuff, opts])
    else
      {:error, _} = err -> err
      err -> {:error, err}
    end
  end

  @doc """
  Exchanges one token for another with different token types.

  The token is first decoded and verified to ensure that there is no escalation
  Of privileges.

  Tokens must have their type included in the `from_type` argument.

  ### Lifecycle

  * `<TokenModule>.exchange` - exchange the old token for the new one
  * `<ImplModule>.on_exchange` - will be invoked after the exchange happens

  ### Options

  All options are passed through all calls to the token module and
  appropriate callbacks.
  """
  @spec exchange(
          module,
          Guardian.Token.token(),
          String.t() | [String.t(), ...],
          String.t(),
          options
        ) ::
          {
            :ok,
            {Guardian.Token.token(), Guardian.Token.claims()},
            {Guardian.Token.token(), Guardian.Token.claims()}
          }
          | {:error, any}
  def exchange(mod, old_token, from_type, to_type, opts) do
    with token_mod <- Guardian.token_module(mod),
         {:ok, claims} <- apply(mod, :decode_and_verify, [old_token, %{}, opts]),
         :ok <- validate_exchange_type(claims, from_type),
         {:ok, old_stuff, new_stuff} <- apply(token_mod, :exchange, [mod, old_token, from_type, to_type, opts]) do
      apply(mod, :on_exchange, [old_stuff, new_stuff, opts])
    else
      {:error, _} = err -> err
      err -> {:error, err}
    end
  end

  @doc false
  def returning_tuple({mod, func, args}) do
    result = apply(mod, func, args)

    case result do
      {:ok, _} ->
        result

      {:error, _} ->
        result

      resp ->
        raise MalformedReturnValueError,
          message: "Expected `{:ok, result}` or `{:error, reason}` from #{mod}##{func}, got: #{inspect(resp)}"
    end
  end

  @doc false
  def token_module(mod) do
    apply(mod, :config, [:token_module, @default_token_module])
  end

  @doc false
  def ttl_to_seconds({seconds, unit}) when unit in [:second, :seconds],
    do: seconds

  def ttl_to_seconds({minutes, unit}) when unit in [:minute, :minutes],
    do: minutes * 60

  def ttl_to_seconds({hours, unit}) when unit in [:hour, :hours],
    do: hours * 60 * 60

  def ttl_to_seconds({days, unit}) when unit in [:day, :days],
    do: days * 24 * 60 * 60

  def ttl_to_seconds({weeks, unit}) when unit in [:week, :weeks],
    do: weeks * 7 * 24 * 60 * 60

  def ttl_to_seconds({_, units}),
    do: raise("Unknown Units: #{units}")

  defp validate_exchange_type(claims, from_type) when is_binary(from_type),
    do: validate_exchange_type(claims, [from_type])

  defp validate_exchange_type(claims, from_type) do
    if Enum.member?(from_type, claims["typ"]), do: :ok, else: {:error, :invalid_token_type}
  end
end