# `Lotus.Web.Authorization`
[🔗](https://github.com/elixir-lotus/lotus_web/blob/v1.2.0/lib/lotus/web/authorization.ex#L1)

Asks the host whether the current user may perform an action.

The dashboard asks one question in one place: **may this user do this
action, on this resource?** It asks before a control renders, so a control
the user may not use is not shown, and again in the event handler, so a
crafted event is refused too.

The host answers through `c:Lotus.Web.Resolver.authorize/3`. A resolver
that does not implement it gets a decision derived from
`c:Lotus.Web.Resolver.resolve_access/1`, see `default_decision/2`.

## Actions and resources

| Action | Resource |
|---|---|
| `:query` | the data source name |
| `:export` | the data source name |
| `:ai_generate` | the data source name |
| `:discover` | the data source name |
| `:create_query` | `nil` |
| `:update_query` | the stored `%Lotus.Storage.Query{}` |
| `:delete_query` | the `%Lotus.Storage.Query{}` |
| `:share_query` | the `%Lotus.Storage.Query{}` (no dashboard control asks it yet) |
| `:share_dashboard` | the `%Lotus.Storage.Dashboard{}` whose public link changes |
| `:view_dashboard` | the `%Lotus.Storage.Dashboard{}` |
| `:manage_dashboard` | `nil` for a new dashboard, else the `%Lotus.Storage.Dashboard{}` |
| `:manage_source` | `nil` |
| `:manage_cache` | `nil` |

## Cost

Templates ask on every render. At mount the dashboard computes the actions
that take no resource once, into the `:permissions` assign, and
`allowed?/3` reads that map. Actions with a resource are asked on demand, so
the callback must not do I/O per call; cache the policy in the host.

# `decision`

```elixir
@type decision() :: :allow | {:deny, String.t()}
```

A decision: allow, or deny with a reason the dashboard shows the user.

# `actions`

```elixir
@spec actions() :: [Lotus.Web.Resolver.action(), ...]
```

The action vocabulary, in a fixed order.

# `allowed?`

```elixir
@spec allowed?(map(), Lotus.Web.Resolver.action(), term()) :: boolean()
```

Return `true` when the user in `assigns` may perform `action` on `resource`.

For an action without a resource, reads the `:permissions` assign when the
dashboard computed it at mount. Use it in templates.

# `authorize`

```elixir
@spec authorize(map(), Lotus.Web.Resolver.action(), term()) :: decision()
```

Decide whether the user in `assigns` may perform `action` on `resource`.

Reads `:resolver`, `:user`, `:access` and `:public_view` from `assigns`.
Always asks, and never reads the `:permissions` cache. Use it in event
handlers, and show the reason of a `{:deny, reason}` to the user.

A public dashboard has no user, so the host is not asked: the decision
derives from `:read_only` access.

Assigns with no `:resolver` or `:access` key mean the dashboard never handed
them down. The error is logged and the decision derives from `:read_only`
access, so the mistake fails closed. Under
`config :lotus_web, strict_actor: true` it raises instead, the same as
`Lotus.Web.Actor.opts/1` does for a missing actor.

# `decide`

```elixir
@spec decide(
  module() | nil,
  Lotus.Web.Resolver.user(),
  Lotus.Web.Resolver.access_level(),
  Lotus.Web.Resolver.action(),
  term()
) :: decision()
```

Decide for an explicit resolver, user and access level.

Calls `c:Lotus.Web.Resolver.authorize/3` when the resolver implements it,
else returns `default_decision/2` for `access`. Use it where no socket
assigns exist, such as a controller.

# `default_decision`

```elixir
@spec default_decision(Lotus.Web.Resolver.access_level(), Lotus.Web.Resolver.action()) ::
  decision()
```

The decision for an access level when the resolver does not implement
`c:Lotus.Web.Resolver.authorize/3`.

| Access | Decision |
|---|---|
| `:all` | `:allow` for every action |
| `:read_only` | `:allow` for `:query`, `:export`, `:discover` and `:view_dashboard`, `{:deny, reason}` for the rest |
| `:forbidden`, `{:forbidden, path}` | `{:deny, reason}` for every action |

A host resolver can call it to keep the default for some actions.

# `discoverable?`

```elixir
@spec discoverable?(map(), String.t()) :: boolean()
```

Return `true` when the user in `assigns` may browse `source`: see it in the
source list, and see its schemas, tables and columns.

Browsing needs `:discover` or `:query` on the source. Running a query on it
still needs `:query`.

# `permissions`

```elixir
@spec permissions(map()) :: %{required(Lotus.Web.Resolver.action()) =&gt; boolean()}
```

Compute the decisions for the actions without a resource, as a map of
action to boolean. The dashboard stores it as the `:permissions` assign.

# `reason`

```elixir
@spec reason(Lotus.Web.Resolver.action()) :: String.t()
```

The reason the dashboard shows when it denies `action`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
