Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions lib/live_svelte.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ defmodule LiveSvelte do
doc: "Class to apply to the Svelte component",
examples: ["my-class", "my-class another-class"]

attr :csp_nonce, :string,
default: nil,
doc: "A Content-Security-Policy nonce for the generated <script> and <style> tags"

attr :csp_script_nonce, :string,
default: nil,
doc: "A Content-Security-Policy nonce for the generated <script> tag"

attr :csp_style_nonce, :string,
default: nil,
doc: "A Content-Security-Policy nonce for the generated <style> tag"

attr :ssr, :boolean,
default: true,
doc: "Whether to render the component via NodeJS on the server",
Expand Down Expand Up @@ -124,6 +136,8 @@ defmodule LiveSvelte do

streams_diff = calculate_streams_diff(assigns, init or dead)

csp_attrs = if nonce = assigns.csp_nonce, do: [nonce: nonce]

assigns =
assigns
|> assign(:init, init)
Expand All @@ -134,9 +148,17 @@ defmodule LiveSvelte do
|> assign(:use_diff, use_diff)
|> assign(:props_diff, props_diff)
|> assign(:streams_diff, streams_diff)
|> assign(
:csp_script_attrs,
csp_attrs || if(nonce = assigns.csp_script_nonce, do: [nonce: nonce], else: [])
)
|> assign(
:csp_style_attrs,
csp_attrs || if(nonce = assigns.csp_style_nonce, do: [nonce: nonce], else: [])
)

~H"""
<script>
<script {@csp_script_attrs}>
<%= raw(@ssr_render["head"]) %>
</script>
<div
Expand All @@ -154,7 +176,7 @@ defmodule LiveSvelte do
>
<div id={"#{@svelte_id}-target"} data-svelte-target>
{raw(@ssr_render["head"])}
<style>
<style {@csp_style_attrs}>
<%= raw(@ssr_render["css"]["code"]) %>
</style>
{raw(@ssr_render["html"])}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
},
"exports": {
".": {
"import": "./assets/js/live_svelte/index.ts",
"types": "./assets/js/live_svelte/types.d.ts"
"types": "./assets/js/live_svelte/types.d.ts",
"import": "./assets/js/live_svelte/index.ts"
},
"./vitePlugin": "./assets/js/live_svelte/vite_plugin.js"
},
Expand Down
60 changes: 60 additions & 0 deletions test/csp_nonce_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
defmodule LiveSvelte.CspNonceTest do
use ExUnit.Case, async: true

defp render_html(opts) do
%{
__changed__: nil,
socket: nil,
name: "TestComponent",
id: "test-csp",
key: nil,
props: %{},
ssr: false,
class: nil,
loading: [],
inner_block: [],
csp_nonce: opts[:csp_nonce],
csp_script_nonce: opts[:csp_script_nonce],
csp_style_nonce: opts[:csp_style_nonce]
}
|> LiveSvelte.svelte()
|> Phoenix.HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
end

setup do
%{nonce: System.unique_integer([:positive])}
end

test "no nonce attributes when none specified" do
refute render_html(%{}) =~ "nonce="
end

test "csp_nonce applies to both script and style", %{nonce: nonce} do
html = render_html(%{csp_nonce: nonce})

assert html =~ ~r/<script nonce="#{nonce}">/
assert html =~ ~r/<style nonce="#{nonce}">/
end

test "csp_script_nonce applies only to script", %{nonce: nonce} do
html = render_html(%{csp_script_nonce: nonce})

assert html =~ ~r/<script nonce="#{nonce}">/
refute html =~ ~r/<style nonce=/
end

test "csp_style_nonce applies only to style", %{nonce: nonce} do
html = render_html(%{csp_style_nonce: nonce})

assert html =~ ~r/<style nonce="#{nonce}">/
refute html =~ ~r/<script nonce=/
end

test "separate script and style nonces", %{nonce: nonce} do
html = render_html(%{csp_script_nonce: nonce, csp_style_nonce: nonce * 2})

assert html =~ ~r/<script nonce="#{nonce}">/
assert html =~ ~r/<style nonce="#{nonce * 2}">/
end
end