# `PhoenixKit.Users.ActiveRole`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.23.0/lib/phoenix_kit/users/active_role.ex#L1)

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 `t:config/0` are pure, so the rules are unit-testable
without a database.

# `config`

```elixir
@type config() :: %{enabled?: boolean(), always_on: [String.t()]}
```

# `role`

```elixir
@type role() :: %{uuid: String.t(), name: String.t()}
```

# `config`

```elixir
@spec config() :: config()
```

The switcher configuration, read from settings.

Only the enabled flag is read while the feature is off.

# `effective_role_names`

```elixir
@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.

# `effective_roles`

```elixir
@spec effective_roles([role()], role() | nil, config()) :: [role()]
```

The roles in effect while acting as `active`: the active role plus every
always-on role held. With no active role, every held role.

# `location`

```elixir
@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).

# `parse_always_on`

```elixir
@spec parse_always_on(term()) :: [String.t()]
```

Parses the `role_switcher_always_on_roles` setting: comma-separated role
uuids, returned without duplicates. Anything that is not a string is `[]`.

# `parse_location`

```elixir
@spec parse_location(term()) :: :menu | :header
```

Parses `role_switcher_location`. Anything unrecognised is `:menu`.

# `resolve`

```elixir
@spec resolve([role()], String.t() | nil, config()) :: role() | nil
```

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.

# `session_role_names`

```elixir
@spec session_role_names([map()]) :: %{optional(String.t()) =&gt; String.t() | nil}
```

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.

# `session_role_uuid`

```elixir
@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).

# `switch`

```elixir
@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.

# `switchable?`

```elixir
@spec switchable?(role(), config()) :: boolean()
```

Whether `role` is a mode the user can act as, rather than always on.

# `switchable_roles`

```elixir
@spec switchable_roles([role()], config()) :: [role()]
```

The held roles that are switchable, in the order given.

---

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