# `PhoenixKit.Users.Auth.Scope`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.2.0/lib/phoenix_kit/users/auth/scope.ex#L1)

Scope module for encapsulating PhoenixKit authentication state.

This module provides a structured way to handle user authentication context
throughout your Phoenix application, similar to Phoenix's built-in authentication
patterns but with PhoenixKit prefixing to avoid conflicts.

## Nil scopes

**Every predicate and accessor here tolerates a `nil` scope and answers as if
the visitor were unauthenticated.** Host layouts render on kit auth pages with
`phoenix_kit_current_scope` unset, so `authenticated?(nil)` and
`anonymous?(nil)` are ordinary calls, not programmer errors — and "no scope"
and "a scope with no user" mean the same thing to a caller deciding what to
render.

The exception is `to_map/1`, which deliberately does not accept `nil`. There
is nothing meaningful to serialize for a non-scope, and returning an empty map
would fabricate data that looks real; it is a debug/serialization helper, so
failing surfaces the bug where permissiveness would bury it. Its spec keeps
`t()`, so the type checker rejects `to_map(nil)` at compile time rather than
leaving it to blow up at runtime.

Apply the same rule to anything added here: if `nil` has a correct answer,
give it one; if answering would mean inventing data, let it raise.

## Usage

    # Create scope for authenticated user
    scope = Scope.for_user(user)

    # Create scope for anonymous user
    scope = Scope.for_user(nil)

    # Check authentication status
    Scope.authenticated?(scope)  # true or false

    # Get user information
    Scope.user(scope)        # %User{} or nil
    Scope.user_uuid(scope)   # user.uuid or nil
    Scope.user_email(scope)  # user.email or nil

## Role & State Checks

    Scope.has_role?(scope, "Admin")  # true/false
    Scope.owner?(scope)             # Owner role?
    Scope.can_access_admin_area?(scope)  # Owner, Admin, or custom role with permissions?
    Scope.system_role?(scope)       # Strictly Owner or Admin (not custom roles)?
    Scope.anonymous?(scope)         # Not authenticated?
    Scope.user_roles(scope)         # ["Admin", "User"]
    Scope.user_full_name(scope)     # "John Doe" or nil
    Scope.user_active?(scope)       # true/false
    Scope.to_map(scope)             # Debug-friendly map of all fields

## Module-Level Permissions

Permissions are cached in the scope when it is built via `for_user/1`
(on mount and on PubSub-triggered refresh). Owner gets every key
automatically. Admin defaults to all keys via seeding/auto-grant but is
genuinely gated by its rows — the full-access fallback applies only on an
unseeded install (no permission rows exist at all).

    Scope.has_module_access?(scope, "billing")          # Single key check (pure cache)
    Scope.can?(scope, "calendar.view_others")           # Key held AND module enabled
    Scope.has_any_module_access?(scope, ["billing", "shop"])  # Any of these?
    Scope.has_all_module_access?(scope, ["billing", "shop"])  # All of these?
    Scope.accessible_modules(scope)                     # MapSet of granted keys
    Scope.permission_count(scope)                       # Number of granted keys

## Struct Fields

- `:user` - The current user struct or nil
- `:authenticated?` - Boolean indicating if user is authenticated
- `:cached_roles` - List of role name strings, loaded at scope creation
- `:cached_permissions` - MapSet of granted permission keys, loaded at scope creation

# `t`

```elixir
@type t() :: %PhoenixKit.Users.Auth.Scope{
  authenticated?: boolean(),
  cached_permissions: MapSet.t() | nil,
  cached_roles: [String.t()] | nil,
  multi_session_accounts: list(),
  multi_session_allowed?: boolean(),
  user: PhoenixKit.Users.Auth.User.t() | nil
}
```

# `accessible_modules`

```elixir
@spec accessible_modules(t()) :: MapSet.t()
```

Returns the set of module keys the user can access.

# `admin?`

> This function is deprecated. Use can_access_admin_area?/1 — `admin?` is true for ANY permission holder, not just the Admin role..

```elixir
@spec admin?(t()) :: boolean()
```

Deprecated alias for `can_access_admin_area?/1`.

The name misleads: it returns `true` for ANY permission holder, not just the
Admin role. Call `can_access_admin_area?/1` instead.

# `anonymous?`

```elixir
@spec anonymous?(t() | nil) :: boolean()
```

Checks if the scope represents an anonymous (non-authenticated) user.

## Examples

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.anonymous?(scope)
    true

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.anonymous?(scope)
    false

# `authenticated?`

```elixir
@spec authenticated?(t() | nil) :: boolean()
```

Checks if the scope represents an authenticated user.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.authenticated?(scope)
    true

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.authenticated?(scope)
    false

# `can?`

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

Checks whether the user holds a permission key AND that key is currently
effective — the module behind it (or behind its parent, for sub-permission
keys like `"calendar.view_others"`) is enabled.

This is the check modules should use for fine-grained, in-page
authorization. Unlike `has_module_access?/2` (a pure cache lookup used on
hot paths where enablement is enforced separately at mount), `can?/2`
consults live module-enablement state, so a scope snapshotted before a
module was disabled cannot keep authorizing its actions.

## Examples

    Scope.can?(scope, "calendar.edit_others")
    Scope.can?(scope, "calendar")

# `can_access_admin_area?`

```elixir
@spec can_access_admin_area?(t()) :: boolean()
```

Checks if the user can access the admin AREA — the `/admin` shell entry gate.

Returns true when the user holds the Admin or Owner role, OR has been
explicitly granted any module-level permission (via `RolePermission`) — so a
custom role (e.g. "Editor", "Support") holding at least one permission can
enter the admin area.

This is a COARSE entry gate only. It does NOT mean the user is a privileged
operator: holding a single grant is enough. Which pages and actions are
actually allowed is enforced per-view by `has_module_access?/2` / `can?/2`.
For a "can do everything, like Owner" check use `holds_all_enabled_permissions?/1`
or `superadmin?/1` instead — never this.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.can_access_admin_area?(scope)
    true

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.can_access_admin_area?(scope)
    false

# `for_user`

```elixir
@spec for_user(PhoenixKit.Users.Auth.User.t() | nil) :: t()
```

Creates a new scope for the given user.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001", email: "user@example.com"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> scope.authenticated?
    true
    iex> scope.user.email
    "user@example.com"

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> scope.authenticated?
    false
    iex> scope.user
    nil

# `has_all_module_access?`

```elixir
@spec has_all_module_access?(t(), [String.t()]) :: boolean()
```

Checks if the user has access to all of the given module keys.

## Examples

    Scope.has_all_module_access?(scope, ["billing", "shop"])

# `has_any_module_access?`

```elixir
@spec has_any_module_access?(t(), [String.t()]) :: boolean()
```

Checks if the user has access to at least one of the given module keys.

## Examples

    Scope.has_any_module_access?(scope, ["billing", "shop"])

# `has_module_access?`

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

Checks if the user has access to a specific admin module/section.

Looks up `module_key` in `cached_permissions`. Owner access works because
`for_user/1` pre-populates all keys for owners; this function itself does
not special-case roles.

# `has_role?`

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

Checks if the user has a specific role.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.has_role?(scope, "Admin")
    true

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.has_role?(scope, "Admin")
    false

# `holds_all_enabled_permissions?`

```elixir
@spec holds_all_enabled_permissions?(t()) :: boolean()
```

Whether this scope holds EVERY currently-grantable permission key — i.e. it
can reach everything the permission system exposes right now, exactly like
Owner. Purely permission-based and role-AGNOSTIC: a custom role granted all
permissions returns `true`, with no role-name special-casing.

Compared against `Permissions.enabled_module_keys/0` (the grantable set the
permissions matrix produces), NOT `all_module_keys/0`. Disabled modules
expose no reachable admin surface and their keys can't be granted via the UI,
so a grant-all custom role legitimately lacks them — including them would
re-introduce the Owner-vs-custom asymmetry this is meant to remove (Owner's
set is a superset that trivially satisfies the subset test either way). The
`size > 0` guard stops an empty grantable set from making `subset?/2`
vacuously true (a fail-open) for every scope; in practice the 5 core + 2
integration keys are a non-disableable floor, but the guard never rots.

# `owner?`

```elixir
@spec owner?(t()) :: boolean()
```

Checks if the user is an owner.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.owner?(scope)
    true

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.owner?(scope)
    false

# `permission_count`

```elixir
@spec permission_count(t()) :: non_neg_integer()
```

Returns the number of module permissions the user has been granted.

# `superadmin?`

```elixir
@spec superadmin?(t()) :: boolean()
```

Whether the scope holds the wildcard superadmin key (`"*"`) — a blanket,
drift-immune grant to every permission-gated FEATURE/VIEW. Owner holds it by
construction; a host can grant it to a custom role to make that role
Owner-equivalent for feature access with one grant, and unlike a "grant every
current key" role it stays complete as new modules are added.

Scope: feature/view access only. The Owner-only role-management safety rails
(editing the Admin role, assigning Owner/Admin, the last-Owner guard) remain
role-name-based by design and are NOT unlocked by `"*"`.

# `system_role?`

```elixir
@spec system_role?(t()) :: boolean()
```

Checks if the user holds the Owner or Admin system role.

Unlike `admin?/1` which also returns true for custom roles with permissions,
this strictly checks for the two built-in system roles.

# `to_map`

```elixir
@spec to_map(t()) :: map()
```

Converts scope to a map for debugging or logging purposes.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001", email: "user@example.com"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.to_map(scope)
    %{
      authenticated?: true,
      user_uuid: "019...",
      user_email: "user@example.com",
      user_roles: ["Admin", "User"],
      owner?: false,
      admin?: true
    }

# `user`

```elixir
@spec user(t() | nil) :: PhoenixKit.Users.Auth.User.t() | nil
```

Gets the user from the scope.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001", email: "user@example.com"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.user(scope)
    %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001", email: "user@example.com"}

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.user(scope)
    nil

# `user_active?`

```elixir
@spec user_active?(t() | nil) :: boolean()
```

Checks if the user is active.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{is_active: true}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.user_active?(scope)
    true

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.user_active?(scope)
    false

# `user_email`

```elixir
@spec user_email(t() | nil) :: String.t() | nil
```

Gets the user email from the scope.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001", email: "user@example.com"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.user_email(scope)
    "user@example.com"

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.user_email(scope)
    nil

# `user_full_name`

```elixir
@spec user_full_name(t() | nil) :: String.t() | nil
```

Gets the user's full name.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{first_name: "John", last_name: "Doe"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.user_full_name(scope)
    "John Doe"

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.user_full_name(scope)
    nil

# `user_roles`

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

Gets all roles for the user.

## Examples

    iex> user = %PhoenixKit.Users.Auth.User{uuid: "0193a5e4-0000-7000-8000-000000000001"}
    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.user_roles(scope)
    ["Admin", "User"]

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.user_roles(scope)
    []

# `user_uuid`

```elixir
@spec user_uuid(t() | nil) :: String.t() | nil
```

Gets the user ID (UUID) from the scope.

## Examples

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(user)
    iex> PhoenixKit.Users.Auth.Scope.user_uuid(scope)
    "0193a5e4-..."

    iex> scope = PhoenixKit.Users.Auth.Scope.for_user(nil)
    iex> PhoenixKit.Users.Auth.Scope.user_uuid(scope)
    nil

---

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