# `PhoenixKit.Users.Auth.SessionRef`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v3.5.0/lib/phoenix_kit/users/auth/session_ref.ex#L1)

A live browser session as an actor, for code that runs on a person's behalf without a
`Plug.Conn` (an assistant LiveView, a task it starts).

A session is referred to by `{session_token_uuid, user_uuid}`: the `uuid` of its row in
`phoenix_kit_users_tokens` and the user it belongs to. Neither is a secret, so the pair
may travel in the signed (but not encrypted) session map of a nested LiveView; the raw
session token must not, and never leaves the cookie session.

`scope_for_session/2` rebuilds the user's `PhoenixKit.Users.Auth.Scope` from that row
on every call, by the rules a web request follows
(`PhoenixKit.Users.Auth.UserToken.verify_session_token_uuid_query/1`): the row is a
`"session"` row and has not expired, its user is not a service user, and the scope is
narrowed to the role this session acts as (`PhoenixKit.Users.ActiveRole`). The account
must also pass the gate every authenticated page applies: a confirmed email while
`require_email_confirmation` is on, and the invite-only gate
(`PhoenixKit.Users.Referrals.access_satisfied?/1`), so a session acts with no more than
its pages would let it. Logging out, revoking the session or blocking the user takes
effect on the next call.

`from_session/1` reads the reference from a LiveView's session map.

The pair is not a secret, but it is an in-process credential: any code that hands it to
`PhoenixKit.API.Executor.execute_as/5` or `visible_for/3` acts as that session. Take it
only from the signed session of the LiveView that runs in that session
(`from_session/1` in its connected mount), never from params or client events.

Maintenance mode is deliberately not checked on this path: the web shows the maintenance
layout at mount, where nothing that calls the Executor is rendered.

# `account_refusal`

```elixir
@type account_refusal() :: gate_refusal() | :service_user | :managed_organization
```

Why `scope_for_user/2` stops an account that has no web session of its own: a service user
or a managed organization. A session never belongs to either on its own, so
`scope_for_session/2` never answers these.

# `gate_refusal`

```elixir
@type gate_refusal() :: :inactive | :unconfirmed | :referral_required
```

Why the account gate stops a user: `:inactive` (the user is blocked), `:unconfirmed` (the
email is not confirmed and confirmation is required) or `:referral_required` (the
invite-only gate still asks for a referral code).

# `refusal`

```elixir
@type refusal() :: :session_gone | :mismatch | gate_refusal()
```

Why a session cannot act: `:session_gone` (no such live session: logged out, revoked,
expired, not a session row, or a service user's), `:mismatch` (the session belongs to
another user), or a refusal of the account gate (`t:gate_refusal/0`).

# `t`

```elixir
@type t() :: {String.t(), String.t()}
```

`{session_token_uuid, user_uuid}`.

# `from_session`

```elixir
@spec from_session(map()) :: {:ok, t()} | :error
```

The reference of the session a LiveView runs in, read from its session map: the
`"user_token"` of the cookie session resolved to `{session_token_uuid, user_uuid}`.
`:error` when there is no token or it is not a live session.

Call it in the nested LiveView itself (a sticky `live_render` beside the page, e.g. a
widget in the layout): LiveView merges the cookie session into the session of every
LiveView, nested ones included, both in the connected mount (the socket's
`connect_info: [session: ...]`) and in the static render (the parent's conn session).
The reference read in a static render is the same one, but act on it (call the
executor) only in the connected mount.
Do not have a host page read the reference and pass it in the nested LiveView's
`:session`: a widget rendered by the core layout has no host that holds the cookie
session. Like `PhoenixKitWeb.Users.MultiSession.impersonated?/1`, call it where the
session is signed and trusted, never with data from params or events.

# `scope_for_session`

```elixir
@spec scope_for_session(term(), term()) ::
  {:ok, PhoenixKit.Users.Auth.Scope.t()} | {:error, refusal()}
```

The scope of the live session `session_token_uuid`, which must belong to `user_uuid`.

Returns `{:ok, scope}` narrowed to the session's active role, or `{:error, reason}`
(`t:refusal/0`). Never raises on a malformed reference.

# `scope_for_user`

```elixir
@spec scope_for_user(PhoenixKit.Users.Auth.User.t(), keyword()) ::
  {:ok, PhoenixKit.Users.Auth.Scope.t()} | {:error, account_refusal()}
```

The scope of `user` if the web's account gate lets them in: the user is active, has a
confirmed email while `require_email_confirmation` is on, and passes the invite-only
gate. These are the checks `scope_for_session/2` applies after finding the session, for
an actor that is not a browser session (a linked Telegram chat, see
`PhoenixKit.API.Executor.execute_as/5`). Pass the user as just read from the database.

Two kinds of account have no web session of their own, and so no scope here either: a
service user (`api_only`) and a managed organization (`managed_by_members`), whose only
session is one minted for a manager and bound to that person's grant
(`PhoenixKit.Users.Auth.UserToken`). A caller that has no such session to check must not
act for them, so both are refused first.

Options: `:narrow` (default `true`), as `PhoenixKit.Users.Auth.Scope.for_user/2`:
narrowed to the role the user acts as, or (`false`) the union of every held role.

Returns `{:ok, scope}` or `{:error, reason}` (`t:account_refusal/0`).

---

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