NaN
Heroicon defined in core_components not working
phx is 1.7
Neither of these are working… what am I missing???
<button id="expandButton" class="hero-document-arrow-down"><.icon name="hero-document-arrow-down" /></button>
Other icons work are are available in app.css. But none that I add… I dont get it
Marked As Solved
Also Liked
slouchpie
Make your own CSP headers.
For example:
plug :put_secure_browser_headers, %{"content-security-policy" => MyAppWeb.CSP.header_value()}
with csp module like this:
defmodule MyAppWeb.CSP do
@moduledoc """
Content-Security-Policy
https://www.w3.org/TR/CSP2/#directives
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
"""
@hcaptcha ["https://hcaptcha.com", "https://*.hcaptcha.com"]
@stripe ["https://js.stripe.com"]
@google_fonts ["https://fonts.googleapis.com"]
@env_specific_csp if Mix.env() == :dev,
do: ["'unsafe-inline'", "'unsafe-eval'", "blob:", "http://localhost:4007"],
else: []
@header_kvp_list [
default_src: ["'self'"],
img_src: ["'self'", "data:"] ++ @env_specific_csp,
script_src: ["'self'"] ++ @hcaptcha ++ @stripe ++ @env_specific_csp,
object_src: ["'self'"],
frame_src: ["'self'"] ++ @hcaptcha ++ @stripe,
style_src: ["'self'", "'unsafe-inline'"] ++ @hcaptcha ++ @stripe ++ @google_fonts ++ @env_specific_csp,
connect_src: ["'self'"] ++ @hcaptcha ++ @stripe
]
@spec header_value() :: String.t()
def header_value do
configured_header_kvp_list = Application.get_env(:my_app, :csp_headers, [])
@header_kvp_list
|> Keyword.merge(configured_header_kvp_list)
|> Enum.map_join("; ", &map_header_kvp_to_string/1)
end
defp map_header_kvp_to_string({atom_key, list_value}) do
string_key = atom_key |> to_string() |> String.replace("_", "-")
string_value = Enum.join(list_value, " ")
"#{string_key} #{string_value}"
end
end
That is lifted straight from one of my hobby projects. localhost:4007 is an imgproxy server.
Anyway, I think the thing that is needed is the google fonts bit? Not sure, but this solved heroicons for me in terms of CSP.
Edit 26 March 2024: read this instead: Blog Post: Content Security Policy header with Phoenix LiveView
peterhartman
Am feeling embarrassed I confused CORS with CSP! Building on @slouchpie’s solution I created a plug that sets the CSP for me and adds a nonce to the assigns so it can be used in various places.
defmodule TvpNgWeb.CSP do
@moduledoc """
Content-Security-Policy
With thanks to the authors of:
https://www.w3.org/TR/CSP2/#directives
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
https://csp-evaluator.withgoogle.com/
https://furlough.merecomplexities.com/elixir/phoenix/security/2021/02/26/content-security-policy-configuration-in-phoenix.html
https://francis.chabouis.fr/posts/csp-nonce-with-phoenix/
"""
def init(options), do: options
defp generate_nonce(size \\ 10),
do: size |> :crypto.strong_rand_bytes() |> Base.url_encode64(padding: false)
def call(conn, _opts) do
# a random string is generated
nonce = generate_nonce()
csp_headers = header_value(nonce)
conn
# the nonce is saved in the connection assigns
|> Plug.Conn.assign(:csp_nonce_value, nonce)
|> Phoenix.Controller.put_secure_browser_headers(%{"content-security-policy" => csp_headers})
end
@env_specific_csp (case Mix.env() do
:prod -> []
_ -> ["'unsafe-inline'"]
end)
@header_kvp_list [
img_src: ["'self'", "data:"] ++ @env_specific_csp,
script_src: ["'strict-dynamic'", "NONCE"],
object_src: ["'none'"],
base_uri: ["'none'"],
# require_trusted_types_for: ["'script'"] - See https://github.com/phoenixframework/phoenix_live_view/issues/3166
]
defp header_value(nonce) do
configured_header_kvp_list = Application.get_env(:tvp_ng, :csp_headers, [])
@header_kvp_list
|> Keyword.merge(configured_header_kvp_list)
|> Enum.map_join("; ", &map_header_kvp_to_string(nonce, &1))
end
defp map_header_kvp_to_string(nonce, {atom_key, list_value}) do
string_key = atom_key |> to_string() |> String.replace("_", "-")
string_value = list_value |> Enum.join(" ") |> String.replace("NONCE", "'nonce-#{nonce}'")
"#{string_key} #{string_value}"
end
end
eg. phoenix_live_dashboard:
live_dashboard("/dashboard", metrics: TvpNgWeb.Telemetry, csp_nonce_assign_key: :csp_nonce_value)
and root.html.heex
<script defer phx-track-static nonce={@csp_nonce_value} type="text/javascript" src={~p"/assets/app.js"}>
I found Google’s CSP Chrome plugin from to be super helpful with this - https://csp-evaluator.withgoogle.com/
tj0
Just fyi, there are also items that should be added for backward compatibility. By default, for javascript, it should be using “strict-dynamic”. Any browser supporting that will use it, but if not, there should be a fallback.
This is from playing around with https://csp-evaluator.withgoogle.com/
script-src 'self' 'unsafe-inline' https: 'nonce-#{nonce}' 'strict-dynamic';
I’ve also added HSTS headers and enabled feature-policy. There are some adjustments for safari. Note that feature-policy could break people using extensions on your website. I’m still running prod in report-only mode, but the following is the code.
defp generate_nonce(size \\ 10), do: size |> :crypto.strong_rand_bytes() |> Base.url_encode64(padding: false)
@doc """
Sets CSP, HSTS, and Feature/Permissions policies
https://csp-evaluator.withgoogle.com/
Consider adding 'unsafe-inline' (ignored by browsers supporting nonces/hashes) to be backward compatible with older browsers.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
Need to add more than self for safari for connect-src
https://github.com/w3c/webappsec-csp/issues/7
https://bugs.webkit.org/show_bug.cgi?id=235873
"""
def put_extra_secure_browser_headers(conn, _) do
default_headers = %{
"strict-transport-security" => "max-age=31536000; includeSubDomains",
#"Permissions-Policy" => "geolocation=(self), microphone=(), camera=()",
"feature-policy" => "geolocation 'self'; microphone 'none'; camera 'none';"
}
nonce = generate_nonce()
# script-src 'self' 'nonce-#{nonce}' https://sandbox-checkout.paddle.com/ https://sandbox-cdn.paddle.com/ https://plausible.io/;
csp_content = case Application.get_env(:your_app, :environment) do
:prod ->
"""
report-uri /csp/report/;
base-uri 'self';
default-src 'self';
script-src 'self' 'unsafe-inline' https: 'nonce-#{nonce}' 'strict-dynamic';
frame-src 'self' https://subscription-management.paddle.com https://buy.paddle.com;
style-src 'self' 'unsafe-inline' https://cdn.paddle.com/;
img-src 'self' https://cdn.paddle.com/ blob: data:;
object-src 'none';
connect-src 'self' https://plausible.io/ wss://your.app ws://your.app;
"""
_ ->
"""
report-uri /csp/report/;
base-uri 'self';
default-src 'self';
script-src 'self' 'unsafe-inline' https: 'nonce-#{nonce}' 'strict-dynamic';
frame-src 'self' https://sandbox-subscription-management.paddle.com https://sandbox-buy.paddle.com;
style-src 'self' 'unsafe-inline' https://sandbox-cdn.paddle.com/;
img-src 'self' https://cdn.paddle.com/ blob: data:;
object-src 'none';
connect-src 'self' https://plausible.io/ wss://test.your.app ws://test.your.app wss://localhost:* ws://localhost:*;
"""
end
csp_header = case Application.get_env(:hora, :environment) do
:prod -> %{"content-security-policy-report-only" => csp_content |> String.replace("\n", "")}
_ -> %{"content-security-policy" => csp_content |> String.replace("\n", "")}
end
headers = Map.merge(default_headers, csp_header )
Plug.Conn.merge_resp_headers(conn, headers)
|> assign(:csp_nonce, nonce)
|> Plug.Conn.put_session(:csp_nonce, nonce)
end







