defmodule LivebookWeb.UserPlug do @moduledoc false # Initializes the session and cookies with user-related info. # # The first time someone visits Livebook # this plug stores a new random user id # in the session under `:current_user_id`. # # Additionally the cookies are checked for the presence # of `"user_data"` and if there is none, a new user # attributes are stored there. This makes sure # the client-side can always access some `"user_data"` # for `connect_params` of the socket connection. @behaviour Plug import Plug.Conn alias Livebook.Users.User @impl true def init(opts), do: opts @impl true def call(conn, _opts) do conn |> ensure_current_user_id() |> ensure_user_data() |> mirror_user_data_in_session() end defp ensure_current_user_id(conn) do if get_session(conn, :current_user_id) do conn else user_id = Livebook.Utils.random_id() put_session(conn, :current_user_id, user_id) end end defp ensure_user_data(conn) do if Map.has_key?(conn.req_cookies, "lb:user_data") do conn else user_data = user_data(User.new()) encoded = user_data |> Jason.encode!() |> Base.encode64() put_resp_cookie( conn, "lb:user_data", encoded, # We disable HttpOnly, so that it can be accessed on the client # and set expiration to 5 years [http_only: false, max_age: 157_680_000] ++ cookie_options() ) end end defp cookie_options() do if Livebook.Config.within_iframe?() do [same_site: "None", secure: true] else [same_site: "Lax"] end end defp user_data(user) do user |> Map.from_struct() |> Map.delete(:id) end # Copies user_data from cookie to session, so that it's # accessible to LiveViews defp mirror_user_data_in_session(conn) do user_data = conn.cookies["lb:user_data"] |> Base.decode64!() |> Jason.decode!() put_session(conn, :user_data, user_data) end end