diff --git a/README.md b/README.md index 7481047..baf375c 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,26 @@ defmodule MyPhoenixAppWeb.Router do end ``` +### Content Security Policy + +Content security policy nonces can be passed into the router to allow usage of strict content security policies throughout an application. + +This can be achieved by passing in a `csp_nonce_assign_key` to the `FunWithFlags.UI.Router` forward. Values for the nonces should be set in the conn assigns before reaching this router. + +The value of this can either be a single nonce assign key, or separate assign keys for script and style tags. + +For example: + +``` elixir +forward "/", FunWithFlags.UI.Router, namespace: "feature-flags", csp_nonce_assign_key: :my_csp_nonce +``` + +Or: + +``` elixir +forward "/", FunWithFlags.UI.Router, namespace: "feature-flags", csp_nonce_assign_key: %{style: :my_style_nonce, script: :my_script_nonce} +``` + ## Caveats While the base `fun_with_flags` library is quite relaxed in terms of valid flag names, group names and actor identifers, this web dashboard extension applies some more restrictive rules. diff --git a/lib/fun_with_flags/ui/router.ex b/lib/fun_with_flags/ui/router.ex index 3251925..61413f8 100644 --- a/lib/fun_with_flags/ui/router.ex +++ b/lib/fun_with_flags/ui/router.ex @@ -30,7 +30,10 @@ defmodule FunWithFlags.UI.Router do @doc false def call(conn, opts) do - conn = extract_namespace(conn, opts) + conn = + conn + |> extract_namespace(opts) + |> extract_csp_nonce_key(opts) super(conn, opts) end @@ -286,6 +289,16 @@ defmodule FunWithFlags.UI.Router do Plug.Conn.assign(conn, :namespace, "/" <> ns) end + defp extract_csp_nonce_key(conn, opts) do + csp_nonce_assign_key = + case opts[:csp_nonce_assign_key] do + nil -> nil + key when is_atom(key) -> %{style: key, script: key} + %{} = keys -> Map.take(keys, [:style, :script]) + end + + Plug.Conn.put_private(conn, :csp_nonce_assign_key, csp_nonce_assign_key) + end defp assign_csrf_token(conn, _opts) do csrf_token = Plug.CSRFProtection.get_csrf_token() diff --git a/lib/fun_with_flags/ui/templates.ex b/lib/fun_with_flags/ui/templates.ex index 8b0f6cb..ca1b43e 100644 --- a/lib/fun_with_flags/ui/templates.ex +++ b/lib/fun_with_flags/ui/templates.ex @@ -82,4 +82,12 @@ defmodule FunWithFlags.UI.Templates do |> to_string() |> URI.encode() end + + def csp_nonce(conn, type) do + assign_key = conn.private[:csp_nonce_assign_key][type] + case conn.assigns[assign_key] do + nil -> "" + nonce -> "nonce=\"#{nonce}\"" + end + end end diff --git a/lib/fun_with_flags/ui/templates/_head.html.eex b/lib/fun_with_flags/ui/templates/_head.html.eex index 3422e90..80542b6 100644 --- a/lib/fun_with_flags/ui/templates/_head.html.eex +++ b/lib/fun_with_flags/ui/templates/_head.html.eex @@ -2,6 +2,6 @@