ArcanaWeb.Router (Arcana v3.0.0)

Copy Markdown View Source

Provides LiveView routing for the Arcana dashboard.

Usage

Add to your router:

import ArcanaWeb.Router

scope "/" do
  pipe_through :browser

  arcana_dashboard "/arcana"
end

Options

  • :live_socket_path - The path to the LiveView socket. Defaults to "/live".

  • :repo - The Ecto repo to use for Arcana operations. If not provided, falls back to Application.get_env(:arcana, :repo).

  • :on_mount - Optional list of Phoenix.LiveView.on_mount/1 callbacks to add to the dashboard's live_session.

  • :live_session_name - The name of the dashboard's live_session. Defaults to :arcana_dashboard. Phoenix requires live_session names to be unique within a router, so mounting the dashboard more than once (say, a superuser dashboard plus a scoped one) needs a distinct name for each extra mount.

  • :collections - Optional {module, function} pair that scopes the dashboard to a subset of collections. The function receives the %Plug.Conn{} of the page request and must return either :all (no restriction) or a list of collection names. Every dashboard surface (listings, search, ask, ingest, evaluation, maintenance, stats) is limited to those collections, and events naming any other collection are rejected server-side. A plain {module, function} tuple is required (not a function capture) so the route metadata stays serializable. See "Scoping collections" for when the decision is re-evaluated.

Example with options

arcana_dashboard "/arcana",
  repo: MyApp.Repo,
  on_mount: [MyAppWeb.Auth]

Scoping collections

arcana_dashboard "/arcana",
  collections: {MyAppWeb.ArcanaAccess, :allowed_collections}

defmodule MyAppWeb.ArcanaAccess do
  def allowed_collections(conn) do
    case conn.assigns.current_user do
      %{admin: true} -> :all
      %{tenant: tenant} -> ["#{tenant}-docs"]
    end
  end
end

When the decision is made

The function runs while the page request is being served, and its result is snapshotted into the LiveView session: Phoenix signs it into the rendered data-phx-session payload, which stays valid for the session's max age (14 days by default). The websocket connect, every reconnect, and every live_patch/live_redirect inside the dashboard read back that snapshot instead of calling the function again. Only a full page request re-runs it.

So narrowing a user's permissions does not narrow an already-rendered dashboard. Until the user loads a fresh page, they keep the scope they mounted with. The on_mount hook cannot close that gap on its own — there is no %Plug.Conn{} in a LiveView mount to re-resolve from.

The mitigation is to cut the socket when permissions change. Set a live_socket_id per user in your session (Phoenix's put_session(conn, :live_socket_id, "users_socket:#{user.id}")), then broadcast a disconnect when their access changes:

MyAppWeb.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})

The client reconnects through a full request, which re-runs the MFA with the current %Plug.Conn{}. Pair it with a short session max age if you need a hard upper bound on how long a stale scope can live.

Summary

Functions

Defines an Arcana dashboard route.

Functions

arcana_dashboard(path, opts \\ [])

(macro)

Defines an Arcana dashboard route.

It expects the path the dashboard will be mounted at and a set of options.