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.
Summary
Types
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.
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).
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 (gate_refusal/0).
{session_token_uuid, user_uuid}.
Functions
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.
The scope of the live session session_token_uuid, which must belong to user_uuid.
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.
Types
@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.
@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).
@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 (gate_refusal/0).
{session_token_uuid, user_uuid}.
Functions
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.
@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}
(refusal/0). Never raises on a malformed reference.
@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} (account_refusal/0).