The role a session is currently acting as.
By default a user's access is the union of every role they hold. With the
role switcher on (role_switcher_enabled), a user holding two or more
switchable roles acts as exactly one of them at a time, and their scope —
roles and permissions alike — is narrowed to that role plus every
always-on role they hold. An Admin who switches to "Seller" has no admin
access at all until they switch back.
Switchable and always-on roles
- Owner and Admin are always switchable — otherwise narrowing could never take admin access away.
- User is always on: it applies whichever role is active.
- Custom roles are switchable unless listed in
role_switcher_always_on_roles(comma-separated role uuids).
Where the active role lives
On the session token (phoenix_kit_users_tokens.active_role_uuid,
V190), so it is per browser session: Admin on the laptop, Seller on the
phone, a role of its own for an impersonation, a fresh start for a second
multi-session account. NULL means the default role — the first
switchable role the user holds in role order (phoenix_kit_user_roles. position: Owner, Admin, then the operator's order) — and nothing is written
until the user switches.
It reaches PhoenixKit.Users.Auth.Scope.for_user/1 through the user's
virtual field active_role_uuid, which only
PhoenixKit.Users.Auth.UserToken.verify_session_token_query/1 fills: every
web path starts from a session token, so every scope build narrows without
knowing about tokens. A user loaded any other way (by uuid, in a background
job, in an admin list) has nil there and acts as their default role —
the same rule as a session that never switched, and never wider than a
session could be. Never copy the value anywhere else (a session key, an
assign): the scope is rebuilt from the token in plugs, every LiveView mount
and the role-change refresh.
The stored value is never trusted on its own. resolve/3 only ever returns a
role the user holds and that is switchable right now, so the narrowed scope
can never exceed the user's real grants. Nothing here writes on read.
The functions taking a config/0 are pure, so the rules are unit-testable
without a database.
Summary
Functions
The switcher configuration, read from settings.
The names of the roles in effect for user: the active role plus the
always-on roles while narrowed, every held role otherwise.
The roles in effect while acting as active: the active role plus every
always-on role held. With no active role, every held role.
Where the switcher is shown: :menu (the account dropdown, default) or
:header (a header control from sm up, the account dropdown below it).
Parses the role_switcher_always_on_roles setting: comma-separated role
uuids, returned without duplicates. Anything that is not a string is [].
Parses role_switcher_location. Anything unrecognised is :menu.
The role a session acts as, given the roles the user holds (in role order)
and the uuid stored on the session. nil means no narrowing: the feature
is off, or the user holds fewer than two switchable roles.
The role in effect for each session in sessions, for the sessions lists.
The role uuid the current session stored, carried on the user loaded from
that session's token, or nil (no session, or the session never switched).
Makes role_uuid the role the session identified by the raw session
token acts as. user is that session's user, as loaded from the token.
Whether role is a mode the user can act as, rather than always on.
The held roles that are switchable, in the order given.
Types
Functions
@spec config() :: config()
The switcher configuration, read from settings.
Only the enabled flag is read while the feature is off.
@spec effective_role_names(PhoenixKit.Users.Auth.User.t()) :: [String.t()]
The names of the roles in effect for user: the active role plus the
always-on roles while narrowed, every held role otherwise.
The same roles Scope.for_user/1 puts in cached_roles, without loading
permissions — for callers that only need names (account labels, the
impersonation authority). Like for_user/1, honours the session role only
when user was loaded from its session token.
The roles in effect while acting as active: the active role plus every
always-on role held. With no active role, every held role.
@spec location() :: :menu | :header
Where the switcher is shown: :menu (the account dropdown, default) or
:header (a header control from sm up, the account dropdown below it).
Parses the role_switcher_always_on_roles setting: comma-separated role
uuids, returned without duplicates. Anything that is not a string is [].
@spec parse_location(term()) :: :menu | :header
Parses role_switcher_location. Anything unrecognised is :menu.
The role a session acts as, given the roles the user holds (in role order)
and the uuid stored on the session. nil means no narrowing: the feature
is off, or the user holds fewer than two switchable roles.
A stored uuid naming a role that is not held, or not switchable, is ignored
and the default applies: the first switchable role in role order. So is
nil — a session that never switched.
The role in effect for each session in sessions, for the sessions lists.
Takes maps with :token_uuid, :user_uuid and :active_role_uuid (what
PhoenixKit.Users.Sessions selects) and returns %{token_uuid => name}
with nil where the session is not narrowed — one roles query for the
whole page, then the pure rules per row.
@spec session_role_uuid(PhoenixKit.Users.Auth.User.t()) :: String.t() | nil
The role uuid the current session stored, carried on the user loaded from
that session's token, or nil (no session, or the session never switched).
@spec switch(PhoenixKit.Users.Auth.User.t(), binary(), String.t()) :: {:ok, role()} | {:error, :disabled | :not_switchable | :not_found}
Makes role_uuid the role the session identified by the raw session
token acts as. user is that session's user, as loaded from the token.
Refused unless the switcher is on and the role is one of the user's switchable roles — and there are at least two of those, or there is nothing to switch between. Switching to the role already in effect writes nothing.
On a change the choice is stored on the session token, logged as
session.role_switched, and broadcast through ScopeNotifier, so every
open LiveView of this user rebuilds its scope — each from its own token, so
only this session actually changes — and leaves pages the new role cannot
reach. Other sessions of the same user, and the user's own sessions while
someone impersonates them, are untouched.
Whether role is a mode the user can act as, rather than always on.
The held roles that are switchable, in the order given.