Skip to content

Plug.Test.put_req_cookie/3 doesn't support cookie options #1300

@rwillians

Description

@rwillians

The function put_req_cookie/3 wasn't working for my case because my plug application expected encrypted cookies with certain attributes, such as domain and path. I dug through the code, turns out it simply puts the header {"cookie", "#{key}=#{value}"}.

I had to "reverse-engineer" put_resp_cookie/4 to come up with a solution. This issue is meant to document such workaround in case someone else stumble upon the same problem - although, it would be nice if Plug.Test.put_req_cookie/3 were made symmetric to Plug.Conn.put_resp_cookie/4.

defmodule Ex.Plug.Test do
  @moduledoc ~S"""
  Extends `Plug.Test`.
  """

  @doc """
  Puts a request cookie.
  """
  @spec put_req_cookie(Plug.Conn.t(), binary, binary, keyword) :: Plug.Conn.t()

  # mostly from https://github.com/elixir-plug/plug/blob/main/lib/plug/conn.ex#L1671-L1678
  def put_req_cookie(%Plug.Conn{} = conn, key, value, opts \\ [])
      when is_binary(key) and is_binary(value) and is_list(opts) do
    conn = Plug.Test.delete_req_cookie(conn, key)
    {to_send_value, opts} = maybe_sign_or_encrypt_cookie(conn, key, value, opts)
    cookie = Map.new([{:value, to_send_value} | opts])
    value = Plug.Conn.Cookies.encode(key, cookie)

    %{conn | req_headers: [{"cookie", value} | conn.req_headers]}
  end

  # from https://github.com/elixir-plug/plug/blob/main/lib/plug/conn.ex#L1680-L1701
  defp maybe_sign_or_encrypt_cookie(conn, key, value, opts) do
    {sign?, opts} = Keyword.pop(opts, :sign, false)
    {encrypt?, opts} = Keyword.pop(opts, :encrypt, false)

    case {sign?, encrypt?} do
      {true, true} ->
        raise ArgumentError,
              ":encrypt automatically implies :sign. Please pass only one or the other"

      {true, false} ->
        {Plug.Crypto.sign(conn.secret_key_base, key <> "_cookie", value, max_age(opts)), opts}

      {false, true} ->
        {Plug.Crypto.encrypt(conn.secret_key_base, key <> "_cookie", value, max_age(opts)), opts}

      {false, false} when is_binary(value) ->
        {value, opts}

      {false, false} ->
        raise ArgumentError, "cookie value must be a binary unless the cookie is signed/encrypted"
    end
  end

  # from https://github.com/elixir-plug/plug/blob/main/lib/plug/conn.ex#L1703-L1706
  defp max_age(opts) do
    max_age = Keyword.get(opts, :max_age) || 86400
    [keys: Plug.Keys, max_age: max_age]
  end
end

Not my finest, but it's honest work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions