lib/guardian/plug/verify_cookie.ex

if Code.ensure_loaded?(Plug) do
  defmodule Guardian.Plug.VerifyCookie do
    @moduledoc """
    Looks for and validates a token found in the request cookies.

    This module is deprecated in favor of using
    `Guardian.Plug.VerifySession` or the `Guardian.Plug.VerifyHeader`
    plug with the `:refresh_from_cookie` option

    In the case where either:

    1. The cookies are not loaded
    2. A token is already found for `:key`

    This plug will not do anything.

    This, like all other Guardian plugs, requires a Guardian pipeline to be setup.
    It requires an implementation module, an error handler and a key.

    These can be set either:

    1. Upstream on the connection with `plug Guardian.Pipeline`
    2. Upstream on the connection with `Guardian.Pipeline.{put_module, put_error_handler, put_key}`
    3. Inline with an option of `:module`, `:error_handler`, `:key`

    If a token is found but is invalid, the error handler will be called with
    `auth_error(conn, {:invalid_token, reason}, opts)`

    If a token is expired, the error handler WON'T be called, the error can be
    handled with the ensure_authenticated plug

    Once a token has been found it will be exchanged for an access (default) token.
    This access token will be placed into the session and connection.

    They will be available using `Guardian.Plug.current_claims/2` and `Guardian.Plug.current_token/2`.

    Tokens from cookies should be of type `refresh` and have a relatively long life.
    They will be exchanged for `access` tokens (default).

    Options:

    * `:key` - The location of the token (default `:default`)
    * `:exchange_from` - The type of the cookie (default `"refresh"`)
    * `:exchange_to` - The type of token to provide. Defaults to the
      implementation modules `default_type`
    * `:ttl` - The time to live of the exchanged token. Defaults to configured values.
    * `:halt` - Whether to halt the connection in case of error. Defaults to `true`
    """

    import Plug.Conn
    import Guardian.Plug.Keys
    import Guardian.Plug, only: [find_token_from_cookies: 2]

    alias Guardian.Plug.Pipeline

    @behaviour Plug

    @impl Plug
    @spec init(opts :: Keyword.t()) :: Keyword.t()
    @deprecated "Use Guardian.Plug.VerifySession or Guardian.Plug.VerifyHeader plug with `:refresh_from_cookie` option."
    def init(opts), do: opts

    @impl Plug
    @spec call(conn :: Plug.Conn.t(), opts :: Keyword.t()) :: Plug.Conn.t()
    def call(conn, opts) do
      refresh_from_cookie(conn, opts)
    end

    def refresh_from_cookie(%{req_cookies: %Plug.Conn.Unfetched{}} = conn, opts) do
      conn
      |> fetch_cookies()
      |> refresh_from_cookie(opts)
    end

    def refresh_from_cookie(conn, opts) do
      with nil <- Guardian.Plug.current_token(conn, opts),
           {:ok, token} <- find_token_from_cookies(conn, opts),
           module <- Pipeline.fetch_module!(conn, opts),
           key <- storage_key(conn, opts),
           exchange_from <-
             Keyword.get(opts, :exchange_from, "refresh"),
           default_type <- module.default_token_type(),
           exchange_to <- Keyword.get(opts, :exchange_to, default_type),
           active_session? <- Guardian.Plug.session_active?(conn),
           {:ok, _old, {new_t, new_c}} <-
             Guardian.exchange(module, token, exchange_from, exchange_to, opts) do
        conn
        |> Guardian.Plug.put_current_token(new_t, key: key)
        |> Guardian.Plug.put_current_claims(new_c, key: key)
        |> maybe_put_in_session(active_session?, new_t, opts)
      else
        :no_token_found ->
          conn

        # Let the ensure_authenticated plug handle the token expired later in the pipeline
        {:error, :token_expired} ->
          conn

        {:error, reason} ->
          conn
          |> Pipeline.fetch_error_handler!(opts)
          |> apply(:auth_error, [conn, {:invalid_token, reason}, opts])
          |> Guardian.Plug.maybe_halt(opts)

        _ ->
          conn
      end
    end

    defp maybe_put_in_session(conn, false, _, _), do: conn

    defp maybe_put_in_session(conn, true, token, opts) do
      key = conn |> storage_key(opts) |> token_key()
      put_session(conn, key, token)
    end

    defp storage_key(conn, opts), do: Pipeline.fetch_key(conn, opts)
  end
end