Behavior for customizing Lotus Web dashboard access and functionality.
Summary
Types
An action the dashboard asks authorize/3 about. See
Lotus.Web.Authorization for the resource each action takes.
Callbacks
Decide whether a user may perform an action on a resource.
Determine the access level for a user.
Build the actor that the dashboard passes to Lotus core as :context.
Build the scope that the dashboard passes to Lotus core as :scope.
Extract the current user from a Plug.Conn when the dashboard mounts.
Types
@type access_level() :: :all | :read_only | :forbidden | {:forbidden, String.t()}
@type action() ::
:query
| :export
| :discover
| :create_query
| :update_query
| :delete_query
| :share_query
| :share_dashboard
| :view_dashboard
| :manage_dashboard
| :ai_generate
| :manage_source
| :manage_cache
An action the dashboard asks authorize/3 about. See
Lotus.Web.Authorization for the resource each action takes.
Callbacks
@callback authorize(user :: user(), action :: action(), resource :: term()) :: :allow | {:deny, String.t()}
Decide whether a user may perform an action on a resource.
The dashboard asks before it renders a control, and hides the control when
the answer is a deny. It asks again in the event handler and shows the
reason of a deny to the user. The CSV export route asks for :query and
:export on the source and answers 403 on a deny. The query editor offers,
browses and autocompletes only the sources the user may :discover or
:query.
resource is nil for an action that takes no resource, a data source
name for :query, :export, :discover and :ai_generate, and a query or dashboard
struct for the content actions. Lotus.Web.Authorization lists each one.
Without this callback the decision derives from resolve_access/1, see
Lotus.Web.Authorization.default_decision/2. The dashboard calls it on
render, so it must not do I/O per call. A public dashboard never calls it.
Examples
def authorize(%{role: :analyst}, action, _resource)
when action in [:query, :export, :view_dashboard, :create_query],
do: :allow
def authorize(%{role: :admin}, _action, _resource), do: :allow
def authorize(_user, action, _resource),
do: {:deny, "Your role does not allow #{action}"}
@callback resolve_access(user :: user()) :: access_level()
Determine the access level for a user.
Based on the user returned from resolve_user/1, this callback determines
what operations the user can perform in the Lotus dashboard.
Return Values
:all- Full access to all Lotus features:read_only- Can only view and run queries, no modifications:forbidden- No access{:forbidden, path}- Redirect to the given path
Build the actor that the dashboard passes to Lotus core as :context.
Core hands :context to middleware and to the :after_discover telemetry
event without interpreting it, so this is where a host app says who is
behind a dashboard-driven query. Without it an access-control plug sees
nil for everything the dashboard runs.
Returns nil by default. The value is rebuilt on every mount, so it does
not need to survive a session round trip.
Examples
def resolve_context(%{id: id, roles: roles}), do: %{user_id: id, roles: roles}
def resolve_context(nil), do: nil
Build the scope that the dashboard passes to Lotus core as :scope.
Core hands :scope to the visibility resolver and hashes it into the
discovery and result cache keys, so two scopes never read each other's
cached rows. Keep it low-cardinality — a tenant id, not a user id with a
timestamp — or the caches stop earning their keep.
Returns nil by default, which keeps cache keys identical to an
unscoped dashboard.
Examples
def resolve_scope(%{tenant_id: tenant_id}), do: %{tenant_id: tenant_id}
def resolve_scope(nil), do: nil
@callback resolve_user(conn :: Plug.Conn.t()) :: user()
Extract the current user from a Plug.Conn when the dashboard mounts.
This callback is invoked when the Lotus dashboard is accessed. The returned user value will be passed to other callbacks for access control decisions.