Repository: phoenixframework/phoenix_html
Pull request #341 (2021-07-01 18:18:01)

Expose csrf_token_value on tag


Patch:
diff --git a/lib/phoenix_html/form_data.ex b/lib/phoenix_html/form_data.ex
index 7ceef54..9a277dc 100644
--- a/lib/phoenix_html/form_data.ex
+++ b/lib/phoenix_html/form_data.ex
@@ -149,12 +149,11 @@ defimpl Phoenix.HTML.FormData, for: [Plug.Conn, Atom] do
 
   def input_value(_conn_or_atom, %{data: data, params: params}, field)
       when is_atom(field) or is_binary(field) do
-    case Map.fetch(params, field_to_string(field)) do
-      {:ok, value} ->
-        value
+    key = field_to_string(field)
 
-      :error ->
-        Map.get(data, field)
+    case params do
+      %{^key => value} -> value
+      %{} -> Map.get(data, field)
     end
   end
 
diff --git a/lib/phoenix_html/link.ex b/lib/phoenix_html/link.ex
index 90f9cf5..a39392c 100644
--- a/lib/phoenix_html/link.ex
+++ b/lib/phoenix_html/link.ex
@@ -65,28 +65,28 @@ defmodule Phoenix.HTML.Link do
 
   ### Overriding the default confirm behaviour
 
-  `phoenix_html.js` does trigger a custom event `phoenix.link.click` on the 
+  `phoenix_html.js` does trigger a custom event `phoenix.link.click` on the
   clicked DOM element when a click happened. This allows you to intercept the
-  event on it's way bubbling up to `window` and do your own custom logic to 
+  event on it's way bubbling up to `window` and do your own custom logic to
   enhance or replace how the `data-confirm` attribute is handled.
 
-  You could for example replace the browsers `confirm()` behavior with a 
+  You could for example replace the browsers `confirm()` behavior with a
   custom javascript implementation:
 
   ```javascript
-  // listen on document.body, so it's executed before the default of 
+  // listen on document.body, so it's executed before the default of
   // phoenix_html, which is listening on the window object
   document.body.addEventListener('phoenix.link.click', function (e) {
     // Prevent default implementation
     e.stopPropagation();
-    
+
     // Introduce alternative implementation
     var message = e.target.getAttribute("data-confirm");
     if(!message){ return true; }
     vex.dialog.confirm({
       message: message,
       callback: function (value) {
-        if (value == false) { e.preventDefault(); } 
+        if (value == false) { e.preventDefault(); }
       }
     })
   }, false);
@@ -105,7 +105,7 @@ defmodule Phoenix.HTML.Link do
   }, false);
   ```
 
-  The latter could also be bound to any `click` event, but this way you can be 
+  The latter could also be bound to any `click` event, but this way you can be
   sure your custom code is only executed when the code of `phoenix_html.js` is run.
 
   ## CSRF Protection
@@ -231,18 +231,13 @@ defmodule Phoenix.HTML.Link do
         {[csrf: csrf], opts}
 
       {true, opts} ->
-        {[csrf: csrf_token(to)], opts}
+        {[csrf: Phoenix.HTML.Tag.csrf_token_value(to)], opts}
 
       {false, opts} ->
         {[], opts}
     end
   end
 
-  defp csrf_token(to) do
-    {mod, fun, args} = Application.fetch_env!(:phoenix_html, :csrf_token_reader)
-    apply(mod, fun, [to | args])
-  end
-
   defp pop_required_option!(opts, key, error_message) do
     {value, opts} = Keyword.pop(opts, key)
 
diff --git a/lib/phoenix_html/tag.ex b/lib/phoenix_html/tag.ex
index 447538b..7d4ab24 100644
--- a/lib/phoenix_html/tag.ex
+++ b/lib/phoenix_html/tag.ex
@@ -282,7 +282,7 @@ defmodule Phoenix.HTML.Tag do
         {extra <> ~s'<input name="#{@csrf_param}" type="hidden" value="#{csrf_token}">', opts}
 
       {true, opts} ->
-        csrf_token = csrf_token(to)
+        csrf_token = csrf_token_value(to)
         {extra <> ~s'<input name="#{@csrf_param}" type="hidden" value="#{csrf_token}">', opts}
 
       {false, opts} ->
@@ -290,7 +290,12 @@ defmodule Phoenix.HTML.Tag do
     end
   end
 
-  defp csrf_token(to) do
+  @doc """
+  Returns the csrf_token value to be used by forms, meta tags, etc.
+
+  By default, CSRF tokens are generated through `Plug.CSRFProtection`.
+  """
+  def csrf_token_value(to) do
     {mod, fun, args} = Application.fetch_env!(:phoenix_html, :csrf_token_reader)
     apply(mod, fun, [to | args])
   end
@@ -310,7 +315,7 @@ defmodule Phoenix.HTML.Tag do
       :meta,
       charset: "UTF-8",
       name: "csrf-token",
-      content: csrf_token(%URI{host: nil}),
+      content: csrf_token_value(%URI{host: nil}),
       "csrf-param": @csrf_param,
       "method-param": @method_param
     )


Test patch:
diff --git a/test/phoenix_html/tag_test.exs b/test/phoenix_html/tag_test.exs
index bdbd624..3537c01 100644
--- a/test/phoenix_html/tag_test.exs
+++ b/test/phoenix_html/tag_test.exs
@@ -223,4 +223,25 @@ defmodule Phoenix.HTML.TagTest do
     assert safe_to_string(csrf_meta_tag()) ==
              ~s(<meta charset="UTF-8" content="#{csrf_token}" csrf-param="_csrf_token" method-param="_method" name="csrf-token">)
   end
+
+  describe "csrf_token_value" do
+    def custom_csrf(to, extra), do: "#{extra}:#{to}"
+
+    test "with default" do
+      assert csrf_token_value("/") == Plug.CSRFProtection.get_csrf_token()
+    end
+
+    @default_reader Application.fetch_env!(:phoenix_html, :csrf_token_reader)
+    test "with configured MFA" do
+      Application.put_env(
+        :phoenix_html,
+        :csrf_token_reader,
+        {__MODULE__, :custom_csrf, ["extra"]}
+      )
+
+      assert csrf_token_value("/foo") == "extra:/foo"
+    after
+      Application.put_env(:phoenix_html, :csrf_token_reader, @default_reader)
+    end
+  end
 end