diff --git a/config/runtime.exs b/config/runtime.exs index 9e26bdd6b1..6b02434b80 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -232,6 +232,11 @@ if byte_size(websocket_url) > 0 and """ end +secure_cookie = + config_dir + |> get_var_from_path_or_env("SECURE_COOKIE", if(is_selfhost, do: "false", else: "true")) + |> String.to_existing_atom() + config :plausible, environment: env, mailer_email: mailer_email, @@ -254,7 +259,8 @@ config :plausible, PlausibleWeb.Endpoint, protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192] ], secret_key_base: secret_key_base, - websocket_url: websocket_url + websocket_url: websocket_url, + secure_cookie: secure_cookie maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: [] diff --git a/lib/plausible_web/endpoint.ex b/lib/plausible_web/endpoint.ex index 8009ccdc17..5c1611a546 100644 --- a/lib/plausible_web/endpoint.ex +++ b/lib/plausible_web/endpoint.ex @@ -3,9 +3,10 @@ defmodule PlausibleWeb.Endpoint do use Phoenix.Endpoint, otp_app: :plausible @session_options [ + # key to be patched + key: "", store: :cookie, - key: "_plausible_key", - signing_salt: "3IL0ob4k", + signing_salt: "I45i0SKHEku2f3tJh6y4v8gztrb/eG5KGCOe/o/AwFb7VHeuvDOn7AAq6KsdmOFM", # 5 years, this is super long but the SlidingSessionTimeout will log people out if they don't return for 2 weeks max_age: 60 * 60 * 24 * 365 * 5, extra: "SameSite=Lax" @@ -16,58 +17,62 @@ defmodule PlausibleWeb.Endpoint do # # You should set gzip to true if you are running phx.digest # when deploying your static files in production. - plug PlausibleWeb.Tracker - plug PlausibleWeb.Favicon + plug(PlausibleWeb.Tracker) + plug(PlausibleWeb.Favicon) - plug Plug.Static, + plug(Plug.Static, at: "/", from: :plausible, gzip: false, only: ~w(css images js favicon.ico robots.txt) + ) - plug Plug.Static, + plug(Plug.Static, at: "/kaffy", from: :kaffy, gzip: false, only: ~w(assets) + ) # Code reloading can be explicitly enabled under the # :code_reloader configuration of your endpoint. if code_reloading? do - socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket - plug Phoenix.LiveReloader - plug Phoenix.CodeReloader + socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket) + plug(Phoenix.LiveReloader) + plug(Phoenix.CodeReloader) end - plug Plug.RequestId - plug PromEx.Plug, prom_ex_module: Plausible.PromEx - plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + plug(Plug.RequestId) + plug(PromEx.Plug, prom_ex_module: Plausible.PromEx) + plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint]) - plug Plug.Parsers, + plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library() + ) - plug Sentry.PlugContext + plug(Sentry.PlugContext) - plug Plug.MethodOverride - plug Plug.Head + plug(Plug.MethodOverride) + plug(Plug.Head) - plug PlausibleWeb.Plugs.RuntimeSessionAdapter, @session_options + plug(PlausibleWeb.Plugs.RuntimeSessionAdapter, @session_options) - socket "/live", Phoenix.LiveView.Socket, + socket("/live", PlausibleWeb.LiveSocket, websocket: [ check_origin: true, connect_info: [session: {__MODULE__, :patch_session_opts, []}] ] + ) - plug CORSPlug - plug PlausibleWeb.Router + plug(CORSPlug) + plug(PlausibleWeb.Router) + + def secure_cookie?, do: config!(:secure_cookie) def websocket_url() do - :plausible - |> Application.fetch_env!(__MODULE__) - |> Keyword.fetch!(:websocket_url) + config!(:websocket_url) end def patch_session_opts() do @@ -75,6 +80,15 @@ defmodule PlausibleWeb.Endpoint do # is used to inject the domain - this way we can authenticate # websocket requests within single root domain, in case websocket_url() # returns a ws{s}:// scheme (in which case SameSite=Lax is not applicable). - Keyword.put(@session_options, :domain, host()) + @session_options + |> Keyword.put(:domain, host()) + |> Keyword.put(:key, "_plausible_#{Application.fetch_env!(:plausible, :environment)}") + |> Keyword.put(:secure, secure_cookie?()) + end + + defp config!(key) do + :plausible + |> Application.fetch_env!(__MODULE__) + |> Keyword.fetch!(key) end end diff --git a/lib/plausible_web/live_socket.ex b/lib/plausible_web/live_socket.ex new file mode 100644 index 0000000000..2f8fdaf484 --- /dev/null +++ b/lib/plausible_web/live_socket.ex @@ -0,0 +1,15 @@ +defmodule PlausibleWeb.LiveSocket do + use Phoenix.LiveView.Socket + require Logger + + def connect(params, %Phoenix.Socket{} = socket, connect_info) do + case Phoenix.LiveView.Socket.connect(params, socket, connect_info) do + {:ok, %Phoenix.Socket{private: %{connect_info: %{session: nil}}}} -> + Logger.error("Could not connect the live socket: no session found") + {:error, :socket_auth_error} + + other -> + other + end + end +end diff --git a/lib/plausible_web/plugs/runtime_session_adapter.ex b/lib/plausible_web/plugs/runtime_session_adapter.ex index ae5b1f2217..f06c047360 100644 --- a/lib/plausible_web/plugs/runtime_session_adapter.ex +++ b/lib/plausible_web/plugs/runtime_session_adapter.ex @@ -21,10 +21,16 @@ defmodule PlausibleWeb.Plugs.RuntimeSessionAdapter do end defp patch_cookie_domain(%{cookie_opts: cookie_opts} = runtime_opts) do - Map.replace( + Map.put( runtime_opts, :cookie_opts, - Keyword.put_new(cookie_opts, :domain, PlausibleWeb.Endpoint.host()) + cookie_opts + |> Keyword.put_new(:domain, PlausibleWeb.Endpoint.host()) + |> Keyword.put( + :secure, + Application.fetch_env!(:plausible, PlausibleWeb.Endpoint)[:secure_cookie] + ) ) + |> Map.put(:key, "_plausible_#{Application.fetch_env!(:plausible, :environment)}") end end