Skip to content

Commit

Permalink
Allow passing csp_nonce_assign_key into router
Browse files Browse the repository at this point in the history
  • Loading branch information
hoyon committed Apr 4, 2024
1 parent 30120c9 commit a8ab382
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 4 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

This an either be a single nonce value, or separate values 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.
Expand Down
15 changes: 14 additions & 1 deletion lib/fun_with_flags/ui/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions lib/fun_with_flags/ui/templates.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,9 @@ defmodule FunWithFlags.UI.Templates do
|> to_string()
|> URI.encode()
end

def csp_nonce(conn, type) do
csp_nonce_assign_key = conn.private.csp_nonce_assign_key[type]
conn.assigns[csp_nonce_assign_key]
end
end
4 changes: 2 additions & 2 deletions lib/fun_with_flags/ui/templates/_head.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
<meta charset="utf-8">
<title>FunWithFlags - <%= @title %></title>

<link rel="stylesheet" href="<%= path(@conn, "/assets/bootstrap.min.css") %>">
<link rel="stylesheet" href="<%= path(@conn, "/assets/style.css") %>">
<link nonce="<%= csp_nonce(@conn, :style) %>" rel="stylesheet" href="<%= path(@conn, "/assets/bootstrap.min.css") %>">
<link nonce="<%= csp_nonce(@conn, :style) %>" rel="stylesheet" href="<%= path(@conn, "/assets/style.css") %>">
</head>
2 changes: 1 addition & 1 deletion lib/fun_with_flags/ui/templates/details.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,6 @@
</div>
</div>
</div>
<script type="text/javascript" src="<%= path(@conn, "/assets/details.js") %>"></script>
<script nonce="<%= csp_nonce(@conn, :script) %>" type="text/javascript" src="<%= path(@conn, "/assets/details.js") %>"></script>
</body>
</html>

0 comments on commit a8ab382

Please sign in to comment.