diff --git a/assets/js/app.js b/assets/js/app.js index 727741c..9e8c6a2 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -22,6 +22,25 @@ import { Socket } from "phoenix"; import { LiveSocket } from "phoenix_live_view"; import topbar from "../vendor/topbar"; +// Initialize Sentry for JavaScript error tracking +if (typeof window.Sentry !== 'undefined') { + window.Sentry.init({ + dsn: "https://337ece4c07ff53c6719d900adfddd6e4@o4509627566063616.ingest.us.sentry.io/4509691336785920", + environment: process.env.NODE_ENV || "production", + integrations: [ + new window.Sentry.BrowserTracing(), + ], + tracesSampleRate: 0.1, // Capture 10% of transactions for performance monitoring + beforeSend(event, hint) { + // Filter out known non-critical errors + if (hint.originalException?.message?.includes('ResizeObserver loop limit exceeded')) { + return null; + } + return event; + } + }); +} + let csrfToken = document.querySelector("meta[name='csrf-token']")?.getAttribute("content") || ""; if (!csrfToken) { console.error("CSRF token not found in meta tags"); diff --git a/lib/aprsme_web/components/layouts/root.html.heex b/lib/aprsme_web/components/layouts/root.html.heex index 6b7812a..222c701 100644 --- a/lib/aprsme_web/components/layouts/root.html.heex +++ b/lib/aprsme_web/components/layouts/root.html.heex @@ -64,6 +64,8 @@ + {@inner_content} diff --git a/lib/aprsme_web/plugs/content_security_policy.ex b/lib/aprsme_web/plugs/content_security_policy.ex new file mode 100644 index 0000000..a15f634 --- /dev/null +++ b/lib/aprsme_web/plugs/content_security_policy.ex @@ -0,0 +1,38 @@ +defmodule AprsmeWeb.Plugs.ContentSecurityPolicy do + @moduledoc """ + Sets Content Security Policy headers with Sentry integration support. + """ + import Plug.Conn + + def init(opts), do: opts + + def call(conn, _opts) do + put_resp_header(conn, "content-security-policy", csp_policy()) + end + + defp csp_policy do + Enum.join( + [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.sentry-cdn.com https://unpkg.com https://cdn.jsdelivr.net", + "style-src 'self' 'unsafe-inline' https://unpkg.com", + "img-src 'self' data: https: blob:", + "font-src 'self' data:", + "connect-src 'self' wss://#{host()} https://*.ingest.sentry.io https://*.sentry.io https://nominatim.openstreetmap.org https://tile.openstreetmap.org https://*.tile.openstreetmap.org", + "media-src 'self'", + "object-src 'none'", + "frame-ancestors 'none'", + "base-uri 'self'", + "form-action 'self'", + "frame-src 'self'", + "manifest-src 'self'", + "worker-src 'self' blob:" + ], + "; " + ) + end + + defp host do + Application.get_env(:aprsme, AprsmeWeb.Endpoint)[:url][:host] || "localhost" + end +end diff --git a/lib/aprsme_web/router.ex b/lib/aprsme_web/router.ex index b6703ca..4502076 100644 --- a/lib/aprsme_web/router.ex +++ b/lib/aprsme_web/router.ex @@ -15,6 +15,7 @@ defmodule AprsmeWeb.Router do plug :put_root_layout, {AprsmeWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers + plug AprsmeWeb.Plugs.ContentSecurityPolicy plug :fetch_current_user plug AprsmeWeb.Plugs.SetLocale plug IPGeolocation