API for managing user roles in PhoenixKit.
This module provides functions for assigning, removing, and querying user roles. It works with the role system to provide authorization capabilities.
Role Management
- Assign and remove roles from users
- Query users by role
- Check user permissions
- Bulk role operations
Who may change roles
Every entry point that gives a role, takes one away or deletes one accepts an
:actor (a %User{} or a %Scope{}; omitted or :system = unrestricted, for
seeding and system callers). With a real actor the rule is
can_manage_role?/2: an Owner may do anything; anyone else only for a role
that is neither Owner nor Admin and whose every permission key they hold
themselves (a role holding "*" only by a holder of "*"). Nobody but an
Owner changes their own roles ({:error, :cannot_modify_own_roles}).
System Roles
PhoenixKit includes three built-in system roles:
- Owner: System owner with full access (assigned automatically to first user)
- Admin: Administrator with elevated privileges
- User: Standard user with basic access (default for new users)
Examples
# Check if user has a role
iex> user_has_role?(user, "Admin")
true
# Get all user roles
iex> get_user_roles(user)
["Admin", "User"]
# Assign a role to user
iex> assign_role(user, "Admin")
{:ok, %RoleAssignment{}}
# Get all users with a specific role
iex> users_with_role("Admin")
[%User{}, %User{}]
Summary
Functions
Assigns a role to a user.
Assigns roles to existing users who don't have any PhoenixKit roles.
Checks if user can be deactivated safely.
Whether actor may give role to someone, take it away from someone, delete
it, or make it the new-user default.
Counts active users with Owner role.
Role counts for the admin list's summary cards: %{total: n, system: n, custom: n}.
Counts users with a specific role.
Creates a new role.
The role a new self-registered account gets: the new_user_default_role
setting when it names an existing role other than Owner and Admin, else
"User". Owner is reserved for the first user, and Admin would hand every
self-registered account the admin panel, so a stored value naming either is
never used.
Deletes a role safely.
Safely demotes a user from Admin or Owner role with protection.
Safely assigns Owner role to first user using database transaction.
Gets only custom (non-system) roles.
Gets comprehensive user statistics including activity and confirmation status.
Gets a role by its name.
Gets a single role by its UUID.
get_user_role_records/1 for many users at once: %{user_uuid => [role]},
each list in role order. Users holding no role are absent.
Gets role statistics for dashboard display.
Gets the roles a user holds as %{uuid, name} maps, in role order
(position, then name).
Gets all active roles for a user.
Lists all roles.
One page of roles for the admin list, system roles first, then by name.
The names of the roles actor may manage (can_manage_role?/2), in role
order. One permissions query for all roles — for a UI that disables the rest.
Moves one role one step :up or :down in role order. A move past either
end is a no-op.
Promotes a user to admin role.
Removes a role from a user by deleting the assignment.
Sets the role order (position) to the order of ordered_uuids; any role
not listed keeps its relative order after them.
Safely removes role with Owner protection.
Synchronizes user roles with a given list of role names.
Updates a role.
Checks if a user has a specific role.
Checks if a user has an "Admin" role.
Checks if a user has an "Owner" role.
Gets all users who have a specific role.
Functions
Assigns a role to a user.
Security
The Owner role cannot be assigned through this function to maintain system security. Only one Owner is automatically assigned during initialization.
Parameters
user: The user to assign the role torole_name: The name of the role to assignassigned_by(optional): The user who is assigning the roleopts::broadcast(defaulttrue);:actor— the acting%User{}or%Scope{}, checked againstcan_manage_role?/2and the own-roles rule ({:error, :not_permitted}/{:error, :cannot_modify_own_roles}). Omitted = a system caller, unchecked.
Examples
iex> assign_role(user, "Admin")
{:ok, %RoleAssignment{}}
iex> assign_role(user, "Admin", assigned_by_user)
{:ok, %RoleAssignment{}}
iex> assign_role(user, "Owner")
{:error, :owner_role_protected}
iex> assign_role(user, "NonexistentRole")
{:error, :role_not_found}
Assigns roles to existing users who don't have any PhoenixKit roles.
This is useful for migration scenarios where PhoenixKit is installed into an existing application with users.
Parameters
opts: Options for role assignment:make_first_owner(default: true) - Make first user without roles an Owner
Returns
{:ok, stats}with assignment statistics{:error, reason}on failure
Examples
iex> assign_roles_to_existing_users()
{:ok, %{assigned_owner: 1, assigned_users: 5, total_processed: 6}}
Checks if user can be deactivated safely.
Prevents deactivation of last active Owner.
Parameters
user: User to check for deactivation
Examples
iex> can_deactivate_user?(last_owner)
{:error, :cannot_deactivate_last_owner}
iex> can_deactivate_user?(regular_user)
:ok
@spec can_manage_role?( PhoenixKit.Users.Auth.User.t() | PhoenixKit.Users.Auth.Scope.t() | :system, PhoenixKit.Users.Role.t() | String.t() ) :: boolean()
Whether actor may give role to someone, take it away from someone, delete
it, or make it the new-user default.
actor is a %User{}, a %Scope{} or :system (always true). An Owner (by
their real, held roles) may manage any role. Anyone else may manage a role
only when it is neither Owner nor Admin and they hold every permission key
the role holds — checked against their effective permissions (the active
role, when they act as one), so a role holding "*" needs "*". role is a
%Role{} or a role name; an unknown name answers false.
Counts active users with Owner role.
Critical security function - ensures we never have zero owners.
Examples
iex> count_active_owners()
1
Role counts for the admin list's summary cards: %{total: n, system: n, custom: n}.
Counts users with a specific role.
Parameters
role_name: The name of the role to count
Examples
iex> count_users_with_role("Admin")
3
Creates a new role.
Parameters
attrs: Attributes for the new role
Examples
iex> create_role(%{name: "Manager", description: "Department manager"})
{:ok, %Role{}}
iex> create_role(%{name: ""})
{:error, %Ecto.Changeset{}}
@spec default_role_for_new_users() :: String.t()
The role a new self-registered account gets: the new_user_default_role
setting when it names an existing role other than Owner and Admin, else
"User". Owner is reserved for the first user, and Admin would hand every
self-registered account the admin panel, so a stored value naming either is
never used.
Deletes a role safely.
Prevents deletion of system roles and roles currently assigned to users.
Parameters
role: The role to deleteopts::actor— the acting%User{}or%Scope{}. Only an actor who holds every key of the role may delete it (can_manage_role?/2; an Owner always):{:error, :not_permitted}otherwise. Omitted = a system caller.
Examples
iex> delete_role(custom_role)
{:ok, %Role{}}
iex> delete_role(system_role)
{:error, :system_role_protected}
iex> delete_role(role_with_users)
{:error, :role_in_use}
Safely demotes a user from Admin or Owner role with protection.
Prevents demotion of last Owner to maintain system security.
Parameters
user: The user to demoteopts::actor— as forremove_role/3: only an Owner actor may take Owner or Admin away. Omitted = a system caller, unchecked.
Examples
iex> demote_to_user(admin_user)
{:ok, %RoleAssignment{}}
iex> demote_to_user(last_owner)
{:error, :cannot_demote_last_owner}
Safely assigns Owner role to first user using database transaction.
Concurrent registrations are serialized by a row lock on the Owner role row, taken only while no active Owner exists yet — see the transaction body.
Parameters
user: The user to potentially make Owner
Returns
{:ok, :owner}if user became Owner{:ok, :user}if user became regular User{:error, reason}on failure
Examples
iex> ensure_first_user_is_owner(user)
{:ok, :owner}
Gets only custom (non-system) roles.
Examples
iex> get_custom_roles()
[%Role{name: "Manager"}, %Role{name: "Editor"}]
Gets comprehensive user statistics including activity and confirmation status.
Examples
iex> get_extended_stats()
%{
total_users: 10,
owner_count: 1,
admin_count: 2,
user_count: 7,
active_users: 8,
inactive_users: 2,
confirmed_users: 9,
pending_users: 1
}
Gets a role by its name.
Parameters
name: The name of the role
Examples
iex> get_role_by_name("Admin")
%Role{name: "Admin"}
iex> get_role_by_name("NonexistentRole")
nil
Gets a single role by its UUID.
Returns nil if no role is found.
Parameters
uuid: The UUID of the role to find
Examples
iex> get_role_by_uuid("01234567-89ab-cdef-0123-456789abcdef")
%Role{uuid: "01234567-89ab-cdef-0123-456789abcdef", name: "Editor"}
iex> get_role_by_uuid("nonexistent-uuid")
nil
@spec get_role_records_for_users([String.t()]) :: %{ optional(String.t()) => [%{uuid: String.t(), name: String.t()}] }
get_user_role_records/1 for many users at once: %{user_uuid => [role]},
each list in role order. Users holding no role are absent.
Gets role statistics for dashboard display.
Examples
iex> get_role_stats()
%{
total_users: 10,
owner_count: 1,
admin_count: 2,
user_count: 7
}
@spec get_user_role_records(PhoenixKit.Users.Auth.User.t()) :: [ %{uuid: String.t(), name: String.t()} ]
Gets the roles a user holds as %{uuid, name} maps, in role order
(position, then name).
The same query as get_user_roles/1, keeping the uuid: the active-role
resolution in PhoenixKit.Users.ActiveRole identifies roles by uuid, because
role names are editable — and relies on this order: the first switchable
role here is a session's default.
Gets all active roles for a user.
Parameters
user: The user to get roles for
Examples
iex> get_user_roles(user)
["Admin", "User"]
iex> get_user_roles(user_with_no_roles)
[]
Lists all roles.
Examples
iex> list_roles()
[%Role{}, %Role{}, %Role{}]
One page of roles for the admin list, system roles first, then by name.
Options :page (default 1) and :per_page (default 25). Returns
%{roles: [...], total_count: n, total_pages: n, page: n} — page is
clamped into [1, total_pages] before the offset is applied, so a link
past the end (the last custom role on the last page was just deleted)
shows the last page rather than an empty one.
@spec manageable_role_names( PhoenixKit.Users.Auth.User.t() | PhoenixKit.Users.Auth.Scope.t() | :system ) :: [String.t()]
The names of the roles actor may manage (can_manage_role?/2), in role
order. One permissions query for all roles — for a UI that disables the rest.
@spec move_role(String.t(), :up | :down) :: :ok
Moves one role one step :up or :down in role order. A move past either
end is a no-op.
Promotes a user to admin role.
Parameters
user: The user to promoteassigned_by(optional): The user who is doing the promotionopts::actor— as forassign_role/4. Admin is a system role, so only an Owner actor may promote ({:error, :not_permitted}otherwise). Omitted = a system caller (seeding, fixtures), unchecked — no web page calls this.
Examples
iex> promote_to_admin(user)
{:ok, %RoleAssignment{}}
Removes a role from a user by deleting the assignment.
Parameters
user: The user to remove the role fromrole_name: The name of the role to removeopts::broadcast(defaulttrue);:actor, as forassign_role/4
Examples
iex> remove_role(user, "Admin")
{:ok, %RoleAssignment{}}
iex> remove_role(user, "NonexistentRole")
{:error, :assignment_not_found}
@spec reorder_roles([String.t()]) :: :ok
Sets the role order (position) to the order of ordered_uuids; any role
not listed keeps its relative order after them.
The order decides the default role of a new session and how the role
switcher lists roles (PhoenixKit.Users.ActiveRole). Broadcasts
:role_updated for the roles whose position changed.
Safely removes role with Owner protection.
Prevents removal of Owner role if it would leave system without owners.
Parameters
user: User to remove role fromrole_name: Name of role to removeopts::actor— as forremove_role/3. Omitted = a system caller.
Examples
iex> safely_remove_role(owner_user, "Owner")
{:error, :cannot_remove_last_owner}
iex> safely_remove_role(admin_user, "Admin")
{:ok, %RoleAssignment{}}
Synchronizes user roles with a given list of role names.
This function ensures the user has exactly the specified roles.
Authorization (:actor option)
This is the authorization boundary for role changes — never trust the
caller UI to have gated it. Pass actor: %User{} (the acting admin) and:
- Changes to the system roles (Owner, Admin) are applied only when the actor is an Owner. A non-Owner's attempt to grant Owner/Admin to another account (privilege escalation) or to strip them (the disabled-checkbox form drops Owner from the submitted set, which would otherwise read as a removal) is DROPPED — that role is left exactly as it was.
- Any other role is changed only when the actor may manage it
(
can_manage_role?/2: the actor holds every permission key of the role). A change the actor may not make is DROPPED like a system-role change and its role name is returned in:refused, so the caller can say so. - A non-Owner actor may not change their own roles at all: the whole
call fails with
{:error, :cannot_modify_own_roles}when it would change anything. - The last Owner can never be removed, regardless of actor, via the
same guard as
safely_remove_role/2.
Omitting :actor (or actor: :system) skips the actor gate — for
seeding/system callers only — but the last-Owner guard still applies.
Parameters
user: The user to synchronize roles forrole_names: List of role names the user should haveopts::actor— the acting%User{}or%Scope{}(see above)
Returns {:ok, %{assignments: [...], roles_before: [...], roles_after: [...], refused: [...]}} on success — roles_before/roles_after are the role-name lists captured
inside the transaction, so callers can audit the exact delta this transaction
applied (which can differ from what was submitted when unauthorized changes
are dropped or a concurrent edit intervened).
Examples
iex> sync_user_roles(user, ["Admin", "Manager"], actor: current_user)
{:ok, %{assignments: [%RoleAssignment{}], roles_before: ["Manager"], roles_after: ["Admin", "Manager"]}}
Updates a role.
Parameters
role: The role to updateattrs: Attributes to update
Examples
iex> update_role(role, %{description: "Updated description"})
{:ok, %Role{}}
iex> update_role(system_role, %{name: "NewName"})
{:error, %Ecto.Changeset{}}
Checks if a user has a specific role.
Parameters
user: The user to checkrole_name: The name of the role to check for
Examples
iex> user_has_role?(user, "Admin")
true
iex> user_has_role?(user, "Owner")
false
Checks if a user has an "Admin" role.
Parameters
user: The user to check
Examples
iex> user_has_role_admin?(user)
true
Checks if a user has an "Owner" role.
Parameters
user: The user to check
Examples
iex> user_has_role_owner?(user)
true
Gets all users who have a specific role.
Parameters
role_name: The name of the role to search for
Examples
iex> users_with_role("Admin")
[%User{}, %User{}]
iex> users_with_role("NonexistentRole")
[]