defmodule WhoNeedHelpWeb.SecurityHeaders do @moduledoc false import Plug.Conn def put_content_security_policy(conn, _opts) do tile_origin = :who_need_help |> Application.fetch_env!(:map_tile_url) |> origin() websocket_origin = case conn.scheme do :http -> origin(%URI{scheme: "ws", host: conn.host, port: conn.port}) :https -> origin(%URI{scheme: "wss", host: conn.host, port: conn.port}) _other -> nil end connect_sources = sources(["'self'", tile_origin, websocket_origin]) image_sources = sources(["'self'", "data:", "blob:", tile_origin]) policy = [ "default-src 'self'", "base-uri 'self'", "frame-ancestors 'none'", "object-src 'none'", "script-src 'self'", "style-src 'self' 'unsafe-inline'", "img-src #{image_sources}", "connect-src #{connect_sources}", "worker-src 'self' blob:", "manifest-src 'self'", "form-action 'self'" ] |> Enum.join("; ") put_resp_header(conn, "content-security-policy", policy) end defp sources(values), do: values |> Enum.reject(&is_nil/1) |> Enum.uniq() |> Enum.join(" ") defp origin(url) when is_binary(url), do: url |> URI.parse() |> origin() defp origin(%URI{scheme: scheme, host: host} = uri) when scheme in ["http", "https", "ws", "wss"] and is_binary(host) and host != "" do URI.to_string(%URI{scheme: scheme, host: host, port: uri.port}) end defp origin(_uri), do: nil end