Limen.LiveView (limen v0.2.1)

Copy Markdown View Source

Gates LiveView connections, see Limen.Socket.

In the root layout, embed the socket token:

<meta name="limen-socket" content={Limen.LiveView.token(@conn)} />

in app.js, send it when connecting:

const limen = document.querySelector("meta[name='limen-socket']")?.content
const liveSocket = new LiveSocket("/live", Socket, {
  params: {_csrf_token: csrfToken, _limen: limen}
})

configure the socket's connect info in the endpoint:

socket "/live", Phoenix.LiveView.Socket,
  websocket: [connect_info: [:peer_data, :x_headers, :user_agent, :uri, session: @session]]

and check connections in the router, with the instance that gates the pages (see Limen.Socket.check/3 for the options):

live_session :default, on_mount: {Limen.LiveView, otp_app: :my_app} do
  ...
end

With a :policy, its trust rules are checked first, with the facts stated by put_facts/2 in an earlier hook, so that clients the HTTP gate trusts (say, signed-in users) connect whatever their token, as they browse whatever their prefix:

live_session :default,
  on_mount: [MyAppWeb.LimenFacts, {Limen.LiveView, otp_app: :my_app, policy: MyApp.BotPolicy}] do
  ...
end

Only connected mounts are checked; the initial HTTP render went through Limen.Plug. A connection that fails the check is redirected to the page it was mounting, which takes the client back through the HTTP gate (and its challenge, if needed). LiveView does not give hooks the page's URL, so it is rebuilt from the route the view is mounted at and its parameters; a view rendered outside the router goes to /.

The hook also keeps the connection's connect info, which LiveView only exposes while mounting, so that check_form/3 can check form traps (see Limen.Trap) in handle_event/3. The connect info describes the socket, whose URI is the socket's own (/live/websocket), so for views mounted at the router the hook also follows the page's URL as LiveView reports it to handle_params/3, and form checks record the page's path.

Summary

Functions

Checks a form submitted to a LiveView for the tells of Limen.Trap.form_fields/2, see Limen.Trap.check_form/3.

States facts about the connection, as Limen.put_facts/2 does for requests.

Issues the socket token for the page being rendered, see Limen.Socket.token/2.

Functions

check_form(socket, params, opts)

@spec check_form(Phoenix.LiveView.Socket.t(), map(), keyword()) ::
  {:ok, Limen.Decision.t()} | {:trapped, Limen.Decision.t()}

Checks a form submitted to a LiveView for the tells of Limen.Trap.form_fields/2, see Limen.Trap.check_form/3.

def handle_event("save", params, socket) do
  case Limen.LiveView.check_form(socket, params, otp_app: :my_app) do
    {:ok, _decision} -> save(socket, params)
    {:trapped, _decision} -> {:noreply, pretend_it_worked(socket)}
  end
end

LiveView only gives access to connect info while mounting, so the socket must have been mounted with the on_mount hook of this module, which keeps the connect info Limen.Socket documents for later checks. Options are those of Limen.Trap.check_form/3, where :instance or :otp_app is required.

put_facts(socket, facts)

States facts about the connection, as Limen.put_facts/2 does for requests.

Call it from an on_mount hook that runs before this module's, such as the one that loads the signed-in user:

live_session :default,
  on_mount: [{MyAppWeb.UserAuth, :mount_current_user}, MyAppWeb.LimenFacts,
             {Limen.LiveView, otp_app: :my_app, policy: MyApp.BotPolicy}] do
  ...
end

defmodule MyAppWeb.LimenFacts do
  def on_mount(:default, _params, _session, socket) do
    {:cont, Limen.LiveView.put_facts(socket, signed_in: socket.assigns.current_user != nil)}
  end
end

The socket check and check_form/3 record them in their decisions, and the trust rules of the hook's :policy can use them.

token(conn, opts \\ [])

@spec token(Plug.Conn.t(), keyword()) :: String.t() | nil

Issues the socket token for the page being rendered, see Limen.Socket.token/2.