Skip to content

Commit

Permalink
chore: Remove csp directive script-src 'unsafe-eval' in project #139 (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nwittstruck authored Mar 24, 2024
1 parent 1bbe586 commit db428a2
Show file tree
Hide file tree
Showing 14 changed files with 155 additions and 75 deletions.
51 changes: 32 additions & 19 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import { Modal } from "bootstrap"
//

import "phoenix_html"
import {Socket} from "phoenix"
import { Socket } from "phoenix"
import NProgress from "nprogress"
import {LiveSocket} from "phoenix_live_view"
import { LiveSocket } from "phoenix_live_view"
import QRCodeStyling from "qr-code-styling";
import ClipboardJS from "clipboard"
import {buildQrCodeOptions} from "./qrCodeUtils.js"
import { buildQrCodeOptions } from "./qrCodeUtils.js"

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")

Expand All @@ -39,12 +39,12 @@ Hooks.NativeSharingButton = {
text: this.el.getAttribute(`data-native-sharing-button-share-data-text`) || 'Join my brainstorming',
url: this.el.getAttribute(`data-native-sharing-button-share-data-url`) || document.getElementById("brainstorming-link").value
}

if (navigator.share) {
this.el.addEventListener('click', (event) => {
navigator.share(shareData)
.then() // Do nothing
.catch(err => { console.log(`Error: ${err}`) })
.then() // Do nothing
.catch(err => { console.log(`Error: ${err}`) })
})
}
}
Expand All @@ -59,25 +59,25 @@ Hooks.Modal = {
// See https://fullstackphoenix.com/tutorials/create-a-reusable-modal-with-liveview-component
const modal = new Modal(this.el, { backdrop: 'static', keyboard: false })
modal.show()

const hideModal = () => modal && modal.hide()

this.el.addEventListener('submit', hideModal)

const formCancelElement = this.el.querySelector(".form-cancel")
formCancelElement && formCancelElement.addEventListener('click', hideModal)

const phxModalCloseElement = this.el.querySelector(".phx-modal-close")
phxModalCloseElement && phxModalCloseElement.addEventListener('click', hideModal)

this.el.addEventListener('keyup', (keyEvent) => {
if (keyEvent.key === 'Escape') {
// This will tell the "#modal" div to send a "close" event to the server
this.pushEventTo("#modal", "close")
hideModal()
}
})

window.addEventListener('popstate', () => {
hideModal()
// To avoid multiple registers
Expand All @@ -90,10 +90,10 @@ Hooks.QrCodeCanvas = {
mounted() {
const qrCodeCanvasElement = this.el
const qrCodeUrl = qrCodeCanvasElement.getAttribute("data-qr-code-url")

const qrCodeOptions = buildQrCodeOptions(qrCodeUrl)
const qrCode = new QRCodeStyling(qrCodeOptions)

qrCode.append(qrCodeCanvasElement);
}
}
Expand All @@ -103,19 +103,33 @@ Hooks.QrCodeDownloadButton = {
const qrCodeUrl = this.el.getAttribute("data-qr-code-url");
const qrCodeFilename = this.el.getAttribute("data-qr-code-filename") || qrCodeUrl || "qrcode";
const qrCodeFileExtension = this.el.getAttribute("data-qr-code-file-extension") || "png";

const qrCodeOptions = buildQrCodeOptions(qrCodeUrl)
const qrCode = new QRCodeStyling(qrCodeOptions)

this.el && this.el.addEventListener('click', () => {
qrCode.download({ name: qrCodeFilename, extension: qrCodeFileExtension })
.then() // Do nothing
.catch(err => { console.log(`Error: ${err}`) })
.catch(err => { console.log(`Error: ${err}`) })
})
}
}

let liveSocket = new LiveSocket("/live", Socket, { hooks: Hooks, params: {_csrf_token: csrfToken}})
Hooks.SetIdeaLabelColor = {
mounted() {
const color = this.el.getAttribute("data-color");
this.el.style.color = color;
},
};

Hooks.SetIdeaLabelBackgroundColor = {
mounted() {
const color = this.el.getAttribute("data-color");
this.el.style.backgroundColor = color;
},
};

let liveSocket = new LiveSocket("/live", Socket, { hooks: Hooks, params: { _csrf_token: csrfToken } })

// Show progress bar on live navigation and form submits
window.addEventListener("phx:page-loading-start", info => NProgress.start())
Expand All @@ -128,5 +142,4 @@ liveSocket.connect()
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket

window.liveSocket = liveSocket
9 changes: 9 additions & 0 deletions assets/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ a .bi {
.phx-disconnected {
cursor: wait;
}

.phx-disconnected * {
pointer-events: none;
}
Expand All @@ -64,3 +65,11 @@ a .bi {
.alert:empty {
display: none;
}

.form-control-color {
max-width: 50px;
}

.heading-error {
font-size: 8rem;
}
8 changes: 8 additions & 0 deletions assets/scss/live/idea_live/_index_component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
.IndexComponent__IdeaLabel {
@extend %bi-icon-before;
@extend %bi-circle-fill-before;

&:hover {
@extend %bi-plus-circle-fill-before;
}
Expand All @@ -61,7 +62,14 @@
.IndexComponent__IdeaLabel--active {
@extend %bi-icon-before;
@extend %bi-check-circle-fill-before;

&:hover {
@extend %bi-x-circle-fill-before;
}
}

.preview-url {
width: 100px;
margin: auto;
display: block;
}
4 changes: 3 additions & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ config :mindwendel, :options,
["", "true"],
String.trim(System.get_env("MW_FEATURE_BRAINSTORMING_TEASER") || "")
),
feature_brainstorming_removal_after_days: delete_brainstormings_after_days
feature_brainstorming_removal_after_days: delete_brainstormings_after_days,
# use a strict csp everywhere except in development. we need to relax the setting a bit for webpack
csp_relax: config_env() == :dev

if config_env() == :prod || config_env() == :dev do
config :mindwendel, Oban,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@
<%= color_input(p, :color,
class:
"form-control form-control-color #{if p.errors[:color], do: "is-invalid"} #{if p.source.changes[:name] || p.source.changes[:color], do: "border-success"}",
style: "max-width: 50px",
title: gettext("Choose the label color")
) %>
<%= text_input(p, :name,
Expand Down
14 changes: 10 additions & 4 deletions lib/mindwendel_web/live/idea_live/index_component.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

<%= for idea_label <- Enum.sort_by(idea.idea_labels, &(&1.position_order)) do %>
<span
id={"idea-label-#{uuid()}"}
class="IndexComponent__IdeaLabelBadge mb-3"
data-testid={idea_label.id}
style={"background-color: #{idea_label.color}"}
data-color={idea_label.color}
phx-hook="SetIdeaLabelBackgroundColor"
>
<%= idea_label.name %>
</span>
Expand All @@ -38,7 +40,7 @@
<div class="row">
<div class="col-md-3">
<%= img_tag(idea.link.img_preview_url,
style: "width: 100px; margin: auto; display: block;"
class: "preview-url"
) %>
</div>
<div class="col-md-9">
Expand Down Expand Up @@ -73,18 +75,22 @@
<%= unless Enum.find(idea.idea_labels, fn idea_label -> idea_label.id == brainstorming_idea_label.id end) do %>
<%= link to: "#", class: "text-decoration-none me-1", data_testid: brainstorming_idea_label.id, title: "Label #{brainstorming_idea_label.name}", phx_click: "add_idea_label_to_idea", phx_target: @myself, phx_value_idea_id: idea.id, phx_value_idea_label_id: brainstorming_idea_label.id do %>
<i
id={"idea-label-#{uuid()}"}
class="IndexComponent__IdeaLabel"
data-testid={brainstorming_idea_label.id}
style={"color: #{brainstorming_idea_label.color};"}
data-color={brainstorming_idea_label.color}
phx-hook="SetIdeaLabelColor"
>
</i>
<% end %>
<% else %>
<%= link to: "#", class: "text-decoration-none me-1", data_testid: brainstorming_idea_label.id, title: "Label #{brainstorming_idea_label.name}", phx_click: "remove_idea_label_from_idea", phx_target: @myself, phx_value_idea_id: idea.id, phx_value_idea_label_id: brainstorming_idea_label.id do %>
<i
id={"idea-label-#{uuid()}"}
class="IndexComponent__IdeaLabel--active"
data-testid={brainstorming_idea_label.id}
style={"color: #{brainstorming_idea_label.color};"}
data-color={brainstorming_idea_label.color}
phx-hook="SetIdeaLabelColor"
>
</i>
<% end %>
Expand Down
4 changes: 4 additions & 0 deletions lib/mindwendel_web/live/live_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ defmodule MindwendelWeb.LiveHelpers do
modal_opts = [id: :modal, return_to: path, component: component, opts: opts]
live_component(MindwendelWeb.ModalComponent, modal_opts)
end

def uuid do
Ecto.UUID.generate()
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@ defmodule Mindwendel.Plugs.SetResponseHeaderContentSecurityPolicy do
"font-src 'self' ;",
"frame-src 'self' ;",

# We add csp sources http: and https: to allow the browser to load the link preview image extracted from the idea body
"img-src 'self' data: https: http: ;",
# We add csp sources https: to allow the browser to load the link preview image extracted from the idea body
"img-src 'self' data: https: ;",

# We need to add csp 'unsafe-eval', otherwise we get an error in development
# because webpack js bundle uses `eval` for hot reloading.
# TODO: Lets evaluate this for production
"script-src 'self' 'unsafe-eval' ;",
"style-src 'self' 'unsafe-inline' ;"
"script-src #{get_script_src()} ;",
"style-src #{get_style_src()} ;"
]
|> Enum.join(" ")
end
Expand All @@ -50,6 +49,18 @@ defmodule Mindwendel.Plugs.SetResponseHeaderContentSecurityPolicy do
|> Keyword.fetch!(:host)
end

def get_script_src() do
if Application.fetch_env!(:mindwendel, :options)[:csp_relax],
do: "'self' 'unsafe-eval'",
else: "'self'"
end

def get_style_src() do
if Application.fetch_env!(:mindwendel, :options)[:csp_relax],
do: "'self' 'unsafe-inline'",
else: "'self'"
end

def get_scheme() do
:mindwendel
|> Application.fetch_env!(MindwendelWeb.Endpoint)
Expand Down
2 changes: 1 addition & 1 deletion lib/mindwendel_web/templates/error/error_page.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

<main role="main" class="container" id="main-container">
<div class="text-center">
<h1 style="font-size: 8rem;">
<h1 class="heading-error">
<%= for i <- @status |> Integer.to_string |> String.graphemes do %>
<%= if i == "0" do %>
<i class="bi-lightbulb-off text-black"></i>
Expand Down
2 changes: 1 addition & 1 deletion lib/mindwendel_web/templates/layout/static_page.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</script>
</head>

<body class="d-flex h-100" style="background-color: #000000;">
<body class="d-flex h-100 bg-black">
<%= @inner_content %>
</body>
</html>
Loading

0 comments on commit db428a2

Please sign in to comment.