Packages

phoenix_kit

1.7.214
1.7.214 1.7.213 1.7.212 1.7.211 1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit users auth scope.ex
Raw

lib/phoenix_kit/users/auth/scope.ex

defmodule PhoenixKit.Users.Auth.Scope do
@moduledoc """
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.
## 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
"""
alias PhoenixKit.Users.Auth.User
alias PhoenixKit.Users.Permissions
alias PhoenixKit.Users.Role
@type t :: %__MODULE__{
user: User.t() | nil,
authenticated?: boolean(),
cached_roles: [String.t()] | nil,
cached_permissions: MapSet.t() | nil,
multi_session_accounts: list(),
multi_session_allowed?: boolean()
}
# `multi_session_*` are transient, request-scoped UI fields populated by the
# scope-mounting hook/plug (which have the Plug session) for the header
# account switcher; they are NOT loaded by `for_user/1` and default empty.
defstruct user: nil,
authenticated?: false,
cached_roles: nil,
cached_permissions: nil,
multi_session_accounts: [],
multi_session_allowed?: false
@doc """
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
"""
# Mirrors `Permissions.superadmin_key/0`, kept as a local literal to avoid a
# compile-time call into `Permissions` (which aliases this module — a cycle).
# Holding this wildcard key is a blanket grant honored by `has_module_access?/2`,
# `base_held?/2`, and `can?/2` via `holds?/2`.
@superadmin_key "*"
@spec for_user(User.t() | nil) :: t()
def for_user(%User{} = user) do
# Pre-load user roles to cache them in the scope
cached_roles = User.get_roles(user)
# Load permissions: Owner gets all, others get from DB
roles = Role.system_roles()
cached_permissions =
cond do
roles.owner in cached_roles ->
MapSet.new(Permissions.all_module_keys())
roles.admin in cached_roles ->
case Permissions.get_permissions_for_user(user) do
# Admin with no explicit permissions falls back to full access
# ONLY when the permissions table is genuinely MISSING (pre-V53
# / migrations not yet run). Once the table exists, zero rows
# means an Owner deliberately revoked everything from this
# admin's roles, and that must stick — keyed on table presence,
# NOT row count, so stripping every role bare can never restore
# full access, and a transient query error fails closed to
# empty rather than escalating.
[] ->
if Permissions.permissions_table_ready?() do
MapSet.new()
else
MapSet.new(Permissions.all_module_keys())
end
perms ->
MapSet.new(perms)
end
true ->
Permissions.get_permissions_for_user(user) |> MapSet.new()
end
%__MODULE__{
user: user,
authenticated?: true,
cached_roles: cached_roles,
cached_permissions: cached_permissions
}
end
def for_user(nil) do
%__MODULE__{
user: nil,
authenticated?: false,
cached_roles: [],
cached_permissions: MapSet.new()
}
end
@doc """
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
"""
@spec authenticated?(t()) :: boolean()
def authenticated?(%__MODULE__{authenticated?: authenticated?}), do: authenticated?
@doc """
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
"""
@spec user(t()) :: User.t() | nil
def user(%__MODULE__{user: user}), do: user
@doc """
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
"""
@spec user_uuid(t()) :: String.t() | nil
def user_uuid(%__MODULE__{user: %User{uuid: uuid}}), do: uuid
def user_uuid(%__MODULE__{user: nil}), do: nil
@doc """
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
"""
@spec user_email(t()) :: String.t() | nil
def user_email(%__MODULE__{user: %User{email: email}}), do: email
def user_email(%__MODULE__{user: nil}), do: nil
@doc """
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
"""
@spec anonymous?(t()) :: boolean()
def anonymous?(%__MODULE__{authenticated?: authenticated?}), do: not authenticated?
@doc """
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
"""
@spec has_role?(t(), String.t()) :: boolean()
def has_role?(%__MODULE__{cached_roles: cached_roles}, role_name)
when is_binary(role_name) and is_list(cached_roles) do
role_name in cached_roles
end
def has_role?(_, _role_name), do: false
@doc """
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
"""
@spec owner?(t()) :: boolean()
def owner?(%__MODULE__{cached_roles: cached_roles})
when is_list(cached_roles) do
roles = Role.system_roles()
roles.owner in cached_roles
end
def owner?(_), do: false
@doc """
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
"""
@spec can_access_admin_area?(t()) :: boolean()
def can_access_admin_area?(%__MODULE__{cached_roles: cached_roles, cached_permissions: perms})
when is_list(cached_roles) do
roles = Role.system_roles()
roles.admin in cached_roles or roles.owner in cached_roles or
(not is_nil(perms) and MapSet.size(perms) > 0)
end
def can_access_admin_area?(_), do: false
@doc """
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.
"""
@deprecated "Use can_access_admin_area?/1 — `admin?` is true for ANY permission holder, not just the Admin role."
@spec admin?(t()) :: boolean()
def admin?(scope), do: can_access_admin_area?(scope)
@doc """
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)
[]
"""
@spec user_roles(t()) :: [String.t()]
def user_roles(%__MODULE__{cached_roles: cached_roles}) when is_list(cached_roles) do
cached_roles
end
def user_roles(_), do: []
@doc """
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
"""
@spec user_full_name(t()) :: String.t() | nil
def user_full_name(%__MODULE__{user: %User{} = user}) do
User.full_name(user)
end
def user_full_name(%__MODULE__{user: nil}), do: nil
@doc """
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
"""
@spec user_active?(t()) :: boolean()
def user_active?(%__MODULE__{user: %User{is_active: is_active}}) do
is_active
end
def user_active?(%__MODULE__{user: nil}), do: false
@doc """
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
}
"""
@spec to_map(t()) :: map()
def to_map(%__MODULE__{} = scope) do
%{
authenticated?: authenticated?(scope),
user_uuid: user_uuid(scope),
user_email: user_email(scope),
user_full_name: user_full_name(scope),
user_roles: user_roles(scope),
owner?: owner?(scope),
# Map key kept as `admin?:` for back-compat; sourced from the non-deprecated fn.
admin?: can_access_admin_area?(scope),
user_active?: user_active?(scope)
}
end
@doc """
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.
"""
@spec has_module_access?(t(), String.t()) :: boolean()
def has_module_access?(%__MODULE__{cached_permissions: perms}, module_key)
when is_binary(module_key) and not is_nil(perms) do
holds?(perms, module_key)
end
def has_module_access?(_, _), do: false
# Does this permission set grant `key`? True if the key is held directly, OR
# the set holds the wildcard superadmin key (`"*"`) — a blanket grant that
# covers every key, present and future. For a dotted sub-key, the caller is
# responsible for also checking `base_held?/2` (a raw sub-key membership
# without its base is an orphan). Centralised so has_module_access?/base_held?/
# can? all honor superadmin identically.
defp holds?(perms, key) do
MapSet.member?(perms, key) or MapSet.member?(perms, @superadmin_key)
end
@doc """
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 `"*"`.
"""
@spec superadmin?(t()) :: boolean()
def superadmin?(%__MODULE__{cached_permissions: perms}) when not is_nil(perms) do
MapSet.member?(perms, @superadmin_key)
end
def superadmin?(_), do: false
@doc """
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.
"""
@spec holds_all_enabled_permissions?(t()) :: boolean()
def holds_all_enabled_permissions?(%__MODULE__{cached_permissions: perms} = scope)
when not is_nil(perms) do
# Compare against the OPERATOR baseline: every grantable key EXCEPT the ones
# a default Admin is never auto-granted — the built-in opt-in key (personal
# `integrations`) AND any `auto_grant_admin: false` custom key. Requiring
# those would wrongly deny a default Admin (which holds every operator key
# but not those opt-out extras) Owner-equivalence, locking it out of unmapped
# admin views (the REG-1 regression, which an opt-out custom key otherwise
# reopens). The `"*"` superadmin key satisfies this directly and
# drift-immunely (it is deliberately NOT in `enabled_module_keys/0`).
required =
MapSet.difference(
Permissions.enabled_module_keys(),
MapSet.new(Permissions.admin_baseline_exclusions())
)
superadmin?(scope) or
(MapSet.size(required) > 0 and MapSet.subset?(required, perms))
end
def holds_all_enabled_permissions?(_), do: false
@doc """
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")
"""
@spec can?(t(), String.t()) :: boolean()
def can?(%__MODULE__{cached_permissions: perms}, key)
when is_binary(key) and not is_nil(perms) do
holds?(perms, key) and base_held?(perms, key) and Permissions.feature_enabled?(key)
end
def can?(_, _), do: false
# A sub-permission is only effective while its BASE is also held. The cascade
# guarantees this in normal operation (granting a sub grants its base), so
# this is defensive: it closes the orphan case where a sub row survives its
# base being removed (e.g. a module/sub-key temporarily leaves the registry
# and returns) — without it, `can?/2` would honor a dotted key whose base
# the role no longer holds.
defp base_held?(perms, key) do
case Permissions.parent_key(key) do
nil -> true
base -> holds?(perms, base)
end
end
@doc """
Returns the set of module keys the user can access.
"""
@spec accessible_modules(t()) :: MapSet.t()
def accessible_modules(%__MODULE__{cached_permissions: perms} = scope) when not is_nil(perms) do
# A superadmin ("*") can reach everything, so surface the full key set — the
# same set Owner gets. Without this, a `"*"`-only role's set is just `{"*"}`,
# and the callers that do raw membership (the Modules page's per-card
# `key in @accessible_modules`, and the matrix/roles grantable ceiling) would
# render an empty page / block every concrete-key grant for that role.
if superadmin?(scope), do: MapSet.new(Permissions.all_module_keys()), else: perms
end
def accessible_modules(_), do: MapSet.new()
@doc """
Returns the number of module permissions the user has been granted.
"""
@spec permission_count(t()) :: non_neg_integer()
def permission_count(%__MODULE__{cached_permissions: perms}) when not is_nil(perms) do
MapSet.size(perms)
end
def permission_count(_), do: 0
@doc """
Checks if the user has access to at least one of the given module keys.
## Examples
Scope.has_any_module_access?(scope, ["billing", "shop"])
"""
@spec has_any_module_access?(t(), [String.t()]) :: boolean()
def has_any_module_access?(%__MODULE__{cached_permissions: perms}, keys)
when is_list(keys) and not is_nil(perms) do
Enum.any?(keys, &holds?(perms, &1))
end
def has_any_module_access?(_, _), do: false
@doc """
Checks if the user has access to all of the given module keys.
## Examples
Scope.has_all_module_access?(scope, ["billing", "shop"])
"""
@spec has_all_module_access?(t(), [String.t()]) :: boolean()
def has_all_module_access?(%__MODULE__{cached_permissions: perms}, keys)
when is_list(keys) and not is_nil(perms) do
Enum.all?(keys, &holds?(perms, &1))
end
def has_all_module_access?(_, _), do: false
@doc """
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.
"""
@spec system_role?(t()) :: boolean()
def system_role?(%__MODULE__{cached_roles: cached_roles}) when is_list(cached_roles) do
roles = Role.system_roles()
roles.owner in cached_roles or roles.admin in cached_roles
end
def system_role?(_), do: false
end