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
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.
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.
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.
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 role
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
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{}}
Deletes a role safely.
Prevents deletion of system roles and roles currently assigned to users.
Parameters
role: The role to delete
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 demote
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 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 promotion
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 remove
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 remove
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.
- 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{}(see above)
Returns {:ok, %{assignments: [...], roles_before: [...], roles_after: [...]}}
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")
[]